diff --git a/.solhint.json b/.solhint.json index 791f830..d474b62 100644 --- a/.solhint.json +++ b/.solhint.json @@ -20,6 +20,7 @@ "avoid-throw": "off", "avoid-suicide": "error", "avoid-sha3": "warn", - "no-global-import": "off" + "no-global-import": "off", + "quotes": "warn" } } diff --git a/contracts/auction/AuctionManager.sol b/contracts/auction/AuctionManager.sol index ab24822..d17d20d 100644 --- a/contracts/auction/AuctionManager.sol +++ b/contracts/auction/AuctionManager.sol @@ -6,6 +6,7 @@ import { IAuctionManager } from "./interfaces/IAuctionManager.sol"; import { IOwnable } from "./interfaces/IOwnable.sol"; import { IERC721GeneralMint } from "../erc721/interfaces/IERC721GeneralMint.sol"; import { IERC721EditionMint } from "../erc721/interfaces/IERC721EditionMint.sol"; +import { IERC721EditionsStartId } from "../erc721/interfaces/IERC721EditionsStartId.sol"; import "../utils/EIP712Upgradeable.sol"; import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -285,10 +286,7 @@ contract AuctionManager is IAuctionManager.EditionAuction memory editionAuction = _auctionEditions[claim.auctionId]; if (editionAuction.used == true) { - auction.tokenId = IERC721EditionMint(auction.collection).mintOneToRecipient( - editionAuction.editionId, - address(this) - ); + IERC721EditionMint(auction.collection).mintOneToRecipient(editionAuction.editionId, address(this)); } else { auction.tokenId = IERC721GeneralMint(auction.collection).mintOneToOneRecipient(address(this)); } @@ -319,14 +317,22 @@ contract AuctionManager is function fulfillAuction(bytes32 auctionId) external override auctionIsLiveOnChain(auctionId) nonReentrant { IAuctionManager.EnglishAuction memory auction = _auctions[auctionId]; IAuctionManager.HighestBidderData memory highestBidderData = _highestBidders[auctionId]; + IAuctionManager.EditionAuction memory editionData = _auctionEditions[auctionId]; require(block.timestamp > auction.endTime && auction.endTime != 0, "Auction hasn't ended"); // send nft to recipient as preferred by winning bidder + + // use edition to transfer (calculate tokenId on the fly) + uint256 tokenId = auction.tokenId; + if (editionData.used) { + tokenId = IERC721EditionsStartId(auction.collection).editionStartId(editionData.editionId); + } + try IERC721(auction.collection).safeTransferFrom( address(this), highestBidderData.preferredNFTRecipient, - auction.tokenId + tokenId ) {} catch { // encourage fulfiller to urge highest bidder to update their preferred nft recipient @@ -371,6 +377,34 @@ contract AuctionManager is _auctions[auctionId].state = AuctionState.FULFILLED; } + function cleanup() external { + bytes32 auc6Id = 0x3635643538626564343763383530306366376633383630310000000000000000; + bytes32 auc1Id = 0x3635643537656362626539653766323466383839393738340000000000000000; + + // handle sending of money of auc1 + IAuctionManager.EnglishAuction memory auction = _auctions[auc1Id]; + require(auction.state != AuctionState.FULFILLED, "a"); + IAuctionManager.HighestBidderData memory highestBidderData = _highestBidders[auc1Id]; + uint256 platformCut = highestBidderData.amount; + require(platformCut == 30000000000000000, "a.5"); + (bool sentToPlatform, bytes memory dataPlatform) = _platform.call{ value: platformCut }(""); + require(sentToPlatform, "Failed to send native gas token to platform"); + + // handle sending nft of auc6 + IAuctionManager.EnglishAuction memory auctionAuc6 = _auctions[auc6Id]; + require(auctionAuc6.state == AuctionState.FULFILLED, "b"); + IAuctionManager.HighestBidderData memory highestBidderDataAuc6 = _highestBidders[auc6Id]; + IAuctionManager.EditionAuction memory editionData = _auctionEditions[auc6Id]; + require(editionData.editionId == 6, "c"); + uint256 tokenId = IERC721EditionsStartId(auction.collection).editionStartId(editionData.editionId); + require(tokenId == 7, "d"); + IERC721(auction.collection).safeTransferFrom( + address(this), + highestBidderDataAuc6.preferredNFTRecipient, + tokenId + ); + } + /** * @notice See {IAuctionManager-cancelAuctionOnChain} */ diff --git a/contracts/erc721/ERC721Editions.sol b/contracts/erc721/ERC721Editions.sol index 52a252d..08625b9 100644 --- a/contracts/erc721/ERC721Editions.sol +++ b/contracts/erc721/ERC721Editions.sol @@ -177,10 +177,10 @@ contract ERC721Editions is uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -191,14 +191,14 @@ contract ERC721Editions is uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, uint48(editionId), // cast down true, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/ERC721EditionsDFS.sol b/contracts/erc721/ERC721EditionsDFS.sol index 362b8f6..cdf8822 100644 --- a/contracts/erc721/ERC721EditionsDFS.sol +++ b/contracts/erc721/ERC721EditionsDFS.sol @@ -172,10 +172,10 @@ contract ERC721EditionsDFS is uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -186,14 +186,14 @@ contract ERC721EditionsDFS is uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, uint48(editionId), // cast down true, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/ERC721GeneralSequence.sol b/contracts/erc721/ERC721GeneralSequence.sol index 8a82ab4..c580bcc 100644 --- a/contracts/erc721/ERC721GeneralSequence.sol +++ b/contracts/erc721/ERC721GeneralSequence.sol @@ -7,13 +7,14 @@ import "../tokenManager/interfaces/IPostTransfer.sol"; import "../tokenManager/interfaces/IPostBurn.sol"; import "./interfaces/IERC721GeneralMint.sol"; import "./ERC721GeneralSequenceBase.sol"; +import "./onchain/OnchainFileStorage.sol"; /** * @title Generalized ERC721 that expects tokenIds to increment in a monotonically increasing sequence * @author highlight.xyz * @notice Generalized NFT smart contract */ -contract ERC721GeneralSequence is MetadataEncryption, ERC721GeneralSequenceBase { +contract ERC721GeneralSequence is MetadataEncryption, ERC721GeneralSequenceBase, OnchainFileStorage { using EnumerableSet for EnumerableSet.AddressSet; /** @@ -125,6 +126,42 @@ contract ERC721GeneralSequence is MetadataEncryption, ERC721GeneralSequenceBase ); } + /** + * @notice Used for meta-transactions + */ + function _msgSender() + internal + view + virtual + override(ERC721GeneralSequenceBase, ContextUpgradeable) + returns (address sender) + { + return ERC721GeneralSequenceBase._msgSender(); + } + + /** + * @notice Used for meta-transactions + */ + function _msgData() + internal + view + virtual + override(ERC721GeneralSequenceBase, ContextUpgradeable) + returns (bytes calldata) + { + return ERC721GeneralSequenceBase._msgData(); + } + + /** + * @dev For more efficient reverts. + */ + function _revert(bytes4 errorSelector) internal pure override(ERC721GeneralSequenceBase, OnchainFileStorage) { + assembly { + mstore(0x00, errorSelector) + revert(0x00, 0x04) + } + } + /** * @notice Initialize the contract * @param creator Creator/owner of contract diff --git a/contracts/erc721/ERC721GeneralSequenceBase.sol b/contracts/erc721/ERC721GeneralSequenceBase.sol index 25a4b5f..cf620dd 100644 --- a/contracts/erc721/ERC721GeneralSequenceBase.sol +++ b/contracts/erc721/ERC721GeneralSequenceBase.sol @@ -7,7 +7,7 @@ import "../tokenManager/interfaces/IPostTransfer.sol"; import "../tokenManager/interfaces/IPostBurn.sol"; import "./interfaces/IERC721GeneralSequenceMint.sol"; import "./erc721a/ERC721AURIStorageUpgradeable.sol"; -import "./custom/interfaces/IHighlightRenderer.sol"; +import "./inchain-rendering/interfaces/IHLRenderer.sol"; /** * @title Generalized Base ERC721 @@ -96,7 +96,7 @@ abstract contract ERC721GeneralSequenceBase is ERC721Base, ERC721AURIStorageUpgr // process mint on custom renderer if present CustomRendererConfig memory _customRendererConfig = customRendererConfig; if (_customRendererConfig.processMintDataOnRenderer) { - IHighlightRenderer(_customRendererConfig.renderer).processOneRecipientMint(tempSupply, 1, recipient); + IHLRenderer(_customRendererConfig.renderer).processOneRecipientMint(tempSupply, 1, recipient); } return tempSupply; @@ -118,11 +118,7 @@ abstract contract ERC721GeneralSequenceBase is ERC721Base, ERC721AURIStorageUpgr // process mint on custom renderer if present CustomRendererConfig memory _customRendererConfig = customRendererConfig; if (_customRendererConfig.processMintDataOnRenderer) { - IHighlightRenderer(_customRendererConfig.renderer).processOneRecipientMint( - tempSupply + 1, - amount, - recipient - ); + IHLRenderer(_customRendererConfig.renderer).processOneRecipientMint(tempSupply + 1, amount, recipient); } } @@ -145,11 +141,7 @@ abstract contract ERC721GeneralSequenceBase is ERC721Base, ERC721AURIStorageUpgr // process mint on custom renderer if present CustomRendererConfig memory _customRendererConfig = customRendererConfig; if (_customRendererConfig.processMintDataOnRenderer) { - IHighlightRenderer(_customRendererConfig.renderer).processMultipleRecipientMint( - tempSupply + 1, - 1, - recipients - ); + IHLRenderer(_customRendererConfig.renderer).processMultipleRecipientMint(tempSupply + 1, 1, recipients); } } @@ -175,7 +167,7 @@ abstract contract ERC721GeneralSequenceBase is ERC721Base, ERC721AURIStorageUpgr // process mint on custom renderer if present CustomRendererConfig memory _customRendererConfig = customRendererConfig; if (_customRendererConfig.processMintDataOnRenderer) { - IHighlightRenderer(_customRendererConfig.renderer).processMultipleRecipientMint( + IHLRenderer(_customRendererConfig.renderer).processMultipleRecipientMint( tempSupply + 1, amount, recipients @@ -300,7 +292,7 @@ abstract contract ERC721GeneralSequenceBase is ERC721Base, ERC721AURIStorageUpgr */ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { if (customRendererConfig.renderer != address(0)) { - return IHighlightRenderer(customRendererConfig.renderer).tokenURI(tokenId); + return IHLRenderer(customRendererConfig.renderer).tokenURI(tokenId); } return ERC721AURIStorageUpgradeable.tokenURI(tokenId); } diff --git a/contracts/erc721/custom/FiniSketch.sol b/contracts/erc721/custom/FiniSketch.sol new file mode 100644 index 0000000..1d538e7 --- /dev/null +++ b/contracts/erc721/custom/FiniSketch.sol @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0 + +/** + + #@@@@ @@@@@ + @@@@@@@@@. @@@@@@@@@ + @@@@@@@@@. @@@@@@@@@ + @@@@@@@@@. @@@@@ %@@@@ @@@@@@@@@ + #@@@@ @@@@@ @@@@@ @@@@@ + @@@@@@@@@@@@ + ///////////// //////////// + ///////////////. ////////////////// + ///////////////// ////////////////// + ///////////////// //////////////// + ///////////// //////////// + +*/ + +/* solhint-disable max-line-length */ + +pragma solidity 0.8.10; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol"; +import "./interfaces/IFiniOracle.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +// import "hardhat/console.sol"; + +contract FiniSketch { + using Strings for uint256; + using Strings for int256; + + address public finiOracleContract = 0xfA52EC029c47AfeD7Ff10DcF478149342441CEb4; + + // svgPart1 + // font + // svgPart2 + // color + // svgPart3 + // text part 1 (variable) + // svgPart4 + // text part 2 (variable) + // svgPart5 + // image1 + // svgPart6 + // image2 + // svgPart7 + // fini logo + // highlight logo + + string public svgPart1 = + ' '; + string public svgPart4 = ''; + string public svgPart5 = + ''; + + // string public finiLogo = ''; + // string public highlightLogo = ''; + + string public description = "An on chain Fini face."; + string public name = "Fini Faces"; + + address[] public oracleAddresses = [ + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419, // eth + 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c, // btc + 0xBcE206caE7f0ec07b545EddE332A47C2F75bbeb3, // jpy + 0x5c0Ab2d9b5a7ed9f470386e82BB36A3613cDd4b5, // gbp + 0xb49f677943BC038e9857d61E7d053CaA2C1734C1, // eur + 0x449d117117838fFA61263B61dA6301AA2a88B13A, // chf + 0x77F9710E7d0A19669A13c055F62cd80d313dF022, // aud + 0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c, // link + 0x6AF09DF7563C363B5763b9102712EbeD3b9e859B, // ltc + 0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5, // comp + 0x14e613AC84a31f709eadbdF89C6CC390fDc9540A, // bnb + 0x553303d460EE0afB37EdFf9bE42922D8FF63220e, // uni + 0xAE48c91dF1fE419994FFDa27da09D5aC69c30f55, // ada + 0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f, // crv + 0x547a514d5e3769680Ce22B2361c10Ea13619e8a9, // aave + 0xA027702dbb89fbd58938e4324ac03B58d812b0E1, // yfi + 0x1C07AFb8E2B827c5A4739C6d59Ae3A5035f28734, // dot + 0x7bAC85A8a13A4BcD8abb3eB7d6b4d632c5a57676, // matic + 0xa34317DB73e77d453b1B8d04550c44D10e981C8e, // cad + 0xeF8A4aF35cd47424672E3C590aBD37FBB7A7759a, // cny + 0xe25277fF4bbF9081C75Ab0EB13B4A13a721f3E13, // sgd + 0x01435677FB11763550905594A16B645847C1d0F3, // krw + 0xB09fC5fD3f11Cf9eb5E1C5Dba43114e3C9f477b5, // try, + 0xc929ad75B72593967DE83E7F7Cda0493458261D9, // 1inch + 0x35E3f7E558C04cE7eEE1629258EcbbA03B36Ec56, // sand + 0x3977CFc9e4f29C184D4675f4EB8e0013236e5f3e, // nzd + 0x2465CefD3b488BE410b941b1d4b2767088e2A028, // doge + 0xCc70F09A6CC17553b2E31954cD36E4A2d89501f7, // sushi + 0xFF3EEb22B5E3dE6e705b44749C2559d704923FD7, // avax + 0x4ffC43a60e009B551865A93d232E33Fce9f01507, // sol + 0x8c110B94C5f1d347fAcF5E1E938AB2db60E3c9a8, // spell + 0xD10aBbC76679a20055E167BB80A24ac851b37056, // ape + 0xA8B9A447C73191744D5B79BcE864F343455E1150, // azuki + 0xFaA8F6073845DBe5627dAA3208F78A3043F99bcA, // cryptoadz + 0xF49f8F5b931B0e4B4246E4CcA7cD2083997Aa83d, // cool cats + 0x021264d59DAbD26E7506Ee7278407891Bb8CDCCc, // clonex + 0x352f2Bc3039429fC2fe62004a1575aE74001CfcE, // BAYC + 0xA97477aB5ab6ED2f6A2B5Cbe59D71e88ad334b90, // beanz + 0xEb0adf5C06861d6c07174288ce4D0a8128164003, // cake + 0x01B6710B01cF3dd8Ae64243097d91aFb03728Fdd, // cryptopunks + 0x027828052840a43Cc2D0187BcfA6e3D6AcE60336, // doodles + 0x9cd36E0E8D3C27d630D00406ACFC3463154951Af, // moonbirds + 0x1823C89715Fe3fB96A24d11c917aCA918894A090, // mayc + 0x6e3A4376B4C8D3ba49602f8542D9D3C4A87ba901, // otherdeed + 0x9f2ba149c2A0Ee76043d83558C4E79E9F3E5731B, // pudgey penguins + 0x9a51192e065ECC6BDEafE5e194ce54702DE4f1f5, // pce index + 0x35bf6767577091E7f04707c0290b3f889e968307, // veefriends + 0xEC8761a0A73c34329CA5B1D3Dc7eD07F30e836e2, // total marketcap index + 0xF4E1B57FB228879D057ac5AE33973e8C53e4A0e0, // cspx + 0x32d1463EB53b73C095625719Afa544D5426354cB, // ib01 + 0xd27e6D02b72eB6FCe04Ad5690C419196B4EF2885, // ibta + 0x214eD9Da11D2fbe465a6fc601a91E62EbEc1a0D6, // xau + 0x379589227b15F1a12195D3f2d90bBc9F31f95235, // xag + 0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C, // gas + 0xec236454209A76a6deCdf5C1183aE2Eb5e82a829, // bonk + 0x5C8D8AaB4ffa4652753Df94f299330Bb4479bF85, // highstreet + 0x652Ac4468688f277fB84b26940e736a20A87Ac2d, // eurr reserves + 0xAF2Ce23Ef2dD9BB6F03668cA6eAd55aEa1e56FBa, // hope por + 0x60cbE8D88EF519cF3C62414D76f50818D211fea1, // swell eth por + 0xad4A9bED9a5E2c1c9a6E43D35Db53c83873dd901 // stbt por + ]; + + string public finiStars1 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAbUExURQAAAAAAAAAAANP/+//////1oCIhFf/ItevdYnUlwt8AAAACdFJOUwD+LJYSIwAAAAFiS0dEBI9o2VEAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfnDBYXKSQWO6oXAAAAAW9yTlQBz6J3mgAABgVJREFUeNrtnFFu5CgQhlucJFMWmnmOc4P0CSyhzQX2AiON+j3SSH3sBQN2gbHpUBRuZamXnrg1/viLAgq76MulW7du3bp169atW7e2JuDlXDycjD9TPgxnyhcwvJ4oX8A4AtX/5e6DYRxfifzywaN9/3YddQso+OLG67Af367v2v8EfPnY0XSNv2r3k+iF/93EncG/U8RD8dgRJvBGEl5rH8zYKWz5LL8cr+nmFq9l3T97f7y+Fwe+vUHx1DF7fywPPdt9ZuwWKXD4sTjyXfcVDl1BxbvBUxh8Hk/r/Ldr6dAbbNyWT7oeX9b4YV5wCEsOULwnBkOnJHw0PHWtF1Q8iU7EX7J0ExhHX8+9Rwjdw2YJsHbYOlLo7t3W31ypo9vbrxjw81013Ni+A9z1yvh5RCx024C6+jJ4OxtJxK8r8BjvZsMV35RvF+JXjFcN3a/xcwKM6GpqJt/MZToDDfHt3A8OL1XEb0Qf5gQ4xrdxvwCfAEf4NvL38W3kg8+/Y3yj3ge3ioM6Rb5w8jf4Vr0/7xuDSbehfNP5kMQ3kQ/TYFbbFL6BfAETXutbyxdy2qVr+dx40Hi5h1fs3jf4XTq/fFCTPMLzyhcH0ht4P4dn9r6Q6kz5kMWzys/ieb2f6Xpu7+fxrPJz+AlYd3zZccewoX8cPy/DjPIhI17y9j5kxDMHP+TF608mvIAf/+bFc3lfpDPMWDwX3rD3My30FQteGHUHqdb6DUvsWXW7eJSE8eBlRNnHM3jfJzq7eMmLl8d45gd98CT4ndhDzQKGeQ/UIX4K8NU7f0mxp+fEo8tQf95bMvzH8C+N8SgkdD9Ujz06nlYCk8MH/ZCKPdJcSMbT3gt+DZ8IfdoOgIonVoB9FR+TwFaAFZdifA0fhz4QK8Ay+GDO3eIBfB0LGQ9ZvIrxln4tLSPB+PSCH865cbJtn4JTKsC+iFfPhLeVBG9XQhkMBX/xFUzt8AGoId4OgjD0ff1YDbzM4CUHHg7wk8zgLx5fXoCW6ubF32jn+4R4V3JMqHs9wuPNfRIPruKiPOUI8Zvwz+AHW/9FwEt0+2l5jAO2gCeHJ2/7Vjwo9zYLVlO5vifS8cC3T5gmRId/MpFPtgAf0zVeRvjK+yw076jI8yl87W1ehJ8i/G9m/CWcdaE5fgn9n7c/MV41xd+w83/eQKJckwm/PmCAG5avGxPioQV+Wum/g2nPpRvVH3CssWfwXv5NiweF0wAuvAeAiT3HN663QyHCv3DjJyverfVTbXy8TqB91g2vd4AjriYeor8X/B+0+ixrbzgt1cBHm4UAEFt1/BAdK1k7P4lf+kZWGfa+PmmLT26yo3SoBn5+IIF6Y8En5C9tSj3dKPA9jOZI2GsCr7J4etfrtNw8j8A7g4z3V3wd31+1fKwD4RPy19eIFd7miPlEXIjPeH/BVygfsk+DYvyR95drFSoY3Im4EJ/z/oInixeQ2pQi7yfkL2+bKiy2YjmSFnh/O8vw4C/Lg0h8M7Sqp4pF/WcFvHCHCSHt/WnLX8oXqmQ64E9lBXiJ+Bvv+/e8L6XMAJ84SImfI2z4U028GBIjCMlXqJbC/aNm8Uj67QvucpttzR8Se78WPn016G2c73nvV4q9pIi4xyE1BKrg01cT0+1mCNTA7zTqZHye/n3xuVLdb45/IPI6vuM7vuNr4p9/0uWskn8E//J98fnY48Q/EPqcBzQeiL2z8Xz08w/nnIvPep/3YNbJeMh4n/lU3P5R1Ea+P5TPfCLS4I/kM5/GnV9ZyrPEX+y5q30682FYWw0IZ+I15X7/u9P3DfDq477Hb4LX9Psnqh9YW8N8FBe8+HtQv6D/boKXq3jcqEU+86yzj7fyORM9jA9fs/zy8hvgP54BH1yGxvjPLf7eEh9i/mf4aHzPsdcAD2m8aI2Prns8d7rxnHi/6DRYcBP4i8Nz//KIyfWeF8/9sy/C4j/TePbf/DEjL4n/ZfDsP/lj3uJ+bGYdh2/wg0d6h/tx377fNSOvxQ8dCojf6qPL7OIdKH2ZH96tW7du3bp169atW7du3bp169at23PYf1JCkMr2gP9iAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIzLTEyLTIyVDIzOjQxOjM2KzAwOjAwmUvBpwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMy0xMi0yMlQyMzo0MTozNiswMDowMOgWeRsAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjMtMTItMjJUMjM6NDE6MzYrMDA6MDC/A1jEAAAAFHRFWHRkYzpmb3JtYXQAaW1hZ2UvdGlmZiR7P8MAAAARdEVYdGV4aWY6Q29sb3JTcGFjZQAxD5sCSQAAABh0RVh0ZXhpZjpQaXhlbFhEaW1lbnNpb24AMjUy1w1rXQAAABh0RVh0ZXhpZjpQaXhlbFlEaW1lbnNpb24AMjUySgKKKwAAADh0RVh0aWNjOmNvcHlyaWdodABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnn5V3k3AAAAIXRFWHRpY2M6ZGVzY3JpcHRpb24Ac1JHQiBJRUM2MTk2Ni0yLjFXrdpHAAAAJnRFWHRpY2M6bWFudWZhY3R1cmVyAElFQyBodHRwOi8vd3d3LmllYy5jaBx/AEwAAAA3dEVYdGljYzptb2RlbABJRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0JEU0ipAAAAFXRFWHRwaG90b3Nob3A6Q29sb3JNb2RlADNWArNAAAAAJnRFWHRwaG90b3Nob3A6SUNDUHJvZmlsZQBzUkdCIElFQzYxOTY2LTIuMRwvbAsAAAAVdEVYdHRpZmY6YWxwaGEAYXNzb2NpYXRlZGh/o/4AAAAPdEVYdHRpZmY6ZW5kaWFuAG1zYlR1fXQAAAAUdEVYdHRpZmY6cGhvdG9tZXRyaWMAUkdCsyBJ3wAAABd0RVh0dGlmZjpyb3dzLXBlci1zdHJpcAAyNTKOq7QMAAAALXRFWHR0aWZmOnNvZnR3YXJlAEFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkHuFwnAAAAInRFWHR0aWZmOnRpbWVzdGFtcAAyMDE2OjA3OjI1IDIwOjQ1OjA3sPp7/wAAACh0RVh0eG1wOkNyZWF0ZURhdGUAMjAxNi0wNy0yNVQyMDo0NTowNy0wNDowMA7oPy8AAAAvdEVYdHhtcDpDcmVhdG9yVG9vbABBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gp6wwOCQAAACp0RVh0eG1wOk1ldGFkYXRhRGF0ZQAyMDE2LTA3LTI1VDIwOjQ1OjA3LTA0OjAwhrJQqAAAACh0RVh0eG1wOk1vZGlmeURhdGUAMjAxNi0wNy0yNVQyMDo0NTowNy0wNDowMLoWAxYAAAA5dEVYdHhtcE1NOkRvY3VtZW50SUQAeG1wLmRpZDo3QjMzMzVBNjA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q0YhDLsAAAA5dEVYdHhtcE1NOkluc3RhbmNlSUQAeG1wLmlpZDo3QzMzMzVBNjA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q/XUJN8AAABBdEVYdHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRAB4bXAuZGlkOjdCMzMzNUE2MDcyMDY4MTE4MjJBQTFGQUE5OTU3ODVD79suewAAAABJRU5ErkJggg=="; + string public finiStars2 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURQAAAAAAANP/+//////1oP/ItevdYlAnsvwAAAABdFJOUwBA5thmAAAAAWJLR0QDEQxM8gAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB+cMFhcqHvsaIGYAAAABb3JOVAHPoneaAAAGU0lEQVR42u2cW24jKxCGzQ5cUTvv7uzgeAVIjt8jxQsYaeT9L2Eobg30BRgKyBlRb77ly18UFBTQp9OwYcOGDRs2bNiwYcOGDcs2du6KB+gqvi8euuIB3uDcjS5cP/eTLzw/z9de8gX9bZ7nXs0v4bfbtRMfXS/w6IAeeIbyEX/t5/2PW7fg0/heXY9pfIZ4ygyF+MzAo/RUPp40Q0E2nnSIyMaTZiiG+Qb/YvoPKDMUA23J2kkzFJPk5GiizlBMCWGpeIR/5GUoVEjkLZmhPjIylG1ckn+A6TE6MUMp+HS/Z0TXsfycDKXZaJyGn5Gh2AJHI+GbMTounnlwIn5yiljRsQHi/zQRfoOO+kvxqSlii04gPxG/SUd+GT0xQwnX3zetUH5ihtqjl8pPy1A7ri9v/aQMxXbpxd5PsV3XEwRfioemnvgj8fW9rzpHNzwAwH7o33ldvBpx9uVXbnwlnO/Lr4o3w+0B/lxdvJxcNcbr4dg08R6/VuzhZHoJ+d3or4d3OzzcW+NnVzHceVO8mIlcnQFnv+NXw8+Lfg574W/xtAOAmAVifdDgLzr8pz08I+2BzMfD5Vut7NazfU0lW/Za399u/121u+Hy1PiQr/GMFq8WfwbP4WnxAV/hBZ2yjMt8PCCewxZf4cVknbKOK+sO84K/uHiPj3gmazSEBTrQy59Z59unh3f5As9kgQqHCTL9Sj5K0uK/XbzDl/hZVwkIS2RK/lXWUj4x8vjF4ft4FSji23TydcVN4LkN/6+1fGx7dJXEk9ZHZw3iZuB9OvwtPGnwzRDMtDAEVvL5WZVGZTchE3/CccQMefcFv5LPz2zBk9HZQuCWhP0vlM/P4nuqm9AFnltGs/MMviWf4/fI8TBZ0c6UZ0M+TgJ4DbyhOn3ckz9hDpIrEHI8c5nLCAsQ4gE/xnwjrA7eHeDdvqddDzYTEw75C9PLbyD4XuiZArd0RXW8UHv58iNv0gtgOBHiHcHu5GI951hK7FTs3chz+EtnbItXgaaYOAuwCYHM8wK/tPfO2gpNzAKe3zYGCPGQhH+i2bGXju5FXhqe1Pc/CH9PwZP63om8HnhIw38ueNoFnhP4RxVdi6ctLjrIg34n+V/0eJaMv38qPPHaPhmvPqf1vRv4/wc8bWEVMvHERXWv2yfgiQ+IuV09hpdzLVLf+/h7FE+9oZGFp6/o5+FJ5zknf9SJ4TmQ+97t9lH8RO77EjzFcZIMPK7D3d8SLLTK8MXys/C+7xmUl5cy8BxC8eXFtRL8m9wAaoafAt/rSmgjfEASvs85/FeO93yPVXisrZaVF9Px3OvmtrBdeJrmL/H64OetsLbq46djvOf7WV+NaIT3I4/Zmxl0+MOpJvgbuBpfeC8jGc+DBMNUXb009CAVP0Hww5mgtpuMXw1vJHhvrnewwOYQ7t1rfGHjJ+JhG59zPD+KPzi2AFMYY2bzqxA/peBxCyf8odrIJMVPu+JXU+xi4dKSOj7uowS+J7pl4uNhT/x6hp96KyH81Tl4HcfLPRQCqaeNg7Lx0OeEK1sWtllsXalO8FCtrthbcPAghudqA4mGjonSHyyiy1qkk61sMU9f0/HcHKAk8j0E81N2iDc7OVRnxWCFn47YdgePCo+zc/cCYwRv2oAGz/Td2SS8GwI0eOiKZ3p10A1vZmgpoUfv/OV8kvl7TfHO8aw8PN2wo0Y9i4+mHEq8KYlk4glTTmbGo8Wzt2Cx1hgf1iKb44PWaIpfHTlpjQ9fT03xoaWNO/8sPqnx/1V8UuhXvI+UEvqd8RWvI6WEfk18vPFrXkXrjE9ofNq7GLl4XhUf9T5A3cafIuIn6o1zzyJ4yuJKvnxe4chGIB8i4ut2vaPgo62u7MiP0CvfgbX41+v126eb85kt8A+B/+VchFnKK03wgv4Cz4wz6uInK97Hi9cN8Drnv6TvnbfBxkLVjn+EV/KrXn538P7q+93Ib4B//AS89z70x7/q42EZdLwPmuN9TBv8aRf/3hXPfgi+7iM/5KD/CLt9Mzzbw5ukU/eJHzL0H2G3P6nQrz7Z6o2XjX+Ar/6wGYX/tY2vuspBQ+9v4t8RX/3BpbjSeawCX+Ori5cPfHm8YOOQgpz91aabp4Btv11dvAZtv10fPmzYsGHDhg0bNmzYsGHDhg0bNmzYz7A/n6fsUx4RIvgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjMtMTItMjJUMjM6NDI6MzArMDA6MDARrE+eAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIzLTEyLTIyVDIzOjQyOjMwKzAwOjAwYPH3IgAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyMy0xMi0yMlQyMzo0MjozMCswMDowMDfk1v0AAAAUdEVYdGRjOmZvcm1hdABpbWFnZS90aWZmJHs/wwAAABF0RVh0ZXhpZjpDb2xvclNwYWNlADEPmwJJAAAAGHRFWHRleGlmOlBpeGVsWERpbWVuc2lvbgAyNTLXDWtdAAAAGHRFWHRleGlmOlBpeGVsWURpbWVuc2lvbgAyNTJKAoorAAAAOHRFWHRpY2M6Y29weXJpZ2h0AENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueflXeTcAAAAhdEVYdGljYzpkZXNjcmlwdGlvbgBzUkdCIElFQzYxOTY2LTIuMVet2kcAAAAmdEVYdGljYzptYW51ZmFjdHVyZXIASUVDIGh0dHA6Ly93d3cuaWVjLmNoHH8ATAAAADd0RVh0aWNjOm1vZGVsAElFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQkRTSKkAAAAVdEVYdHBob3Rvc2hvcDpDb2xvck1vZGUAM1YCs0AAAAAmdEVYdHBob3Rvc2hvcDpJQ0NQcm9maWxlAHNSR0IgSUVDNjE5NjYtMi4xHC9sCwAAABV0RVh0dGlmZjphbHBoYQBhc3NvY2lhdGVkaH+j/gAAAA90RVh0dGlmZjplbmRpYW4AbXNiVHV9dAAAABR0RVh0dGlmZjpwaG90b21ldHJpYwBSR0KzIEnfAAAAF3RFWHR0aWZmOnJvd3MtcGVyLXN0cmlwADI1Mo6rtAwAAAAtdEVYdHRpZmY6c29mdHdhcmUAQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKQe4XCcAAAAidEVYdHRpZmY6dGltZXN0YW1wADIwMTY6MDc6MjUgMjA6NDU6MDew+nv/AAAAKHRFWHR4bXA6Q3JlYXRlRGF0ZQAyMDE2LTA3LTI1VDIwOjQ1OjA3LTA0OjAwDug/LwAAAC90RVh0eG1wOkNyZWF0b3JUb29sAEFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCnrDA4JAAAAKnRFWHR4bXA6TWV0YWRhdGFEYXRlADIwMTYtMDctMjVUMjA6NDU6MDctMDQ6MDCGslCoAAAAKHRFWHR4bXA6TW9kaWZ5RGF0ZQAyMDE2LTA3LTI1VDIwOjQ1OjA3LTA0OjAwuhYDFgAAADl0RVh0eG1wTU06RG9jdW1lbnRJRAB4bXAuZGlkOkVFMDRGNDlFMDcyMDY4MTE4MjJBQTFGQUE5OTU3ODVDSeZ99QAAADl0RVh0eG1wTU06SW5zdGFuY2VJRAB4bXAuaWlkOkVGMDRGNDlFMDcyMDY4MTE4MjJBQTFGQUE5OTU3ODVD59yEMwAAAEF0RVh0eG1wTU06T3JpZ2luYWxEb2N1bWVudElEAHhtcC5kaWQ6RUUwNEY0OUUwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUPgHF81AAAAAElFTkSuQmCC"; + + string public finiCope1 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAPUExURQAAAAAAAP/1oP///+vdYjRZzFAAAAABdFJOUwBA5thmAAAAAWJLR0QDEQxM8gAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB+cMFhclOkCB2HgAAAABb3JOVAHPoneaAAAD90lEQVR42u2cyZHjMAxFWxkYshMgywm4FIG7lH9Mw00UKVEUD/OBqhn8Sy+Hfv4ACC5N6edHpVKpVCqVSqVSqVQqlUqlUqlUKpVKpVKpVKq/pYm8HpJwKb4Hz9ZaI8JPcCG+i7zdxM8v6YHPi8+RF+FPNd1a3vBXoU98QfM+/Hz2z+Y57TfMe/t8eNsQW/Rb5vnsT208l31qxp6t+C7MM4296cI8k/0r80zFd2mepfimDt7g8dexZ4l+xzxH9GXxUxcPT/40d/Ho5JMw3naFTr4sfrrBg2vvFo9Nfr/w4Xi6wYOTL42/oWNr767ywMmXxt+l3iUfiL+tPGztSeNv6dDSH8Hjkn9f+OJ4YOkPjDt5/AOFHxh3yNIfSb04HpZ8WfzQuMOV/lDhy+Mf/zMeNfKGus6/ix+ii+NBI4/G+Di8aNujQT4Kv9BQ8cPwy5B9DH5y+M8Q/oHCLyPRR+FpDI/pO/62wvKp8NQcDFz4dHmCD/8s8YZCNZz4TPhAX87NgAdv6Lkk/gEPGfhnfKSfmgEPfjN/Ho2YtnfGU/wIfPiC5EahH3aN6MPwhVHjJ0AK/rnwJajALzz4aoyZZ8QTG/758bFPbcY8P8z40F9NvqmW8Yfcg9aaU+Ca2YYU8ONj0Gf39/13Bb4e96B9RjxeMD79AZ9Ljxjx20dwXW8beAc8aodd41PyG10Hha9cGj8Qg3mWcXe6L5SWG59j6mGbrCMnzjcs032Y7w9RjpoPv32A8Oa4qGzRUXjHGdpkoFIvjPd/+h4PbDqC+NBy76OPiv0UZ1mp2Ifp1tzZh90ZjLP93ekK7MpgWmz0T7dwFybTeTZ1T7dw9yXzcXrHPvC2aMZ3yg94WfSw1GqbZ8HTekUHXh7YU07r+tsMPfLuwp5yh2+caPkhAby7MBX47/lELe7AgPhsn1aPqvmUdl4wfP5vhqFv3OakX6QfLBi/2c9xd19fa90Gofi0s34Vj2MdagAafI8yFfBUgED8bLZH8C67Prjt+XT35jx01337MS+CD9DV4Y0Y/u3wsxHJfcKTIH71+M5ynwHfW2/B8V/qrbew+HfE28Nkx4n3xk39HOwvM97urcf1wZUdn7cbJIPfzpWIjAg+TfbuJ3plPPiiaInfHb8sE9628MUMBL0kPLfxZSB48cSIj8nv4cEPB8ySeL/PqPGmaLfo2Md3DsQpJ+PX2jwU7/nvCv+qdtr4B9LqRb7Dr2yx3/h78t0Wr7DP8xjyAb/b53kIuzrkWXf7XO8/yNmngF9Zze/v/Ajb+2yf790XsfrjYmOzT4yvvkjrnPC/tVD8xElP79opPggv/fhBpOAqlUqlUqlUKpVKpVKpVCqVSqVi0R940HytZt6XyQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0xMi0yMlQyMzozNzo1NyswMDowMN12plAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMTItMjJUMjM6Mzc6NTcrMDA6MDCsKx7sAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDIzLTEyLTIyVDIzOjM3OjU4KzAwOjAwDXZP2gAAABR0RVh0ZGM6Zm9ybWF0AGltYWdlL3RpZmYkez/DAAAAEXRFWHRleGlmOkNvbG9yU3BhY2UAMQ+bAkkAAAAYdEVYdGV4aWY6UGl4ZWxYRGltZW5zaW9uADI1MtcNa10AAAAYdEVYdGV4aWY6UGl4ZWxZRGltZW5zaW9uADI1MkoCiisAAAA4dEVYdGljYzpjb3B5cmlnaHQAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55+Vd5NwAAACF0RVh0aWNjOmRlc2NyaXB0aW9uAHNSR0IgSUVDNjE5NjYtMi4xV63aRwAAACZ0RVh0aWNjOm1hbnVmYWN0dXJlcgBJRUMgaHR0cDovL3d3dy5pZWMuY2gcfwBMAAAAN3RFWHRpY2M6bW9kZWwASUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCRFNIqQAAABV0RVh0cGhvdG9zaG9wOkNvbG9yTW9kZQAzVgKzQAAAACZ0RVh0cGhvdG9zaG9wOklDQ1Byb2ZpbGUAc1JHQiBJRUM2MTk2Ni0yLjEcL2wLAAAAFXRFWHR0aWZmOmFscGhhAGFzc29jaWF0ZWRof6P+AAAAD3RFWHR0aWZmOmVuZGlhbgBtc2JUdX10AAAAFHRFWHR0aWZmOnBob3RvbWV0cmljAFJHQrMgSd8AAAAXdEVYdHRpZmY6cm93cy1wZXItc3RyaXAAMjUyjqu0DAAAAC10RVh0dGlmZjpzb2Z0d2FyZQBBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpB7hcJwAAACJ0RVh0dGlmZjp0aW1lc3RhbXAAMjAxNjowNzoyNSAyMDo0NDoxMxYw6cIAAAAodEVYdHhtcDpDcmVhdGVEYXRlADIwMTYtMDctMjVUMjA6NDQ6MTMtMDQ6MDDZz3CcAAAAL3RFWHR4bXA6Q3JlYXRvclRvb2wAQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKesMDgkAAAAqdEVYdHhtcDpNZXRhZGF0YURhdGUAMjAxNi0wNy0yNVQyMDo0NDoxMy0wNDowMFGVHxsAAAAodEVYdHhtcDpNb2RpZnlEYXRlADIwMTYtMDctMjVUMjA6NDQ6MTMtMDQ6MDBtMUylAAAAOXRFWHR4bXBNTTpEb2N1bWVudElEAHhtcC5kaWQ6NzY0OTk4ODUwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUPjqt1QAAAAOXRFWHR4bXBNTTpJbnN0YW5jZUlEAHhtcC5paWQ6RDZEQkI5ODUwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUNA829jAAAAQXRFWHR4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQAeG1wLmRpZDo3NjQ5OTg4NTA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q0pQ/5AAAAAASUVORK5CYII="; + string public finiCope2 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAhUExURQAAAMv7+AAAAP/1oAQEBP///6LKx9X65tX8+aLJxuvdYlvNu3UAAAACdFJOUwDM5EFDowAAAAFiS0dEBfhv6ccAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfnDBYXJTpAgdh4AAAAAW9yTlQBz6J3mgAAA+RJREFUeNrtnDuWmzAUhonPbGI6jpQ0lOBq2sFZgZ2TFeAFuPAO5ngJ4xZ3rDJICJDEQ07xX4XJ/ZrMpJhP/9UD2QKShGEYhmEYhmEYhmEYhmEYhmEYhmEYhmEYhmH+BtESy70ThmjJZVFE8ndyRQx/W/mih95v2yP4HXtR5LT+Xd/vcfJ74bU/jRee1j8Nr/0Rw6vhRxR/NxeeLv5seLr4C3qi+Au1p4q/FJ4o/rKeIv5i7Wnir+kJ4i/XniT+qh4ff6X2Kn5cfQ6u/m5dj44fWy/X9eDqi6AeGj+kB1c/0PXo6j+hR8YP6qHVD807cPVj60MDH1v9DeiB1d+AHlj9Lehx1X9Kj4u/CT2s+pvQw6r/pB4Vfxt6VPU3ogdVfyP6IrY+jarHVH8rekznb0effkF9+ENWP/a+oP6JD9i9HjL2tqLHzLw5famIpi/LWT9GL33NvjycTqdj+R5Hr+3KT6HffffKvC9PHX58mL6cCT+Nj9H/8Lq51GbVCBK9cHOq2h/VwD/41YfpbdH+MOiPBPqkNdnxlb6b92T68hRb/x5Lrybe4RhN//LP6SlHvq+nnfd67Fn6xVUP9QWH183Dmv+rukTQmyve7Vadr44+xehfym7VVfcpmvi/b7fbR3V19LDv1kTXzeZuSbXb0fYzjT65f35Uqm9VC3T8slJcrxeCrk++3T+r80Xr827jWf5s7YULTv96r9qguSr88q4fd6TwenfHOLE+fIqNPUsUTxzkIg/znjjGRh4kh+8hENDqB/USeYwf6ny9JCH1MhAed8nRd2jLUHjoWWIuQuFhux395d5afNM2WOe37rX4pmlI/Vp80zLY2OtGVmjuw8aejjc7+mv7/6B6vdnyRkDtNAnV+cIU1/O/NYJGP0j0CFDtyJqmqb3+QI09y6Ln4EhBobfOFPJMV7xrgjcWCc6RlX5xBqL0wtLXK7tdjD5x9JJePw79TETQC0tfkOvHR2PERC/w+sR+GM+fbQT6/oG46WwX2QOv19s9s9xJN3zWEOgTs8rWwt33CNHQ6KW+ztTutk9ffEj07Z9vOr21CAkVf/yNQj9uu/RPIqPVD5Mv1//m44qYkujN9JsuAQR60U//iR16r7ij14WXRTz9DNDnBFRYX5/LuHprvQc/pKH2e2+t39U35Hrp6B80tdfbzRm9teDjnw/q1x1L/xjCp3D9mzv2lL6hCd9Xf6J/GDs2fPdZQ+mlq2/MIoi1D9V3LvdNF5/ibQAm/kRf07wLY9fFr1093YtA9G7b7nxz3U1J7ONrT4bw1K8gsf0x375CWvV5fxw5wzAMwzAMwzAMwzAMw/y3/AEOkyRH9n6uIgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0xMi0yMlQyMzozNzo1NyswMDowMN12plAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMTItMjJUMjM6Mzc6NTcrMDA6MDCsKx7sAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDIzLTEyLTIyVDIzOjM3OjU4KzAwOjAwDXZP2gAAABR0RVh0ZGM6Zm9ybWF0AGltYWdlL3RpZmYkez/DAAAAEXRFWHRleGlmOkNvbG9yU3BhY2UAMQ+bAkkAAAAYdEVYdGV4aWY6UGl4ZWxYRGltZW5zaW9uADI1MtcNa10AAAAYdEVYdGV4aWY6UGl4ZWxZRGltZW5zaW9uADI1MkoCiisAAAA4dEVYdGljYzpjb3B5cmlnaHQAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55+Vd5NwAAACF0RVh0aWNjOmRlc2NyaXB0aW9uAHNSR0IgSUVDNjE5NjYtMi4xV63aRwAAACZ0RVh0aWNjOm1hbnVmYWN0dXJlcgBJRUMgaHR0cDovL3d3dy5pZWMuY2gcfwBMAAAAN3RFWHRpY2M6bW9kZWwASUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCRFNIqQAAABV0RVh0cGhvdG9zaG9wOkNvbG9yTW9kZQAzVgKzQAAAACZ0RVh0cGhvdG9zaG9wOklDQ1Byb2ZpbGUAc1JHQiBJRUM2MTk2Ni0yLjEcL2wLAAAAFXRFWHR0aWZmOmFscGhhAGFzc29jaWF0ZWRof6P+AAAAD3RFWHR0aWZmOmVuZGlhbgBtc2JUdX10AAAAFHRFWHR0aWZmOnBob3RvbWV0cmljAFJHQrMgSd8AAAAXdEVYdHRpZmY6cm93cy1wZXItc3RyaXAAMjUyjqu0DAAAAC10RVh0dGlmZjpzb2Z0d2FyZQBBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpB7hcJwAAACJ0RVh0dGlmZjp0aW1lc3RhbXAAMjAxNjowNzoyNSAyMDo0NDoxNIhUfGEAAAAodEVYdHhtcDpDcmVhdGVEYXRlADIwMTYtMDctMjVUMjA6NDQ6MTQtMDQ6MDAcaE4SAAAAL3RFWHR4bXA6Q3JlYXRvclRvb2wAQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKesMDgkAAAAqdEVYdHhtcDpNZXRhZGF0YURhdGUAMjAxNi0wNy0yNVQyMDo0NDoxNC0wNDowMJQyIZUAAAAodEVYdHhtcDpNb2RpZnlEYXRlADIwMTYtMDctMjVUMjA6NDQ6MTQtMDQ6MDColnIrAAAAOXRFWHR4bXBNTTpEb2N1bWVudElEAHhtcC5kaWQ6QjMxRDIxODYwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUPjUatrAAAAOXRFWHR4bXBNTTpJbnN0YW5jZUlEAHhtcC5paWQ6QjQxRDIxODYwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUN29PHpAAAAQXRFWHR4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQAeG1wLmRpZDpCMzFEMjE4NjA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q0qriasAAAAASUVORK5CYII="; + + string public finiNeutral1 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURQAAAAAAAP/1oP/ItQUFBevdYv///3ltZJQAAAABdFJOUwBA5thmAAAAAWJLR0QGYWa4fQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB+cMFhcrFXXTyK8AAAABb3JOVAHPoneaAAADiklEQVR42u2a2Y3jMBBEZcABWMYmYK0iWCdgCHQG6/xTWR4WTUri8VPdxmwVMJgZ/jxVsXmJGgaKoiiKoiiKoiiKoiiKoiiKorAavS6acC3+OGryLXUKsn8p0h1f2v4poTu+Jl2cP47XDH8TjX9rXtj+ji5q/7SJXtj+3ryk/SPz1r4U/si8s69pXsz+6dC8WPGNBbxM+qXshdIvZS+EL5oXSb+cvbUvgZ/K+AscXzEvkb4uvpa9QOc38Bcwvpa9TV8ZD06/nr02Ht359ezx+DodXHut7MGdr4xvdT2483Xxzeyx6Wvjm9lDF71210M7v509Et/R9ch5Txvf0fXA0u+pPG08buT1dD2u9LsqD4jvyh5W+sr4vsqDlX4nHlX6fZVHPEKdwx418LXxnYUPGnnEK5a+Mr53zv2heF9W4Rlu+aNsWoknHoAP687u/jpr/bH4gLjtrs+zVuC04y/x1pu8W/Sbt+LwNwsZP9RITFqhK57/TidCkweJl5uYc8ZpaznFJ61gfFrw49E6BMJ7e7/vVn/2ll3zHY739MiPlR/obz7ojLnHf5TiQQf88YO5b+jvpwqPhcJfO/EQei8edb53taeM7yg92Etdm74yPp92tnzcguPU90pZF4+8Trg26cgP17Svkr79GhH70WAzffBXaw376C8mG/bhn+yZq6L54Wxq/F8Gbd6YygdzePySbm+3PT+ZB5R+NuFAVZIAvqYZm77NXhVfTH39jaQXs5+F8aaEfyDxSwEfawKKN038LIKfc/znXyx++hJ8Pv4//8rgN9OP+a/w87bwpy/BY8f9cfZTMiKg+OXQvDDeFPEGO+ebYHEp4GcDLb2zB+/MS+EHRzb7Lc/aYhYs3m7zzd78FEsSjHf7/J33OBTsg4G3use7rfldEwscvxT4xv9g93o1vK8J8EY72e1tq88XBRxfP+aAu14bXz9mobMP854ivpo+PPtq+tjVvmkfn33VvoD5ddFXyt7ZL/CNRPYpPztrCZn3m47AzWKQMh/52Z4Tu8k94qf7/VnO/MpPzMvSB1d/afaS0a/+jXzVp/YXhapP7C+K5rPXbPLms/dc2viHIt48NfGzef1VSD++6VHFz+b5eunhrXkd/BLNa+DDnteb18Evjv5Uwg/+UO/Na1T+m6+HD4/gsn9o4e2246Uy50f7RmXFS/wr0imKoiiKoiiKoiiKoiiKokT1D54wvXwxI1S4AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIzLTEyLTIyVDIzOjQzOjIxKzAwOjAwlLMvigAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMy0xMi0yMlQyMzo0MzoyMSswMDowMOXulzYAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjMtMTItMjJUMjM6NDM6MjErMDA6MDCy+7bpAAAAFHRFWHRkYzpmb3JtYXQAaW1hZ2UvdGlmZiR7P8MAAAARdEVYdGV4aWY6Q29sb3JTcGFjZQAxD5sCSQAAABh0RVh0ZXhpZjpQaXhlbFhEaW1lbnNpb24AMjUy1w1rXQAAABh0RVh0ZXhpZjpQaXhlbFlEaW1lbnNpb24AMjUySgKKKwAAADh0RVh0aWNjOmNvcHlyaWdodABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnn5V3k3AAAAIXRFWHRpY2M6ZGVzY3JpcHRpb24Ac1JHQiBJRUM2MTk2Ni0yLjFXrdpHAAAAJnRFWHRpY2M6bWFudWZhY3R1cmVyAElFQyBodHRwOi8vd3d3LmllYy5jaBx/AEwAAAA3dEVYdGljYzptb2RlbABJRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0JEU0ipAAAAFXRFWHRwaG90b3Nob3A6Q29sb3JNb2RlADNWArNAAAAAJnRFWHRwaG90b3Nob3A6SUNDUHJvZmlsZQBzUkdCIElFQzYxOTY2LTIuMRwvbAsAAAAVdEVYdHRpZmY6YWxwaGEAYXNzb2NpYXRlZGh/o/4AAAAPdEVYdHRpZmY6ZW5kaWFuAG1zYlR1fXQAAAAUdEVYdHRpZmY6cGhvdG9tZXRyaWMAUkdCsyBJ3wAAABd0RVh0dGlmZjpyb3dzLXBlci1zdHJpcAAyNTKOq7QMAAAALXRFWHR0aWZmOnNvZnR3YXJlAEFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkHuFwnAAAAInRFWHR0aWZmOnRpbWVzdGFtcAAyMDE2OjA3OjI1IDIwOjQ0OjU1mz+J8wAAACh0RVh0eG1wOkNyZWF0ZURhdGUAMjAxNi0wNy0yNVQyMDo0NDo1NS0wNDowMD5VS1wAAAAvdEVYdHhtcDpDcmVhdG9yVG9vbABBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gp6wwOCQAAACp0RVh0eG1wOk1ldGFkYXRhRGF0ZQAyMDE2LTA3LTI1VDIwOjQ0OjU1LTA0OjAwtg8k2wAAACh0RVh0eG1wOk1vZGlmeURhdGUAMjAxNi0wNy0yNVQyMDo0NDo1NS0wNDowMIqrd2UAAAA5dEVYdHhtcE1NOkRvY3VtZW50SUQAeG1wLmRpZDpFNjA0RjQ5RTA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q7wk7TUAAAA5dEVYdHhtcE1NOkluc3RhbmNlSUQAeG1wLmlpZDpFNzA0RjQ5RTA3MjA2ODExODIyQUExRkFBOTk1Nzg1Qw/RxVEAAABBdEVYdHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRAB4bXAuZGlkOkU2MDRGNDlFMDcyMDY4MTE4MjJBQTFGQUE5OTU3ODVDFd7P9QAAAABJRU5ErkJggg=="; + string public finiNeutral2 = + "iVBORw0KGgoAAAANSUhEUgAAAPwAAAD8BAMAAAChhdRUAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURQAAAAAAAP/1oP/ItQUFBevdYv///3ltZJQAAAABdFJOUwBA5thmAAAAAWJLR0QGYWa4fQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB+cMFhI3BYjYR30AAAABb3JOVAHPoneaAAADkUlEQVR42u2a3W3jMBCELUAFmEIaME8VnBsIBKaDuP9WTqQkkqL49zK7uXgHSAzr5dPMLimK9O0mEolEIpFIJBKJRCKRSCQSiUQikQgo5XRnpXPdgFKc/EGpSTs9OPiezsIfAt3xGen0fKX0SbTxJ+ap7afmLZ/O/sW8i5/RPKn9jHlC+0PGPKH9rHky+0MB/6DCa82YfsE8VfpFPEn6peyt/TsBfirjCeyrMp4g/Ur2FOlz46cKHp++YsbrmtDpDz8aj06/Wnp2PDr9evarfSh+aOKh6TPjW6UHF58X38wemz43vpn9mj4O3y49tPg9eODE0y49tPi8+I7GR/ZeJ/6Ownd0HhDf0/jA1u/Eo4rfVXpc7/Hi+xof1nt9jc+OB7X+e+P7hj1s4PPie8cdaOC/Ob6z9G+Pv/9evDp9eGT0AZn2BC/47cnzSPHR1d+M3xDXI/ToJiB4dYCm/Z8DZq5ipp0jbqWmR6CG+/BXMUtd5XNWETS6keMqaLEV6uwrH/BxEFh83PAq81sCKP7Pc9XfaxD28nO7BsQ7euB789vlJxA/ZfBeEf4OxD8DJ9Z+V/a2oC9ZbTzqDVf9D3jc3s7U03rMeNy2orpMOykf9sC5db7lIXe0J158h33gaQbzUVLPOR4U37QPPcUcWvbBB/gt++Dz+4Z9+E9X6vY/PsH40VTsfxgDx1d+MLfiwfbN8ijYd4tePL7cdmsuMzb90dQHHjMenP64Zz+XajDT4EspYNM3BbxPgwavkxEQioHF6yb+kwKfpB++MuOhrR+gSe+R48/F91/fA5/Me774WPzCizcFvE+fCX/Yx4770GIX/EKADzVO8HoLBvzIKeM3+9i1rvEtdl302cLM2NWO0UfzXfGzsULS13G/t1h2uWPQ6/yVbfYa65ywSz271lszdn9Zgd3bhfZWYza8LfGiC3govfaShZ90+PH1tyz0C24dD57zrGrpw7OPn/gM2VfTJzBfS5/AfCV9+LaW01iwD9/Vyto3xPTE/rHCpIl+s7/EmRPTV/tR/LMhpid8twCipJ/jN4Zgqr/wo/SJvd/O3U825PL26c2f7DOYj5sfvLbPyjDjv2g2k/IazeubZDengl/48F+v11F9Jvxhnwl/2GcY9w6/2SdZ3+bwL/fg45lzX86+ge+ntPAMdDvrWToL2uHN1zdH1XeNbLl7Ph9cJBKJRCKRSCQSiUQikUgkEom49A+kML18KMdJ/QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0xMi0yMlQxODo1NTowNSswMDowMHXhORsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMTItMjJUMTg6NTU6MDUrMDA6MDAEvIGnAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDIzLTEyLTIyVDE4OjU1OjA1KzAwOjAwU6mgeAAAABR0RVh0ZGM6Zm9ybWF0AGltYWdlL3RpZmYkez/DAAAAEXRFWHRleGlmOkNvbG9yU3BhY2UAMQ+bAkkAAAAYdEVYdGV4aWY6UGl4ZWxYRGltZW5zaW9uADI1MtcNa10AAAAYdEVYdGV4aWY6UGl4ZWxZRGltZW5zaW9uADI1MkoCiisAAAA4dEVYdGljYzpjb3B5cmlnaHQAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55+Vd5NwAAACF0RVh0aWNjOmRlc2NyaXB0aW9uAHNSR0IgSUVDNjE5NjYtMi4xV63aRwAAACZ0RVh0aWNjOm1hbnVmYWN0dXJlcgBJRUMgaHR0cDovL3d3dy5pZWMuY2gcfwBMAAAAN3RFWHRpY2M6bW9kZWwASUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCRFNIqQAAABV0RVh0cGhvdG9zaG9wOkNvbG9yTW9kZQAzVgKzQAAAACZ0RVh0cGhvdG9zaG9wOklDQ1Byb2ZpbGUAc1JHQiBJRUM2MTk2Ni0yLjEcL2wLAAAAFXRFWHR0aWZmOmFscGhhAGFzc29jaWF0ZWRof6P+AAAAD3RFWHR0aWZmOmVuZGlhbgBtc2JUdX10AAAAFHRFWHR0aWZmOnBob3RvbWV0cmljAFJHQrMgSd8AAAAXdEVYdHRpZmY6cm93cy1wZXItc3RyaXAAMjUyjqu0DAAAAC10RVh0dGlmZjpzb2Z0d2FyZQBBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpB7hcJwAAACJ0RVh0dGlmZjp0aW1lc3RhbXAAMjAxNjowNzoyNSAyMDo0NDo1NZs/ifMAAAAodEVYdHhtcDpDcmVhdGVEYXRlADIwMTYtMDctMjVUMjA6NDQ6NTUtMDQ6MDA+VUtcAAAAL3RFWHR4bXA6Q3JlYXRvclRvb2wAQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKesMDgkAAAAqdEVYdHhtcDpNZXRhZGF0YURhdGUAMjAxNi0wNy0yNVQyMDo0NDo1NS0wNDowMLYPJNsAAAAodEVYdHhtcDpNb2RpZnlEYXRlADIwMTYtMDctMjVUMjA6NDQ6NTUtMDQ6MDCKq3dlAAAAOXRFWHR4bXBNTTpEb2N1bWVudElEAHhtcC5kaWQ6NTZEQ0JGOUUwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUPZ0un9AAAAOXRFWHR4bXBNTTpJbnN0YW5jZUlEAHhtcC5paWQ6NTdEQ0JGOUUwNzIwNjgxMTgyMkFBMUZBQTk5NTc4NUNqJ8GZAAAAQXRFWHR4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQAeG1wLmRpZDo1NkRDQkY5RTA3MjA2ODExODIyQUExRkFBOTk1Nzg1Q3Aoyz0AAAAASUVORK5CYII="; + + string public font = + "@font-face {font-family: '8bit'; src: url('data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAAkAAA4AAAAAKTwAAAilAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGiAGVgCDBggEEQgKwjjGOAuBHgABNgIkA4I4BCAFg0UHgVMbpSmjopQRekdRJckVapVggzHtBy8Yj2OZf2FhXcoV3GiRO1dkGn5jLMnlcwtne2fO9t7b83j4fr//rX3kvi+mSdCQGOKXiEeSWSc0axaahUoVDY0hqqY3eG5F0I8O0NIT3+Dv3TWWWJTttS2y72lCUTS2SPELGPj6bOlvydCvanb27sS0JozMkTkSQ2aEf/t77OS/QmcKrUjFdiYXqLa8umKJUE1v2PI/93v5R1k01j3nC/6vJfSFXuT9nasCT1rjwHYmFBWa2EADHG1uI24Qnf+1rNmbq2piFBZjs0KloNRUb7+ipgZyQ3ZY1GGp/q/nb+qZDT2kqBDGo9UhcXu/X/tvQ7TuMndRUoIJR5qQozzi9fXWZ4aAt5dTCeDJZs0K8O54+NDQgwETEgoSAoGIX7QfWKTb6AW43rrD+Ah58EiaDCALCWDflUULFnQPhjZwIoOAMKB3kRm15BpGdavFSTBkQJasKplddqiFB3sQAliwpjeqen2N7Re5kgs5k8OXehNEMwTePyugaoiGAERCAnYKcqE2oLV2sv9FUao6Zvyh6v/R4tKKVAK9u3ffdoSeOADYuy9O0iwvykpveGt6P4y2eVld+3H6APjaFPSWv3Ep/Yg2AR3sAO7hGzBwCgaHAD0AICkGhCSEDllotCRVlbUUjSRkIYQihKwISVKEIqlCVmWdqiLLqiQLRZYUA1qJoYXpK3YarUlAOMdnM0dv3ZHuHMkqIW07Fm+pZkO/uZeXpZvMvEApylSW+71B3unN1pzO0svZIu3VZRZT61yv6+b06PTnT7LB6Oj4h1VNpu5n3u8WF7tR0L6ZHl0zvw9KzcHDOjlZpFo1MK2QoBlNYVbGF+swoQwmPubqBoN5PS7rWXrvtvHUQOgGEmcPVgaLpdZyv+j+YLWmsub0/Fuf1PUSTtCbr5M5+dOKsGFbAWYb+5kipIswPWAJGyTYl4XIdit9If1+VcLRq7ZUGF9CVurA029kJQ0Sxxp2SXFuiE01UjGPXexk5+JFP0tWHmksuwVgRO+qsCPQW7LjsiCjOd5ubDyDgL8lkEBITNa+KSGzC9qvF2mYKyFkEVDZsQV3hcrwHM47c9ppVQVN+svJP7PRt6LeaQX96XOkpQg64gtIbwTBtr06BiQoKbjGNZbwes1woeU5Z7Q2HAT9DjRBvB2ew2wJhGeYo5CHjDXnoWmn6UFaKGgpHjlfOi2GGPSTZViLDRq9EyxewUX7nSAWbQnrKnDeSv5oHE4c3yERkQDj0opDxl3TDsi1miWD+jbRILi5eVPTy/YblATp1DGiPiDF8SCdFlltQofEnB8OgUEh1S4TrAyu6ldlRVbwhE6T+hROHGqAi9M6AKMp9BJg3UcQ5uREkvGt+ltUKsgvPoL0tIPUhtC0CZeUAaShaLJKQAhEPARGrs7dAoVM5jZosVlL/BheiOMxJkeASIOE3P/bJLKlPzpaVdvFeYmNEAJCDFmIoBvNHCCzpO2957wU4YbjDS0xgC7CCfKLKrLj/Y8avXiGTwtSFsBlAsvb8YZavX0NQPMZ7Cy2UlgBRX2hVZ8uKGdUh9LI7JjS08hxh9uGNuYhdWWLuERmXaRF2HNz1sIv246YUxMH1prmK8Jeg/jCXlFwOIaRkRtmS3jBw7tj25j9V2I3RSAXwpatn0mYPtgxhreTg+PNESqYzrxPnJWzkZveZ1J6fcC/pUz4bV9YfwEHSGk/d9THjN7eyi+iZlQGBlVzaOxersw8uyRsDMj2dTZd1JCc68T/IixEOK5CQPDSPZ5EWIHYbiu30etM3MS+O3zOmWbE7WhymKMd+15QRHB1o4gUmAMbXzcu4kMSeK4UniHUFTruscTuXhnEGGTknubOVQGXJ0JewCOP85806emzX1Its+NHspPx7bMxNHhygICACNMR1mR64CXKWwKdSfHhw7e93K7ZLaxCUzS/AsV+zJthkB6C86Z1g2TJV72ZLJLDBkaS+7d2McDAiWJNpkbMRmF0gLgCpmA1w94Cw1FB+24bcHao0JNLQgc3IEa+Fv12fWcU926pvJAGCFwc7YOM4P28UwKdScJNiegD3bHvkX5apuORpr+pkHHQh0gfS71rb3rSQhPY06ZhgsA4NhmcHerdOndcbkKS/x+W8rCdRdcruPbmA8LQLs3JhwaCS/ZsH+asVur/+/bORK/UeoR/AKdARc3MiozgnLSgJmuoKESiueZDIXgn5sr63uPYCmzTdEdcfkwE37MrvGzt7ZowLUiRhFuP7e6YxkHxABSFGwep3/U2tFofd/qiZUeVOsfqfeIIcKGw9eEtBj6XmDgOenhn11FFBksliY7IEtCPjtOA1DXcdJWvcttkh6Nuwh1m385RryIxxIfQ4zbNc6XiHtCl8YEwUuwHUpJoVqSBeHG81SaZJM70y7OhuaFPnQwh46uHmmlAKwgNfz3nV1GQe79crqCtWMvRD0OlwBuM5uV5l+/37/nzvz9u/KKVG8vpY4B7KwS/GkWbzHKaAIyfwhvFL0LAwCU4ja5GDET3zrwEodBcHZmWRCNII33YstqsUgxHyI41oI1iak3nSCOirQfDIcJ6FkMxhWPAtiS0AqHkyPfznjHa8QYQwMe/F1g4GQHoKwDo80hQKOYgdDoASeMoZBNuQdHpMVT93m/f6IxeWZROzLCBMGQDkh6bkO1RgWLIfajmPBWa3kv/D7uZqpaFmbERt6qhrpE2HLxUvV7Mxr/c1WH3VxvYFrWm+qUi/oWceDGY1FmqqaFraQj/KXD0cDKCzu6AjmO63bAoSA51z8x1r1bg2/PhlBIJIvi9iY49oQfDwkwlELK9x36f5A4ISVa0dOjTb8CgIcNGjBozbsKkKdNmLJEUjc5gYrE5XB5fQEpGTkFJRU1DS6fWaC2cXNw8vHz8AoJCwiKiYuISklLSMrJy8h5voMhRWlfPdZ+OEvIXCMmuphdBAAAA') format('woff2');}"; + + string[] public colors = ["5dd5a5", "6ab4ff", "ff8445", "f1b3d1", "ffd88d", "efaf35", "ff564c", "24fff7"]; + + function tokenURI(uint256 tokenId) public view returns (string memory) { + (int256 delta, int256 price, AggregatorV2V3Interface feed) = getDataForTokenId(tokenId); + + string memory textSnippets = getStringsForData(delta, price, feed); + + string memory image1; + string memory image2; + + if (delta > 100) { + image1 = finiStars1; + image2 = finiStars2; + } else if (delta < -100) { + image1 = finiCope1; + image2 = finiCope2; + } else { + image1 = finiNeutral1; + image2 = finiNeutral2; + } + + string memory svg = string( + abi.encodePacked( + svgPart1, + font, + svgPart2, + getColorForTokenId(tokenId), + textSnippets, + image1, + svgPart6, + image2, + getBottomOfSVG() + ) + ); + return svg; + } + + function getDataForTokenId(uint256 tokenId) public view returns (int256, int256, AggregatorV2V3Interface) { + return getDataForFeedAddress(getOracleAddressForTokenId(tokenId)); + } + + /** + * @notice Get a % delta for a given feed (oracle) address + * @return delta This is a % delta * 100, i.e. it must be divided by 100 to + * get the real %. We're doing this to preserve some precision with the calculation. + */ + function getDataForFeedAddress(address feedAddress) public view returns (int256, int256, AggregatorV2V3Interface) { + // first fetch the current price + AggregatorV2V3Interface feed = AggregatorV2V3Interface(feedAddress); + int256 latestPrice = feed.latestAnswer(); + + // fetch the corresponding previous roundId from 24 hours prior + uint80 roundId = getPreviousRoundId(feed); + + // now derive the previous price from the roundId + (, int256 previousPrice, , , ) = feed.getRoundData(roundId); + + // compare the two + int256 delta = ((latestPrice - previousPrice) * 10000) / previousPrice; + + return (delta, latestPrice, feed); + } + + function getStringsForData( + int256 delta, + int256 latestPrice, + AggregatorV2V3Interface feed + ) public view returns (string memory) { + string memory textOne; + + string memory textTwo = string( + abi.encodePacked("It's $", intToStringWithDecimals(latestPrice, feed.decimals())) + ); + string memory feedName = feed.description(); + + string memory deltaString = intToStringWithDecimals(delta, 2); + + if (delta > 100) { + textOne = string(abi.encodePacked("Yay, ", feedName, " is up ", deltaString, "%!!")); + } else if (delta < -100) { + textOne = string(abi.encodePacked("Oh no, ", feedName, " is down ", deltaString, "%?!")); + } else { + textOne = string(abi.encodePacked("Hm, ", feedName, " has moved ", deltaString, "%")); + } + + string memory svgSnippet = string(abi.encodePacked(svgPart3, textOne, svgPart4, textTwo, svgPart5)); + + return svgSnippet; + } + + /** + * @notice Get the roundId for this feed from 24 hours prior. + * The roundId is used later to fetch an historical price. + * @dev Keeping this separate so we can debug on mainnet if needed. + * @param feed contract + * @return roundId + */ + function getPreviousRoundId(AggregatorV2V3Interface feed) public view returns (uint80) { + IFiniOracle finiOracle = IFiniOracle(finiOracleContract); + // grab the latest roundId + uint80 latestRound = uint80(feed.latestRound()); + + // recurse to find the previous roundId for our frequency + uint80 roundId = finiOracle.findRoundId( + block.timestamp - 86400, // target unix timestamp for 24 hrs ago + feed, + latestRound, + 0, + 12, // jump sizes for 24 hours + false + ); + + return roundId; + } + + function getBottomOfSVG() public view returns (string memory) { + // return string(abi.encodePacked(svgPart7, finiLogo, highlightLogo, '/svg')); + return string(abi.encodePacked(svgPart7, "")); + } + + /** + * @notice Returns oracle address for a tokenId. + * @dev This will be used to determine which token is assigned to which oracle. + */ + function getOracleAddressForTokenId(uint256 tokenId) public view returns (address) { + uint256 index = pseudoRandom(tokenId) % oracleAddresses.length; + return oracleAddresses[index]; + } + + function getColorForTokenId(uint256 tokenId) public view returns (string memory) { + uint256 index = pseudoRandom(tokenId) % colors.length; + return colors[index]; + } + + /** + * @notice Get a pseudorandom idempotent value for each tokenId. + * @dev This will be used to determine which token is assigned to which oracle. + */ + function pseudoRandom(uint256 tokenId) public pure returns (uint) { + return uint(keccak256(abi.encodePacked(tokenId))); + } + + function intToStringWithDecimals(int256 value, uint8 decimals) public pure returns (string memory) { + // Check if the value is negative + bool negative = value < 0; + + // Get what should come before the decimal + uint256 intValue = negative ? uint256(-value) : uint256(value); + // string memory intPart = toString(intValue); + uint256 intPart = intValue / 10 ** decimals; + + // Get what should come after the decimals + uint256 fractionalPart = (intValue - (intPart * 10 ** decimals)); + + // Concatenate the integer and fractional parts + string memory result; + if (negative) { + result = string(abi.encodePacked("-", intPart.toString(), ".", fractionalPart.toString())); + } else { + result = string(abi.encodePacked(intPart.toString(), ".", fractionalPart.toString())); + } + + return result; + } +} diff --git a/contracts/erc721/custom/[untitled]/CuratedRendererV1.sol b/contracts/erc721/custom/[untitled]/CuratedRendererV1.sol new file mode 100644 index 0000000..0b7d2f0 --- /dev/null +++ b/contracts/erc721/custom/[untitled]/CuratedRendererV1.sol @@ -0,0 +1,484 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "../../inchain-rendering/interfaces/IHLRenderer.sol"; +import "../../interfaces/IERC721GeneralSupplyMetadata.sol"; +import "../interfaces/IHLFS.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; +import "../interfaces/IOwnable.sol"; + +/** + * @notice HL in-chain Renderer for curated hash based projects + * @dev Currently supports a fairly fixed configuratoin + * @author highlight.xyz + */ +contract CuratedRendererV1 { + /** + * @notice Throw when invalid input to process mint data + */ + error InvalidMintData(); + + /** + * @notice Throw when seed input for a token cannot be found + */ + error SeedInputNotFound(); + + /** + * @notice Throw when transaction sender isn't collection owner + */ + error NotCollectionOwner(); + + /** + * @notice Input that seeds token metadata + * @param previousBlockHash Hash of the block before the one the tokens were minted on + * @param blockTimestamp Timestamp of block that tokens were minted on + * @param startTokenId ID of first token of minted batch + */ + struct SeedInput { + bytes32 previousBlockHash; + uint48 blockTimestamp; + uint176 startTokenId; + uint32 numMinted; + } + + /** + * @notice Simple collection config (to be made more complex) + */ + struct CollectionConfig { + string name; + string previewsBaseUri; + string htmlLang; + bool htmlBodyExpected; + bool useCDN; + } + + uint256 private constant _32_BIT_MAX_MINUS_1 = 2 ** 32 - 1; + + /** + * @notice Store the seed inputs for each token batch (for each nft contract) + * @dev Assume startTokenIds are incrementing (up to implementer), assume first batch's startTokenId is 1 + */ + mapping(address => SeedInput[]) public collectionSeedInputs; + + /** + * @notice Store config per collection + */ + mapping(address => CollectionConfig) public collectionConfig; + + /** + * @notice Set a collection's config + */ + function setCollectionConfig(CollectionConfig calldata config, address collection) external { + if (IOwnable(collection).owner() == msg.sender || msg.sender == collection) { + collectionConfig[collection] = config; + } else { + _revert(NotCollectionOwner.selector); + } + } + + /** + * @notice See {IHlRenderer-processOneRecipientMint} + */ + function processOneRecipientMint(uint256 firstTokenId, uint256 numTokens, address recipient) external { + collectionSeedInputs[msg.sender].push( + SeedInput(blockhash(block.number - 1), uint48(block.timestamp), uint176(firstTokenId), uint32(numTokens)) + ); + } + + /** + * @notice See {IHLRenderer-tokenURI} + */ + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { + address collection = msg.sender; + SeedInput memory _seedInput = getSeedInput(tokenId, collection); + bytes32 curatedHash = getCuratedHash(tokenId, collection); + + string memory tokenIdStr = Strings.toString(tokenId); + + bytes[] memory metadata = new bytes[](3); + bytes[] memory encodedJson = new bytes[](2); + + // inject values in JS + string memory injectedToken = string( + abi.encodePacked( + 'const injectedToken = {"blockHash": "', + Strings.toHexString(uint256(_seedInput.previousBlockHash)), + '", "', + 'tokenId": "', + tokenIdStr, + '", "', + 'timestamp": "', + Strings.toString(_seedInput.blockTimestamp), + '", "', + 'hash": "', + Strings.toHexString(uint256(curatedHash)), + '", "', + 'isCurated": "1"', + "};" + ) + ); + + metadata[0] = abi.encodePacked( + '{"name": "', + collectionConfig[collection].name, + " #", + tokenIdStr, + '", "', + 'description": "', + IHLFS(collection).fileContents("description.txt"), + '", "', + 'image": "', + collectionConfig[collection].previewsBaseUri, + "/", + Strings.toString(tokenId), + ".png", + '", "' + 'animation_url": "data:text/html;base64,' + ); + metadata[1] = bytes(Base64.encode(_generateHTML(collection, injectedToken, false))); + metadata[2] = bytes('"}'); + + encodedJson[0] = bytes("data:application/json;base64,"); + encodedJson[1] = bytes(Base64.encode(concat(metadata))); + return string(concat(encodedJson)); + } + + /** + * @notice See {IHLRenderer-tokenURI} + */ + function tokenURIWithCDN(uint256 tokenId, address collection) public view virtual returns (string memory) { + SeedInput memory _seedInput = getSeedInput(tokenId, collection); + bytes32 curatedHash = getCuratedHash(tokenId, collection); + + string memory tokenIdStr = Strings.toString(tokenId); + + bytes[] memory metadata = new bytes[](3); + bytes[] memory encodedJson = new bytes[](2); + + // inject values in JS + string memory injectedToken = string( + abi.encodePacked( + 'const injectedToken = {"blockHash": "', + Strings.toHexString(uint256(_seedInput.previousBlockHash)), + '", "', + 'tokenId": "', + tokenIdStr, + '", "', + 'timestamp": "', + Strings.toString(_seedInput.blockTimestamp), + '", "', + 'hash": "', + Strings.toHexString(uint256(curatedHash)), + '", "', + 'isCurated": "1"', + "};" + ) + ); + + metadata[0] = abi.encodePacked( + '{"name": "', + collectionConfig[collection].name, + " #", + tokenIdStr, + '", "', + 'description": "', + IHLFS(collection).fileContents("description.txt"), + '", "', + 'image": "', + collectionConfig[collection].previewsBaseUri, + "/", + Strings.toString(tokenId), + ".png", + '", "' + 'animation_url": "data:text/html;base64,' + ); + metadata[1] = bytes(Base64.encode(_generateHTML(collection, injectedToken, true))); + metadata[2] = bytes('"}'); + + encodedJson[0] = bytes("data:application/json;base64,"); + encodedJson[1] = bytes(Base64.encode(concat(metadata))); + return string(concat(encodedJson)); + } + + /** + * @notice Get a token's curated hash + */ + function getCuratedHash(uint256 tokenId, address collection) public view returns (bytes32) { + bytes32[] memory curatedHashes = _parseCuratedHashText( + IHLFS(collection).fileContents("curatedHashes.txt"), + IERC721GeneralSupplyMetadata(collection).limitSupply() + ); + uint256 initialCuratedHashesLength = curatedHashes.length; + bytes32 lastCuratedHash = bytes32(0); + + for (uint256 i = 0; i < tokenId; i++) { + SeedInput memory _seedInput = getSeedInput(i + 1, collection); + bytes32 seed = _getSeed(_seedInput, i + 1); + uint256 generatedIndex = _prngSeedInput( + seed, + _seedInput.previousBlockHash, + i + 1, + initialCuratedHashesLength - i + ); + uint256 virtualIndexPlusOne = 0; + + for (uint256 j = 0; j < initialCuratedHashesLength; j++) { + if (curatedHashes[j] != bytes32(0)) { + virtualIndexPlusOne += 1; + if (virtualIndexPlusOne == generatedIndex + 1) { + // curated hash found for final token id + if (i == tokenId - 1) { + lastCuratedHash = curatedHashes[j]; + } + curatedHashes[j] = bytes32(0); + break; + } + } + } + } + + return lastCuratedHash; + } + + /** + * Get a token's seed + */ + function getSeed(uint256 tokenId, address collection) public view returns (bytes32) { + SeedInput memory _seedInput = getSeedInput(tokenId, collection); + return _getSeed(_seedInput, tokenId); + } + + /** + * @notice Get a token's seed input + */ + function getSeedInput(uint256 tokenId, address collection) public view returns (SeedInput memory) { + SeedInput[] memory _seedInputs = collectionSeedInputs[collection]; + uint256 numInputs = _seedInputs.length; + if (numInputs == 0) { + _revert(SeedInputNotFound.selector); + } + for (uint256 i = numInputs - 1; i >= 0; i--) { + if (tokenId >= _seedInputs[i].startTokenId) { + // assume first batch's startTokenId is 1 + if (_seedInputs[i].startTokenId + _seedInputs[i].numMinted <= tokenId) { + _revert(SeedInputNotFound.selector); + } else { + return _seedInputs[i]; + } + } + } + } + + /** + * @notice Generate the project's HTML file + */ + function _generateHTML( + address collection, + string memory injectedToken, + bool useCDNOverride + ) public view returns (bytes memory) { + bytes[] memory html = new bytes[](4); + + bool useCDN = useCDNOverride ? true : collectionConfig[collection].useCDN; + string memory libScriptPrefix = useCDN ? ' src="' : ">"; + string memory libScriptSuffix = useCDN ? '">' : ""; + html[0] = abi.encodePacked( + '', + "", + IHLFS(collection).fileContents("headPrefix.html"), + "", + "", + "" + ); + + string memory HTMLBody = ""; + if (collectionConfig[collection].htmlBodyExpected) { + HTMLBody = IHLFS(collection).fileContents("body.html"); + } + html[3] = abi.encodePacked("", HTMLBody, "", ""); + + return concat(html); + } + + /** + * @notice Base64 Encode a metadata JSON + */ + function _encodeMetadataJSON(bytes memory json) private pure returns (string memory) { + return string(abi.encodePacked("data:application/json;base64,", Base64.encode(json))); + } + + /** + * @notice Concatenate byte arrays + */ + function concat(bytes[] memory arrays) public pure returns (bytes memory) { + uint totalLength = 0; + for (uint i = 0; i < arrays.length; i++) { + totalLength += arrays[i].length; + } + + bytes memory result = new bytes(totalLength); + uint resultPtr; + assembly { + resultPtr := add(result, 0x20) + } + + for (uint i = 0; i < arrays.length; i++) { + bytes memory array = arrays[i]; + uint arrayLength = array.length; + + uint arrayPtr; + assembly { + arrayPtr := add(array, 0x20) + } + + // Efficiently copy memory block + for (uint j = 0; j < arrayLength; j += 32) { + assembly { + let chunk := mload(add(arrayPtr, j)) + mstore(add(resultPtr, j), chunk) + } + } + + resultPtr += arrayLength; + } + + return result; + } + + /** + * @notice Parse out all curated hashes for a mint + */ + function _parseCuratedHashText( + string memory curatedHashesText, + uint256 numLines + ) private pure returns (bytes32[] memory) { + bytes32[] memory curatedHashes = new bytes32[](numLines); + + uint256 arrayIndex = 0; + bytes memory stringBytes = bytes(curatedHashesText); + uint256 i = 0; + + while (i < stringBytes.length && arrayIndex < numLines) { + // Skip the "0x" prefix at the start of each line + if (i == 0 || stringBytes[i - 1] == "\n") { + i += 2; + } + + bytes32 line; + for (uint j = 0; j < 32; j++) { + // Convert two hex characters to one byte + bytes1 b1 = _parseHexChar(stringBytes[i]); + bytes1 b2 = _parseHexChar(stringBytes[i + 1]); + line |= bytes32((uint8(b1) * 16 + uint8(b2)) * 2 ** (8 * (31 - j))); + i += 2; + } + + curatedHashes[arrayIndex] = line; + arrayIndex++; + + // Skip the newline character + if (i < stringBytes.length && stringBytes[i] == "\n") { + i++; + } + } + + return curatedHashes; + } + + /** + * @notice Parse a byte value represented in string to the byte representation of the value + */ + function _parseHexChar(bytes1 char) internal pure returns (bytes1) { + if (uint8(char) >= 48 && uint8(char) <= 57) { + return bytes1(uint8(char) - 48); // 0-9 + } + if (uint8(char) >= 65 && uint8(char) <= 70) { + return bytes1(uint8(char) - 55); // A-F + } + if (uint8(char) >= 97 && uint8(char) <= 102) { + return bytes1(uint8(char) - 87); // a-f + } + revert("Invalid hex character"); + } + + /** + * @notice Generate an index via a prng, given seed input and a max value + */ + function _prngSeedInput( + bytes32 generalHash, + bytes32 blockHash, + uint256 tokenId, + uint256 max + ) private pure returns (uint256) { + uint256 seed = tokenId; + + // process each byte of generalHash and blockHash + for (uint256 i = 0; i < 32; i++) { + // extract and process high and low nibbles for generalHash + uint8 highNibbleTx = uint8(generalHash[i]) >> 4; + uint8 lowNibbleTx = uint8(generalHash[i]) & 0x0F; + if (highNibbleTx <= 9) { + seed += highNibbleTx; + } + if (lowNibbleTx <= 9) { + seed += lowNibbleTx; + } + + // extract and process high and low nibbles for blockHash + uint8 highNibbleBlock = uint8(blockHash[i]) >> 4; + uint8 lowNibbleBlock = uint8(blockHash[i]) & 0x0F; + if (highNibbleBlock <= 9) { + seed += highNibbleBlock; + } + if (lowNibbleBlock <= 9) { + seed += lowNibbleBlock; + } + } + + uint256 t = (seed + 0x6D2B79F5) & (_32_BIT_MAX_MINUS_1); + t = imul(t ^ (t >> 15), t | 1) & (_32_BIT_MAX_MINUS_1); + t ^= (t + imul(t ^ (t >> 7), t | 61)) & (_32_BIT_MAX_MINUS_1); + t = (t ^ (t >> 14)) & (_32_BIT_MAX_MINUS_1); + return t % max; + } + + /** + * @notice Replicate js Math.imul + */ + function imul(uint256 a, uint256 b) private pure returns (uint256) { + return (a * b) % (_32_BIT_MAX_MINUS_1 + 1); + } + + /** + * @notice Get a token's seed util + */ + function _getSeed(SeedInput memory _seedInput, uint256 tokenId) private view returns (bytes32) { + return keccak256(abi.encodePacked(_seedInput.previousBlockHash, tokenId, _seedInput.blockTimestamp)); + } + + /** + * @notice Efficient revert + */ + function _revert(bytes4 errorSelector) private pure { + assembly { + mstore(0x00, errorSelector) + revert(0x00, 0x04) + } + } +} diff --git a/contracts/erc721/custom/[untitled]/TestRead.sol b/contracts/erc721/custom/[untitled]/TestRead.sol new file mode 100644 index 0000000..6ff1b5b --- /dev/null +++ b/contracts/erc721/custom/[untitled]/TestRead.sol @@ -0,0 +1,154 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +library Bytecode { + error InvalidCodeAtRange(uint256 _size, uint256 _start, uint256 _end); + + /** + @notice Generate a creation code that results on a contract with `_code` as bytecode + @param _code The returning value of the resulting `creationCode` + @return creationCode (constructor) for new contract + */ + function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) { + /* + 0x00 0x63 0x63XXXXXX PUSH4 _code.length size + 0x01 0x80 0x80 DUP1 size size + 0x02 0x60 0x600e PUSH1 14 14 size size + 0x03 0x60 0x6000 PUSH1 00 0 14 size size + 0x04 0x39 0x39 CODECOPY size + 0x05 0x60 0x6000 PUSH1 00 0 size + 0x06 0xf3 0xf3 RETURN + + */ + + return abi.encodePacked(hex"63", uint32(_code.length), hex"80_60_0E_60_00_39_60_00_F3", _code); + } + + /** + @notice Returns the size of the code on a given address + @param _addr Address that may or may not contain code + @return size of the code on the given `_addr` + */ + function codeSize(address _addr) internal view returns (uint256 size) { + assembly { + size := extcodesize(_addr) + } + } + + /** + @notice Returns the code of a given address + @dev It will fail if `_end < _start` + @param _addr Address that may or may not contain code + @param _start number of bytes of code to skip on read + @param _end index before which to end extraction + @return oCode read from `_addr` deployed bytecode + + Forked from: https://gist.github.com/KardanovIR/fe98661df9338c842b4a30306d507fbd + */ + function codeAt(address _addr, uint256 _start, uint256 _end) internal view returns (bytes memory oCode) { + uint256 csize = codeSize(_addr); + if (csize == 0) return bytes(""); + + if (_start > csize) return bytes(""); + if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end); + + unchecked { + uint256 reqSize = _end - _start; + uint256 maxSize = csize - _start; + + uint256 size = maxSize < reqSize ? maxSize : reqSize; + + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + oCode := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(oCode, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(oCode, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(oCode, 0x20), _start, size) + } + } + } +} + +contract TestRead { + address[] public chunks; + + constructor(address[] memory _chunks) { + chunks = _chunks; + } + + function readLibrary() external view returns (string memory lib) { + uint256 size; + uint ptr = 0x20; + address currentChunk; + unchecked { + // solhint-disable-next-line no-inline-assembly + assembly { + lib := mload(0x40) + } + + // Copy chunks from storage into memory + for (uint i = 0; i < chunks.length; i++) { + currentChunk = chunks[i]; + size = Bytecode.codeSize(currentChunk) - 1; + + // solhint-disable-next-line no-inline-assembly + assembly { + extcodecopy(currentChunk, add(lib, ptr), 1, size) + } + ptr += size; + } + + // solhint-disable-next-line no-inline-assembly + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + // new "memory end" including padding + mstore(0x40, add(lib, and(add(ptr, 0x1f), not(0x1f)))) + // store length in memory + mstore(lib, sub(ptr, 0x20)) + } + } + return lib; + } + + function fileContents() external view returns (string memory) { + uint256 chunksLength = chunks.length; + string memory contents = ""; + + for (uint256 i = 0; i < chunksLength; i++) { + contents = string( + abi.encodePacked(contents, string(_readBytecode(chunks[i], 1, chunks[i].code.length - 1))) + ); + } + + return contents; + } + + /** + * @notice Read bytecode at an address + * @ author SOLMATE + */ + function _readBytecode(address pointer, uint256 start, uint256 size) private view returns (bytes memory data) { + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + data := mload(0x40) + + // Update the free memory pointer to prevent overriding our data. + // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). + // Adding 31 to size and running the result through the logic above ensures + // the memory pointer remains word-aligned, following the Solidity convention. + mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) + + // Store the size of the data in the first 32 byte chunk of free memory. + mstore(data, size) + + // Copy the code into memory right after the 32 bytes we used to store the size. + extcodecopy(pointer, add(data, 32), start, size) + } + } +} diff --git a/contracts/erc721/custom/interfaces/IBaseURI.sol b/contracts/erc721/custom/interfaces/IBaseURI.sol new file mode 100644 index 0000000..c007bda --- /dev/null +++ b/contracts/erc721/custom/interfaces/IBaseURI.sol @@ -0,0 +1,12 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @notice Interface for gen series base uri + */ +interface IBaseURI { + /** + * @notice Return base uri + */ + function baseURI() external view returns (string memory); +} diff --git a/contracts/erc721/custom/interfaces/IFiniOracle.sol b/contracts/erc721/custom/interfaces/IFiniOracle.sol new file mode 100644 index 0000000..79e4367 --- /dev/null +++ b/contracts/erc721/custom/interfaces/IFiniOracle.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 + +/// @title IDescriptor interface + +pragma solidity 0.8.10; +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol"; + +interface IFiniOracle { + function findRoundId( + uint256 targetTimestamp, + AggregatorV2V3Interface feed, + uint80 roundId, + uint16 counter, + uint16 jumpSize, + bool jumpDirection + ) external view returns (uint80); +} diff --git a/contracts/erc721/custom/interfaces/IHLFS.sol b/contracts/erc721/custom/interfaces/IHLFS.sol new file mode 100644 index 0000000..37b2833 --- /dev/null +++ b/contracts/erc721/custom/interfaces/IHLFS.sol @@ -0,0 +1,9 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +interface IHLFS { + /** + * @notice Get contents of a file on a HL FS + */ + function fileContents(string calldata fileName) external view returns (string memory); +} diff --git a/contracts/erc721/custom/interfaces/IOwnable.sol b/contracts/erc721/custom/interfaces/IOwnable.sol new file mode 100644 index 0000000..db0ad2d --- /dev/null +++ b/contracts/erc721/custom/interfaces/IOwnable.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.10; + +/** + * @notice Simple interface to interact with EIP-173 implementing contracts + */ +interface IOwnable { + function owner() external view returns (address); +} diff --git a/contracts/erc721/custom/shloms404/Renderer404.sol b/contracts/erc721/custom/shloms404/Renderer404.sol new file mode 100644 index 0000000..0c1e6d5 --- /dev/null +++ b/contracts/erc721/custom/shloms404/Renderer404.sol @@ -0,0 +1,147 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "../../inchain-rendering/interfaces/IHLRenderer.sol"; +import "../../interfaces/IERC721GeneralSupplyMetadata.sol"; +import "../interfaces/IHLFS.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; + +/** + * @notice Custom HL renderer for 404 by Shl0ms + * @author highlight.xyz + */ +contract Renderer404 { + /** + * @notice Constant CIDs + */ + string private constant _CREATOR_CID = "bafkreigki7ryypgtde7ykgy7zzkyabkwgu4nmqw6tyapfzmsbmknnnltau"; + string private constant _FILE_FORMAT_CID = "bafkreih5tlzemmxra3p635ljecifcefcyooplayoc77o3m4gxipz6ycism"; + string private constant _IMAGE_DIMENSIONS_CID = "bafkreigqorkwpylsg5mh4ve3ngw2mpm4ssk2nthbdljfl47oq7iqypqzve"; + + /** + * @notice See {IHLRenderer-tokenURI} + */ + function tokenURI(uint256 tokenId) external view virtual returns (string memory) { + return _tokenURI(tokenId, msg.sender); + } + + /** + * @notice tokenURI for easier inspection + */ + function tokenURIInspection(uint256 tokenId, address collection) external view returns (string memory) { + return _tokenURI(tokenId, collection); + } + + /** + * @notice Generate uri for a token + */ + function _tokenURI(uint256 tokenId, address collection) private view returns (string memory) { + uint256 limitSupply = IERC721GeneralSupplyMetadata(collection).limitSupply(); + if (limitSupply == 0 || tokenId > limitSupply || tokenId == 0) { + revert("Invalid"); + } + + string memory tokenIdStr = Strings.toString(tokenId); + if (tokenId < 10) { + tokenIdStr = string(abi.encodePacked("00", tokenIdStr)); + } else if (tokenId < 100) { + tokenIdStr = string(abi.encodePacked("0", tokenIdStr)); + } + + bytes memory metadataPt1 = abi.encodePacked( + '{"name": "', + IHLFS(collection).fileContents("monospace404.txt"), + " // ", + _getTraitCID(tokenId, collection, limitSupply, "monospaceTokenIds.txt", 12), + '", "', + 'description": "', + IHLFS(collection).fileContents("description.txt"), + '", "', + 'image": "ipfs://', + _getTraitCID(tokenId, collection, limitSupply, "images.txt", 59), + '", "' + ); + bytes memory metadataAttributesPt1 = abi.encodePacked( + 'attributes": [' + '{"trait_type": "FILE FORMAT", "value": "ipfs://', + _FILE_FORMAT_CID, + '"}, ', + '{"trait_type": "IMAGE DIMENSIONS", "value": "ipfs://', + _IMAGE_DIMENSIONS_CID, + '"}, ', + '{"trait_type": "COLOR SCHEME", "value": "ipfs://', + _getTraitCID(tokenId, collection, limitSupply, "colorSchemes.txt", 59), + '"}, ' + ); + bytes memory metadataAttributesPt2 = abi.encodePacked( + '{"trait_type": "MUSICAL ACCOMPANIMENT", "value": "ipfs://', + _getTraitCID(tokenId, collection, limitSupply, "musicalAccompaniments.txt", 59), + '"}, ', + '{"trait_type": "CREATOR", "value": "ipfs://', + _CREATOR_CID, + '"}, ', + '{"trait_type": "FILE NO.", "value": "', + tokenIdStr, + '"}]}' + ); + + return + string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode(abi.encodePacked(metadataPt1, metadataAttributesPt1, metadataAttributesPt2)) + ) + ); + } + + /** + * @notice Get the CID for a token trait (image or attribute) + */ + function _getTraitCID( + uint256 tokenId, + address collection, + uint256 numTokens, + string memory fileName, + uint256 numBytesPerLine + ) private view returns (string memory) { + return _parseCIDsText(IHLFS(collection).fileContents(fileName), numTokens, numBytesPerLine)[tokenId - 1]; + } + + /** + * @notice Parse out all CIDs in a text file + */ + function _parseCIDsText( + string memory cidsText, + uint256 numLines, + uint256 numBytesPerLine + ) private pure returns (string[] memory) { + // example CID, all lines expected to follow this format: + // bafkreiapwnok3zsifqvdvotlgv4z5hfdi247wdxtmxa4u7tzfxsbysn3pa + // 59 characters long + // Parse lines that are 60 characters long (CID + \n) (for images.txt for eg.) + + string[] memory cids = new string[](numLines); + + uint256 arrayIndex = 0; + bytes memory stringBytes = bytes(cidsText); + uint256 i = 0; + + while (i < stringBytes.length && arrayIndex < numLines) { + bytes memory line = new bytes(numBytesPerLine); + for (uint j = 0; j < numBytesPerLine; j++) { + line[j] = stringBytes[i]; + i++; + } + cids[arrayIndex] = string(line); + arrayIndex++; + + // Skip the newline character + if (i < stringBytes.length && stringBytes[i] == "\n") { + i++; + } + } + + return cids; + } +} diff --git a/contracts/erc721/inchain-rendering/TestHighlightRenderer.sol b/contracts/erc721/inchain-rendering/TestHighlightRenderer.sol new file mode 100644 index 0000000..873feac --- /dev/null +++ b/contracts/erc721/inchain-rendering/TestHighlightRenderer.sol @@ -0,0 +1,136 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "./interfaces/IHLRenderer.sol"; +import "../interfaces/IERC721GeneralSupplyMetadata.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +/** + * @notice Mock implementation of IHLRenderer + */ +contract TestHighlightRenderer is IHLRenderer { + /** + * @notice Throw when mint details are queried for a token that hasn't been minted + */ + error InvalidTokenId(); + + /** + * @notice Details that seed token metadata + */ + struct SeedDetails { + bytes32 previousBlockHash; + uint256 blockTimestamp; + // etc. + } + + /** + * @notice Store the seed details for each token batch (for each nft contract) + */ + mapping(address => mapping(uint256 => SeedDetails)) private _startTokenIdToSeedDetails; + + /** + * @notice Store the first token id of each minted batch (for each nft contract) + */ + mapping(address => uint256[]) private _startTokenIds; + + /** + * @notice See {IHLRenderer-processMultipleRecipientMint} + */ + function processMultipleRecipientMint( + uint256 firstTokenId, + uint256 numTokensPerRecipient, // unused in this implementation + address[] calldata orderedRecipients // unused in this implementation + ) external { + _startTokenIdToSeedDetails[msg.sender][firstTokenId] = SeedDetails( + blockhash(block.number - 1), + block.timestamp + ); + _startTokenIds[msg.sender].push(firstTokenId); + } + + /** + * @notice See {IHLRenderer-processOneRecipientMint} + */ + function processOneRecipientMint( + uint256 firstTokenId, + uint256 numTokens, // unused in this implementation + address recipient // unused in this implementation + ) external { + _startTokenIdToSeedDetails[msg.sender][firstTokenId] = SeedDetails( + blockhash(block.number - 1), + block.timestamp + ); + _startTokenIds[msg.sender].push(firstTokenId); + } + + /** + * @notice See {IHLRenderer-tokenURI} + */ + function tokenURI(uint256 tokenId) external view returns (string memory) { + // typically return a base64-encoded json + // probably store a preview images base uri globally (stored via Highlight) + // for demonstration purposes, just return a simple string here: + uint256 numTokens = IERC721GeneralSupplyMetadata(msg.sender).supply(); + return concatenateSeedDetails(getSeedDetails(tokenId, numTokens + 1, msg.sender), tokenId); + } + + /** + * @notice Concatenate seed details into a fake uri + */ + function concatenateSeedDetails( + SeedDetails memory _seedDetails, + uint256 tokenId + ) public view returns (string memory) { + return + string( + abi.encodePacked( + Strings.toString(uint256(_seedDetails.previousBlockHash)), + Strings.toString(_seedDetails.blockTimestamp), + Strings.toString(tokenId) + ) + ); + } + + /** + * @notice Get a token's seed details + * @dev Assumes _startTokenIds are in ascending order + * @param tokenId ID of token to get seed details for + * @param nextTokenId ID of immediate token that hasn't been minted on NFT contract + * @param nftContract NFT contract + */ + function getSeedDetails( + uint256 tokenId, + uint256 nextTokenId, + address nftContract + ) public view returns (SeedDetails memory) { + uint256[] memory tempStartTokenIds = _startTokenIds[nftContract]; + uint256 numBatches = tempStartTokenIds.length; + + if (numBatches == 0) { + revert InvalidTokenId(); + } + + uint256 previousStartTokenId = tempStartTokenIds[0]; + if (numBatches == 1) { + if (tokenId >= previousStartTokenId && tokenId < nextTokenId) { + return _startTokenIdToSeedDetails[nftContract][previousStartTokenId]; + } else { + revert InvalidTokenId(); + } + } + + for (uint256 i = 1; i < numBatches; i++) { + if (tokenId >= previousStartTokenId && tokenId < tempStartTokenIds[i]) { + return _startTokenIdToSeedDetails[nftContract][previousStartTokenId]; + } + + previousStartTokenId = tempStartTokenIds[i]; + } + + if (tokenId >= previousStartTokenId && tokenId < nextTokenId) { + return _startTokenIdToSeedDetails[nftContract][previousStartTokenId]; + } else { + revert InvalidTokenId(); + } + } +} diff --git a/contracts/erc721/inchain-rendering/interfaces/IHLRenderer.sol b/contracts/erc721/inchain-rendering/interfaces/IHLRenderer.sol new file mode 100644 index 0000000..225ceb7 --- /dev/null +++ b/contracts/erc721/inchain-rendering/interfaces/IHLRenderer.sol @@ -0,0 +1,36 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @notice Highlight's custom renderer interface for collections + */ +interface IHLRenderer { + /** + * @notice Process a mint to multiple recipients (likely store mint details) + * @dev Implementations should assume msg.sender to be the NFT contract + * @param firstTokenId ID of first token to be minted (next ones are minted sequentially) + * @param numTokensPerRecipient Number of tokens minted to each recipient + * @param orderedRecipients Recipients to mint tokens to, sequentially + */ + function processMultipleRecipientMint( + uint256 firstTokenId, + uint256 numTokensPerRecipient, + address[] calldata orderedRecipients + ) external; + + /** + * @notice Process a mint to one recipient (likely store mint details) + * @dev Implementations should assume msg.sender to be the NFT contract + * @param firstTokenId ID of first token to be minted (next ones are minted sequentially) + * @param numTokens Number of tokens minted + * @param recipient Recipient to mint to + */ + function processOneRecipientMint(uint256 firstTokenId, uint256 numTokens, address recipient) external; + + /** + * @notice Return token metadata for a token + * @dev Implementations should assume msg.sender to be the NFT contract + * @param tokenId ID of token to return metadata for + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/contracts/erc721/instances/GenerativeSeries.sol b/contracts/erc721/instances/GenerativeSeries.sol index d939062..2301113 100644 --- a/contracts/erc721/instances/GenerativeSeries.sol +++ b/contracts/erc721/instances/GenerativeSeries.sol @@ -37,7 +37,7 @@ contract GenerativeSeries is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address @@ -69,10 +69,10 @@ contract GenerativeSeries is Proxy { uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -83,14 +83,14 @@ contract GenerativeSeries is Proxy { uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, 0, false, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/instances/MultipleEditions.sol b/contracts/erc721/instances/MultipleEditions.sol index ac53b49..bf52367 100644 --- a/contracts/erc721/instances/MultipleEditions.sol +++ b/contracts/erc721/instances/MultipleEditions.sol @@ -47,7 +47,7 @@ contract MultipleEditions is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address diff --git a/contracts/erc721/instances/MultipleEditionsDFS.sol b/contracts/erc721/instances/MultipleEditionsDFS.sol index d39d46a..8287b99 100644 --- a/contracts/erc721/instances/MultipleEditionsDFS.sol +++ b/contracts/erc721/instances/MultipleEditionsDFS.sol @@ -45,7 +45,7 @@ contract MultipleEditionsDFS is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address diff --git a/contracts/erc721/instances/Series.sol b/contracts/erc721/instances/Series.sol index 5efb784..893656c 100644 --- a/contracts/erc721/instances/Series.sol +++ b/contracts/erc721/instances/Series.sol @@ -37,7 +37,7 @@ contract Series is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address @@ -66,10 +66,10 @@ contract Series is Proxy { uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -80,14 +80,14 @@ contract Series is Proxy { uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, 0, false, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/instances/SingleEdition.sol b/contracts/erc721/instances/SingleEdition.sol index 89e97b6..e0dd887 100644 --- a/contracts/erc721/instances/SingleEdition.sol +++ b/contracts/erc721/instances/SingleEdition.sol @@ -37,7 +37,7 @@ contract SingleEdition is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address @@ -69,10 +69,10 @@ contract SingleEdition is Proxy { uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -83,14 +83,14 @@ contract SingleEdition is Proxy { uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, 0, true, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/instances/SingleEditionDFS.sol b/contracts/erc721/instances/SingleEditionDFS.sol index a79d353..bb2eec5 100644 --- a/contracts/erc721/instances/SingleEditionDFS.sol +++ b/contracts/erc721/instances/SingleEditionDFS.sol @@ -37,7 +37,7 @@ contract SingleEditionDFS is Proxy { * @ param tokenLimitPerTx * @ param maxTotalClaimableViaVector * @ param maxUserClaimableViaVector - * @ param allowlistRoot + * @ param currency * @param mechanicVectorData Mechanic mint vector data * @ param mechanicVectorId Global mechanic vector ID * @ param mechanic Mechanic address @@ -69,10 +69,10 @@ contract SingleEditionDFS is Proxy { uint48 tokenLimitPerTx, uint48 maxTotalClaimableViaVector, uint48 maxUserClaimableViaVector, - bytes32 allowlistRoot + address currency ) = abi.decode( mintVectorData, - (address, address, uint48, uint48, uint192, uint48, uint48, uint48, bytes32) + (address, address, uint48, uint48, uint192, uint48, uint48, uint48, address) ); IAbridgedMintVector(mintManager).createAbridgedVector( @@ -83,14 +83,14 @@ contract SingleEditionDFS is Proxy { uint160(paymentRecipient), maxTotalClaimableViaVector, 0, - 0, + uint160(currency), tokenLimitPerTx, maxUserClaimableViaVector, pricePerToken, 0, true, false, - allowlistRoot + 0 ) ); } diff --git a/contracts/erc721/interfaces/IERC721EditionsStartId.sol b/contracts/erc721/interfaces/IERC721EditionsStartId.sol new file mode 100644 index 0000000..a41b282 --- /dev/null +++ b/contracts/erc721/interfaces/IERC721EditionsStartId.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @author highlight.xyz + */ +interface IERC721EditionsStartId { + /** + * @notice Get an edition's start id + */ + function editionStartId(uint256 editionId) external view returns (uint256); +} diff --git a/contracts/erc721/onchain/Bytecode.sol b/contracts/erc721/onchain/Bytecode.sol new file mode 100644 index 0000000..a69a24f --- /dev/null +++ b/contracts/erc721/onchain/Bytecode.sol @@ -0,0 +1,77 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @notice From MathCastles deployment + */ +library Bytecode { + error InvalidCodeAtRange(uint256 _size, uint256 _start, uint256 _end); + + /** + @notice Generate a creation code that results on a contract with `_code` as bytecode + @param _code The returning value of the resulting `creationCode` + @return creationCode (constructor) for new contract + */ + function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) { + /* + 0x00 0x63 0x63XXXXXX PUSH4 _code.length size + 0x01 0x80 0x80 DUP1 size size + 0x02 0x60 0x600e PUSH1 14 14 size size + 0x03 0x60 0x6000 PUSH1 00 0 14 size size + 0x04 0x39 0x39 CODECOPY size + 0x05 0x60 0x6000 PUSH1 00 0 size + 0x06 0xf3 0xf3 RETURN + + */ + + return abi.encodePacked(hex"63", uint32(_code.length), hex"80_60_0E_60_00_39_60_00_F3", _code); + } + + /** + @notice Returns the size of the code on a given address + @param _addr Address that may or may not contain code + @return size of the code on the given `_addr` + */ + function codeSize(address _addr) internal view returns (uint256 size) { + assembly { + size := extcodesize(_addr) + } + } + + /** + @notice Returns the code of a given address + @dev It will fail if `_end < _start` + @param _addr Address that may or may not contain code + @param _start number of bytes of code to skip on read + @param _end index before which to end extraction + @return oCode read from `_addr` deployed bytecode + + Forked from: https://gist.github.com/KardanovIR/fe98661df9338c842b4a30306d507fbd + */ + function codeAt(address _addr, uint256 _start, uint256 _end) internal view returns (bytes memory oCode) { + uint256 csize = codeSize(_addr); + if (csize == 0) return bytes(""); + + if (_start > csize) return bytes(""); + if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end); + + unchecked { + uint256 reqSize = _end - _start; + uint256 maxSize = csize - _start; + + uint256 size = maxSize < reqSize ? maxSize : reqSize; + + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + oCode := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(oCode, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(oCode, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(oCode, 0x20), _start, size) + } + } + } +} diff --git a/contracts/erc721/onchain/OnchainFileStorage.sol b/contracts/erc721/onchain/OnchainFileStorage.sol index 2ecd0c4..6d6483f 100644 --- a/contracts/erc721/onchain/OnchainFileStorage.sol +++ b/contracts/erc721/onchain/OnchainFileStorage.sol @@ -1,6 +1,7 @@ //SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.10; +import "./Bytecode.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /** @@ -105,40 +106,39 @@ abstract contract OnchainFileStorage is OwnableUpgradeable { uint256 fileStorageAddressesLength = fileStorageAddresses.length; string memory contents = ""; - for (uint256 i = 0; i < fileStorageAddressesLength; i++) { - contents = string( - abi.encodePacked( - contents, - string(_readBytecode(fileStorageAddresses[i], 1, fileStorageAddresses[i].code.length - 1)) - ) - ); - } - - return contents; - } - - /** - * @notice Read bytecode at an address - * @ author SOLMATE - */ - function _readBytecode(address pointer, uint256 start, uint256 size) private view returns (bytes memory data) { - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - data := mload(0x40) + // @author of the following section: @xaltgeist (0x16cc845d144a283d1b0687fbac8b0601cc47a6c3 on Ethereum mainnet) + // edited with HL FS -like variable names + uint256 size; + uint ptr = 0x20; + address currentChunk; + unchecked { + // solhint-disable-next-line no-inline-assembly + assembly { + contents := mload(0x40) + } - // Update the free memory pointer to prevent overriding our data. - // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). - // Adding 31 to size and running the result through the logic above ensures - // the memory pointer remains word-aligned, following the Solidity convention. - mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) + for (uint i = 0; i < fileStorageAddressesLength; i++) { + currentChunk = fileStorageAddresses[i]; + size = Bytecode.codeSize(currentChunk) - 1; - // Store the size of the data in the first 32 byte chunk of free memory. - mstore(data, size) + // solhint-disable-next-line no-inline-assembly + assembly { + extcodecopy(currentChunk, add(contents, ptr), 1, size) + } + ptr += size; + } - // Copy the code into memory right after the 32 bytes we used to store the size. - extcodecopy(pointer, add(data, 32), start, size) + // solhint-disable-next-line no-inline-assembly + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + // new "memory end" including padding + mstore(0x40, add(contents, and(add(ptr, 0x1f), not(0x1f)))) + // store length in memory + mstore(contents, sub(ptr, 0x20)) + } } + return contents; } /** diff --git a/contracts/erc721/onchain/interfaces/IHLFS.sol b/contracts/erc721/onchain/interfaces/IHLFS.sol new file mode 100644 index 0000000..59aaaa8 --- /dev/null +++ b/contracts/erc721/onchain/interfaces/IHLFS.sol @@ -0,0 +1,19 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +interface IHLFS { + /** + * @notice Return registered file names + */ + function files() external view returns (string[] memory); + + /** + * @notice Return storage bytecode addresses for a file + */ + function fileStorage(string calldata fileName) external view returns (address[] memory); + + /** + * @notice Return file contents + */ + function fileContents(string calldata fileName) external view returns (string memory); +} diff --git a/contracts/mint/MintFeeOracle.sol b/contracts/mint/MintFeeOracle.sol new file mode 100644 index 0000000..4a5523f --- /dev/null +++ b/contracts/mint/MintFeeOracle.sol @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./interfaces/IMintFeeOracle.sol"; +import "./interfaces/IAbridgedMintVector.sol"; +import "./mechanics/interfaces/IMechanicMintManagerView.sol"; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "../utils/FullMath.sol"; +import "../utils/IUniswapV3PoolState.sol"; +import "./referrals/IReferralManagerView.sol"; + +/** + * @title MintManager's mint fee oracle + * @author highlight.xyz + */ +contract MintFeeOracle is UUPSUpgradeable, OwnableUpgradeable { + /** + * @notice Throw when an action is unauthorized + */ + error Unauthorized(); + + /** + * @notice Throw when an ERC20 is invalid + */ + error InvalidERC20(); + + /** + * @notice Throw when an ERC20 config is invalid + */ + error InvalidERC20Config(); + + /** + * @notice Throw when caller is not the MintManager + */ + error NotMintManager(); + + /** + * @notice Throw when an invalid ether value is sent in when processing an ether mint fee cap + */ + error InvalidEtherMintFeeCap(); + + /** + * @notice Throw when sending ether fails + */ + error EtherSendFailed(); + + /** + * @notice Throw when a mint vector's expected type is false + */ + error InvalidVectorType(); + + /** + * @notice Throw when resolved referrer is invalid + */ + error InvalidReferrer(); + + /** + * @notice Config for allowlisted ERC20s + * @param baseMintFee Base fee fee amount per token (if price isn't real-time) + * @param realTimeOracle Address of real time oracle to query if price is real-time + */ + struct ERC20Config { + uint96 baseMintFee; + address realTimeOracle; + } + + /** + * @notice MintManager + */ + address private _mintManager; + + /** + * @notice Mint fee subsidized config (vector + user) + */ + mapping(bytes32 => bool) private _subsidizedMintConfig; + + /** + * @notice Gasless mechanic address + */ + address private _gaslessMechanicAddress; + + /** + * @notice Allowlisted ERC20s -> mint fee + */ + mapping(address => ERC20Config) private _allowlistedERC20s; + + /** + * @notice When true, creator rewards is enabled + */ + bool private _creatorRewardsEnabled; + + /** + * @notice Constants for uniswap price calculation + */ + uint256 public constant ETH_WEI = 10 ** 18; + uint256 public constant FULL_MATH_SHIFT = 1 << 192; + + /** + * @notice Backup referral manager + */ + address private _backupReferralManager; + + /** + * @notice Backup referral manager + */ + address private _backupDiscreteDutchAuctionMechanic; + + /** + * @notice Backup referral manager + */ + address private _backupRankedAuctionMechanic; + + /** + * @notice Emitted when a referrer is paid out a portion of the mint fee + * @param vectorId Vector ID + * @param referrer Referrer + * @param currency Currency + * @param referralPayout Amount paid out to referrer + */ + event ReferralPayout( + bytes32 indexed vectorId, + address indexed referrer, + address indexed currency, + uint256 referralPayout + ); + + /** + * @notice Only let the mint manager call + */ + modifier onlyMintManager() { + if (msg.sender != _mintManager) { + _revert(NotMintManager.selector); + } + _; + } + + /* solhint-disable no-empty-blocks */ + /** + * @notice Initialize contract + */ + function initialize( + address mintManager, + address platform, + address gaslessMechanic, + address backupReferralManager, + address backupDiscreteDutchAuctionMechanic, + address backupRankedAuctionMechanic + ) external initializer { + __Ownable_init(); + _transferOwnership(platform); + _mintManager = mintManager; + _gaslessMechanicAddress = gaslessMechanic; + _backupReferralManager = backupReferralManager; + _backupDiscreteDutchAuctionMechanic = backupDiscreteDutchAuctionMechanic; + _backupRankedAuctionMechanic = backupRankedAuctionMechanic; + } + + /** + * @notice Set an allowlisted erc20 config + * @param erc20 ERC20 address + * @param config ERC20 config + */ + function setAllowlistedERC20Config(address erc20, ERC20Config calldata config) external onlyOwner { + if ( + !(config.baseMintFee != 0 && config.realTimeOracle == address(0)) && + !(config.baseMintFee == 0 && config.realTimeOracle != address(0)) + ) { + _revert(InvalidERC20Config.selector); + } + _allowlistedERC20s[erc20] = config; + } + + /** + * @notice Delist an allowlisted erc20 config + * @param erc20 ERC20 address + */ + function delistERC20(address erc20) external onlyOwner { + delete _allowlistedERC20s[erc20]; + } + + /** + * @notice Set mint manager + */ + function setMintManager(address newMintManager) external onlyOwner { + _mintManager = newMintManager; + } + + /** + * @notice Set backup referral manager + */ + function setBackupReferralManager(address newBackupReferralManager) external onlyOwner { + _backupReferralManager = newBackupReferralManager; + } + + /** + * @notice Set backup discrete dutch auction mechanic + */ + function setBackupDiscreteDutchAuctionMechanic(address newBackupDiscreteDutchAuctionMechanic) external onlyOwner { + _backupDiscreteDutchAuctionMechanic = newBackupDiscreteDutchAuctionMechanic; + } + + /** + * @notice Set backup ranked auction mechanic + */ + function setBackupRankedAuctionMechanic(address newBackupRankedAuctionMechanic) external onlyOwner { + _backupRankedAuctionMechanic = newBackupRankedAuctionMechanic; + } + + /** + * @notice Set gasless mechanic + */ + function setGaslessMechanic(address newGaslessMechanic) external onlyOwner { + _gaslessMechanicAddress = newGaslessMechanic; + } + + /** + * @notice Set creator rewards enabled + */ + function setCreatorRewardsEnabled(bool creatorRewardsEnabled) external onlyOwner { + _creatorRewardsEnabled = creatorRewardsEnabled; + } + + /** + * @notice Subsidize mint fee for a mint config (vector + sender) + */ + function subsidizeMintConfig(bytes32 vectorId, address minter) external onlyOwner { + bytes32 mintConfig = _encodeMintConfig(vectorId, minter); + require(!_subsidizedMintConfig[mintConfig], "Already subsidized"); + _subsidizedMintConfig[mintConfig] = true; + } + + /** + * @notice Subsidize mint fee for a mint config (vector + sender) + */ + function unsubsidizeMintVector(bytes32 vectorId, address minter) external onlyOwner { + bytes32 mintConfig = _encodeMintConfig(vectorId, minter); + require(_subsidizedMintConfig[mintConfig], "Not already subsidized"); + _subsidizedMintConfig[mintConfig] = false; + } + + /** + * @notice Withdraw native gas token owed to platform + */ + function withdrawNativeGasToken(uint256 amountToWithdraw, address payable recipient) external onlyOwner { + (bool sentToPlatform, ) = recipient.call{ value: amountToWithdraw }(""); + if (!sentToPlatform) { + _revert(EtherSendFailed.selector); + } + } + + /** + * @notice Withdraw ERC20 owed to platform + */ + function withdrawERC20(address currency, uint256 amountToWithdraw, address recipient) external onlyOwner { + IERC20(currency).transfer(recipient, amountToWithdraw); + } + + /* solhint-disable code-complexity */ + /** + * @notice See {IMintFeeOracle-processClassicVectorMintFeeCap} + */ + function processClassicVectorMintFeeCap( + bytes32 vectorId, + bool payoutCreatorReward, + address vectorPaymentRecipient, + address currency, + uint256 amount, + address minter + ) external payable onlyMintManager returns (uint256) { + if (currency == address(0)) { + if (msg.value != amount) { + _revert(InvalidEtherMintFeeCap.selector); + } + } + + address referralManager = _referralManager(); + if (referralManager == minter) { + uint256 referralPayout = (amount * 10) / 100; + // get referrer via referral manager + address referrer = IReferralManagerView(referralManager).getCurrentReferrer(vectorId); + if (referrer == address(0)) { + _revert(InvalidReferrer.selector); + } + + // only send referral if minter wasn't referrer + if (referrer != tx.origin) { + if (currency == address(0)) { + (bool sentToRecipient, ) = payable(referrer).call{ value: referralPayout }(""); + if (!sentToRecipient) { + _revert(EtherSendFailed.selector); + } + } else { + IERC20(currency).transfer(referrer, referralPayout); + } + + emit ReferralPayout(vectorId, referrer, currency, referralPayout); + } + } + + if (payoutCreatorReward) { + uint256 creatorPayout = amount / 2; + if (currency == address(0)) { + (bool sentToRecipient, ) = vectorPaymentRecipient.call{ value: creatorPayout }(""); + if (!sentToRecipient) { + _revert(EtherSendFailed.selector); + } + } else { + IERC20(currency).transfer(vectorPaymentRecipient, creatorPayout); + } + + return creatorPayout; + } + + return 0; + } + + /* solhint-enable code-complexity */ + + /** + * @notice See {IMintFeeOracle-getClassicVectorMintFeeCap} + */ + function getClassicVectorMintFeeCap( + bytes32 vectorId, + uint256 numToMint, + address minter, + address currency + ) external view returns (uint256) { + if (_isFeeSubsidized(vectorId, minter)) { + return 0; + } + if (currency == address(0)) { + return (block.chainid == 137 ? 2265000000000000000 : 800000000000000) * numToMint; + } else { + return _getClassicVectorERC20MintFeeCap(currency, numToMint); + } + } + + /** + * @notice See {IMintFeeOracle-getMechanicMintFee} + */ + function getMechanicMintFee( + bytes32 mechanicVectorId, + uint32 numToMint, + address mechanic, + address minter + ) external view returns (uint256) { + if (_isMintFeeWaivedMechanic(mechanic) || _isFeeSubsidized(mechanicVectorId, minter)) { + return 0; + } else { + return (block.chainid == 137 ? 2265000000000000000 : 800000000000000) * uint256(numToMint); + } + } + + /** + * @notice Get public vector mint fee (optimized for offchain querying) + */ + function getPublicVectorMintFee( + uint256 vectorId, + uint256 numToMint, + address minter + ) external view returns (uint256, address) { + if (_isFeeSubsidized(bytes32(vectorId), minter)) { + return (0, address(0)); + } + IAbridgedMintVector.AbridgedVector memory _vector = IAbridgedMintVector(_mintManager).getAbridgedVector( + vectorId + ); + if (_vector.contractAddress == address(0)) { + _revert(InvalidVectorType.selector); + } + if (_vector.currency != address(0)) { + return (_getClassicVectorERC20MintFeeCap(_vector.currency, numToMint), _vector.currency); + } else { + return ((block.chainid == 137 ? 2265000000000000000 : 800000000000000) * uint256(numToMint), address(0)); + } + } + + /** + * @notice Get gated vector mint fee (optimized for offchain querying) + */ + function getGatedVectorMintFee( + bytes32 vectorId, + uint256 numToMint, + address minter, + address currency + ) external view returns (uint256, address) { + if (_isFeeSubsidized(vectorId, minter)) { + return (0, currency); + } + if (currency != address(0)) { + return (_getClassicVectorERC20MintFeeCap(currency, numToMint), currency); + } + + return ((block.chainid == 137 ? 2265000000000000000 : 800000000000000) * uint256(numToMint), address(0)); + } + + /** + * @notice Get mechanic vector mint fee (optimized for offchain querying) + */ + function getMechanicVectorMintFee( + bytes32 vectorId, + uint256 numToMint, + address minter + ) external view returns (uint256, address) { + IMechanicData.MechanicVectorMetadata memory _mechanicMetadata = IMechanicMintManagerView(_mintManager) + .mechanicVectorMetadata(vectorId); + if (_mechanicMetadata.contractAddress == address(0)) { + _revert(InvalidVectorType.selector); + } + if (_isMintFeeWaivedMechanic(_mechanicMetadata.mechanic) || _isFeeSubsidized(vectorId, minter)) { + return (0, address(0)); + } + + return ((block.chainid == 137 ? 2265000000000000000 : 800000000000000) * uint256(numToMint), address(0)); + } + + /** + * @notice Limit upgrades of contract to MintFeeOracle owner + * @param // New implementation address + */ + function _authorizeUpgrade(address) internal override onlyOwner {} + + /** + * @dev For more efficient reverts. + */ + function _revert(bytes4 errorSelector) internal pure { + assembly { + mstore(0x00, errorSelector) + revert(0x00, 0x04) + } + } + + /** + * @notice Return if mint fee is subsidized for a mint config + * @param vectorId ID of vector + * @param minter Original minter address + */ + function _isFeeSubsidized(bytes32 vectorId, address minter) private view returns (bool) { + return _subsidizedMintConfig[_encodeMintConfig(vectorId, minter)]; + } + + /** + * @notice Encode a mint config + * @param vectorId ID of vector + * @param minter Original minter address + */ + function _encodeMintConfig(bytes32 vectorId, address minter) private pure returns (bytes32) { + return keccak256(abi.encodePacked(vectorId, minter)); + } + + function _getClassicVectorERC20MintFeeCap(address currency, uint256 numToMint) private view returns (uint256) { + ERC20Config memory config = _allowlistedERC20s[currency]; + if (config.baseMintFee != 0) { + return config.baseMintFee * numToMint; + } else if (config.realTimeOracle != address(0)) { + (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3PoolState(config.realTimeOracle).slot0(); + return + (block.chainid == 137 ? 2265000000000000000 : 800000000000000) * + sqrtPriceX96ToUint(sqrtPriceX96) * + numToMint; + } else { + _revert(InvalidERC20.selector); + } + } + + /* solhint-disable code-complexity */ + + function _isMintFeeWaivedMechanic(address mechanic) private view returns (bool) { + // RAM, DDAM + // TODO: add gasless mechanic + if (block.chainid == 1) { + return + mechanic == 0xDFEe0Ed4A217F37b3FA87624eE00fe5685bDc509 || + mechanic == 0x94Fa6e7Fc2555aDA63eA56cfFF425558360F0074; + } else if (block.chainid == 8453) { + return + mechanic == 0x922E9f8cc491fACBd403afa143AA53ee9146474C || + mechanic == 0xA748BE280C9a00edaF7d04076FE8A93c59e95B03; + } else if (block.chainid == 10) { + return + mechanic == 0xb207774Ac4E32eCE47771e64BDE5ec3894C1De6b || + mechanic == 0x15753e20667961fB30d5aa92e2255B876568BE7e; + } else if (block.chainid == 42161) { + return + mechanic == 0x7f75358787f880506c5dc6100386F77be8DE0A30 || + mechanic == 0x3a2aFe86E594540cbf3eA345dd29e09228f186D2; + } else if (block.chainid == 7777777) { + return + mechanic == 0x0AFB6566C836D1C4788cD2b54Bd9cA0158CC2D3D || + mechanic == 0xf12A4018647DD2275072967Fd5F3ac5Fef7a0471; + } else if (block.chainid == 137) { + return + mechanic == 0x4CCB72E7E0Cd948aF50bC7Bf598Fc4E027b70f98 || + mechanic == 0xAE22Cd8052D64e7C2aF6B5E3045Fab0a86C8334C; + } else if (block.chainid == 11155111) { + return + mechanic == 0xa2D14CA9985De170db128c8CB74Cecb35eEAF47E || + mechanic == 0xceBc3B3134FbEF95ED13AEcdF997D4371d022385; + } else if (block.chainid == 84532) { + return + mechanic == 0x9958F83F383CA150BB2252B4275D3e3051be469F || + mechanic == 0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7; + } + + return + mechanic == _backupDiscreteDutchAuctionMechanic || + mechanic == _backupRankedAuctionMechanic || + mechanic == _gaslessMechanicAddress; + } + + /** + * @notice Get the referral manager + */ + function _referralManager() private view returns (address) { + if (block.chainid == 1) { + return 0xD3C63951b2Ed18e8d92B5b251C3B636A45A547d0; + } else if (block.chainid == 8453) { + return 0xd9E58978808d17F99ccCEAb5195B052E972c0188; + } else if (block.chainid == 10) { + return 0x9CF5B12D2e2a88083647Ff2Fe0610F818b28eC77; + } else if (block.chainid == 7777777) { + return 0x7Cb2cecFCFFdccE0bf69366e52caec6BD719CD44; + } else if (block.chainid == 42161) { + return 0x617b2383D93909590fAC0b2aaa547EC5615d82eF; + } else if (block.chainid == 137) { + return 0x6fd07d4B5fd7093762Fb2f278769aa7e2511d45c; + } else if (block.chainid == 84532) { + return 0x4619b9673241eB41B642Dc04371100d238b73fFE; + } else if (block.chainid == 11155111) { + return 0xd33c1bE264bb98F86e18CD816D5fd44e97cb7163; + } else { + return _backupReferralManager; + } + } + + /** + * @notice Convert uniswap sqrtX96 price + * @dev token0 always assumed to be ETH + */ + function sqrtPriceX96ToUint(uint160 sqrtPriceX96) private pure returns (uint256) { + return FullMath.mulDiv(uint256(sqrtPriceX96) * uint256(sqrtPriceX96), ETH_WEI, FULL_MATH_SHIFT); + } +} diff --git a/contracts/mint/MintManager.sol b/contracts/mint/MintManager.sol index 2bef0c1..9952823 100644 --- a/contracts/mint/MintManager.sol +++ b/contracts/mint/MintManager.sol @@ -9,6 +9,7 @@ import "./interfaces/INativeMetaTransaction.sol"; import "../utils/EIP712Upgradeable.sol"; import "../metatx/ERC2771ContextUpgradeable.sol"; import "./interfaces/IAbridgedMintVector.sol"; +import "./interfaces/IMintFeeOracle.sol"; import "./mechanics/interfaces/IMechanicMintManager.sol"; import "./mechanics/interfaces/IMechanic.sol"; @@ -357,6 +358,16 @@ contract MintManager is */ mapping(bytes32 => MechanicVectorMetadata) public mechanicVectorMetadata; + /** + * @notice Mint fee oracle + */ + address private _mintFeeOracle; + + /** + * @notice Gasless mechanic address + */ + address private _gaslessMechanicAddress; + /** * @notice Emitted when platform executor is added or removed * @param executor Changed executor @@ -503,6 +514,25 @@ contract MintManager is */ event MechanicVectorPauseSet(bytes32 indexed mechanicVectorId, bool indexed paused); + /** + * @notice Emitted when the platform mint fee is updated + * @param newPlatformMintFee New platform mint fee + */ + event PlatformMintFeeUpdated(uint256 indexed newPlatformMintFee); + + /** + * @notice Emit when creator is paid out + * @param currency Currency creator reward is in + * @param rewardRecipient Creator reward recipient + * @param amount Amount of payout + */ + event CreatorRewardPayout( + bytes32 indexed vectorId, + address indexed currency, + address indexed rewardRecipient, + uint256 amount + ); + /** * @notice Restricts calls to platform */ @@ -618,8 +648,8 @@ contract MintManager is if (updateConfig.updatePricePerToken > 0) { _abridgedVectors[vectorId].pricePerToken = _newVector.pricePerToken; } - if (updateConfig.updateAllowlistRoot > 0) { - _abridgedVectors[vectorId].allowlistRoot = _newVector.allowlistRoot; + if (updateConfig.updateCurrency > 0) { + _abridgedVectors[vectorId].currency = uint160(_newVector.currency); } if (updateConfig.updateRequireDirectEOA > 0) { _abridgedVectors[vectorId].requireDirectEOA = _newVector.requireDirectEOA; @@ -638,23 +668,6 @@ contract MintManager is /* solhint-enable code-complexity */ - /** - * @notice See {IAbridgedMintVector-deleteAbridgedVector} - */ - function deleteAbridgedVector(uint256 vectorId) external { - address contractAddress = address(_abridgedVectors[vectorId].contractAddress); - address msgSender = _msgSender(); - // check .owner() first, more likely - if (Ownable(contractAddress).owner() == msgSender || msgSender == contractAddress) { - delete _abridgedVectors[vectorId]; - delete _abridgedVectorMetadata[vectorId]; - - emit VectorDeleted(vectorId); - } else { - _revert(Unauthorized.selector); - } - } - /** * @notice See {IAbridgedMintVector-setAbridgedVectorMetadata} */ @@ -732,6 +745,7 @@ contract MintManager is emit MechanicVectorPauseSet(mechanicVectorId, pause); } + /* solhint-disable code-complexity */ /** * @notice See {IMechanicMintManager-mechanicMintNum} */ @@ -739,9 +753,10 @@ contract MintManager is bytes32 mechanicVectorId, address recipient, uint32 numToMint, - bytes calldata data + bytes memory data ) external payable { MechanicVectorMetadata memory _mechanicVectorMetadata = mechanicVectorMetadata[mechanicVectorId]; + address msgSender = _msgSender(); if (_mechanicVectorMetadata.paused) { _revert(MechanicPaused.selector); @@ -749,17 +764,35 @@ contract MintManager is if (_mechanicVectorMetadata.isChoose) { _revert(InvalidMechanic.selector); } - uint256 _platformFee = (numToMint * _platformMintFee); - if (msg.value < _platformFee) { - _revert(MintFeeTooLow.selector); + + uint256 _platformFee = 0; + // constant gasless mechanic address + if (_mechanicVectorMetadata.mechanic == _gaslessMechanicAddress) { + uint256 fee; + address feeCollector; + assembly { + fee := calldataload(sub(calldatasize(), 32)) + feeCollector := shr(96, calldataload(sub(calldatasize(), 72))) + } + + data = abi.encode(fee, feeCollector, data); + } else { + _platformFee = IMintFeeOracle(_mintFeeOracle).getMechanicMintFee( + mechanicVectorId, + numToMint, + _mechanicVectorMetadata.mechanic, + msgSender + ); + if (msg.value < _platformFee) { + _revert(MintFeeTooLow.selector); + } } - uint256 amountWithoutMintFee = msg.value - _platformFee; - IMechanic(_mechanicVectorMetadata.mechanic).processNumMint{ value: amountWithoutMintFee }( + IMechanic(_mechanicVectorMetadata.mechanic).processNumMint{ value: msg.value - _platformFee }( mechanicVectorId, recipient, numToMint, - _msgSender(), + msgSender, _mechanicVectorMetadata, data ); @@ -798,9 +831,10 @@ contract MintManager is bytes32 mechanicVectorId, address recipient, uint256[] calldata tokenIds, - bytes calldata data + bytes memory data ) external payable { MechanicVectorMetadata memory _mechanicVectorMetadata = mechanicVectorMetadata[mechanicVectorId]; + address msgSender = _msgSender(); if (_mechanicVectorMetadata.paused) { _revert(MechanicPaused.selector); @@ -809,7 +843,12 @@ contract MintManager is _revert(InvalidMechanic.selector); } uint32 numToMint = uint32(tokenIds.length); - uint256 _platformFee = (numToMint * _platformMintFee); + uint256 _platformFee = IMintFeeOracle(_mintFeeOracle).getMechanicMintFee( + mechanicVectorId, + numToMint, + _mechanicVectorMetadata.mechanic, + msgSender + ); if (msg.value < _platformFee) { _revert(MintFeeTooLow.selector); } @@ -819,7 +858,7 @@ contract MintManager is mechanicVectorId, recipient, tokenIds, - _msgSender(), + msgSender, _mechanicVectorMetadata, data ); @@ -868,11 +907,13 @@ contract MintManager is } if (Ownable(collection).owner() == msgSender || msgSender == collection) { - // validate platform mint fee + // platform mint fee deprecated for creator reserves mints + /* uint256 mintFeeAmount = _platformMintFee * numToMint; if (mintFeeAmount > msg.value) { _revert(InvalidPaymentAmount.selector); } + */ if (isEditionBased) { if (numToMint == 1) { @@ -924,44 +965,77 @@ contract MintManager is ) external payable { address msgSender = _msgSender(); uint256 numTokensToMint = tokenIds.length; - _processGatedSeriesMintClaim(claim, claimSignature, numTokensToMint, msgSender); - - // mint NFT(s) if (claim.claimer != msgSender && mintRecipient != claim.claimer) { _revert(UnsafeMintRecipient.selector); } + + _verifyAndUpdateSeriesClaim(claim, claimSignature, numTokensToMint); + _processClassicVectorPayments( + claim.offchainVectorId, + numTokensToMint, + msgSender, + claim.currency, + claim.pricePerToken, + claim.paymentRecipient + ); + emit ChooseTokenMint(claim.offchainVectorId, claim.contractAddress, false, tokenIds); + + // mint NFT(s) if (numTokensToMint == 1) { IERC721GeneralMint(claim.contractAddress).mintSpecificTokenToOneRecipient(mintRecipient, tokenIds[0]); } else { IERC721GeneralMint(claim.contractAddress).mintSpecificTokensToOneRecipient(mintRecipient, tokenIds); } - - emit ChooseTokenMint(claim.offchainVectorId, claim.contractAddress, false, tokenIds); } /** - * @notice Mint on a Series collection with a valid claim + * @notice Mint on a collection with sequentially minted token IDs with a valid claim * @param claim Claim * @param claimSignature Signed + encoded claim * @param mintRecipient Who to mint the NFT(s) to. * Can't mint to different recipient if tx isn't sent by claim.claimer. */ - function gatedSeriesMint( + function gatedNumMint( Claim calldata claim, bytes calldata claimSignature, - address mintRecipient + address mintRecipient, + bool isEditionBased ) external payable { address msgSender = _msgSender(); - _processGatedMintClaim(claim, claimSignature, msgSender); - - // mint NFT(s) if (claim.claimer != msgSender && mintRecipient != claim.claimer) { _revert(UnsafeMintRecipient.selector); } - if (claim.numTokensToMint == 1) { - IERC721GeneralMint(claim.contractAddress).mintOneToOneRecipient(mintRecipient); + + _verifyAndUpdateClaim(claim, claimSignature); + _processClassicVectorPayments( + claim.offchainVectorId, + claim.numTokensToMint, + msgSender, + claim.currency, + claim.pricePerToken, + claim.paymentRecipient + ); + emit NumTokenMint(claim.offchainVectorId, claim.contractAddress, false, claim.numTokensToMint); + + if (isEditionBased) { + if (claim.numTokensToMint == 1) { + IERC721EditionMint(claim.contractAddress).mintOneToRecipient(claim.editionId, mintRecipient); + } else { + IERC721EditionMint(claim.contractAddress).mintAmountToRecipient( + claim.editionId, + mintRecipient, + claim.numTokensToMint + ); + } } else { - IERC721GeneralMint(claim.contractAddress).mintAmountToOneRecipient(mintRecipient, claim.numTokensToMint); + if (claim.numTokensToMint == 1) { + IERC721GeneralMint(claim.contractAddress).mintOneToOneRecipient(mintRecipient); + } else { + IERC721GeneralMint(claim.contractAddress).mintAmountToOneRecipient( + mintRecipient, + claim.numTokensToMint + ); + } } } @@ -998,6 +1072,7 @@ contract MintManager is _vector, numTokensToMint, mintRecipient, + msgSender, newNumClaimedViaVector, newNumClaimedForUser ); @@ -1007,42 +1082,13 @@ contract MintManager is _vector, numTokensToMint, mintRecipient, + msgSender, newNumClaimedViaVector, newNumClaimedForUser ); } } - /** - * @notice Mint on an ERC721Editions or ERC721SingleEdiion collection with a valid claim - * @param _claim Claim - * @param _signature Signed + encoded claim - * @param _recipient Who to mint the NFT(s) to. - * Can't mint to different recipient if tx isn't sent by claim.claimer. - */ - function gatedMintEdition721( - Claim calldata _claim, - bytes calldata _signature, - address _recipient - ) external payable { - address msgSender = _msgSender(); - _processGatedMintClaim(_claim, _signature, msgSender); - - // mint NFT(s) - if (_claim.claimer != msgSender && _recipient != _claim.claimer) { - _revert(UnsafeMintRecipient.selector); - } - if (_claim.numTokensToMint == 1) { - IERC721EditionMint(_claim.contractAddress).mintOneToRecipient(_claim.editionId, _recipient); - } else { - IERC721EditionMint(_claim.contractAddress).mintAmountToRecipient( - _claim.editionId, - _recipient, - _claim.numTokensToMint - ); - } - } - /** * @notice Withdraw native gas token owed to platform */ @@ -1056,29 +1102,30 @@ contract MintManager is /** * @notice Update platform payment address */ - function updatePlatformAndMintFee(address payable newPlatform, uint256 newPlatformMintFee) external onlyOwner { + function updatePlatformAndMintFeeOracle( + address payable newPlatform, + address newOracle, + address gaslessMechanic + ) external onlyOwner { if (newPlatform == address(0)) { _revert(Unauthorized.selector); } _platform = newPlatform; - _platformMintFee = newPlatformMintFee; + if (_mintFeeOracle != newOracle) { + _mintFeeOracle = newOracle; + } + if (_gaslessMechanicAddress != gaslessMechanic) { + _gaslessMechanicAddress = gaslessMechanic; + } } /** - * @notice Returns platform executors + * @notice Returns if an address is a platform executor */ function isPlatformExecutor(address _executor) external view returns (bool) { return _platformExecutors.contains(_executor); } - /** - * @notice Returns claim ids used for an offchain vector - * @param vectorId ID of offchain vector - */ - function getClaimNoncesUsedForOffchainVector(bytes32 vectorId) external view returns (bytes32[] memory) { - return _offchainVectorsToNoncesUsed[vectorId].values(); - } - /** * @notice Returns number of NFTs minted by user on vector * @param vectorId ID of offchain vector @@ -1182,84 +1229,6 @@ contract MintManager is return ERC2771ContextUpgradeable._msgData(); } - /** - * @notice Process, verify, and update the state of a gated mint claim - * @param claim Claim - * @param claimSignature Signed + encoded claim - * @param msgSender Transaction sender - */ - function _processGatedMintClaim(Claim calldata claim, bytes calldata claimSignature, address msgSender) private { - _verifyAndUpdateClaim(claim, claimSignature); - - // calculate mint fee amount - uint256 mintFeeAmount = _platformMintFee * claim.numTokensToMint; - - // make payments - if (claim.currency == address(0) && claim.pricePerToken > 0) { - // pay in native gas token - uint256 amount = claim.numTokensToMint * claim.pricePerToken; - _processNativeGasTokenPayment(amount, mintFeeAmount, claim.paymentRecipient, claim.offchainVectorId); - } else if (claim.pricePerToken > 0) { - // pay in ERC20 - uint256 amount = claim.numTokensToMint * claim.pricePerToken; - _processERC20Payment( - amount, - mintFeeAmount, - claim.paymentRecipient, - msgSender, - claim.currency, - claim.offchainVectorId - ); - } else { - if (mintFeeAmount > msg.value) { - _revert(MintFeeTooLow.selector); - } - } - - emit NumTokenMint(claim.offchainVectorId, claim.contractAddress, false, claim.numTokensToMint); - } - - /** - * @notice Process, verify, and update the state of a gated series mint claim - * @param claim Series Claim - * @param claimSignature Signed + encoded claim - * @param numTokensToMint Number of tokens to mint on series - * @param msgSender Transaction sender - */ - function _processGatedSeriesMintClaim( - SeriesClaim calldata claim, - bytes calldata claimSignature, - uint256 numTokensToMint, - address msgSender - ) private { - _verifyAndUpdateSeriesClaim(claim, claimSignature, numTokensToMint); - - // calculate mint fee amount - uint256 mintFeeAmount = _platformMintFee * numTokensToMint; - - // make payments - if (claim.currency == address(0) && claim.pricePerToken > 0) { - // pay in native gas token - uint256 amount = numTokensToMint * claim.pricePerToken; - _processNativeGasTokenPayment(amount, mintFeeAmount, claim.paymentRecipient, claim.offchainVectorId); - } else if (claim.pricePerToken > 0) { - // pay in ERC20 - uint256 amount = numTokensToMint * claim.pricePerToken; - _processERC20Payment( - amount, - mintFeeAmount, - claim.paymentRecipient, - msgSender, - claim.currency, - claim.offchainVectorId - ); - } else { - if (mintFeeAmount > msg.value) { - _revert(MintFeeTooLow.selector); - } - } - } - /** * @notice Verify, and update the state of a gated mint claim * @param claim Claim @@ -1313,7 +1282,7 @@ contract MintManager is if ( !_platformExecutors.contains(signer) || - numTokensToMint > claim.maxPerTxn || + (numTokensToMint > claim.maxPerTxn && claim.maxPerTxn != 0) || _offchainVectorsToNoncesUsed[claim.offchainVectorId].contains(claim.claimNonce) || block.timestamp > claim.claimExpiryTimestamp || (expectedNumClaimedViaVector > claim.maxClaimableViaVector && claim.maxClaimableViaVector != 0) || @@ -1335,13 +1304,15 @@ contract MintManager is * @param numTokensToMint Number of NFTs to mint on vector * @param newNumClaimedViaVector New number of NFTs minted via vector after this ones * @param newNumClaimedForUser New number of NFTs minted by user via vector after this ones + * @param msgSender Minter */ function _processVectorMint( uint256 _vectorId, AbridgedVectorData memory _vector, uint48 numTokensToMint, uint48 newNumClaimedViaVector, - uint48 newNumClaimedForUser + uint48 newNumClaimedForUser, + address msgSender ) private { if ( (_vector.maxTotalClaimableViaVector < newNumClaimedViaVector && _vector.maxTotalClaimableViaVector != 0) || @@ -1358,34 +1329,14 @@ contract MintManager is _revert(MintPaused.selector); } - // calculate mint fee amount - uint256 mintFeeAmount = _platformMintFee * numTokensToMint; - - if (_vector.currency == 0 && _vector.pricePerToken > 0) { - // pay in native gas token - uint256 amount = numTokensToMint * _vector.pricePerToken; - _processNativeGasTokenPayment( - amount, - mintFeeAmount, - payable(address(_vector.paymentRecipient)), - bytes32(_vectorId) - ); - } else if (_vector.pricePerToken > 0) { - // pay in ERC20 - uint256 amount = numTokensToMint * _vector.pricePerToken; - _processERC20Payment( - amount, - mintFeeAmount, - payable(address(_vector.paymentRecipient)), - _msgSender(), - address(_vector.currency), - bytes32(_vectorId) - ); - } else { - if (mintFeeAmount > msg.value) { - _revert(MintFeeTooLow.selector); - } - } + _processClassicVectorPayments( + bytes32(_vectorId), + uint256(numTokensToMint), + msgSender, + address(_vector.currency), + _vector.pricePerToken, + payable(address(_vector.paymentRecipient)) + ); emit NumTokenMint(bytes32(_vectorId), address(_vector.contractAddress), true, numTokensToMint); } @@ -1396,6 +1347,7 @@ contract MintManager is * @param _vector Vector being minted on * @param numTokensToMint Number of tokens to mint * @param mintRecipient Who to mint the NFT(s) to + * @param msgSender Minter * @param newNumClaimedViaVector New number of NFTs minted via vector after this ones * @param newNumClaimedForUser New number of NFTs minted by user via vector after this ones */ @@ -1404,10 +1356,18 @@ contract MintManager is AbridgedVectorData memory _vector, uint48 numTokensToMint, address mintRecipient, + address msgSender, uint48 newNumClaimedViaVector, uint48 newNumClaimedForUser ) private { - _processVectorMint(_vectorId, _vector, numTokensToMint, newNumClaimedViaVector, newNumClaimedForUser); + _processVectorMint( + _vectorId, + _vector, + numTokensToMint, + newNumClaimedViaVector, + newNumClaimedForUser, + msgSender + ); if (numTokensToMint == 1) { IERC721GeneralMint(address(_vector.contractAddress)).mintOneToOneRecipient(mintRecipient); } else { @@ -1424,6 +1384,7 @@ contract MintManager is * @param _vector Vector being minted on * @param numTokensToMint Number of tokens to mint * @param mintRecipient Who to mint the NFT(s) to + * @param msgSender Minter * @param newNumClaimedViaVector New number of NFTs minted via vector after this ones * @param newNumClaimedForUser New number of NFTs minted by user via vector after this ones */ @@ -1432,10 +1393,18 @@ contract MintManager is AbridgedVectorData memory _vector, uint48 numTokensToMint, address mintRecipient, + address msgSender, uint48 newNumClaimedViaVector, uint48 newNumClaimedForUser ) private { - _processVectorMint(_vectorId, _vector, numTokensToMint, newNumClaimedViaVector, newNumClaimedForUser); + _processVectorMint( + _vectorId, + _vector, + numTokensToMint, + newNumClaimedViaVector, + newNumClaimedForUser, + msgSender + ); if (numTokensToMint == 1) { IERC721EditionMint(address(_vector.contractAddress)).mintOneToRecipient(_vector.editionId, mintRecipient); } else { @@ -1448,23 +1417,13 @@ contract MintManager is } /** - * @notice Process payment in native gas token, sending to creator and platform + * @notice Process payment in native gas token * @param totalAmount Total amount being paid - * @param mintFeeAmount Amount to pay platform * @param recipient Creator recipient of payment * @param vectorId ID of vector (on-chain or off-chain) */ - function _processNativeGasTokenPayment( - uint256 totalAmount, - uint256 mintFeeAmount, - address payable recipient, - bytes32 vectorId - ) private { - if (totalAmount + mintFeeAmount != msg.value) { - _revert(InvalidPaymentAmount.selector); - } - - (bool sentToRecipient, bytes memory dataRecipient) = recipient.call{ value: totalAmount }(""); + function _processNativeGasTokenPayment(uint256 totalAmount, address payable recipient, bytes32 vectorId) private { + (bool sentToRecipient, ) = recipient.call{ value: totalAmount }(""); if (!sentToRecipient) { _revert(EtherSendFailed.selector); } @@ -1472,9 +1431,8 @@ contract MintManager is } /** - * @notice Process payment in ERC20, sending to creator and platform + * @notice Process payment in ERC20 * @param totalAmount Total amount being paid - * @param mintFeeAmount Amount to pay platform in mint fees * @param recipient Creator recipient of payment * @param payer Payer * @param currency ERC20 currency @@ -1482,18 +1440,12 @@ contract MintManager is */ function _processERC20Payment( uint256 totalAmount, - uint256 mintFeeAmount, address recipient, address payer, address currency, bytes32 vectorId ) private { - if (mintFeeAmount != msg.value) { - _revert(MintFeeTooLow.selector); - } IERC20(currency).transferFrom(payer, recipient, totalAmount); - // IERC20(currency).transferFrom(payer, _platform, totalAmount - amountToCreator); - emit ERC20Payment(currency, recipient, vectorId, payer, totalAmount, 10000); } @@ -1539,6 +1491,94 @@ contract MintManager is */ } + /* solhint-disable code-complexity */ + /** + * @notice Process payments (sale + mint fee) for classic vectors (direct + gated) + * @param vectorId Vector ID + * @param numToMint Number of tokens being minted + * @param msgSender Minter + * @param currency Sale currency + * @param salePrice Sale price + * @param salePaymentRecipient Sale payment recipient + */ + function _processClassicVectorPayments( + bytes32 vectorId, + uint256 numToMint, + address msgSender, + address currency, + uint256 salePrice, + address payable salePaymentRecipient + ) private { + address _oracle = _mintFeeOracle; + uint256 mintFeeCap = IMintFeeOracle(_oracle).getClassicVectorMintFeeCap( + vectorId, + numToMint, + msgSender, + currency + ); + uint256 mintFeeEtherValue = currency == address(0) ? mintFeeCap : 0; + uint256 saleAmount = numToMint * salePrice; + if (currency == address(0)) { + if (mintFeeEtherValue + saleAmount != msg.value) { + _revert(InvalidPaymentAmount.selector); + } + if (saleAmount > 0) { + // pay in native gas token + _processNativeGasTokenPayment(saleAmount, salePaymentRecipient, vectorId); + } + } else if (saleAmount > 0) { + // pay in ERC20 + // tx.origin instead of msgSender for referrals + _processERC20Payment(saleAmount, salePaymentRecipient, tx.origin, currency, vectorId); + } + + _processClassicVectorMintFee( + mintFeeCap, + msgSender, + currency, + _oracle, + salePaymentRecipient, + vectorId, + mintFeeEtherValue, + salePrice == 0 + ); + } + + /** + * @notice Helper util to process the classic vector mint fee + */ + function _processClassicVectorMintFee( + uint256 mintFeeCap, + address msgSender, + address currency, + address _oracle, + address salePaymentRecipient, + bytes32 vectorId, + uint256 mintFeeEtherValue, + bool isSaleFree + ) private { + if (mintFeeCap > 0) { + if (currency != address(0)) { + // send erc20 mint fee cap to the mint fee oracle + // tx.origin instead of msgSender for referrals + IERC20(currency).transferFrom(tx.origin, _oracle, mintFeeCap); + } + uint256 creatorPayout = IMintFeeOracle(_oracle).processClassicVectorMintFeeCap{ value: mintFeeEtherValue }( + vectorId, + isSaleFree, + salePaymentRecipient, + currency, + mintFeeCap, + msgSender + ); + if (creatorPayout != 0) { + emit CreatorRewardPayout(vectorId, currency, salePaymentRecipient, creatorPayout); + } + } + } + + /* solhint-enable code-complexity */ + /** * @notice Deterministically produce mechanic vector ID from mechanic vector inputs * @param metadata Mechanic vector metadata @@ -1677,4 +1717,4 @@ contract MintManager is revert(0x00, 0x04) } } -} \ No newline at end of file +} diff --git a/contracts/mint/interfaces/IAbridgedMintVector.sol b/contracts/mint/interfaces/IAbridgedMintVector.sol index 757e874..d6af14f 100644 --- a/contracts/mint/interfaces/IAbridgedMintVector.sol +++ b/contracts/mint/interfaces/IAbridgedMintVector.sol @@ -70,7 +70,7 @@ interface IAbridgedMintVector { * @param updateTokenLimitPerTx If 1, update tokenLimitPerTx * @param updateMaxUserClaimableViaVector If 1, update maxUserClaimableViaVector * @param updatePricePerToken If 1, update pricePerToken - * @param updateAllowlistRoot If 1, update allowlistRoot + * @param updateCurrency If 1, update currency * @param updateRequireDirectEOA If 1, update requireDirectEOA * @param updateMetadata If 1, update MintVector metadata */ @@ -82,7 +82,7 @@ interface IAbridgedMintVector { uint16 updateTokenLimitPerTx; uint16 updateMaxUserClaimableViaVector; uint8 updatePricePerToken; - uint8 updateAllowlistRoot; + uint8 updateCurrency; uint8 updateRequireDirectEOA; uint8 updateMetadata; } @@ -109,12 +109,6 @@ interface IAbridgedMintVector { uint128 flexibleData ) external; - /** - * @notice Deletes on-chain vector - * @param vectorId ID of abridged vector to delete - */ - function deleteAbridgedVector(uint256 vectorId) external; - /** * @notice Pauses or unpauses an on-chain mint vector * @param vectorId ID of abridged vector to pause diff --git a/contracts/mint/interfaces/IMintFeeOracle.sol b/contracts/mint/interfaces/IMintFeeOracle.sol new file mode 100644 index 0000000..6b4e20b --- /dev/null +++ b/contracts/mint/interfaces/IMintFeeOracle.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +/** + * @title MintManager interface for a mint fee oracle + * @author highlight.xyz + */ +interface IMintFeeOracle { + /** + * @notice Process the mint fee for a classic mv + * @param vectorId Vector ID + * @param payoutCreatorReward Payout creator reward + * @param vectorPaymentRecipient Vector payment recipient + * @param currency Mint fee currency currency + * @param amount Sale amount + * @param minter Minter address + */ + function processClassicVectorMintFeeCap( + bytes32 vectorId, + bool payoutCreatorReward, + address vectorPaymentRecipient, + address currency, + uint256 amount, + address minter + ) external payable returns (uint256); + + /** + * @notice Get the mint fee cap for a classic mv + * @param vectorId Vector ID (bytes32) + * @param numToMint Number of tokens to mint in this transaction + * @param minter Minter address + * @param currency Sale currency + */ + function getClassicVectorMintFeeCap( + bytes32 vectorId, + uint256 numToMint, + address minter, + address currency + ) external view returns (uint256); + + /** + * @notice Get the mint fee for a mechanic mint mv + * @param vectorId Vector ID + * @param numToMint Number of tokens to mint in this transaction + * @param mechanic Address of mechanic facilitating mint + * @param minter Address minting + */ + function getMechanicMintFee( + bytes32 vectorId, + uint32 numToMint, + address mechanic, + address minter + ) external view returns (uint256); +} diff --git a/contracts/mint/mechanics/DiscreteDutchAuctionMechanic.sol b/contracts/mint/mechanics/DiscreteDutchAuctionMechanic.sol index 75a32a4..4142fda 100644 --- a/contracts/mint/mechanics/DiscreteDutchAuctionMechanic.sol +++ b/contracts/mint/mechanics/DiscreteDutchAuctionMechanic.sol @@ -475,11 +475,17 @@ contract DiscreteDutchAuctionMechanic is MechanicMintManagerClientUpgradeable, U _revert(InvalidDPPFundsWithdrawl.selector); } - (bool sentToPaymentRecipient, bytes memory data) = _vector.paymentRecipient.call{ value: totalRefund }(""); + uint200 platformFee = (totalRefund * 500) / 10000; + (bool sentToPaymentRecipient, ) = _vector.paymentRecipient.call{ value: totalRefund - platformFee }(""); if (!sentToPaymentRecipient) { _revert(EtherSendFailed.selector); } + (bool sentToPlatform, ) = (payable(owner())).call{ value: platformFee }(""); + if (!sentToPlatform) { + _revert(EtherSendFailed.selector); + } + emit DiscreteDutchAuctionDPPFundsWithdrawn( mechanicVectorId, _vector.paymentRecipient, @@ -629,10 +635,16 @@ contract DiscreteDutchAuctionMechanic is MechanicMintManagerClientUpgradeable, U if (_vector.payeeRevenueHasBeenWithdrawn) { // send ether value to payment recipient - (bool sentToPaymentRecipient, bytes memory data) = _vector.paymentRecipient.call{ value: totalPrice }(""); + uint200 platformFee = (totalPrice * 500) / 10000; + (bool sentToPaymentRecipient, ) = _vector.paymentRecipient.call{ value: totalPrice - platformFee }(""); if (!sentToPaymentRecipient) { _revert(EtherSendFailed.selector); } + + (bool sentToPlatform, ) = (payable(owner())).call{ value: platformFee }(""); + if (!sentToPlatform) { + _revert(EtherSendFailed.selector); + } } emit DiscreteDutchAuctionMint(mechanicVectorId, recipient, price, numToMint); @@ -706,7 +718,7 @@ contract DiscreteDutchAuctionMechanic is MechanicMintManagerClientUpgradeable, U // escrowFunds is only final if auction is exhausted or in FPP return ( - uint256(_vector.currentSupply * potentialClearingPrice), + (uint256(_vector.currentSupply * potentialClearingPrice) * 9500) / 10000, // 95% (auctionExhausted || _auctionIsInFPP(_vector.currentSupply, priceIndex, _vector.numPrices)) ); } diff --git a/contracts/mint/mechanics/MechanicMintManagerClientUpgradeable.sol b/contracts/mint/mechanics/MechanicMintManagerClientUpgradeable.sol index 895aad4..ae85394 100644 --- a/contracts/mint/mechanics/MechanicMintManagerClientUpgradeable.sol +++ b/contracts/mint/mechanics/MechanicMintManagerClientUpgradeable.sol @@ -71,6 +71,10 @@ abstract contract MechanicMintManagerClientUpgradeable is OwnableUpgradeable, IM return IMechanicMintManagerView(mintManager).mechanicVectorMetadata(mechanicVectorId); } + function _isPlatformExecutor(address _executor) internal view returns (bool) { + return IMechanicMintManagerView(mintManager).isPlatformExecutor(_executor); + } + /** * @dev For more efficient reverts. */ diff --git a/contracts/mint/mechanics/RankedAuctionMechanic.sol b/contracts/mint/mechanics/RankedAuctionMechanic.sol new file mode 100644 index 0000000..8b01802 --- /dev/null +++ b/contracts/mint/mechanics/RankedAuctionMechanic.sol @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "./MechanicMintManagerClientUpgradeable.sol"; +import "../../erc721/interfaces/IEditionCollection.sol"; +import "../../erc721/interfaces/IERC721GeneralSupplyMetadata.sol"; +import "../../observability/IGengineObservability.sol"; +import "./interfaces/IManifold1155Burn.sol"; + +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +/** + * @notice Ranked auctions + * @author highlight.xyz + */ +contract RankedAuctionMechanic is MechanicMintManagerClientUpgradeable, UUPSUpgradeable { + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.Bytes32Set; + + /** + * @notice Throw when an action is unauthorized + */ + error Unauthorized(); + + /** + * @notice Throw when signer of signature is invalid + */ + error InvalidSigner(); + + /** + * @notice Throw when it is invalid to mint on a vector + */ + error InvalidMint(); + + /** + * @notice Throw when it is invalid to mint a number of tokens + */ + error InvalidMintAmount(); + + /** + * @notice Throw when it is invalid to bid + */ + error InvalidBid(); + + /** + * @notice Throw when a vector is already created with a mechanic vector ID + */ + error VectorAlreadyCreated(); + + /** + * @notice Throw when the vector update is invalid + */ + error InvalidUpdate(); + + /** + * @notice Throw when code gets into impossible state + */ + error ImpossibleState(); + + /** + * @notice Throw when an internal transfer of ether fails + */ + error EtherSendFailed(); + + /** + * @notice Throw when a claim is invalid + */ + error InvalidClaim(); + + /** + * @notice Throw when a claim signature is invalid + */ + error InvalidSignature(); + + /** + * @notice Errors to throw when adding / removing bids from user bid ids + */ + error BidAlreadyAdded(); + error BidAlreadyReclaimed(); + + /** + * @notice On-chain mint vector (stored data) + * @param startTimestamp When minting opens on vector + * @param endTimestamp When minting ends on vector + * @param paymentRecipient Payment recipient + * @param maxUserClaimableViaVector Max number of tokens that can be minted by user via vector + * @param maxTotalClaimableViaVector Max number of tokens that can be minted via vector + * @param latestBidId Total number of bids (valid or invalid, deleted or not) + * @param currency Currency used for payment. Native gas token, if zero address + * @param bidFundsClaimed Bid funds claimed + * @param reserveBid Reserve bid + * @param maxEndTimestamp Maximium time the auction can go till (given extensions) + * @param actionId Action ID (create / update bid) + */ + struct Vector { + uint48 startTimestamp; + uint48 endTimestamp; + address payable paymentRecipient; + uint32 maxUserClaimableViaVector; + uint32 maxTotalClaimableViaVector; + uint32 latestBidId; + address currency; + bool bidFundsClaimed; + uint96 reserveBid; + uint48 maxEndTimestamp; + uint96 actionId; + } + + /** + * @notice Bid + * @dev Only handles bids below ~10B ether + * @param bidAmount Amount of bid + * @param bidder Bidder + */ + struct Bid { + uint96 bidAmount; + address bidder; + } + + /** + * @notice User bids' metadata + * @param numClaimed Number of valid bids redeemed for a token (after mint ends) + * @param numBids Number of bids by user + */ + struct UserBidsMetadata { + uint32 numClaimed; + uint32 numBids; + } + + /** + * @notice Config used to control updating of fields in Vector + */ + struct VectorUpdateConfig { + bool updateStartTimestamp; + bool updateEndTimestamp; + bool updateMaxEndTimestamp; + bool updateMaxUserClaimableViaVector; + bool updateMaxTotalClaimableViaVector; + bool updatePaymentRecipient; + bool updateCurrency; + bool updateReserveBid; + } + + /** + * @notice Used to claim funds from an invalid bid, mint tokens + claim rebate if eligible, claim auction earnings + */ + struct RankedAuctionsClaim { + bytes32 mechanicVectorId; + uint256 rebateAmount; + address claimer; + uint32 claimerNumValidBids; + uint48 claimExpiryTimestamp; + uint256 cumulativeBidAmount; + uint32 bidId; + uint8 claimType; + } + + /** + * @notice Constants that help with EIP-712, signature based minting + */ + bytes32 private constant _DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + + /* solhint-disable max-line-length */ + bytes32 private constant _CLAIM_TYPEHASH = + keccak256( + "RankedAuctionsClaim(bytes32 mechanicVectorId,uint256 rebateAmount,address claimer,uint32 claimerNumValidBids,uint48 claimExpiryTimestamp,uint256 cumulativeBidAmount,uint32 bidId,uint8 claimType)" + ); + /* solhint-enable max-line-length */ + + /** + * @notice Stores seed based vector, indexed by global mechanic vector id + */ + mapping(bytes32 => Vector) private vector; + + /** + * @notice Stores vector's current validity hash + */ + mapping(bytes32 => bytes32) private vectorValidityHash; + + /** + * @notice System-wide vector ids to bids by their ids + */ + mapping(bytes32 => mapping(uint32 => Bid)) public bids; + + /** + * @notice System-wide vector ids to user's bids metadata + */ + mapping(bytes32 => mapping(address => UserBidsMetadata)) private _userBidsMetadata; + + /** + * @notice System-wide vector ids to user's bid ids + */ + mapping(bytes32 => mapping(address => EnumerableSet.UintSet)) private _userBidIds; + + /** + * @notice System-wide used claims + */ + mapping(bytes32 => EnumerableSet.Bytes32Set) private _usedClaims; + + /** + * @notice Emitted when a mint vector is created + */ + event RankedAuctionCreated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when a mint vector is updated + */ + event RankedAuctionUpdated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when a bid is created or updated + */ + event BidCreatedOrUpdated( + bytes32 indexed mechanicVectorId, + bytes32 indexed newValidityHash, + uint96 indexed actionId, + uint32 bidId, + address bidder, + uint96 bidAmount, + address currency, + bool created + ); + + /** + * @notice Emitted when bid funds are reclaimed + */ + event BidReclaimed(bytes32 indexed mechanicVectorId, uint32 indexed bidId, uint96 amount, address currency); + + /** + * @notice Emitted when bid funds are claimed + */ + event AuctionEarningsClaimed( + bytes32 indexed mechanicVectorId, + uint256 earnings, + address paymentRecipient, + address currency + ); + + /** + * @notice Emitted when auction is lengthened + */ + event AuctionLengthened(bytes32 indexed mechanicVectorId, uint48 newEndTimestamp); + + /** + * @notice Initialize mechanic contract + * @param _mintManager Mint manager address + * @param platform Platform owning the contract + */ + function initialize(address _mintManager, address platform) external initializer { + __MechanicMintManagerClientUpgradeable_initialize(_mintManager, platform); + } + + /** + * @notice Create a seed based vector + * @param mechanicVectorId Global mechanic vector ID + * @param vectorData Vector data, to be deserialized into seed based vector data + */ + function createVector(bytes32 mechanicVectorId, bytes memory vectorData) external onlyMintManager { + // precaution, although MintManager tightly controls creation and prevents double creation + if (vector[mechanicVectorId].startTimestamp != 0) { + _revert(VectorAlreadyCreated.selector); + } + ( + uint48 startTimestamp, + uint48 endTimestamp, + uint48 maxEndTimestamp, + address paymentRecipient, + uint32 maxUserClaimableViaVector, + uint32 maxTotalClaimableViaVector, + uint96 reserveBid, + address currency + ) = abi.decode(vectorData, (uint48, uint48, uint48, address, uint32, uint32, uint96, address)); + + if (maxTotalClaimableViaVector == 0) { + _revert(InvalidUpdate.selector); + } + + uint48 st = startTimestamp == 0 ? uint48(block.timestamp) : startTimestamp; + Vector memory _vector = Vector( + st, + endTimestamp == 0 ? uint48(st + 604800) : endTimestamp, // arbitrarily set for a week + payable(paymentRecipient), + maxUserClaimableViaVector, + maxTotalClaimableViaVector, + 0, + currency, + false, + reserveBid, + maxEndTimestamp, + 0 + ); + + vector[mechanicVectorId] = _vector; + + emit RankedAuctionCreated(mechanicVectorId); + } + + /* solhint-disable code-complexity */ + /** + * @notice Update a seed based vector + * @param mechanicVectorId Global mechanic vector ID + * @param newVector New vector fields + * @param updateConfig Config denoting what fields on vector to update + */ + function updateVector( + bytes32 mechanicVectorId, + Vector calldata newVector, + VectorUpdateConfig calldata updateConfig + ) external { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if ( + OwnableUpgradeable(metadata.contractAddress).owner() != msg.sender && metadata.contractAddress != msg.sender + ) { + _revert(Unauthorized.selector); + } + + // rather than updating entire vector, update per-field + if (updateConfig.updateStartTimestamp) { + vector[mechanicVectorId].startTimestamp = newVector.startTimestamp == 0 + ? uint48(block.timestamp) + : newVector.startTimestamp; + } + if (updateConfig.updateEndTimestamp) { + if (newVector.endTimestamp == 0) { + _revert(InvalidUpdate.selector); + } + vector[mechanicVectorId].endTimestamp = newVector.endTimestamp; + } + if (updateConfig.updateMaxEndTimestamp) { + if (newVector.maxEndTimestamp == 0) { + _revert(InvalidUpdate.selector); + } + vector[mechanicVectorId].maxEndTimestamp = newVector.maxEndTimestamp; + } + if (updateConfig.updateMaxUserClaimableViaVector) { + vector[mechanicVectorId].maxUserClaimableViaVector = newVector.maxUserClaimableViaVector; + } + if (updateConfig.updateMaxTotalClaimableViaVector) { + if ( + newVector.maxTotalClaimableViaVector == 0 || + newVector.maxTotalClaimableViaVector < vector[mechanicVectorId].maxTotalClaimableViaVector + ) { + _revert(InvalidUpdate.selector); + } + vector[mechanicVectorId].maxTotalClaimableViaVector = newVector.maxTotalClaimableViaVector; + } + if (updateConfig.updateCurrency) { + if (vector[mechanicVectorId].latestBidId > 0) { + _revert(InvalidUpdate.selector); + } + vector[mechanicVectorId].currency = newVector.currency; + } + if (updateConfig.updatePaymentRecipient) { + vector[mechanicVectorId].paymentRecipient = newVector.paymentRecipient; + } + if (updateConfig.updateReserveBid) { + if (vector[mechanicVectorId].latestBidId > 0) { + _revert(InvalidUpdate.selector); + } + vector[mechanicVectorId].reserveBid = newVector.reserveBid; + } + + emit RankedAuctionUpdated(mechanicVectorId); + } + + /** + * @notice Create a new bid + */ + function bid(bytes32 mechanicVectorId, uint96 bidAmount) external payable { + Vector memory _vector = vector[mechanicVectorId]; + uint32 newUserNumBids = _userBidsMetadata[mechanicVectorId][msg.sender].numBids + 1; + if ( + _vector.endTimestamp < uint48(block.timestamp) || + _vector.startTimestamp > uint48(block.timestamp) || + bidAmount < _vector.reserveBid || + bidAmount != msg.value || + (_vector.maxUserClaimableViaVector != 0 && newUserNumBids > uint256(_vector.maxUserClaimableViaVector)) + ) { + _revert(InvalidBid.selector); + } + + _vector.latestBidId += 1; + _vector.actionId += 1; + + bids[mechanicVectorId][_vector.latestBidId] = Bid(bidAmount, msg.sender); + if (!_userBidIds[mechanicVectorId][msg.sender].add(uint256(_vector.latestBidId))) { + // impossible state + _revert(BidAlreadyAdded.selector); + } + _userBidsMetadata[mechanicVectorId][msg.sender].numBids = newUserNumBids; + vector[mechanicVectorId].latestBidId = _vector.latestBidId; + vector[mechanicVectorId].actionId = _vector.actionId; + + if (_vector.endTimestamp - uint48(block.timestamp) <= 300) { + _vector.endTimestamp = _vector.maxEndTimestamp != 0 + ? ( + _vector.maxEndTimestamp > uint48(block.timestamp) + 300 + ? uint48(block.timestamp) + 300 + : _vector.maxEndTimestamp + ) + : uint48(block.timestamp) + 300; + vector[mechanicVectorId].endTimestamp = _vector.endTimestamp; + emit AuctionLengthened(mechanicVectorId, _vector.endTimestamp); + } + + bytes32 newValidityHash = _updateValidityHash(mechanicVectorId, _vector.latestBidId, bidAmount); + + emit BidCreatedOrUpdated( + mechanicVectorId, + newValidityHash, + _vector.actionId, + _vector.latestBidId, + msg.sender, + bidAmount, + _vector.currency, + true + ); + } + + /** + * @notice Update a bid + */ + function updateBid(bytes32 mechanicVectorId, uint32 bidId, uint96 newBidAmount) external payable { + Vector memory _vector = vector[mechanicVectorId]; + Bid memory _bid = bids[mechanicVectorId][bidId]; + if ( + newBidAmount <= _bid.bidAmount || + _bid.bidder == address(0) || + _vector.endTimestamp < uint48(block.timestamp) || + _vector.startTimestamp > uint48(block.timestamp) || + newBidAmount < _vector.reserveBid || + msg.value != newBidAmount - _bid.bidAmount + ) { + _revert(InvalidBid.selector); + } + if (_bid.bidder != msg.sender) { + _revert(Unauthorized.selector); + } + + _vector.actionId += 1; + + bids[mechanicVectorId][bidId].bidAmount = newBidAmount; + vector[mechanicVectorId].actionId = _vector.actionId; + + if (_vector.endTimestamp - uint48(block.timestamp) <= 300) { + uint48 newEndTimestamp = _vector.maxEndTimestamp != 0 + ? ( + _vector.maxEndTimestamp > uint48(block.timestamp) + 300 + ? uint48(block.timestamp) + 300 + : _vector.maxEndTimestamp + ) + : uint48(block.timestamp) + 300; + vector[mechanicVectorId].endTimestamp = newEndTimestamp; + emit AuctionLengthened(mechanicVectorId, newEndTimestamp); + } + + bytes32 newValidityHash = _updateValidityHash(mechanicVectorId, bidId, newBidAmount); + + emit BidCreatedOrUpdated( + mechanicVectorId, + newValidityHash, + _vector.actionId, + bidId, + msg.sender, + newBidAmount, + _vector.currency, + false + ); + } + + /** + * @notice Claim back funds for a bid that is currently invalid (effectively deleting the bid) + */ + function reclaimBid(RankedAuctionsClaim calldata claim, bytes calldata claimSignature) external { + // validate signature + _validateClaim(claim, msg.sender, 1, claimSignature); + + Bid memory _bid = bids[claim.mechanicVectorId][claim.bidId]; + if (_bid.bidder != claim.claimer) { + _revert(Unauthorized.selector); + } + + _sendEther(_bid.bidAmount, payable(_bid.bidder)); + + emit BidReclaimed(claim.mechanicVectorId, claim.bidId, _bid.bidAmount, vector[claim.mechanicVectorId].currency); + + // remove bid + _userBidsMetadata[claim.mechanicVectorId][claim.claimer].numBids -= 1; + if (!_userBidIds[claim.mechanicVectorId][claim.claimer].remove(claim.bidId)) { + _revert(BidAlreadyReclaimed.selector); + } + delete bids[claim.mechanicVectorId][claim.bidId]; + } + + /** + * @notice Withdraw auction earnings to payment recipient + */ + function withdrawAuctionEarnings(RankedAuctionsClaim calldata claim, bytes calldata claimSignature) external { + _validateClaim(claim, msg.sender, 2, claimSignature); + + Vector memory _vector = vector[claim.mechanicVectorId]; + // currently, only native gas token supported + if ( + uint48(block.timestamp) <= _vector.endTimestamp || _vector.currency != address(0) || _vector.bidFundsClaimed + ) { + _revert(InvalidClaim.selector); + } + + // 5% to platform + uint256 platformAmount = (claim.cumulativeBidAmount * 500) / 10000; + _sendEther(platformAmount, payable(owner())); + _sendEther(claim.cumulativeBidAmount - platformAmount, _vector.paymentRecipient); + + vector[claim.mechanicVectorId].bidFundsClaimed = true; + + emit AuctionEarningsClaimed( + claim.mechanicVectorId, + claim.cumulativeBidAmount, + _vector.paymentRecipient, + _vector.currency + ); + } + + /** + * @notice See {IMechanic-processNumMint} + */ + function processNumMint( + bytes32 mechanicVectorId, + address recipient, + uint32 numToMint, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + _processMint(mechanicVectorId, minter, numToMint, data); + } + + /** + * @notice See {IMechanic-processChooseMint} + */ + function processChooseMint( + bytes32 mechanicVectorId, + address recipient, + uint256[] calldata tokenIds, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + // currently we don't support "choose token to mint" functionality for seed based mints + _revert(InvalidMint.selector); + } + + /** + * @notice State readers + */ + function getRawVector(bytes32 mechanicVectorId) external view returns (Vector memory _vector) { + _vector = vector[mechanicVectorId]; + } + + function getVectorState( + bytes32 mechanicVectorId + ) + external + view + returns (Vector memory _vector, bytes32 validityHash, uint256 collectionSupply, uint256 collectionSize) + { + _vector = vector[mechanicVectorId]; + validityHash = vectorValidityHash[mechanicVectorId]; + (collectionSupply, collectionSize) = _collectionSupplyAndSize(mechanicVectorId); + } + + function getBids(bytes32 mechanicVectorId, uint32[] calldata bidIds) external view returns (Bid[] memory) { + uint256 bidIdsLength = bidIds.length; + Bid[] memory _bids = new Bid[](bidIdsLength); + for (uint256 i = 0; i < bidIdsLength; i++) { + _bids[i] = bids[mechanicVectorId][bidIds[i]]; + } + return _bids; + } + + function getUserBids( + bytes32 mechanicVectorId, + address user + ) external view returns (Bid[] memory, uint256[] memory bidIds, uint32 numBids, uint32 numClaimed) { + UserBidsMetadata memory metadata = _userBidsMetadata[mechanicVectorId][user]; + + uint256[] memory _bidIds = _userBidIds[mechanicVectorId][user].values(); + uint256 bidIdsLength = _bidIds.length; + Bid[] memory _bids = new Bid[](bidIdsLength); + + for (uint256 i = 0; i < bidIdsLength; i++) { + _bids[i] = bids[mechanicVectorId][uint32(_bidIds[i])]; + } + + return (_bids, _bidIds, metadata.numBids, metadata.numClaimed); + } + + /* solhint-disable no-empty-blocks */ + /** + * @notice Limit upgrades of contract to SeedBasedMintMechanic owner + * @param // New implementation address + */ + function _authorizeUpgrade(address) internal override onlyOwner {} + + /** + * @notice Process sequential mint logic + * @param mechanicVectorId Mechanic vector ID + * @param minter Minter + * @param numToMint Number of tokens to mint + * @param data Mechanic mint data (signature) + */ + function _processMint(bytes32 mechanicVectorId, address minter, uint32 numToMint, bytes calldata data) private { + (RankedAuctionsClaim memory _claim, bytes memory claimSignature) = _unwrapRankedAuctionClaim( + mechanicVectorId, + data + ); + + _validateClaim(_claim, minter, 3, claimSignature); + + if (vector[mechanicVectorId].endTimestamp >= uint48(block.timestamp)) { + _revert(InvalidMint.selector); + } + uint32 numClaimed = _userBidsMetadata[mechanicVectorId][minter].numClaimed; + if (numToMint + numClaimed > _claim.claimerNumValidBids) { + _revert(InvalidMintAmount.selector); + } + _userBidsMetadata[mechanicVectorId][minter].numClaimed = numClaimed + numToMint; + + // handle rebate + if (_claim.rebateAmount > 0) { + _sendEther(_claim.rebateAmount, payable(_claim.claimer)); + } + } + + /** + * @notice Send ether to a recipient + */ + function _sendEther(uint256 amount, address payable recipient) private { + (bool sent, ) = recipient.call{ value: amount }(""); + if (!sent) { + _revert(EtherSendFailed.selector); + } + } + + /** + * @notice Update vector's validity hash + */ + function _updateValidityHash(bytes32 mechanicVectorId, uint32 bidId, uint96 bidAmount) private returns (bytes32) { + bytes32 newValidityHash = keccak256( + abi.encodePacked(vectorValidityHash[mechanicVectorId], mechanicVectorId, bidId, bidAmount) + ); + vectorValidityHash[mechanicVectorId] = newValidityHash; + return newValidityHash; + } + + /** + * @notice Validate claim + * @param claim Claim + * @param expectedClaimer Expected claimer + * @param expectedClaimType Expected claim type + * @param claimSignature Claim signature + */ + function _validateClaim( + RankedAuctionsClaim memory claim, + address expectedClaimer, + uint8 expectedClaimType, + bytes memory claimSignature + ) private { + if (claim.claimer != expectedClaimer) { + _revert(Unauthorized.selector); + } + if (claim.claimType != expectedClaimType) { + _revert(InvalidClaim.selector); + } + bytes32 claimId = keccak256( + abi.encode( + _CLAIM_TYPEHASH, + claim.mechanicVectorId, + claim.rebateAmount, + claim.claimer, + claim.claimerNumValidBids, + claim.claimExpiryTimestamp, + claim.cumulativeBidAmount, + claim.bidId, + claim.claimType + ) + ); + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _getDomainSeperator(), claimId)); + + address signer = ECDSA.recover(digest, claimSignature); + if ( + signer == address(0) || !_isPlatformExecutor(signer) || uint48(block.timestamp) > claim.claimExpiryTimestamp + ) { + _revert(InvalidSignature.selector); + } + if (!_usedClaims[claim.mechanicVectorId].add(claimId)) { + // claim already used + _revert(InvalidClaim.selector); + } + } + + /** + * @notice Validate mint claim + * @param mechanicVectorId Mechanic vector id + * @param data Mint data + */ + function _unwrapRankedAuctionClaim( + bytes32 mechanicVectorId, + bytes calldata data + ) private returns (RankedAuctionsClaim memory, bytes memory) { + ( + uint256 rebateAmount, + address claimer, + uint32 claimerNumValidBids, + uint48 claimExpiryTimestamp, + uint256 cumulativeBidAmount, + uint32 bidId, + uint8 claimType, + bytes memory claimSignature + ) = abi.decode(data, (uint256, address, uint32, uint48, uint256, uint32, uint8, bytes)); + + return ( + RankedAuctionsClaim( + mechanicVectorId, + rebateAmount, + claimer, + claimerNumValidBids, + claimExpiryTimestamp, + cumulativeBidAmount, + bidId, + claimType + ), + claimSignature + ); + } + + /** + * @notice Returns a collection's current supply + * @param mechanicVectorId Mechanic vector ID + */ + function _collectionSupplyAndSize(bytes32 mechanicVectorId) private view returns (uint256 supply, uint256 size) { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if (metadata.contractAddress == address(0)) { + revert("Vector doesn't exist"); + } + if (metadata.isEditionBased) { + IEditionCollection.EditionDetails memory edition = IEditionCollection(metadata.contractAddress) + .getEditionDetails(metadata.editionId); + supply = edition.supply; + size = edition.size; + } else { + // supply holds a tighter constraint (no burns), some old contracts don't have it + try IERC721GeneralSupplyMetadata(metadata.contractAddress).supply() returns (uint256 _supply) { + supply = _supply; + } catch { + supply = IERC721GeneralSupplyMetadata(metadata.contractAddress).totalSupply(); + } + size = IERC721GeneralSupplyMetadata(metadata.contractAddress).limitSupply(); + } + } + + /** + * @notice Return EIP712 domain seperator + */ + function _getDomainSeperator() private view returns (bytes32) { + return + keccak256( + abi.encode( + _DOMAIN_TYPEHASH, + keccak256("RankedAuctionMechanic"), + keccak256("1"), + block.chainid, + address(this), + 0x960bb3ecd14c38754109e5fe3a3b72aa0434091106c0fea200392fd413d44da0 // ranked auction mechanic salt + ) + ); + } +} diff --git a/contracts/mint/mechanics/SeedBasedMintMechanic.sol b/contracts/mint/mechanics/SeedBasedMintMechanic.sol new file mode 100644 index 0000000..c900000 --- /dev/null +++ b/contracts/mint/mechanics/SeedBasedMintMechanic.sol @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "./MechanicMintManagerClientUpgradeable.sol"; +import "../../erc721/interfaces/IEditionCollection.sol"; +import "../../erc721/interfaces/IERC721GeneralSupplyMetadata.sol"; +import "../../observability/IGengineObservability.sol"; +import "./interfaces/IManifold1155Burn.sol"; + +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + +/** + * @notice Highlight's bespoke Seed based mint mechanic + * @author highlight.xyz + */ +contract SeedBasedMintMechanic is MechanicMintManagerClientUpgradeable, UUPSUpgradeable { + using EnumerableSet for EnumerableSet.Bytes32Set; + + /** + * @notice Throw when an action is unauthorized + */ + error Unauthorized(); + + /** + * @notice Throw when it is invalid to mint on a vector + */ + error InvalidMint(); + + /** + * @notice Throw when a vector is already created with a mechanic vector ID + */ + error VectorAlreadyCreated(); + + /** + * @notice Throw when the seed has already been used + */ + error SeedAlreadyUsed(); + + /** + * @notice Throw when the transaction sender has sent an invalid payment amount during a mint + */ + error InvalidPaymentAmount(); + + /** + * @notice Throw when an internal transfer of ether fails + */ + error EtherSendFailed(); + + /** + * @notice On-chain mint vector (stored data) + * @param startTimestamp When minting opens on vector + * @param endTimestamp When minting ends on vector + * @param maxUserClaimableViaVector Max number of tokens that can be minted by user via vector + * @param maxTotalClaimableViaVector Max number of tokens that can be minted via vector + * @param totalClaimedViaVector Total number of tokens minted via vector + * @param currency Currency used for payment. Native gas token, if zero address + * @param tokenLimitPerTx Max number of tokens that can be minted in one transaction + * @param paymentRecipient Payment recipient + * @param pricePerToken Price that has to be paid per minted token + * @param requireDirectEOA Require minters to directly be EOAs + */ + struct SeedBasedVector { + uint48 startTimestamp; + uint48 endTimestamp; + uint32 maxUserClaimableViaVector; + uint32 maxTotalClaimableViaVector; + uint48 currentSupply; + uint48 tokenLimitPerTx; + uint192 pricePerToken; + address payable paymentRecipient; + bool uniqueSeeds; + } + + /** + * @notice Config used to control updating of fields in SeedBasedVector + */ + struct SeedBasedVectorUpdateConfig { + bool updateStartTimestamp; + bool updateEndTimestamp; + bool updateMaxUserClaimableViaVector; + bool updateMaxTotalClaimableViaVector; + bool updateTokenLimitPerTx; + bool updatePaymentRecipient; + bool updatePricePerToken; + } + + /** + * @notice Config used to control burn / redeem mechanic when 1155 tokens are being burned + */ + struct BurnRedeem1155Config { + address burnContract; + uint88 tokenId; + uint8 numToBurnPerMint; + } + + /** + * @notice IGengineObservability contract + */ + IGengineObservability public observability; + + /** + * @notice Stores seed based vector, indexed by global mechanic vector id + */ + mapping(bytes32 => SeedBasedVector) private vector; + + /** + * @notice Stores already used seeds per mechanic + */ + mapping(bytes32 => mapping(bytes32 => uint256)) public seedInfo; + + /** + * @notice System-wide vector ids to (user to user claims count) + */ + mapping(bytes32 => mapping(address => uint64)) public userClaims; + + /** + * @notice System-wide vector ids to burn/redeem configuration + */ + mapping(bytes32 => BurnRedeem1155Config) private _burnRedeem1155Config; + + /** + * @notice Emitted when a mint vector is created + */ + event SeedBasedVectorCreated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when a mint vector is updated + */ + event SeedBasedVectorUpdated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when a number of tokens are minted + */ + event SeedBasedMint( + bytes32 indexed mechanicVectorId, + address indexed recipient, + uint200 pricePerToken, + uint48 numMinted + ); + + /** + * @notice Emitted for the seed based data on mint + * @param sender contract emitting the event + * @param contractAddress NFT contract token resides on + * @param data custom mint data + */ + event CustomMintData(address indexed sender, address indexed contractAddress, bytes data); + + /** + * @notice Emitted when payment is made to payment recipient + * @param paymentRecipient Creator recipient of payment + * @param mechanicVectorId Mechanic vector ID + * @param amountToCreator Amount sent to creator + * @param percentageBPSOfTotal Percentage (in basis points) that was sent to creator, of total payment + */ + event SeedBasedNativeTokenPayment( + bytes32 indexed mechanicVectorId, + address indexed paymentRecipient, + uint256 amountToCreator, + uint32 percentageBPSOfTotal + ); + + /** + * @notice Initialize mechanic contract + * @param _mintManager Mint manager address + * @param platform Platform owning the contract + */ + function initialize(address _mintManager, address platform, address _observability) external initializer { + __MechanicMintManagerClientUpgradeable_initialize(_mintManager, platform); + observability = IGengineObservability(_observability); + } + + /** + * @notice Create a seed based vector + * @param mechanicVectorId Global mechanic vector ID + * @param vectorData Vector data, to be deserialized into seed based vector data + */ + function createVector(bytes32 mechanicVectorId, bytes memory vectorData) external onlyMintManager { + // precaution, although MintManager tightly controls creation and prevents double creation + if (vector[mechanicVectorId].startTimestamp != 0) { + _revert(VectorAlreadyCreated.selector); + } + ( + uint48 startTimestamp, + uint48 endTimestamp, + uint32 maxUserClaimableViaVector, + uint32 maxTotalClaimableViaVector, + uint48 tokenLimitPerTx, + uint192 pricePerToken, + address paymentRecipient, + bool uniqueSeeds + ) = abi.decode(vectorData, (uint48, uint48, uint32, uint32, uint48, uint192, address, bool)); + + SeedBasedVector memory _vector = SeedBasedVector( + startTimestamp == 0 ? uint48(block.timestamp) : startTimestamp, + endTimestamp, + maxUserClaimableViaVector, + maxTotalClaimableViaVector, + 0, + tokenLimitPerTx, + pricePerToken, + payable(paymentRecipient), + uniqueSeeds + ); + + vector[mechanicVectorId] = _vector; + + emit SeedBasedVectorCreated(mechanicVectorId); + } + + /* solhint-disable code-complexity */ + /** + * @notice Update a seed based vector + * @param mechanicVectorId Global mechanic vector ID + * @param newVector New vector fields + * @param updateConfig Config denoting what fields on vector to update + */ + function updateVector( + bytes32 mechanicVectorId, + SeedBasedVector calldata newVector, + SeedBasedVectorUpdateConfig calldata updateConfig + ) external { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if ( + OwnableUpgradeable(metadata.contractAddress).owner() != msg.sender && metadata.contractAddress != msg.sender + ) { + _revert(Unauthorized.selector); + } + + // rather than updating entire vector, update per-field + if (updateConfig.updateStartTimestamp) { + vector[mechanicVectorId].startTimestamp = newVector.startTimestamp == 0 + ? uint48(block.timestamp) + : newVector.startTimestamp; + } + if (updateConfig.updateEndTimestamp) { + vector[mechanicVectorId].endTimestamp = newVector.endTimestamp; + } + if (updateConfig.updateMaxUserClaimableViaVector) { + vector[mechanicVectorId].maxUserClaimableViaVector = newVector.maxUserClaimableViaVector; + } + if (updateConfig.updateMaxTotalClaimableViaVector) { + vector[mechanicVectorId].maxTotalClaimableViaVector = newVector.maxTotalClaimableViaVector; + } + if (updateConfig.updateTokenLimitPerTx) { + vector[mechanicVectorId].tokenLimitPerTx = newVector.tokenLimitPerTx; + } + if (updateConfig.updatePaymentRecipient) { + vector[mechanicVectorId].paymentRecipient = newVector.paymentRecipient; + } + if (updateConfig.updatePricePerToken) { + vector[mechanicVectorId].pricePerToken = newVector.pricePerToken; + } + + emit SeedBasedVectorUpdated(mechanicVectorId); + } + + /** + * @notice Set the burn redeem 1155 config for a vector + * @param mechanicVectorId Global mechanic vector ID + * @param newConfig New Burn/Redeem 1155 config + */ + function setBurnRedeem1155Config(bytes32 mechanicVectorId, BurnRedeem1155Config calldata newConfig) external { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if ( + OwnableUpgradeable(metadata.contractAddress).owner() != msg.sender && metadata.contractAddress != msg.sender + ) { + _revert(Unauthorized.selector); + } + + _burnRedeem1155Config[mechanicVectorId] = newConfig; + } + + /* solhint-enable code-complexity */ + + /** + * @notice See {IMechanic-processNumMint} + */ + function processNumMint( + bytes32 mechanicVectorId, + address recipient, + uint32 numToMint, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + _processMint(mechanicVectorId, recipient, numToMint, data); + + BurnRedeem1155Config memory burnRedeemConfig = _burnRedeem1155Config[mechanicVectorId]; + if (burnRedeemConfig.burnContract != address(0)) { + _processBurnRedeem(burnRedeemConfig, minter, numToMint); + } + } + + /** + * @notice See {IMechanic-processChooseMint} + */ + function processChooseMint( + bytes32 mechanicVectorId, + address recipient, + uint256[] calldata tokenIds, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + // currently we don't support "choose token to mint" functionality for seed based mints + _revert(InvalidMint.selector); + } + + /** + * @notice Get raw vector data + * @param mechanicVectorId Mechanic vector ID + */ + function getRawVector(bytes32 mechanicVectorId) external view returns (SeedBasedVector memory _vector) { + _vector = vector[mechanicVectorId]; + } + + /** + * @notice Get a vector's full state + * @param mechanicVectorId Mechanic vector ID + */ + function getVectorState( + bytes32 mechanicVectorId + ) external view returns (SeedBasedVector memory _vector, uint256 collectionSupply, uint256 collectionSize) { + _vector = vector[mechanicVectorId]; + (collectionSupply, collectionSize) = _collectionSupplyAndSize(mechanicVectorId); + } + + /** + * @notice Withdraw native gas token + */ + function withdrawNativeGasToken(uint256 amountToWithdraw, address payable recipient) external onlyOwner { + (bool sentToRecipient, bytes memory data) = recipient.call{ value: amountToWithdraw }(""); + if (!sentToRecipient) { + _revert(EtherSendFailed.selector); + } + } + + /* solhint-disable no-empty-blocks */ + /** + * @notice Limit upgrades of contract to SeedBasedMintMechanic owner + * @param // New implementation address + */ + function _authorizeUpgrade(address) internal override onlyOwner {} + + /** + * @notice Process sequential mint logic + * @param mechanicVectorId Mechanic vector ID + * @param recipient Mint recipient + * @param numToMint Number of tokens to mint + */ + function _processMint(bytes32 mechanicVectorId, address recipient, uint32 numToMint, bytes calldata data) private { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if (metadata.contractAddress == address(0)) { + revert("Vector doesn't exist"); + } + SeedBasedVector memory _vector = vector[mechanicVectorId]; + uint48 newNumClaimedForUser = uint48(userClaims[mechanicVectorId][recipient]) + numToMint; + bytes32 seedData = keccak256(data); + uint256 newSeedCount = seedInfo[mechanicVectorId][seedData] + 1; + + uint48 newSupply = _vector.currentSupply + numToMint; + if ( + block.timestamp < _vector.startTimestamp || + (block.timestamp > _vector.endTimestamp && _vector.endTimestamp != 0) || + (_vector.maxTotalClaimableViaVector != 0 && newSupply > _vector.maxTotalClaimableViaVector) || + (_vector.maxUserClaimableViaVector != 0 && newNumClaimedForUser > _vector.maxUserClaimableViaVector) || + (_vector.tokenLimitPerTx != 0 && numToMint > _vector.tokenLimitPerTx) || + numToMint > 1 + ) { + _revert(InvalidMint.selector); + } + + if (_vector.uniqueSeeds && newSeedCount != 1) { + _revert(SeedAlreadyUsed.selector); + } + + uint200 totalPrice = _vector.pricePerToken * numToMint; + _processPayment(mechanicVectorId, _vector.paymentRecipient, totalPrice); + + seedInfo[mechanicVectorId][seedData] = newSeedCount; + vector[mechanicVectorId].currentSupply = newSupply; + userClaims[mechanicVectorId][recipient] = uint64(newNumClaimedForUser); + + emit SeedBasedMint(mechanicVectorId, recipient, _vector.pricePerToken, numToMint); + + emit CustomMintData(address(this), metadata.contractAddress, data); + } + + /** + * @notice Returns a collection's current supply + * @param mechanicVectorId Mechanic vector ID + */ + function _collectionSupplyAndSize(bytes32 mechanicVectorId) private view returns (uint256 supply, uint256 size) { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if (metadata.contractAddress == address(0)) { + revert("Vector doesn't exist"); + } + if (metadata.isEditionBased) { + IEditionCollection.EditionDetails memory edition = IEditionCollection(metadata.contractAddress) + .getEditionDetails(metadata.editionId); + supply = edition.supply; + size = edition.size; + } else { + // supply holds a tighter constraint (no burns), some old contracts don't have it + try IERC721GeneralSupplyMetadata(metadata.contractAddress).supply() returns (uint256 _supply) { + supply = _supply; + } catch { + supply = IERC721GeneralSupplyMetadata(metadata.contractAddress).totalSupply(); + } + size = IERC721GeneralSupplyMetadata(metadata.contractAddress).limitSupply(); + } + } + + /** + * @notice Process payment in native gas token, sending to creator and platform + * @param mechanicVectorId ID of vector + * @param recipient Creator recipient of payment + * @param totalAmount Total amount being paid + */ + function _processPayment(bytes32 mechanicVectorId, address payable recipient, uint256 totalAmount) private { + if (totalAmount > msg.value) { + _revert(InvalidPaymentAmount.selector); + } + (bool sentToRecipient, bytes memory dataRecipient) = recipient.call{ value: totalAmount }(""); + if (!sentToRecipient) { + _revert(EtherSendFailed.selector); + } + emit SeedBasedNativeTokenPayment(mechanicVectorId, recipient, totalAmount, 10000); + } + + /** + * @notice Process burn / redeem + * @param burnRedeemConfig Burn / redeem config + * @param minter Minter burning tokens + * @param numToMint Number of tokens to mint + */ + function _processBurnRedeem( + BurnRedeem1155Config memory burnRedeemConfig, + address minter, + uint32 numToMint + ) private { + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = uint256(burnRedeemConfig.tokenId); + uint256[] memory amounts = new uint256[](1); + amounts[0] = uint256(burnRedeemConfig.numToBurnPerMint) * uint256(numToMint); + + IManifold1155Burn(burnRedeemConfig.burnContract).burn(minter, tokenIds, amounts); + } +} diff --git a/contracts/mint/mechanics/VerisartMechanic.sol b/contracts/mint/mechanics/VerisartMechanic.sol new file mode 100644 index 0000000..3942aa2 --- /dev/null +++ b/contracts/mint/mechanics/VerisartMechanic.sol @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "./MechanicMintManagerClientUpgradeable.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +/** + * @notice Highlight <> Verisart mint mechanic + */ +contract VerisartMechanic is MechanicMintManagerClientUpgradeable, UUPSUpgradeable { + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @notice Throw when mint recipient cannot receive the mints, due to hitting the per-vector limit + */ + error VectorMintLimitExceeded(); + + /** + * @notice Throw when mint recipient cannot receive the mints, due to hitting the per-user limit + */ + error RecipientUserMintLimitExceeded(); + + /** + * @notice Throw when vector configuration is invalid + */ + error InvalidVectorConfig(); + + /** + * @notice Throw when caller is unauthorized + */ + error Unauthorized(); + + /** + * @notice Throw when admin is invalid to add or remove + */ + error InvalidAdmin(); + + /** + * @notice Throw when minter is invalid to add or remove + */ + error InvalidMinter(); + + /** + * @notice Throw when signed mint has used claim ID + */ + error ClaimUsed(); + + /** + * @notice Throw when mechanic ID is invalid (missing mechanic vector on MintManager) + */ + error InvalidMechanicID(); + + /** + * @notice Throw when attempting to mint via signature on a mint where sig-based mints are disallowed + */ + error SignedMintingDisabled(); + + /** + * @notice Verisart vector data + */ + struct VerisartVector { + uint48 size; // size == 0 means unlimited + uint48 supply; + uint32 maxClaimableByUser; // maxClaimableByUser == 0 means unlimited + bool signedMintingDisabled; + // remaining slots for future data + } + + /** + * @notice Verisart vector update config (used for gas efficiency) + */ + struct VerisartUpdateConfig { + bool updateSize; + bool updateMaxClaimableByUser; + bool updateSignedMintingDisabled; + } + + /** + * @notice Admins + */ + EnumerableSet.AddressSet private _admins; + + /** + * @notice Allowed global minters + */ + EnumerableSet.AddressSet private _globalMinters; + + /** + * @notice Allowed minters per vector (indexed by hash(mechanicVectorId, minter)) + */ + mapping(bytes32 => bool) private _vectorLevelMinters; + + /** + * @notice Data per vector + */ + mapping(bytes32 => VerisartVector) private _vectors; + + /** + * @notice Track number of mints per recipient per vector + */ + mapping(bytes32 => mapping(address => uint32)) private _mintsPerRecipient; + + /** + * @notice Track signed mints to avoid replay attacks + */ + mapping(bytes32 => bool) private _signedMints; + + /** + * @notice Constants that help with EIP-712, signature based minting + */ + bytes32 private constant _DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + + bytes32 private constant _MINT_SIGNED_TYPEHASH = + keccak256("VerisartMint(address sender,address to,bytes32 mechanicVectorId,bytes32 claimNonce)"); + + /** + * @notice Emitted when vector is created + */ + event VerisartVectorCreated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when vector is updated + */ + event VerisartVectorUpdated(bytes32 indexed mechanicVectorId); + + /** + * @notice Emitted when mint is processed + */ + event VerisartMint( + bytes32 indexed mechanicVectorId, + address indexed minterOrSigner, + bool indexed signatureBased, + address recipient, + uint32 numToMint + ); + + /** + * @notice Events emitted during admin and minter re-configurations + */ + event AdminAdded(address indexed admin, address by); + event AdminRemoved(address indexed admin, address by); + event GlobalMinterAdded(address indexed globalMinter, address by); + event GlobalMinterRemoved(address indexed globalMinter, address by); + event VectorLevelMinterAdded(bytes32 indexed mechanicVectorId, address indexed vectorLevelMinter, address by); + event VectorLevelMinterRemoved(bytes32 indexed mechanicVectorId, address indexed vectorLevelMinter, address by); + + /** + * @notice Enforce caller to be an admin + */ + modifier onlyAdmin() { + if (!_admins.contains(msg.sender)) { + _revert(Unauthorized.selector); + } + _; + } + + /** + * @notice Enforce caller to be an admin, the collection contract itself, or the collection owner + */ + modifier onlyVectorAdmin(bytes32 mechanicVectorId) { + MechanicVectorMetadata memory metadata = _getMechanicVectorMetadata(mechanicVectorId); + if (metadata.contractAddress == address(0) || metadata.mechanic != address(this)) { + _revert(InvalidMechanicID.selector); + } + if ( + !_admins.contains(msg.sender) && + metadata.contractAddress != msg.sender && + OwnableUpgradeable(metadata.contractAddress).owner() != msg.sender + ) { + _revert(Unauthorized.selector); + } + _; + } + + /** + * @notice Initialize mechanic contract + * @param _mintManager Mint manager address + * @param platform Platform + * @param initialAdmin Initial admin + * @param initialGlobalMinter Initial global minter + */ + function initialize( + address _mintManager, + address platform, + address initialAdmin, + address initialGlobalMinter + ) external initializer { + __MechanicMintManagerClientUpgradeable_initialize(_mintManager, platform); + if (initialAdmin != address(0)) { + _admins.add(initialAdmin); + } + if (initialGlobalMinter != address(0)) { + _globalMinters.add(initialGlobalMinter); + } + } + + /** + * @notice Create a Verisart mint vector + * @param mechanicVectorId Global mechanic vector ID + * @param vectorData Vector data, to be deserialized into Verisart vector data + */ + function createVector(bytes32 mechanicVectorId, bytes memory vectorData) external onlyMintManager { + (uint48 size, uint32 maxClaimableByUser) = abi.decode(vectorData, (uint48, uint32)); + VerisartVector memory _vector = VerisartVector(size, 0, maxClaimableByUser, false); + + if (size != 0 || maxClaimableByUser != 0) { + _vectors[mechanicVectorId] = _vector; + } + + emit VerisartVectorCreated(mechanicVectorId); + } + + /** + * @notice Update a Verisart mint vector + * @dev Caller must either be the collection contract itself, the collection owner, or an admin + * @param mechanicVectorId Global mechanic vector ID + * @param newVector New vector fields + * @param updateConfig Config denoting what fields on vector to updatae + */ + function updateVector( + bytes32 mechanicVectorId, + VerisartVector calldata newVector, + VerisartUpdateConfig calldata updateConfig + ) external onlyVectorAdmin(mechanicVectorId) { + // one slot, so load entirety into memory + VerisartVector memory _vector = _vectors[mechanicVectorId]; + + if (updateConfig.updateSize) { + if (newVector.size < _vector.supply) { + _revert(InvalidVectorConfig.selector); + } + _vector.size = newVector.size; + } + if (updateConfig.updateMaxClaimableByUser) { + _vector.maxClaimableByUser = newVector.maxClaimableByUser; + } + if (updateConfig.updateSignedMintingDisabled) { + _vector.signedMintingDisabled = newVector.signedMintingDisabled; + } + _vectors[mechanicVectorId] = _vector; + + emit VerisartVectorUpdated(mechanicVectorId); + } + + /** + * @notice See {IMechanic-processNumMint} + */ + function processNumMint( + bytes32 mechanicVectorId, + address recipient, + uint32 numToMint, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + _processMint(mechanicVectorId, numToMint, recipient, minter, data); + } + + /** + * @notice See {IMechanic-processChooseMint} + */ + function processChooseMint( + bytes32 mechanicVectorId, + address recipient, + uint256[] calldata tokenIds, + address minter, + MechanicVectorMetadata calldata mechanicVectorMetadata, + bytes calldata data + ) external payable onlyMintManager { + _processMint(mechanicVectorId, uint32(tokenIds.length), recipient, minter, data); + } + + /** + * @notice Add an admin + * @param admin Admin to add + */ + function addAdmin(address admin) external onlyOwner { + if (!_admins.add(admin)) { + _revert(InvalidAdmin.selector); + } + + emit AdminAdded(admin, msg.sender); + } + + /** + * @notice Remove an admin + * @param admin Admin to remove + */ + function removeAdmin(address admin) external onlyOwner { + if (!_admins.remove(admin)) { + _revert(InvalidAdmin.selector); + } + + emit AdminRemoved(admin, msg.sender); + } + + /** + * @notice Add a global minter + * @param globalMinter Global minter to add + */ + function addGlobalMinter(address globalMinter) external onlyAdmin { + if (!_globalMinters.add(globalMinter)) { + _revert(InvalidMinter.selector); + } + + emit GlobalMinterAdded(globalMinter, msg.sender); + } + + /** + * @notice Remove a global minter + * @param globalMinter Global minter to remove + */ + function removeGlobalMinter(address globalMinter) external onlyAdmin { + if (!_globalMinters.remove(globalMinter)) { + _revert(InvalidMinter.selector); + } + + emit GlobalMinterRemoved(globalMinter, msg.sender); + } + + /** + * @notice Add a vector-level minter + * @param mechanicVectorId ID of vector to add vector-level minter to + * @param vectorLevelMinter Vector level minter to add + */ + function addVectorLevelMinter( + bytes32 mechanicVectorId, + address vectorLevelMinter + ) external onlyVectorAdmin(mechanicVectorId) { + bytes32 permissionId = keccak256(abi.encodePacked(mechanicVectorId, vectorLevelMinter)); + if (_vectorLevelMinters[permissionId]) { + _revert(InvalidMinter.selector); + } else { + _vectorLevelMinters[permissionId] = true; + } + + emit VectorLevelMinterAdded(mechanicVectorId, vectorLevelMinter, msg.sender); + } + + /** + * @notice Remove a vector-level minter + * @param mechanicVectorId ID of vector to remove vector-level minter from + * @param vectorLevelMinter Vector level minter to remove + */ + function removeVectorLevelMinter( + bytes32 mechanicVectorId, + address vectorLevelMinter + ) external onlyVectorAdmin(mechanicVectorId) { + bytes32 permissionId = keccak256(abi.encodePacked(mechanicVectorId, vectorLevelMinter)); + if (!_vectorLevelMinters[permissionId]) { + _revert(InvalidMinter.selector); + } else { + _vectorLevelMinters[permissionId] = false; + } + + emit VectorLevelMinterRemoved(mechanicVectorId, vectorLevelMinter, msg.sender); + } + + /** + * @notice Return vector data + * @param mechanicVectorId Global mechanic vector ID + */ + function getVectorData(bytes32 mechanicVectorId) external view returns (VerisartVector memory) { + return _vectors[mechanicVectorId]; + } + + /** + * @notice Return vector supply + * @param mechanicVectorId Global mechanic vector ID + */ + function getVectorSupply(bytes32 mechanicVectorId) external view returns (uint64) { + return _vectors[mechanicVectorId].supply; + } + + /** + * @notice Return vector size + * @param mechanicVectorId Global mechanic vector ID + */ + function getVectorSize(bytes32 mechanicVectorId) external view returns (uint64) { + return _vectors[mechanicVectorId].size; + } + + /** + * @notice Returns if signed minting is allowed for the vector + * @param mechanicVectorId ID of vector to check + */ + function signedMintingAllowed(bytes32 mechanicVectorId) external view returns (bool) { + return !_vectors[mechanicVectorId].signedMintingDisabled; + } + + /** + * @notice Return global minters + */ + function globalMinters() external view returns (address[] memory) { + return _globalMinters.values(); + } + + /** + * @notice Return admins + */ + function admins() external view returns (address[] memory) { + return _admins.values(); + } + + /** + * @notice Compatible identifier + */ + function minterType() external pure returns (string memory) { + return "VerisartHighlightIntegrationMechanic"; + } + + /** + * @notice Return if a minter is a global minter + * @param minter Minter to check + */ + function isGlobalMinter(address minter) public view returns (bool) { + return _globalMinters.contains(minter); + } + + /** + * @notice Return if minter is enabled as a vector-level minter for a given vector + * @param mechanicVectorId ID of vector + * @param minter Minter to check + */ + function isVectorLevelMinter(bytes32 mechanicVectorId, address minter) public view returns (bool) { + return _vectorLevelMinters[keccak256(abi.encodePacked(mechanicVectorId, minter))]; + } + + /* solhint-disable no-empty-blocks */ + /** + * @notice Limit upgrades of contract to VerisartMechanic owner + * @param // New implementation address + */ + function _authorizeUpgrade(address) internal override onlyOwner {} + + /** + * @notice Process Verisart mint + * @param mechanicVectorId Mechanic vector ID + * @param numToMint Number of tokens to mint + * @param recipient Mint recipient + * @param minter Original caller of mint on MintManager or signer of mint + * @param data Mint signature data (if applicable) + */ + function _processMint( + bytes32 mechanicVectorId, + uint32 numToMint, + address recipient, + address minter, + bytes calldata data + ) private { + (bool isSignatureBased, address minterOrSigner) = _validateMinterOrSigner( + mechanicVectorId, + minter, + recipient, + data + ); + // one slot, so load entirety into memory + VerisartVector memory _vector = _vectors[mechanicVectorId]; + + if (isSignatureBased && _vector.signedMintingDisabled) { + _revert(SignedMintingDisabled.selector); + } + uint48 newVectorSupply = _vector.supply + numToMint; + if (newVectorSupply > _vector.size && _vector.size != 0) { + _revert(VectorMintLimitExceeded.selector); + } + uint32 newUserSupply = _mintsPerRecipient[mechanicVectorId][recipient] + numToMint; + if (newUserSupply > _vector.maxClaimableByUser && _vector.maxClaimableByUser != 0) { + _revert(RecipientUserMintLimitExceeded.selector); + } + + _vectors[mechanicVectorId].supply = newVectorSupply; + _mintsPerRecipient[mechanicVectorId][recipient] = newUserSupply; + + emit VerisartMint(mechanicVectorId, minterOrSigner, isSignatureBased, recipient, numToMint); + } + + /** + * @notice Recover address of signer and update relevant state + * @param args Input to signature + * @param claimNonce Claim identifier + * @param signature Signature + * @param mechanicVectorId ID of vector + */ + function _checkSigned( + bytes memory args, + bytes32 claimNonce, + bytes memory signature, + bytes32 mechanicVectorId + ) private returns (address) { + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _getDomainSeperator(), keccak256(args))); + if (_signedMints[claimNonce]) { + _revert(ClaimUsed.selector); + } + _signedMints[claimNonce] = true; + return ECDSA.recover(digest, signature); + } + + /** + * @notice Validate minter or signer + * @param mechanicVectorId Mechanic vector ID + * @param minter Original minting address + * @param recipient Mint recipient + * @param data Mint signature data (optional) + */ + function _validateMinterOrSigner( + bytes32 mechanicVectorId, + address minter, + address recipient, + bytes calldata data + ) private returns (bool, address) { + bool isSignatureBased = false; + if (data.length > 0) { + isSignatureBased = true; + (bytes32 claimNonce, bytes memory signature) = abi.decode(data, (bytes32, bytes)); + + minter = _checkSigned( + abi.encode(_MINT_SIGNED_TYPEHASH, minter, recipient, mechanicVectorId, claimNonce), + claimNonce, + signature, + mechanicVectorId + ); + } + if (!isGlobalMinter(minter) && !isVectorLevelMinter(mechanicVectorId, minter)) { + _revert(Unauthorized.selector); + } + return (isSignatureBased, minter); + } + + /** + * @notice Return EIP712 domain seperator + */ + function _getDomainSeperator() private view returns (bytes32) { + return + keccak256( + abi.encode( + _DOMAIN_TYPEHASH, + keccak256("Verisart"), + keccak256("1"), + block.chainid, + address(this), + 0xf84c063feaae44fa2f4a846cf2dadc08b50b6a5b0b04bed3d70ed9fa1a199edc // verisart salt + ) + ); + } +} diff --git a/contracts/mint/mechanics/interfaces/IManifold1155Burn.sol b/contracts/mint/mechanics/interfaces/IManifold1155Burn.sol new file mode 100644 index 0000000..151d584 --- /dev/null +++ b/contracts/mint/mechanics/interfaces/IManifold1155Burn.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @notice Interface to burn tokens on a Manifold 1155 Creator contract + */ +interface IManifold1155Burn { + function burn(address account, uint256[] memory tokenIds, uint256[] memory amounts) external; +} diff --git a/contracts/mint/mechanics/interfaces/IMechanic.sol b/contracts/mint/mechanics/interfaces/IMechanic.sol index c9d1307..59698c9 100644 --- a/contracts/mint/mechanics/interfaces/IMechanic.sol +++ b/contracts/mint/mechanics/interfaces/IMechanic.sol @@ -50,4 +50,4 @@ interface IMechanic is IMechanicData { MechanicVectorMetadata calldata mechanicVectorMetadata, bytes calldata data ) external payable; -} \ No newline at end of file +} diff --git a/contracts/mint/mechanics/interfaces/IMechanicMintManagerView.sol b/contracts/mint/mechanics/interfaces/IMechanicMintManagerView.sol index 5d0a23b..78eef8e 100644 --- a/contracts/mint/mechanics/interfaces/IMechanicMintManagerView.sol +++ b/contracts/mint/mechanics/interfaces/IMechanicMintManagerView.sol @@ -9,4 +9,10 @@ interface IMechanicMintManagerView is IMechanicData { * @param mechanicVectorId Global mechanic vector ID */ function mechanicVectorMetadata(bytes32 mechanicVectorId) external view returns (MechanicVectorMetadata memory); + + /** + * @notice Returns whether an address is a valid platform executor + * @param _executor Address to be checked + */ + function isPlatformExecutor(address _executor) external view returns (bool); } diff --git a/contracts/mint/referrals/IReferralManagerView.sol b/contracts/mint/referrals/IReferralManagerView.sol new file mode 100644 index 0000000..bc6124b --- /dev/null +++ b/contracts/mint/referrals/IReferralManagerView.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +interface IReferralManagerView { + /** + * @notice Get referrer for a tx + */ + function getCurrentReferrer(bytes32 vectorId) external view returns (address); +} diff --git a/contracts/mint/referrals/ReferralManager.sol b/contracts/mint/referrals/ReferralManager.sol new file mode 100644 index 0000000..a0c02b7 --- /dev/null +++ b/contracts/mint/referrals/ReferralManager.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +import "./IReferralManagerView.sol"; +import "../MintManager.sol"; + +contract ReferralManager is IReferralManagerView { + /** + * @notice Throw if referrer is passed in is tx sender + */ + error InvalidReferrer_ReferralManager(); + + /** + * @notice Store the referrer for a tx + */ + mapping(bytes32 => address) private _txReferrer; + + /** + * @notice MintManager backup address + */ + address private _backupMintManager; + + /** + * @notice Initialize contract + */ + constructor(address backupMintManager) { + _backupMintManager = backupMintManager; + } + + /** + * @notice Mint via an abridged vector + * @param vectorId ID of vector + * @param numTokensToMint Number of tokens to mint + * @param mintRecipient Who to mint the NFT(s) to + * @param referrer Referrer + */ + function vectorMint721WithReferral( + uint256 vectorId, + uint48 numTokensToMint, + address mintRecipient, + address referrer + ) external payable { + _txReferrer[_encodeCurrentTx(bytes32(vectorId))] = referrer; + + MintManager(_mintManager()).vectorMint721{ value: msg.value }(vectorId, numTokensToMint, mintRecipient); + } + + /** + * @notice Mint on a collection with sequentially minted token IDs with a valid claim + * @param claim Claim + * @param claimSignature Signed + encoded claim + * @param mintRecipient Who to mint the NFT(s) to. + * Can't mint to different recipient if tx isn't sent by claim.claimer. + * @param referrer Referrer + */ + function gatedNumMint721WithReferral( + MintManager.Claim calldata claim, + bytes calldata claimSignature, + address mintRecipient, + bool isEditionBased, + address referrer + ) external payable { + _txReferrer[_encodeCurrentTx(claim.offchainVectorId)] = referrer; + + MintManager(_mintManager()).gatedNumMint{ value: msg.value }( + claim, + claimSignature, + mintRecipient, + isEditionBased + ); + } + + /** + * @notice Mint on a Series with a valid claim where one can choose the tokens to mint + * @param claim Series Claim + * @param claimSignature Signed + encoded claim + * @param mintRecipient Who to mint the NFT(s) to. + * Can't mint to different recipient if tx isn't sent by claim.claimer. + * @param tokenIds IDs of NFTs to mint + * @param referrer Referrer + */ + function gatedChooseMint721WithReferral( + MintManager.SeriesClaim calldata claim, + bytes calldata claimSignature, + address mintRecipient, + uint256[] calldata tokenIds, + address referrer + ) external payable { + _txReferrer[_encodeCurrentTx(claim.offchainVectorId)] = referrer; + + MintManager(_mintManager()).gatedSeriesMintChooseToken{ value: msg.value }( + claim, + claimSignature, + mintRecipient, + tokenIds + ); + } + + /** + * @notice Get referrer for a tx + */ + function getReferrer(bytes32 vectorId, address txSender, uint256 blockNumber) external view returns (address) { + return _txReferrer[_encodeTx(vectorId, txSender, blockNumber)]; + } + + /** + * @notice Get referrer for a tx + */ + function getCurrentReferrer(bytes32 vectorId) external view returns (address) { + return _txReferrer[_encodeCurrentTx(vectorId)]; + } + + /** + * @notice Encode tx for referrer + */ + function _encodeTx(bytes32 vectorId, address txSender, uint256 blockNumber) private view returns (bytes32) { + return keccak256(abi.encodePacked(vectorId, txSender, blockNumber)); + } + + /** + * @notice Encode tx for referrer + */ + function _encodeCurrentTx(bytes32 vectorId) private view returns (bytes32) { + return _encodeTx(vectorId, tx.origin, block.number); + } + + /* solhint-disable code-complexity */ + /** + * @notice Get the MintManager address + */ + function _mintManager() private view returns (address) { + if (block.chainid == 1) { + return 0x1bf979282181f2b7a640d17aB5D2e25125F2de5e; + } else if (block.chainid == 8453) { + return 0x8087039152c472Fa74F47398628fF002994056EA; + } else if (block.chainid == 10) { + return 0xFafd47bb399d570b5AC95694c5D2a1fb5EA030bB; + } else if (block.chainid == 7777777) { + return 0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA; + } else if (block.chainid == 42161) { + return 0x41cbab1028984A34C1338F437C726de791695AE8; + } else if (block.chainid == 137) { + return 0xfbb65C52f439B762F712026CF6DD7D8E82F81eb9; + } else if (block.chainid == 84532) { + return 0x41cbab1028984A34C1338F437C726de791695AE8; + } else if (block.chainid == 11155111) { + return 0xd698911B1Bb2a9c849Bf5e2604aF110766f396b6; + } else { + return _backupMintManager; + } + } +} diff --git a/contracts/observability/GengineObservability.sol b/contracts/observability/GengineObservability.sol new file mode 100644 index 0000000..0865c1e --- /dev/null +++ b/contracts/observability/GengineObservability.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "./IGengineObservability.sol"; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +/** + * @title Observability + * @author highlight.xyz + * @notice Highlight Observability + * @dev Singleton to coalesce select Highlight protocol events + */ +contract GengineObservability is IGengineObservability, UUPSUpgradeable, OwnableUpgradeable { + /** + * @notice Initialize implementation with initial owner + * @param _owner Initial owner + */ + function initialize(address _owner) external initializer { + __Ownable_init(); + _transferOwnership(_owner); + } + + /** + * @notice See {IGengineObservability-emitContractMetadataSet} + */ + function emitContractMetadataSet( + string calldata name, + string calldata symbol, + string calldata contractURI + ) external { + emit ContractMetadataSet(msg.sender, name, symbol, contractURI); + } + + /** + * @notice See {IGengineObservability-emitLimitSupplySet} + */ + function emitLimitSupplySet(uint256 newLimitSupply) external { + emit LimitSupplySet(msg.sender, newLimitSupply); + } + + /** + * @notice See {IGengineObservability-emitBaseUriSet} + */ + function emitBaseUriSet(string calldata newBaseUri) external { + emit BaseUriSet(msg.sender, newBaseUri); + } + + /** + * @notice See {IGengineObservability-emitGenerativeSeriesDeployed} + */ + function emitGenerativeSeriesDeployed(address contractAddress) external { + emit GenerativeSeriesDeployed(msg.sender, contractAddress); + } + + /** + * @notice See {IGengineObservability-emitSeriesDeployed} + */ + function emitSeriesDeployed(address contractAddress) external { + emit SeriesDeployed(msg.sender, contractAddress); + } + + /** + * @notice See {IGengineObservability-emitTokenMint} + */ + function emitTokenMint(address to, uint256 numMinted) external { + emit TokenMint(msg.sender, to, numMinted); + } + + /** + * @notice See {IGengineObservability-emitTokenUpdated} + */ + function emitTokenUpdated(address contractAddress, uint256 tokenId) external { + emit TokenUpdated(msg.sender, tokenId); + } + + /** + * @notice See {IGengineObservability-emitTransfer} + */ + function emitTransfer(address from, address to, uint256 tokenId) external { + emit Transfer(msg.sender, from, to, tokenId); + } + + /** + * @notice See {IGengineObservability-emitCustomMintData} + */ + function emitCustomMintData(address contractAddress, bytes calldata data) external { + emit CustomMintData(msg.sender, contractAddress, data); + } + + /** + * @notice See {IGengineObservability-emitHighlightRegenerate} + */ + function emitHighlightRegenerate(address collection, uint256 tokenId) external { + emit HighlightRegenerate(msg.sender, collection, tokenId); + } + + /* solhint-disable no-empty-blocks */ + /** + * @notice Limit upgrades of contract to owner + * @param // New implementation + */ + function _authorizeUpgrade(address) internal override onlyOwner {} +} diff --git a/contracts/observability/IGengineObservability.sol b/contracts/observability/IGengineObservability.sol new file mode 100644 index 0000000..6e991a4 --- /dev/null +++ b/contracts/observability/IGengineObservability.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +/** + * @title IGengineObservability + * @author highlight.xyz + * @notice Interface to interact with the Highlight Gengine observability singleton + * @dev Singleton to coalesce select Highlight Gengine protocol events + */ +interface IGengineObservability { + /** + * @notice Emitted when contract metadata is set + * @param contractAddress Initial contract that emitted event + * @param name New name + * @param symbol New symbol + * @param contractURI New contract uri + */ + event ContractMetadataSet(address indexed contractAddress, string name, string symbol, string contractURI); + + /** + * @notice Emitted when limit supply is set + * @param contractAddress Initial contract that emitted event + * @param newLimitSupply Limit supply to set + */ + event LimitSupplySet(address indexed contractAddress, uint256 indexed newLimitSupply); + + /** + * @notice Emits when a series collection has its base uri set + * @param contractAddress Contract with updated base uri + * @param newBaseUri New base uri + */ + event BaseUriSet(address indexed contractAddress, string newBaseUri); + + /************************** + Deployment events + **************************/ + + /** + * @notice Emitted when Generative Series contract is deployed + * @param deployer Contract deployer + * @param contractAddress Address of contract that was deployed + */ + event GenerativeSeriesDeployed(address indexed deployer, address indexed contractAddress); + + /** + * @notice Emitted when Series contract is deployed + * @param deployer Contract deployer + * @param contractAddress Address of contract that was deployed + */ + event SeriesDeployed(address indexed deployer, address indexed contractAddress); + + /************************** + ERC721 events + **************************/ + + /** + * @notice Emitted on a mint where a number of tokens are minted + * @param contractAddress Address of contract being minted on + * @param numMinted Number of tokens minted + */ + event TokenMint(address indexed contractAddress, address indexed to, uint256 indexed numMinted); + + /** + * @notice Emitted whenever the metadata for the token is updated + * @param contractAddress NFT contract token resides on + * @param tokenId Token being updated + */ + event TokenUpdated(address indexed contractAddress, uint256 indexed tokenId); + + /** + * @notice Emitted when `tokenId` token is transferred from `from` to `to` on contractAddress + * @param contractAddress NFT contract token resides on + * @param from Token sender + * @param to Token receiver + * @param tokenId Token being sent + */ + event Transfer(address indexed contractAddress, address indexed from, address to, uint256 indexed tokenId); + + /** + * @notice Emitted for the seed based data on mint + * @param sender contract emitting the event + * @param contractAddress NFT contract token resides on + * @param data custom mint data + */ + event CustomMintData(address indexed sender, address indexed contractAddress, bytes data); + + /** + * @notice Emitted to regenerate the generative art for a token + * @param sender contract emitting the event + * @param collection NFT contract token resides on + * @param tokenId Token ID + */ + event HighlightRegenerate(address indexed sender, address indexed collection, uint256 indexed tokenId); + + /** + * @notice Emit ContractMetadataSet + */ + function emitContractMetadataSet( + string calldata name, + string calldata symbol, + string calldata contractURI + ) external; + + /** + * @notice Emit LimitSupplySet + */ + function emitLimitSupplySet(uint256 newLimitSupply) external; + + /** + * @notice Emit BaseUriSet + */ + function emitBaseUriSet(string calldata newBaseUri) external; + + /** + * @notice Emit GenerativeSeriesDeployed + */ + function emitGenerativeSeriesDeployed(address contractAddress) external; + + /** + * @notice Emit SeriesDeployed + */ + function emitSeriesDeployed(address contractAddress) external; + + /** + * @notice Emit Token Mint + */ + function emitTokenMint(address to, uint256 numMinted) external; + + /** + * @notice Emit Token Updated + */ + function emitTokenUpdated(address contractAddress, uint256 tokenId) external; + + /** + * @notice Emit Transfer + */ + function emitTransfer(address from, address to, uint256 tokenId) external; + + /** + * @notice Emit Custom Mint Data + */ + function emitCustomMintData(address contractAddress, bytes calldata data) external; + + /** + * @notice Emit HighlightRegenerate + */ + function emitHighlightRegenerate(address collection, uint256 tokenId) external; +} diff --git a/contracts/test/Manifold1155CreatorMock.sol b/contracts/test/Manifold1155CreatorMock.sol new file mode 100644 index 0000000..9a3e5ce --- /dev/null +++ b/contracts/test/Manifold1155CreatorMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; + +contract Manifold1155CreatorMock is ERC1155Creator { + constructor() ERC1155Creator("MyContract", "MC") {} + + function contractType() external view returns (string memory) { + return "Manifold1155CreatorMock"; + } +} diff --git a/contracts/tokenManager/FarcasterBoundTokenManager.sol b/contracts/tokenManager/FarcasterBoundTokenManager.sol new file mode 100644 index 0000000..32b534e --- /dev/null +++ b/contracts/tokenManager/FarcasterBoundTokenManager.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; + +import "../utils/Ownable.sol"; +import "./InterfaceSupportTokenManager.sol"; +import "./interfaces/IPostTransfer.sol"; +import "./interfaces/IFarcaster.sol"; + +/** + * @author highlight.xyz + * @notice A basic token manager that prevents transfers to addresses without a Farcaster ID + */ +contract FarcasterBoundTokenManager is ITokenManager, IPostTransfer, InterfaceSupportTokenManager { + /** + * @notice See {ITokenManager-canUpdateMetadata} + */ + function canUpdateMetadata( + address sender, + uint256 /* id */, + bytes calldata /* newTokenUri */ + ) external view override returns (bool) { + return Ownable(msg.sender).owner() == sender; + } + + /** + * @notice See {ITokenManager-canSwap} + */ + function canSwap( + address sender, + uint256 /* id */, + address /* newTokenManager */ + ) external view override returns (bool) { + return Ownable(msg.sender).owner() == sender; + } + + /** + * @notice See {ITokenManager-canRemoveItself} + */ + function canRemoveItself(address sender, uint256 /* id */) external view override returns (bool) { + return Ownable(msg.sender).owner() == sender; + } + + /** + * @notice See {IPostTransfer-postSafeTransferFrom} + */ + function postSafeTransferFrom( + address /* operator */, + address /* from */, + address to, + uint256 /* id */, + bytes memory /* data */ + ) external view override { + if (IFarcaster(0x00000000Fc6c5F01Fc30151999387Bb99A9f489b).idOf(to) == 0) { + revert("Can only transfer to a Farcaster user"); + } + } + + /** + * @notice See {IPostTransfer-postTransferFrom} + */ + function postTransferFrom( + address /* operator */, + address /* from */, + address to, + uint256 /* id */ + ) external view override { + if (IFarcaster(0x00000000Fc6c5F01Fc30151999387Bb99A9f489b).idOf(to) == 0) { + revert("Can only transfer to a Farcaster user"); + } + } + + /** + * @notice See {IERC165-supportsInterface}. + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(InterfaceSupportTokenManager) returns (bool) { + return + interfaceId == type(IPostTransfer).interfaceId || + InterfaceSupportTokenManager.supportsInterface(interfaceId); + } +} diff --git a/contracts/tokenManager/interfaces/IFarcaster.sol b/contracts/tokenManager/interfaces/IFarcaster.sol new file mode 100644 index 0000000..c5a3f69 --- /dev/null +++ b/contracts/tokenManager/interfaces/IFarcaster.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @author highlight.xyz + * @notice Interact with idOf on the farcaster id registry + */ +interface IFarcaster { + function idOf(address user) external view returns (uint256); +} diff --git a/contracts/utils/FullMath.sol b/contracts/utils/FullMath.sol new file mode 100644 index 0000000..d152cdf --- /dev/null +++ b/contracts/utils/FullMath.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +/* solhint-disable max-line-length */ +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = (0 - denominator) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } + } +} diff --git a/contracts/utils/IUniswapV3PoolState.sol b/contracts/utils/IUniswapV3PoolState.sol new file mode 100644 index 0000000..ab2f748 --- /dev/null +++ b/contracts/utils/IUniswapV3PoolState.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/* solhint-disable max-line-length */ +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks( + int24 tick + ) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions( + bytes32 key + ) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations( + uint256 index + ) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} diff --git a/hardhat.config.ts b/hardhat.config.ts index c4e7244..dd64164 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,4 +1,5 @@ import "@nomicfoundation/hardhat-toolbox"; +import "@nomiclabs/hardhat-ethers"; import { config as dotenvConfig } from "dotenv"; import "hardhat-contract-sizer"; import "hardhat-gas-reporter"; @@ -26,12 +27,14 @@ export const chainIds = { "polygon-mainnet": 137, "polygon-mumbai": 80001, goerli: 5, + sepolia: 11155111, arbitrum: 42161, "arbitrum-goerli": 421613, optimism: 10, "optimism-goerli": 420, base: 8453, "base-goerli": 84531, + "base-sepolia": 84532, zora: 7777777, "zora-goerli": 999, }; @@ -46,21 +49,25 @@ function getChainConfig(chain: keyof typeof chainIds): NetworkUserConfig { export function getUrl(chain: keyof typeof chainIds): string { if (chain === "arbitrum") { - return "https://arb-mainnet.g.alchemy.com/v2/6RXKTS3PtSM59L41inqVagpZW3-r_rq9"; + return "https://arb1.arbitrum.io/rpc"; } else if (chain === "arbitrum-goerli") { - return "https://arb-goerli.g.alchemy.com/v2/jK7-UD3iCOzaFUqa2L_SVI7fkdzCYfwc"; + return "https://arbitrum-goerli-rpc.publicnode.com"; } else if (chain === "optimism") { - return "https://opt-mainnet.g.alchemy.com/v2/XtgT_4vf4xad9To3EOhQpH_7i62hYhKD"; + return "https://optimism.llamarpc.com"; } else if (chain === "optimism-goerli") { - return "https://opt-goerli.g.alchemy.com/v2/COI6ezi-VSOBEQIMKbX5sImZ_mYy6urr"; + return "https://optimism-goerli-rpc.publicnode.com"; } else if (chain === "base") { - return "https://developer-access-mainnet.base.org"; + return "https://mainnet.base.org"; } else if (chain === "base-goerli") { return "https://base-goerli.public.blastapi.io"; } else if (chain === "zora") { return "https://rpc.zora.co"; } else if (chain === "zora-goerli") { return "https://testnet.rpc.zora.co"; + } else if (chain === "polygon-mainnet") { + return "https://polygon-rpc.com/"; + } else if (chain === "base-sepolia") { + return "https://base-sepolia.blockpi.network/v1/rpc/public "; } else { return "https://" + chain + ".infura.io/v3/" + infuraApiKey; } @@ -74,12 +81,14 @@ const config: HardhatUserConfig = { polygon: process.env.POLYGONSCAN_API_KEY || "", polygonMumbai: process.env.POLYGONSCAN_API_KEY || "", goerli: process.env.ETHERSCAN_API_KEY || "", + sepolia: process.env.ETHERSCAN_API_KEY || "", optimisticEthereum: process.env.OPTIMISMSCAN_API_KEY || "", arbitrumOne: process.env.ARBITRUMSCAN_API_KEY || "", "optimism-goerli": process.env.OPTIMISMSCAN_API_KEY || "", "arbitrum-goerli": process.env.ARBITRUMSCAN_API_KEY || "", base: process.env.BASESCAN_API_KEY || "", "base-goerli": process.env.BASESCAN_API_KEY || "", + "base-sepolia": process.env.BASESCAN_API_KEY || "", zora: process.env.ZORASCAN_API_KEY || "", "zora-goerli": process.env.ZORASCAN_API_KEY || "", }, @@ -100,6 +109,14 @@ const config: HardhatUserConfig = { browserURL: "https://goerli.basescan.org", }, }, + { + network: "base-sepolia", + chainId: 84532, + urls: { + apiURL: "https://api-sepolia.basescan.org/api", + browserURL: "https://sepolia-explorer.base.org/", + }, + }, { network: "optimism-goerli", chainId: 420, @@ -151,6 +168,7 @@ const config: HardhatUserConfig = { }, mainnet: getChainConfig("mainnet"), goerli: getChainConfig("goerli"), + sepolia: getChainConfig("sepolia"), "polygon-mainnet": getChainConfig("polygon-mainnet"), "polygon-mumbai": getChainConfig("polygon-mumbai"), arbitrum: getChainConfig("arbitrum"), @@ -159,6 +177,7 @@ const config: HardhatUserConfig = { "optimism-goerli": getChainConfig("optimism-goerli"), base: getChainConfig("base"), "base-goerli": getChainConfig("base-goerli"), + "base-sepolia": getChainConfig("base-sepolia"), zora: getChainConfig("zora"), "zora-goerli": getChainConfig("zora-goerli"), }, @@ -187,7 +206,7 @@ const config: HardhatUserConfig = { }, typechain: { outDir: "types", - target: "ethers-v5", + target: "ethers-v6", }, contractSizer: { runOnCompile: true, diff --git a/package.json b/package.json index 2251747..81a072f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "evm-contracts", - "version": "1.4.0", - "description": "Highlight EVM Smart Contract Protocols", + "version": "1.5.0", + "description": "Highlight EVM Smart Contract Protocol", "main": "index.js", "repository": "git@github.com:highlightxyz/evm-contracts.git", "author": "Ishan ", @@ -36,7 +36,7 @@ "dotenv": "^16.0.1", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", - "ethers": "^5.7.1", + "ethers": "^6.13.0", "fs-extra": "^10.1.0", "hardhat": "^2.10.1", "hardhat-gas-reporter": "^1.0.8", @@ -45,6 +45,7 @@ "lint-staged": "^13.0.3", "lodash": "^4.17.21", "mocha": "^10.0.0", + "mongodb": "^5.7.0", "pinst": "^3.0.0", "prettier": "^2.7.1", "prettier-plugin-solidity": "^1.0.0-dev.23", @@ -73,18 +74,25 @@ "prettier:fix": "yarn prettier:check -w", "test": "yarn test:separate", "test:individual": "AUTO_MINING_ON=true hardhat test", - "test:separate": "yarn test:individual test/AuctionsTest.ts && yarn test:individual test/EditionsMetadataRendererTest.ts && yarn test:individual test/ERC721BaseTest.ts && yarn test:individual test/ERC721EditionsTest.ts && yarn test:individual test/ERC721GeneralTest.ts && yarn test:individual test/ERC721GeneralSequenceTest.ts && yarn test:individual test/ERC721GenerativeTest.ts && yarn test:individual test/ERC721SingleEditionTest.ts && yarn test:individual test/ERC721StandardTest.ts && yarn test:individual test/MetaTransactionsTest.ts && yarn test:individual test/MintManagerTest.ts && yarn test:individual test/MechanicMintVectorsTest.ts && yarn test:individual test/UpgradesTest.ts", + "test:separate": "yarn test:individual test/AuctionsTest.ts && yarn test:individual test/EditionsMetadataRendererTest.ts && yarn test:individual test/ERC721BaseTest.ts && yarn test:individual test/ERC721EditionsTest.ts && yarn test:individual test/ERC721GeneralTest.ts && yarn test:individual test/ERC721GeneralSequenceTest.ts && yarn test:individual test/ERC721GenerativeTest.ts && yarn test:individual test/ERC721SingleEditionTest.ts && yarn test:individual test/ERC721StandardTest.ts && yarn test:individual test/MetaTransactionsTest.ts && yarn test:individual test/MintManagerTest.ts && yarn test:individual test/MechanicMintVectorsTest.ts && yarn test:individual test/RankedAuctionsMechanicTest.ts", "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain", "local": "hardhat node", "local:ipv4": "hardhat node --hostname 127.0.0.1", "task:local": "hardhat --network localhost", "build-contracts": "sol-merger \"./contracts/factory/*.sol\" ./build", - "deploy:all:local": "hardhat deploy --initial-platform-executor-address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --mint-manager-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --editions-metadata-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --platform-payment-address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + "deploy:all:local": "hardhat deploy --initial-platform-executor-address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --mint-manager-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --editions-metadata-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --platform-payment-address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "reformat:system-contracts-config": "node reformatSystemContractsConfig.js && yarn prettier" }, "dependencies": { + "@chainlink/contracts": "^0.8.0", + "@manifoldxyz/creator-core-solidity": "^3.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.6", "@openzeppelin/contracts-upgradeable": "^4.7.3", + "@typechain/ethers-v6": "^0.5.1", + "@uniswap/v3-core": "^1.0.1", "axios": "^1.5.1", "csv-parse": "^5.5.1", + "ethereum-multicall": "^2.24.0", "hardhat-contract-sizer": "^2.6.1", "keccak256": "^1.0.6", "merkletreejs": "^0.2.32", diff --git a/protocol-addresses.json b/protocol-addresses.json index a63fb05..ef8b873 100644 --- a/protocol-addresses.json +++ b/protocol-addresses.json @@ -6,15 +6,22 @@ "AuctionManager": "0x3216FB0105f64cC375E2f431d1a6D00A1A955559", "ERC721EditionsImplementation": "0x91cDE68af933688116337EEBD7d11e8d63AAA76E", "ERC721SingleEditionImplementation": "0x59cC2a7D1Bf61256EbE39F1e9F2497e95317Ea2D", - "ERC721EditionsDFSImplementation": "0x006cdD31f45F7e544a874B28763E1825C81128d5", + "ERC721EditionsDFSImplementation": "0xbAF6c8B147Bc50F265E7C673Df21d53B0A9325be", "ERC721SingleEditionDFSImplementation": "0xD09F64dbbCAa076Fde30Ddd2e23194f5F786665E", "ERC721GeneralImplementation": "0xAAa81ce4795001654Dc56577ed431950D633dABA", - "ERC721GeneralSequenceImplementation": "0x8d67B6ACE3fC4d90b8c276e1d70646ec705b0C9b", - "ERC721GenerativeImplementation": "0x2Be3CE514884dcF92505a9FDaBDe6541779C129b", + "ERC721GeneralSequenceImplementation": "0x611c4E7cde9eb10019BEa9a7725409Ca154Fd1e6", + "ERC721GenerativeImplementation": "0x68bB0F207F0184bf754C141d56939251BbB38Be7", "MinimalForwarder": "0x7Ab179690168f06D4F897A6C0b749C1524F4C772", "Observability": "0xD21cf74A08CEb52555702658d3556300B0983158", "FileDeployer": "0xd687847559A3bEc088251f3cC33E7BAf31e4aB48", - "DiscreteDutchAuctionMechanic": "0x94fa6e7fc2555ada63ea56cfff425558360f0074" + "DiscreteDutchAuctionMechanic": "0x94Fa6e7Fc2555aDA63eA56cfFF425558360F0074", + "GengineObservability": "0xf40cd0797c7a2ace16fB680B0556eED2b691cDAD", + "VerisartMechanic": "0xe66CE7640A5Ecf430F7e02d17fC6F224bE9afF66", + "MintFeeOracle": "0x31BFD198BC59EC2379A5Fb57F26b5Ef3AD126848", + "SeedBasedMintMechanic": "0x922E9f8cc491fACBd403afa143AA53ee9146474C", + "RandomRankedAuctionMechanic": "0xF01E8a33E3a69799401c181a7b3D2c7F28485EC9", + "RankedAuctionMechanic": "0xDFEe0Ed4A217F37b3FA87624eE00fe5685bDc509", + "ReferralManager": "0xD3C63951b2Ed18e8d92B5b251C3B636A45A547d0" }, "goerli": { "MintManager": "0xBF6B4F9Ef1E4B371c40701b5f856F9Fc1d659c70", @@ -40,7 +47,7 @@ "AuctionManager": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "ERC721EditionsImplementation": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", "ERC721SingleEditionImplementation": "0xcCC80ea84E3e6Ee8CaAB489092d46bb912b493AD", - "ERC721EditionsDFSImplementation": "0xecA1aAfE5437B3a231B9E450c47Ffa8De8575a03", + "ERC721EditionsDFSImplementation": "0x47f690ad0168B6E3476B59A15a5dFe7adb8E34bc", "ERC721SingleEditionDFSImplementation": "0x9fA3eA2B36fed5803Ca743E09fEd3204E2B59866", "ERC721GeneralImplementation": "0x51544960e278b38c13c29F2944C1C839fEfCE6E2", "ERC721GeneralSequenceImplementation": "0xF4007F45DCd05BE758Fe9b26500B0010a07dB3cB", @@ -48,7 +55,11 @@ "MinimalForwarder": "0xC5402e0BAF74c1042D72749cB8cA78c58dD93D6f", "Observability": "0xAA45a6e4e1E6e43c14B366Dd0228874fb1DC0eF9", "FileDeployer": "0x21c3a69EaD9b81863B83757ff2645803fF7c7690", - "DiscreteDutchAuctionMechanic": "0x15753e20667961fB30d5aa92e2255B876568BE7e" + "DiscreteDutchAuctionMechanic": "0x15753e20667961fB30d5aa92e2255B876568BE7e", + "GengineObservability": "0x1f6D904685A84C3417Fe0192b53474320CaAca63", + "MintFeeOracle": "0xD09F64dbbCAa076Fde30Ddd2e23194f5F786665E", + "RankedAuctionMechanic": "0xb207774Ac4E32eCE47771e64BDE5ec3894C1De6b", + "ReferralManager": "0x9CF5B12D2e2a88083647Ff2Fe0610F818b28eC77" }, "polygon-mainnet": { "MintManager": "0xfbb65C52f439B762F712026CF6DD7D8E82F81eb9", @@ -57,7 +68,7 @@ "AuctionManager": "0x3CEDCb3170489f2FB509DB23D8A864A55B45036F", "ERC721EditionsImplementation": "0xF150CB22e56FDA37F3c51A6a35f0aC0fd771db2f", "ERC721SingleEditionImplementation": "0x91cDE68af933688116337EEBD7d11e8d63AAA76E", - "ERC721EditionsDFSImplementation": "0x939Fd86C2a0c58202d1F14F59Acd4466A85bC412", + "ERC721EditionsDFSImplementation": "0xe083c23Bd5d873B637fca3DF8A4b8d1D5e8f3a69", "ERC721SingleEditionDFSImplementation": "0x6abC18F4e8c7D8980DdBb97FDE7d6521B394F16A", "ERC721GeneralImplementation": "0x64b35B64DAB456c489124Dc07aA3eD100DdFeD7E", "ERC721GeneralSequenceImplementation": "0xc27925863bF67384e16Dcb1225228c88d0F44A8f", @@ -65,7 +76,11 @@ "MinimalForwarder": "0x03214f1434D84Dd58FcDFc339577c1B3a7Dd9BdE", "Observability": "0x43Ef6CB43586B4B3ce0F4b728D4AE08dD30a0d1e", "FileDeployer": "0x117542b736cB5314a59453081b66208863CC1Acc", - "DiscreteDutchAuctionMechanic": "0xAE22Cd8052D64e7C2aF6B5E3045Fab0a86C8334C" + "DiscreteDutchAuctionMechanic": "0xAE22Cd8052D64e7C2aF6B5E3045Fab0a86C8334C", + "GengineObservability": "0x086984b6C8CBF78EFD3c16e28029C038e700debE", + "MintFeeOracle": "0x9B64EFf523abbb7c52C7da469569704565Bd6Aa7", + "RankedAuctionMechanic": "0x4CCB72E7E0Cd948aF50bC7Bf598Fc4E027b70f98", + "ReferralManager": "0x6fd07d4B5fd7093762Fb2f278769aa7e2511d45c" }, "optimism-goerli": { "MintManager": "0x41cbab1028984A34C1338F437C726de791695AE8", @@ -108,7 +123,7 @@ "AuctionManager": "0x9AcDfE8020c3c191F7aA158e1c155F12e55c9717", "ERC721EditionsImplementation": "0xa95DE682A887A7e7f781F7832CF52a3b59E336F6", "ERC721SingleEditionImplementation": "0x778b5ef98f0C8803F6424bB07412489b2Fbd58B3", - "ERC721EditionsDFSImplementation": "0x9a304EFD52C63F030f2910f484d517faA2444575", + "ERC721EditionsDFSImplementation": "0x6606cA22030010F6ADAF9334E9f39d2aa89aa523", "ERC721SingleEditionDFSImplementation": "0x295D4e1472CdEe0bB3a2D03fF56dA5a2f8C81197", "ERC721GeneralImplementation": "0x1eB81B6A226591DF4D3248B4f55456De357929e2", "ERC721GeneralSequenceImplementation": "0xae7Fc5F056Ebd29FAdCC390e83EeDaeEEc8674E9", @@ -116,7 +131,13 @@ "MinimalForwarder": "0xAB98CD0e04Bb1FCd6320611fCAD6a7e534d8B302", "Observability": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", "FileDeployer": "0x799d1CC242637847756f0400d1F83FCF94Cb051e", - "DiscreteDutchAuctionMechanic": "0xA748BE280C9a00edaF7d04076FE8A93c59e95B03" + "DiscreteDutchAuctionMechanic": "0xA748BE280C9a00edaF7d04076FE8A93c59e95B03", + "GengineObservability": "0xf410f38BCA0a6Db2Bb543D8980a7147CfAb1441b", + "MintFeeOracle": "0xF05887d5EC9d51Db7aE3117B6F1f1da5E9416A05", + "SeedBasedMintMechanic": "0xDB38e10f38722Ca91006c7eEcc4DA7a731027344", + "RandomRankedAuctionMechanic": "0xE7F8B25C6D14864Bba6f0D5cc34874a5626e523C", + "RankedAuctionMechanic": "0x922E9f8cc491fACBd403afa143AA53ee9146474C", + "ReferralManager": "0xd9E58978808d17F99ccCEAb5195B052E972c0188" }, "arbitrum": { "MintManager": "0x41cbab1028984A34C1338F437C726de791695AE8", @@ -125,7 +146,7 @@ "AuctionManager": "0x79307CeE06153CA7986759B0727023A2472F395B", "ERC721EditionsImplementation": "0xF6c1093E467Ba60a41aBf901D875CDB027F924ac", "ERC721SingleEditionImplementation": "0x20475183625aE0eD5Dcd2553a660B06FF52af8Bd", - "ERC721EditionsDFSImplementation": "0x31C5C70330c9a1D3099d8f77381e82a218d5c71a", + "ERC721EditionsDFSImplementation": "0xa97425f8Ba83B01de22FE5d2F72dcA65A6c26DC5", "ERC721SingleEditionDFSImplementation": "0xB0101CC0443768e5990Cfd9adC03313D283B1a7E", "ERC721GeneralImplementation": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", "ERC721GeneralSequenceImplementation": "0xcCC80ea84E3e6Ee8CaAB489092d46bb912b493AD", @@ -133,7 +154,11 @@ "MinimalForwarder": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "Observability": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", "FileDeployer": "0x4c3896dd0b55B3B62D560620B1D8bF99643fFCCE", - "DiscreteDutchAuctionMechanic": "0x3a2aFe86E594540cbf3eA345dd29e09228f186D2" + "DiscreteDutchAuctionMechanic": "0x3a2aFe86E594540cbf3eA345dd29e09228f186D2", + "GengineObservability": "0x5c70CeB3fFEa9CfA0230511295f5A377e59E15C7", + "MintFeeOracle": "0x21372c2f48A06bEAf7833371Fc2FDD99D6D2E563", + "RankedAuctionMechanic": "0x7f75358787f880506c5dc6100386F77be8DE0A30", + "ReferralManager": "0x617b2383D93909590fAC0b2aaa547EC5615d82eF" }, "polygon-mumbai": { "MintManager": "0x2C92212426Ea6E41C894F8db3bEb1E6f4991c75c", @@ -169,6 +194,29 @@ "FileDeployer": "0xB644D70A52b4e555815EF3Ec76488dbdA9DF972D", "DiscreteDutchAuctionMechanic": "0x887A07d968b9b515E85a428c287397F4488005EE" }, + "base-sepolia": { + "MintManager": "0x41cbab1028984A34C1338F437C726de791695AE8", + "EditionsMetadataRenderer": "0x0266115EBa50E6EE69C067C0D6c5d542E9b40Bd5", + "NonTransferableTokenManager": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", + "AuctionManager": "0x79307CeE06153CA7986759B0727023A2472F395B", + "ERC721EditionsImplementation": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", + "ERC721SingleEditionImplementation": "0xAA45a6e4e1E6e43c14B366Dd0228874fb1DC0eF9", + "ERC721EditionsDFSImplementation": "0x5D1C39936B64bd127905D7A55e72547b87769aaB", + "ERC721SingleEditionDFSImplementation": "0x526fe4Ed6f23f34a97015E41f469fD54f37036f5", + "ERC721GeneralImplementation": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", + "ERC721GeneralSequenceImplementation": "0xF6C67C7bb7018E4609d571023196A4682FdA6F2f", + "ERC721GenerativeImplementation": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", + "MinimalForwarder": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", + "Observability": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", + "FileDeployer": "0x8c23711a0536397C261Bf83Ec474B9aAf05C549B", + "DiscreteDutchAuctionMechanic": "0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7", + "GengineObservability": "0xceBc3B3134FbEF95ED13AEcdF997D4371d022385", + "MintFeeOracle": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", + "SeedBasedMintMechanic": "0x7ac05e1b7dF40EE74D6adeA47bB169923e237e8e", + "RandomRankedAuctionMechanic": "0x66cCdD047d23d17887331BAf500FccDFAc1EB8b9", + "RankedAuctionMechanic": "0x9958F83F383CA150BB2252B4275D3e3051be469F", + "ReferralManager": "0x4619b9673241eB41B642Dc04371100d238b73fFE" + }, "arbitrum-goerli": { "MintManager": "0xd698911B1Bb2a9c849Bf5e2604aF110766f396b6", "EditionsMetadataRenderer": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", @@ -193,7 +241,7 @@ "AuctionManager": "0x41cbab1028984A34C1338F437C726de791695AE8", "ERC721EditionsImplementation": "0x4AFa58b8c2Dfe756e851d9073aeA95467fc1BBf5", "ERC721SingleEditionImplementation": "0xFAd107F688301db69e99693e00D1D891c44a0913", - "ERC721EditionsDFSImplementation": "0x68bB0F207F0184bf754C141d56939251BbB38Be7", + "ERC721EditionsDFSImplementation": "0x9b334866A4f18626460809dF5ec1b9103114edCc", "ERC721SingleEditionDFSImplementation": "0x799d1CC242637847756f0400d1F83FCF94Cb051e", "ERC721GeneralImplementation": "0xbE5AdDc34D89E12572C80C5f672E17C6b6e7c988", "ERC721GeneralSequenceImplementation": "0x4619b9673241eB41B642Dc04371100d238b73fFE", @@ -201,6 +249,33 @@ "MinimalForwarder": "0xFafd47bb399d570b5AC95694c5D2a1fb5EA030bB", "Observability": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", "FileDeployer": "0xB627f0469683f68aC78E1deD4eFA8545aa4c4DE3", - "DiscreteDutchAuctionMechanic": "0xf12A4018647DD2275072967Fd5F3ac5Fef7a0471" + "DiscreteDutchAuctionMechanic": "0xf12A4018647DD2275072967Fd5F3ac5Fef7a0471", + "GengineObservability": "0x44E15126aECC211dE5601d9403Ec318840Cc05f6", + "MintFeeOracle": "0x7E17C071c6171E4B33cad3415e4a51E2Ca6C5cD7", + "RankedAuctionMechanic": "0x0AFB6566C836D1C4788cD2b54Bd9cA0158CC2D3D", + "ReferralManager": "0x7Cb2cecFCFFdccE0bf69366e52caec6BD719CD44" + }, + "sepolia": { + "MintManager": "0xd698911B1Bb2a9c849Bf5e2604aF110766f396b6", + "EditionsMetadataRenderer": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", + "NonTransferableTokenManager": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", + "AuctionManager": "0x970a9F248Fc6AE03BB255E8863Cd6fc36E631e5d", + "ERC721EditionsImplementation": "0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7", + "ERC721SingleEditionImplementation": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", + "ERC721EditionsDFSImplementation": "0x6d5365f07Dc1F99Ebed1F99ecA99E41b569DdaF8", + "ERC721SingleEditionDFSImplementation": "0xd8f0A3AA4067be3D70a5B46A795Ad9dF9E65Cd3C", + "ERC721GeneralImplementation": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", + "ERC721GeneralSequenceImplementation": "0xbc0eec8ab1d314293eFbb0A58b149Ef209aF610d", + "ERC721GenerativeImplementation": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", + "MinimalForwarder": "0xa594011DB733d09C1EEB347fb2f7dFc99d118ba1", + "Observability": "0x526fe4Ed6f23f34a97015E41f469fD54f37036f5", + "FileDeployer": "0x9c602CE508E41ccAF2cF997D93A9FbE0166D8aE6", + "DiscreteDutchAuctionMechanic": "0xceBc3B3134FbEF95ED13AEcdF997D4371d022385", + "GengineObservability": "0xaF4d61951A425BA60ac1E7EA6d51e92d2F4748E4", + "VerisartMechanic": "0x642fEd40AeD8e7A7CF7c4CFf77F2529a4348ccC3", + "MintFeeOracle": "0x17241166279FD953AA4020e1580A57caCfF5f94F", + "SeedBasedMintMechanic": "0xaF2B525fFd402cA926969D730488863dec5cB8d6", + "RankedAuctionMechanic": "0xa2D14CA9985De170db128c8CB74Cecb35eEAF47E", + "ReferralManager": "0xd33c1bE264bb98F86e18CD816D5fd44e97cb7163" } } diff --git a/systemContractsConfig.json b/systemContractsConfig.json index 1f8493b..55794ed 100644 --- a/systemContractsConfig.json +++ b/systemContractsConfig.json @@ -1,6 +1,7 @@ { "__chainIdToNetworkName": { "5": "goerli", + "11155111": "sepolia", "1337": "localhost", "80001": "polygon-mumbai", "137": "polygon-mainnet", @@ -14,6 +15,7 @@ "420": "optimism-goerli", "8453": "base", "84531": "base-goerli", + "84532": "base-sepolia", "7777777": "zora", "999": "zora-goerli" }, @@ -26,6 +28,7 @@ }, "MintManager": { "5": "0xBF6B4F9Ef1E4B371c40701b5f856F9Fc1d659c70", + "11155111": "0xd698911B1Bb2a9c849Bf5e2604aF110766f396b6", "1337": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", "80001": "0x2C92212426Ea6E41C894F8db3bEb1E6f4991c75c", "137": "0xfbb65C52f439B762F712026CF6DD7D8E82F81eb9", @@ -36,12 +39,14 @@ "10": "0xFafd47bb399d570b5AC95694c5D2a1fb5EA030bB", "420": "0x41cbab1028984A34C1338F437C726de791695AE8", "84531": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", + "84532": "0x41cbab1028984A34C1338F437C726de791695AE8", "7777777": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "999": "0x9AcDfE8020c3c191F7aA158e1c155F12e55c9717", "8453": "0x8087039152c472Fa74F47398628fF002994056EA" }, "EditionsMetadataRenderer": { "5": "0xeA2fC80731D96D57Cf80E8643f7DF35C055A0e85", + "11155111": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", "1337": "0x0165878A594ca255338adfa4d48449f69242Eb8F", "80001": "0x77834F55641F13e03951088ADFb54a377A873308", "137": "0x32c56B178cAA486177ee9A942611A2f8844872f5", @@ -52,12 +57,14 @@ "10": "0x41cbab1028984A34C1338F437C726de791695AE8", "420": "0x0266115EBa50E6EE69C067C0D6c5d542E9b40Bd5", "84531": "0x526fe4Ed6f23f34a97015E41f469fD54f37036f5", + "84532": "0x0266115EBa50E6EE69C067C0D6c5d542E9b40Bd5", "7777777": "0x79307CeE06153CA7986759B0727023A2472F395B", "999": "0xd698911B1Bb2a9c849Bf5e2604aF110766f396b6", "8453": "0xa594011DB733d09C1EEB347fb2f7dFc99d118ba1" }, "NonTransferableTokenManager": { "5": "0x2ac592Eb32fe61EFFd57Ac0A681Fe24E87e3069c", + "11155111": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", "1337": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", "80001": "0x3861f9F623611C27b2614873BBB74635c21ffaA7", "137": "0x481f9289257795bbC5Cc9bab8c986D3377450331", @@ -68,12 +75,14 @@ "10": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", "420": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", "84531": "0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7", + "84532": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", "7777777": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", "999": "0xAA45a6e4e1E6e43c14B366Dd0228874fb1DC0eF9", "8453": "0x0266115EBa50E6EE69C067C0D6c5d542E9b40Bd5" }, "AuctionManager": { "5": "0xa94310AeeD50687f7c39ACdAA5FCd311AEDB25f8", + "11155111": "0x970a9F248Fc6AE03BB255E8863Cd6fc36E631e5d", "1337": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", "80001": "0xF9FEf499aDF4550FA87C63E1111C8a0531DF45a1", "137": "0x3CEDCb3170489f2FB509DB23D8A864A55B45036F", @@ -84,6 +93,7 @@ "10": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "420": "0x79307CeE06153CA7986759B0727023A2472F395B", "84531": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", + "84532": "0x79307CeE06153CA7986759B0727023A2472F395B", "7777777": "0x41cbab1028984A34C1338F437C726de791695AE8", "999": "0xa594011DB733d09C1EEB347fb2f7dFc99d118ba1", "8453": "0x9AcDfE8020c3c191F7aA158e1c155F12e55c9717" @@ -93,6 +103,7 @@ }, "ERC721EditionsImplementation": { "5": "0x703Fd59DEee1727eaf7751EDe79ec22c3F7Db07B", + "11155111": "0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7", "1337": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "80001": "0x248AE3998B98D9eb046205f18c9B9210fFECFE2a", "137": "0xF150CB22e56FDA37F3c51A6a35f0aC0fd771db2f", @@ -103,12 +114,14 @@ "10": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", "420": "0xB2416393Ce488DA1EA2Ac86ab0e87a2Cf5d7a44F", "84531": "0x1800E1Db8513Bc6c96E38D9DB840cDFcAb8f9944", + "84532": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", "7777777": "0x4AFa58b8c2Dfe756e851d9073aeA95467fc1BBf5", "999": "0x954386A2b103A8AD2B933E44Ea148036f73DC4B9", "8453": "0xa95DE682A887A7e7f781F7832CF52a3b59E336F6" }, "ERC721SingleEditionImplementation": { "5": "0x8B7E1DC485e931F4a15392a0E2DC0D61A16A68aB", + "11155111": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", "1337": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", "80001": "0x479Cc569416E8934403E12Ec56475Ad6f8aBa3a4", "137": "0x91cDE68af933688116337EEBD7d11e8d63AAA76E", @@ -119,28 +132,32 @@ "10": "0xcCC80ea84E3e6Ee8CaAB489092d46bb912b493AD", "420": "0xC0CEC6dd216C0388CD28DeC2F6FBe9aaFf749e9c", "84531": "0xCbd8d75658f82c680727C36AF6c1c365B118938F", + "84532": "0xAA45a6e4e1E6e43c14B366Dd0228874fb1DC0eF9", "7777777": "0xFAd107F688301db69e99693e00D1D891c44a0913", "999": "0x473F9552a53595887074B8A8B798509e223B118E", "8453": "0x778b5ef98f0C8803F6424bB07412489b2Fbd58B3" }, "ERC721EditionsDFSImplementation": { "5": "0x14d986A1743af0A53B7D60De5189B7fff3494AFa", + "11155111": "0x6d5365f07Dc1F99Ebed1F99ecA99E41b569DdaF8", "1337": "0x9A676e781A523b5d0C0e43731313A708CB607508", "80001": "0xEE5D605bE1aB67344C80F9Dc4836460f56614566", - "137": "0x939Fd86C2a0c58202d1F14F59Acd4466A85bC412", - "137-staging": "0x939Fd86C2a0c58202d1F14F59Acd4466A85bC412", - "1": "0x006cdD31f45F7e544a874B28763E1825C81128d5", - "42161": "0x31C5C70330c9a1D3099d8f77381e82a218d5c71a", + "137": "0xe083c23Bd5d873B637fca3DF8A4b8d1D5e8f3a69", + "137-staging": "0xe083c23Bd5d873B637fca3DF8A4b8d1D5e8f3a69", + "1": "0xbAF6c8B147Bc50F265E7C673Df21d53B0A9325be", + "42161": "0xa97425f8Ba83B01de22FE5d2F72dcA65A6c26DC5", "421613": "0x32e187F0B32C9B8Cbc5980a16C5ED0EcD6f9d96E", - "10": "0xecA1aAfE5437B3a231B9E450c47Ffa8De8575a03", + "10": "0x47f690ad0168B6E3476B59A15a5dFe7adb8E34bc", "420": "0xB0101CC0443768e5990Cfd9adC03313D283B1a7E", "84531": "0xdeAa8693C7085FaC16B20Cd5C69d84F7790926bf", - "7777777": "0x68bB0F207F0184bf754C141d56939251BbB38Be7", + "84532": "0x5D1C39936B64bd127905D7A55e72547b87769aaB", + "7777777": "0x9b334866A4f18626460809dF5ec1b9103114edCc", "999": "0x734ACE995eaE06cFCBfE6cc33e0F524ab27e4ac1", - "8453": "0x9a304EFD52C63F030f2910f484d517faA2444575" + "8453": "0x6606cA22030010F6ADAF9334E9f39d2aa89aa523" }, "ERC721SingleEditionDFSImplementation": { "5": "0xAbAE4df16c1262F8465FCBDcD6E006a75Fb3b739", + "11155111": "0xd8f0A3AA4067be3D70a5B46A795Ad9dF9E65Cd3C", "1337": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", "80001": "0xe4C6a0a3cFe2c004DDFC7eA12726bdE4C53A2784", "137": "0x6abC18F4e8c7D8980DdBb97FDE7d6521B394F16A", @@ -151,12 +168,14 @@ "10": "0x9fA3eA2B36fed5803Ca743E09fEd3204E2B59866", "420": "0x621c7cE76Cde5761c7611721B770f347a0b6376E", "84531": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", + "84532": "0x526fe4Ed6f23f34a97015E41f469fD54f37036f5", "7777777": "0x799d1CC242637847756f0400d1F83FCF94Cb051e", "999": "0x701703EF716c4fe4086ef9a904683683d553e282", "8453": "0x295D4e1472CdEe0bB3a2D03fF56dA5a2f8C81197" }, "ERC721GeneralImplementation": { "5": "0x667f810C960537A53532c00a0973205bE2fe2165", + "11155111": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", "1337": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", "80001": "0xFC954d004b8e4a6F82BEeE38a0C41A89Af3866cE", "137": "0x64b35B64DAB456c489124Dc07aA3eD100DdFeD7E", @@ -167,44 +186,50 @@ "10": "0x51544960e278b38c13c29F2944C1C839fEfCE6E2", "420": "0xBe2099b6361e4551BDdF953011Ed1DD39CCfa2a1", "84531": "0xa12f77d7b39a7b556Ba4BE6ec7328B0049288ac3", + "84532": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", "7777777": "0xbE5AdDc34D89E12572C80C5f672E17C6b6e7c988", "999": "0xf60cb5F236A344080Ca3bF50C5dC523309809F80", "8453": "0x1eB81B6A226591DF4D3248B4f55456De357929e2" }, "ERC721GeneralSequenceImplementation": { "5": "0x96dB8495a5dEA40aDc1d4CFE45eB84F1c82d143B", + "11155111": "0xbc0eec8ab1d314293eFbb0A58b149Ef209aF610d", "1337": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "80001": "0x1333328f8b76a4d0a91a30bea67A9Cd6164A9b96", "137": "0xc27925863bF67384e16Dcb1225228c88d0F44A8f", "137-staging": "0xc27925863bF67384e16Dcb1225228c88d0F44A8f", - "1": "0x8d67B6ACE3fC4d90b8c276e1d70646ec705b0C9b", + "1": "0x611c4E7cde9eb10019BEa9a7725409Ca154Fd1e6", "42161": "0xcCC80ea84E3e6Ee8CaAB489092d46bb912b493AD", "421613": "0xa12f77d7b39a7b556Ba4BE6ec7328B0049288ac3", "10": "0xF4007F45DCd05BE758Fe9b26500B0010a07dB3cB", "420": "0xe254901fC1F3ACd6E6AA409f95dB718235a015c8", "84531": "0x4c3896dd0b55B3B62D560620B1D8bF99643fFCCE", + "84532": "0xF6C67C7bb7018E4609d571023196A4682FdA6F2f", "7777777": "0x4619b9673241eB41B642Dc04371100d238b73fFE", "999": "0x9491aA1c2f46319A645637c4105f4199B251e4dD", "8453": "0xae7Fc5F056Ebd29FAdCC390e83EeDaeEEc8674E9" }, "ERC721GenerativeImplementation": { "5": "0x00Edfc8bE8897893786232e96367c8E040E2eb6D", + "11155111": "0xE019FF8033d9C761985A3EE1fa5d97Cc9Cf6d5c0", "1337": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", "80001": "0xdc01D22327d142f45070Cb01f3f507878734A6f9", "137": "0xa79dafa06bFF0765baa36C4f6731FdC755553887", "137-staging": "0xa79dafa06bFF0765baa36C4f6731FdC755553887", - "1": "0x2Be3CE514884dcF92505a9FDaBDe6541779C129b", + "1": "0x68bB0F207F0184bf754C141d56939251BbB38Be7", "42161": "0xfF1C44BbE0943931E5E8962DAA0885a4f5Dd4fcd", "421613": "0x90618E3338dd970ca634ac92dAa9E1DcF66B1c57", "10": "0x1372557dF3Cc3F8616D416e52217c797Ae3eEdce", "420": "0xab162414800fdf441B18F2f5af94334840b8f678", "84531": "0xdD606eb8af309BD6e901b2d6E6dE2F233358b324", + "84532": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", "7777777": "0xcEC770BA360aDf184C961A3494521f1B5DCEa39C", "999": "0x6e83e7ec8dBF2a21C6FE90d95E250158313FDcc3", "8453": "0x08FD471a972Ad95FE2BF14d490EB2aaFE28f0aff" }, "MinimalForwarder": { "5": "0x4905B2ee259994F664d443e740bC2cA1d9cf2f1D", + "11155111": "0xa594011DB733d09C1EEB347fb2f7dFc99d118ba1", "1337": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "80001": "0xD66A0f91BAFD0Fd6b7503ff97E028c9B54a7001f", "137": "0x03214f1434D84Dd58FcDFc339577c1B3a7Dd9BdE", @@ -215,12 +240,14 @@ "10": "0xC5402e0BAF74c1042D72749cB8cA78c58dD93D6f", "420": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "84531": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1", + "84532": "0x3AD45858a983D193D98BD4e6C14852a4cADcDBeA", "7777777": "0xFafd47bb399d570b5AC95694c5D2a1fb5EA030bB", "999": "0x8087039152c472Fa74F47398628fF002994056EA", "8453": "0xAB98CD0e04Bb1FCd6320611fCAD6a7e534d8B302" }, "Observability": { "5": "0xFfEc25843068E69CAA0E36eA004D7749bD9EfB19", + "11155111": "0x526fe4Ed6f23f34a97015E41f469fD54f37036f5", "1337": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", "80001": "0x74A07B1F2B1d1Dec82341F18959cfb8B89353c87", "137": "0x43Ef6CB43586B4B3ce0F4b728D4AE08dD30a0d1e", @@ -231,12 +258,14 @@ "10": "0xAA45a6e4e1E6e43c14B366Dd0228874fb1DC0eF9", "420": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", "84531": "0xe2CE42156E8456704fbEA047419404858E9324Af", + "84532": "0xF18660E9E7c1B6015c0f491F4b5602fB3a626Caa", "7777777": "0x21fed85E54507164FD6c9Eb76870AFF41098106b", "999": "0xa1Cef877695E24DF6643f5B6B47Eb6fCeF214A38", "8453": "0x4e0AfBa59894060369881f4Bc9ba05731A4119f1" }, "FileDeployer": { "5": "0x29e3F4B932c1E0B989E3B6AbCf56Ae342c5AD65a", + "11155111": "0x9c602CE508E41ccAF2cF997D93A9FbE0166D8aE6", "1337": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", "80001": "0x513bd3bc623c42a807fBD162a58682941A12935F", "137": "0x117542b736cB5314a59453081b66208863CC1Acc", @@ -247,24 +276,85 @@ "10": "0x21c3a69EaD9b81863B83757ff2645803fF7c7690", "420": "0x20475183625aE0eD5Dcd2553a660B06FF52af8Bd", "84531": "0xB644D70A52b4e555815EF3Ec76488dbdA9DF972D", + "84532": "0x8c23711a0536397C261Bf83Ec474B9aAf05C549B", "7777777": "0xB627f0469683f68aC78E1deD4eFA8545aa4c4DE3", "999": "0xAFfC7C9BfB48FFD2a580e1a0d36f8cc7D45Dcb58", "8453": "0x799d1CC242637847756f0400d1F83FCF94Cb051e" }, "DiscreteDutchAuctionMechanic": { "5": "0xae129080C7840538301550802cBc520c336CEEca", + "11155111": "0xceBc3B3134FbEF95ED13AEcdF997D4371d022385", "1337": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", "80001": "0xBf0ddCC1cC1635Ade2F99042771e7cD7a923a187", "137": "0xAE22Cd8052D64e7C2aF6B5E3045Fab0a86C8334C", "137-staging": "0xAE22Cd8052D64e7C2aF6B5E3045Fab0a86C8334C", - "1": "0x94fa6e7fc2555ada63ea56cfff425558360f0074", + "1": "0x94Fa6e7Fc2555aDA63eA56cfFF425558360F0074", "42161": "0x3a2aFe86E594540cbf3eA345dd29e09228f186D2", "421613": "0x5437D752A878f6969bEd14fD733782BBD230489b", "10": "0x15753e20667961fB30d5aa92e2255B876568BE7e", "420": "0x5ae0bE472147dd425f73F5c10069043133401427", "84531": "0x887A07d968b9b515E85a428c287397F4488005EE", + "84532": "0x4821B6e9aC0CCC590acCe2442bb6BB32388C1CB7", "7777777": "0xf12A4018647DD2275072967Fd5F3ac5Fef7a0471", "999": "0x778b5ef98f0C8803F6424bB07412489b2Fbd58B3", "8453": "0xA748BE280C9a00edaF7d04076FE8A93c59e95B03" + }, + "GengineObservability": { + "11155111": "0xaF4d61951A425BA60ac1E7EA6d51e92d2F4748E4", + "1337": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", + "137": "0x086984b6C8CBF78EFD3c16e28029C038e700debE", + "1": "0xf40cd0797c7a2ace16fB680B0556eED2b691cDAD", + "42161": "0x5c70CeB3fFEa9CfA0230511295f5A377e59E15C7", + "10": "0x1f6D904685A84C3417Fe0192b53474320CaAca63", + "84532": "0xceBc3B3134FbEF95ED13AEcdF997D4371d022385", + "7777777": "0x44E15126aECC211dE5601d9403Ec318840Cc05f6", + "8453": "0xf410f38BCA0a6Db2Bb543D8980a7147CfAb1441b" + }, + "VerisartMechanic": { + "11155111": "0x642fEd40AeD8e7A7CF7c4CFf77F2529a4348ccC3", + "1": "0xe66CE7640A5Ecf430F7e02d17fC6F224bE9afF66" + }, + "MintFeeOracle": { + "11155111": "0x17241166279FD953AA4020e1580A57caCfF5f94F", + "42161": "0x21372c2f48A06bEAf7833371Fc2FDD99D6D2E563", + "137": "0x9B64EFf523abbb7c52C7da469569704565Bd6Aa7", + "10": "0xD09F64dbbCAa076Fde30Ddd2e23194f5F786665E", + "7777777": "0x7E17C071c6171E4B33cad3415e4a51E2Ca6C5cD7", + "84532": "0x23E4ffb289f7696b9957De566A06cF9B325d9bCA", + "8453": "0xF05887d5EC9d51Db7aE3117B6F1f1da5E9416A05", + "1": "0x31BFD198BC59EC2379A5Fb57F26b5Ef3AD126848" + }, + "SeedBasedMintMechanic": { + "11155111": "0xaF2B525fFd402cA926969D730488863dec5cB8d6", + "1": "0x922E9f8cc491fACBd403afa143AA53ee9146474C", + "8453": "0xDB38e10f38722Ca91006c7eEcc4DA7a731027344", + "84532": "0x7ac05e1b7dF40EE74D6adeA47bB169923e237e8e" + }, + "RandomRankedAuctionMechanic": { + "84532": "0x66cCdD047d23d17887331BAf500FccDFAc1EB8b9", + "1": "0xF01E8a33E3a69799401c181a7b3D2c7F28485EC9", + "8453": "0xE7F8B25C6D14864Bba6f0D5cc34874a5626e523C" + }, + "RankedAuctionMechanic": { + "11155111": "0xa2D14CA9985De170db128c8CB74Cecb35eEAF47E", + "1337": "", + "137": "0x4CCB72E7E0Cd948aF50bC7Bf598Fc4E027b70f98", + "1": "0xDFEe0Ed4A217F37b3FA87624eE00fe5685bDc509", + "42161": "0x7f75358787f880506c5dc6100386F77be8DE0A30", + "10": "0xb207774Ac4E32eCE47771e64BDE5ec3894C1De6b", + "84532": "0x9958F83F383CA150BB2252B4275D3e3051be469F", + "7777777": "0x0AFB6566C836D1C4788cD2b54Bd9cA0158CC2D3D", + "8453": "0x922E9f8cc491fACBd403afa143AA53ee9146474C" + }, + "ReferralManager": { + "11155111": "0xd33c1bE264bb98F86e18CD816D5fd44e97cb7163", + "1337": "", + "137": "0x6fd07d4B5fd7093762Fb2f278769aa7e2511d45c", + "1": "0xD3C63951b2Ed18e8d92B5b251C3B636A45A547d0", + "42161": "0x617b2383D93909590fAC0b2aaa547EC5615d82eF", + "10": "0x9CF5B12D2e2a88083647Ff2Fe0610F818b28eC77", + "84532": "0x4619b9673241eB41B642Dc04371100d238b73fFE", + "7777777": "0x7Cb2cecFCFFdccE0bf69366e52caec6BD719CD44", + "8453": "0xd9E58978808d17F99ccCEAb5195B052E972c0188" } } diff --git a/test/AuctionsTest.ts b/test/AuctionsTest.ts deleted file mode 100644 index 976fe28..0000000 --- a/test/AuctionsTest.ts +++ /dev/null @@ -1,761 +0,0 @@ -import { parseEther } from "@ethersproject/units"; -import { time } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - EditionsMetadataRenderer, - IAuctionManager, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; -import { signGatedBid } from "./__utils__/auction"; -import { hourFromNow, setupEtherAuctionWithNewToken, setupSystem } from "./__utils__/helpers"; - -describe("Auction Manager", () => { - let initialPlatformExecutor: SignerWithAddress, - additionalPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAccount: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress, - randomEOA: SignerWithAddress; - - let auctionManager: AuctionManager; - let mintManager: MintManager; - let emr: EditionsMetadataRenderer; - let minimalForwarder: MinimalForwarder; - let observability: Observability; - - let editionsImplementation: string; - - before(async () => { - [ - initialPlatformExecutor, - additionalPlatformExecutor, - mintManagerOwner, - editionsMetadataOwner, - platformPaymentAccount, - editionsOwner, - fan1, - randomEOA, - ] = await ethers.getSigners(); - - const { - mintManagerProxy, - auctionManagerProxy, - emrProxy, - observability: observabilityInstance, - minimalForwarder: minimalForwarderContract, - editionsImplementationAddress, - } = await setupSystem( - platformPaymentAccount.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - auctionManager = auctionManagerProxy; - mintManager = mintManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - minimalForwarder = minimalForwarderContract; - editionsImplementation = editionsImplementationAddress; - }); - - describe("Platform Executor", function () { - before(async () => { - auctionManager = auctionManager.connect(mintManagerOwner); - }); - it("Should be able add a new platform executor as Owner", async () => { - await expect(auctionManager.addPlatformExecutor(additionalPlatformExecutor.address)).to.emit( - auctionManager, - "PlatformExecutorChanged", - ); - expect(await auctionManager.platformExecutors()).to.include(additionalPlatformExecutor.address); - }); - it("Should be able deprecate platform executor as Owner", async () => { - //deprecate platform executor - await expect(auctionManager.deprecatePlatformExecutor(additionalPlatformExecutor.address)).to.emit( - auctionManager, - "PlatformExecutorChanged", - ); - expect(await auctionManager.platformExecutors()).to.not.include(additionalPlatformExecutor.address); - }); - it("Should not be able to add Zero address as platform executor", async () => { - await expect(auctionManager.addPlatformExecutor(ethers.constants.AddressZero)).to.be.revertedWith( - "Cannot set to null address", - ); - expect(await auctionManager.platformExecutors()).to.not.include(ethers.constants.AddressZero); - }); - it("Should not be able to add a platform executor that already exists", async () => { - await expect(auctionManager.addPlatformExecutor(additionalPlatformExecutor.address)).to.emit( - auctionManager, - "PlatformExecutorChanged", - ); - expect(await auctionManager.platformExecutors()).to.include(additionalPlatformExecutor.address); - await expect(auctionManager.addPlatformExecutor(additionalPlatformExecutor.address)).to.be.revertedWith( - "Already added", - ); - }); - it("Should reject all platform executor changes from non owner", async () => { - const auctionManagerForFan1 = await auctionManager.connect(fan1); - - //Add platform executor - await expect(auctionManagerForFan1.addPlatformExecutor(mintManagerOwner.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - expect(await auctionManagerForFan1.platformExecutors()).to.not.include(mintManagerOwner.address); - - //deprecate platform executor - await expect(auctionManagerForFan1.deprecatePlatformExecutor(initialPlatformExecutor.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - expect(await auctionManagerForFan1.platformExecutors()).to.include(initialPlatformExecutor.address); - }); - }); - - describe("Auction for new tokens", function () { - let editions: ERC721Editions; - let editions2: ERC721Editions; - let editionsWithMarketplaceFilterer: ERC721Editions; - let defaultAuction: IAuctionManager.EnglishAuctionStruct; - const endTime1 = hourFromNow(); - const endTime2 = hourFromNow(); - const endTime3 = hourFromNow(); - - before(async () => { - auctionManager = auctionManager.connect(editionsOwner); - editions = await setupEtherAuctionWithNewToken( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - emr.address, - minimalForwarder.address, - editionsOwner, - "id1", - endTime1, - editionsOwner.address, - ); - editions2 = await setupEtherAuctionWithNewToken( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - emr.address, - minimalForwarder.address, - editionsOwner, - "id2", - endTime2, - editionsOwner.address, - ); - editionsWithMarketplaceFilterer = await setupEtherAuctionWithNewToken( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - emr.address, - minimalForwarder.address, - editionsOwner, - "id3", - endTime3, - editionsOwner.address, - ); - - const res = await auctionManager.getFullAuctionsData([ - ethers.utils.formatBytes32String("id1"), - ethers.utils.formatBytes32String("id2"), - ethers.utils.formatBytes32String("id3"), - ]); - expect(res[0][0][0]).to.equal(editions.address); - expect(res[0][1][0]).to.equal(editions2.address); - expect(res[0][2][0]).to.equal(editionsWithMarketplaceFilterer.address); - expect(res[0][0][1]).to.equal(ethers.constants.AddressZero).to.equal(res[0][1][1]).to.equal(res[0][2][1]); - expect(res[0][0][4].toNumber()).to.equal(endTime1); - expect(res[0][1][4].toNumber()).to.equal(endTime2); - expect(res[0][2][4].toNumber()).to.equal(endTime3); - - defaultAuction = { - collection: editions.address, - currency: ethers.constants.AddressZero, - owner: editionsOwner.address, - paymentRecipient: editionsOwner.address, - endTime: hourFromNow() + hourFromNow(), - tokenId: 0, - mintWhenReserveMet: true, - state: 0, - }; - }); - - // validate correct events being emitted in all of these - - it("Cannot create auction not as the collection owner", async function () { - auctionManager = auctionManager.connect(fan1); - await expect( - auctionManager.createAuctionForExistingToken(ethers.utils.formatBytes32String("id4"), defaultAuction), - ).to.be.revertedWith("Not collection owner or collection"); - - await expect( - auctionManager.createAuctionForNewToken(ethers.utils.formatBytes32String("id4"), defaultAuction), - ).to.be.revertedWith("Not collection owner or collection"); - }); - - it("Cannot create auction with already used auction id", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect( - auctionManager.createAuctionForNewToken(ethers.utils.formatBytes32String("id2"), defaultAuction), - ).to.be.revertedWith("Auction id used"); - }); - - it("Cannot bid with a non platform executor signer signing the claim", async function () { - const { signature, claim } = await signGatedBid(editionsOwner, auctionManager, { - auctionId: "id1", - bidPrice: "1.0", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead = true; - } - expect(errorOnRead).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Claim signer not executor"); - }); - - it("Cannot bid on non-existent auction", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id4", - bidPrice: "1.0", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead = true; - } - expect(errorOnRead).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Not live"); - }); - - it("Cannot bid with an expired claim", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id2", - bidPrice: "1.0", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "1", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead = true; - } - expect(errorOnRead).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Claim expired"); - }); - - it("Cannot bid below reserve", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id2", - bidPrice: "0.4999", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead = true; - } - expect(errorOnRead).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Reserve price not met"); - }); - - it("Non auction owners cannot update auction", async function () { - auctionManager = auctionManager.connect(fan1); - await expect( - auctionManager.updateEndTime(ethers.utils.formatBytes32String("id1"), hourFromNow()), - ).to.be.revertedWith("Not auction owner"); - - await expect( - auctionManager.updatePaymentRecipient(ethers.utils.formatBytes32String("id1"), initialPlatformExecutor.address), - ).to.be.revertedWith("Not auction owner"); - }); - - it("Non auction owners cannot cancel auction on chain", async function () { - auctionManager = auctionManager.connect(fan1); - await expect(auctionManager.cancelAuctionOnChain(ethers.utils.formatBytes32String("id1"))).to.be.revertedWith( - "Not auction owner", - ); - - await expect(auctionManager.cancelAuctionOnChain(ethers.utils.formatBytes32String("id1"))).to.be.revertedWith( - "Not auction owner", - ); - }); - - it("Auction owners only can cancel auction on chain before minimum reserve bid is made", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.cancelAuctionOnChain(ethers.utils.formatBytes32String("id3"))) - .to.emit(auctionManager, "AuctionCanceledOnChain") - .withArgs( - ethers.utils.formatBytes32String("id3"), - editionsOwner.address, - editionsWithMarketplaceFilterer.address, - 0, - ); - }); - - it("Cannot bid with invalid ether amount", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.0", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith( - "Invalid native gas token payment", - ); - - await expect( - auctionManager.bid(claim, signature, fan1.address, { value: ethers.utils.parseEther("0.9") }), - ).to.be.revertedWith("Invalid native gas token payment"); - }); - - it("Can make valid first bid on auction", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.0", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - const claimVerified = await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - expect(claimVerified).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address, { value: ethers.utils.parseEther("1.0") })) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, auctionManager.address, 1) - .to.emit(auctionManager, "Bid") - .withArgs( - ethers.utils.formatBytes32String("id1"), - editionsOwner.address, - true, - editions.address, - ethers.BigNumber.from(1), - parseEther("1.0"), - false, - fan1.address, - ethers.BigNumber.from(endTime1), - ); - - expect(await editionsOwner.provider?.getBalance(auctionManager.address)).to.equal(ethers.utils.parseEther("1.0")); - expect(await editions.ownerOf(1)).to.equal(auctionManager.address); - expect(await editions.tokenURI(1)).to.equal( - "data:application/json;base64,eyJuYW1lIjogImR1bW15IiwgImRlc2NyaXB0aW9uIjogImRlc2NyaXB0aW9uIiwgImltYWdlIjogImltYWdlVXJsIiwgImFuaW1hdGlvbl91cmwiOiAiYW5pbWF0aW9uVXJsIiwgImV4dGVybmFsX3VybCI6ICJleHRlcm5hbFVybCIsICJhdHRyaWJ1dGVzIjogYXR0cmlidXRlc30=", - ); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("id1")); - expect(res[0][0]).to.equal(editions.address); - // highest bidder data - expect(res[1][0]).to.equal(editionsOwner.address); - expect(res[1][1]).to.equal(fan1.address); - expect(res[1][2]).to.equal(ethers.utils.parseEther("1.0")); - }); - - it("Cannot update end time of auction after minimum reserve bid is made", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.updateEndTime(ethers.utils.formatBytes32String("id1"), endTime2)).to.be.revertedWith( - "Can't update after first valid bid", - ); - }); - - it("Cannot update end time of non-live auction", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.updateEndTime(ethers.utils.formatBytes32String("id3"), endTime2)).to.be.revertedWith( - "Not live", - ); - }); - - it("Cannot cancel auction after minimum reserve bid is made", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.cancelAuctionOnChain(ethers.utils.formatBytes32String("id1"))).to.be.revertedWith( - "Reserve price met already", - ); - }); - - it("Cannot bid on cancelled auction", async function () { - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id3", - bidPrice: "0.6", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead = true; - } - expect(errorOnRead).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Not live"); - }); - - it("Auction owners only can update an auction's payment recipient", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.updatePaymentRecipient(ethers.utils.formatBytes32String("id1"), randomEOA.address)) - .to.emit(auctionManager, "PaymentRecipientUpdated") - .withArgs(ethers.utils.formatBytes32String("id1"), editionsOwner.address, randomEOA.address); - }); - - it("Cannot make bid with bid price not sufficiently higher than the previous one", async function () { - const { signature: signature1, claim: claim1 } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.04", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead1 = false; - try { - await auctionManager.verifyClaim(claim1, signature1, editionsOwner.address); - } catch (error) { - errorOnRead1 = true; - } - expect(errorOnRead1).to.equal(true); - - await expect(auctionManager.bid(claim1, signature1, fan1.address)).to.be.revertedWith( - "Bid not big enough of a jump", - ); - - const { signature: signature2, claim: claim2 } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.059", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 600, - claimer: editionsOwner.address, - }); - - let errorOnRead2 = false; - try { - await auctionManager.verifyClaim(claim2, signature2, editionsOwner.address); - } catch (error) { - errorOnRead2 = true; - } - expect(errorOnRead2).to.equal(true); - - await expect(auctionManager.bid(claim2, signature2, fan1.address)).to.be.revertedWith( - "Bid not big enough of a jump", - ); - }); - - it("Cannot make bid if user exceeds maxClaimsPerAccount", async function () { - const numBids = await auctionManager.auctionBids(ethers.utils.formatBytes32String("id1"), editionsOwner.address); - expect(numBids).to.equal(ethers.BigNumber.from(1)); - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.1", - reservePrice: "0.5", - maxClaimsPerAccount: 1, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 500, - claimer: editionsOwner.address, - }); - - let errorOnRead1 = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead1 = true; - } - expect(errorOnRead1).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith( - "Exceeded max claims for account", - ); - }); - - it("Can make valid higher bids", async function () { - auctionManager = auctionManager.connect(fan1); - const prevHighestBidderBalance = await editionsOwner.provider?.getBalance(editionsOwner.address); - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.04", - reservePrice: "1.8", // reserve price shouldn't matter - maxClaimsPerAccount: 1, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 0, - claimer: fan1.address, - }); - - const claimVerified = await auctionManager.verifyClaim(claim, signature, fan1.address); - expect(claimVerified).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address, { value: ethers.utils.parseEther("1.04") })) - .to.emit(auctionManager, "Bid") - .withArgs( - ethers.utils.formatBytes32String("id1"), - fan1.address, - false, - editions.address, - ethers.BigNumber.from(1), - parseEther("1.04"), - false, - fan1.address, - ethers.BigNumber.from(endTime1), - ); - - // refunded to previous highest bidder - expect(await editionsOwner.provider?.getBalance(editionsOwner.address)).to.equal( - prevHighestBidderBalance?.add(ethers.utils.parseEther("1")), - ); - expect(await editionsOwner.provider?.getBalance(auctionManager.address)).to.equal( - ethers.utils.parseEther("1.04"), - ); - expect(await editions.ownerOf(1)).to.equal(auctionManager.address); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("id1")); - expect(res[0][0]).to.equal(editions.address); - // highest bidder data - expect(res[1][0]).to.equal(fan1.address); - expect(res[1][1]).to.equal(fan1.address); - expect(res[1][2]).to.equal(ethers.utils.parseEther("1.04")); - }); - - it("Cannot bid same price even if minimum percentage increase is 0", async function () { - auctionManager = auctionManager.connect(fan1); - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id1", - bidPrice: "1.04", - reservePrice: "0.5", - maxClaimsPerAccount: 0, - claimExpiryTimestamp: "0", - buffer: 300, - minimumIncrementPerBidPctBPS: 0, - claimer: fan1.address, - }); - - let errorOnRead1 = false; - try { - await auctionManager.verifyClaim(claim, signature, editionsOwner.address); - } catch (error) { - errorOnRead1 = true; - } - expect(errorOnRead1).to.equal(true); - - await expect(auctionManager.bid(claim, signature, fan1.address)).to.be.revertedWith("Bid not higher"); - }); - - it("If bid is made in buffer, end time is extended to current time + buffer", async function () { - auctionManager = auctionManager.connect(fan1); - const { signature, claim } = await signGatedBid(initialPlatformExecutor, auctionManager, { - auctionId: "id2", - bidPrice: "2", - reservePrice: "1.8", // reserve price shouldn't matter - maxClaimsPerAccount: 1, - claimExpiryTimestamp: "0", - buffer: 300000000, - minimumIncrementPerBidPctBPS: 0, - claimer: fan1.address, - }); - - const claimVerified = await auctionManager.verifyClaim(claim, signature, fan1.address); - expect(claimVerified).to.equal(true); - - const blockTime = Math.floor(Date.now() / 1000) + 1000; - const newEndTimeExpectedMin = ethers.BigNumber.from(blockTime + 300000000).add(2); - await time.setNextBlockTimestamp(blockTime); - await expect(auctionManager.bid(claim, signature, fan1.address, { value: ethers.utils.parseEther("2") })) - .to.emit(editions2, "Transfer") - .to.emit(auctionManager, "TimeLengthened") - .to.emit(auctionManager, "Bid") - .withArgs( - ethers.utils.formatBytes32String("id2"), - fan1.address, - true, - editions2.address, - ethers.BigNumber.from(1), - parseEther("2"), - true, - fan1.address, - newEndTimeExpectedMin, - ); - - expect(await editionsOwner.provider?.getBalance(auctionManager.address)).to.equal( - ethers.utils.parseEther("3.04"), - ); - expect(await editions2.ownerOf(1)).to.equal(auctionManager.address); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("id2")); - expect(res[0][0]).to.equal(editions2.address); - // highest bidder data - expect(res[1][0]).to.equal(fan1.address); - expect(res[1][1]).to.equal(fan1.address); - expect(res[1][2]).to.equal(ethers.utils.parseEther("2")); - expect(res[0][4]).to.be.equal(newEndTimeExpectedMin); - }); - - it("Cannot fulfill an auction before it has ended", async function () { - await expect(auctionManager.fulfillAuction(ethers.utils.formatBytes32String("id2"))).to.be.revertedWith( - "Auction hasn't ended", - ); - }); - - it("Cannot fulfill a cancelled auction", async function () { - await expect(auctionManager.fulfillAuction(ethers.utils.formatBytes32String("id3"))).to.be.revertedWith( - "Not live", - ); - }); - - it("Anyone can fulfill won auction", async function () { - await time.increaseTo(Math.floor(Date.now() / 1000) + 300005000); - const provider = editionsOwner.provider!; - const paymentRecipientPreviousBalance = await provider.getBalance(randomEOA.address); - const platformPreviousBalance = await provider.getBalance(platformPaymentAccount.address); - const recipientCut = ethers.utils.parseEther("1.04").mul(9500).div(10000); - const platformCut = ethers.utils.parseEther("1.04").sub(recipientCut); - console.log("pre fulfill"); - await expect(auctionManager.fulfillAuction(ethers.utils.formatBytes32String("id1"))) - .to.emit(auctionManager, "AuctionWon") - .withArgs( - ethers.utils.formatBytes32String("id1"), - 1, - editions.address, - editionsOwner.address, - fan1.address, - randomEOA.address, - fan1.address, - ethers.constants.AddressZero, - ethers.utils.parseEther("1.04"), - 9500, - ); - console.log("post fulfill"); - - expect(await provider.getBalance(randomEOA.address)).to.equal(paymentRecipientPreviousBalance.add(recipientCut)); - expect(await provider.getBalance(platformPaymentAccount.address)).to.equal( - platformPreviousBalance.add(platformCut), - ); - expect(await provider.getBalance(auctionManager.address)).to.equal(ethers.utils.parseEther("2.0")); - expect(await editions.ownerOf(1)).to.equal(fan1.address); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("id1")); - expect(res[0][7]).to.equal(3); // FULFILLED state - }); - - it("Cannot fulfill already fulfilled auction", async function () { - await expect(auctionManager.fulfillAuction(ethers.utils.formatBytes32String("id1"))).to.be.revertedWith( - "Not live", - ); - }); - - it("Non highest bidder cannot call updatePreferredNFTRecipient", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect( - auctionManager.updatePreferredNFTRecipient(ethers.utils.formatBytes32String("id2"), mintManager.address), - ).to.be.revertedWith("Not current highest bidder"); - }); - - it("If preferred nft recipient is invalid, revert", async function () { - auctionManager = auctionManager.connect(fan1); - await expect( - auctionManager.updatePreferredNFTRecipient(ethers.utils.formatBytes32String("id2"), mintManager.address), - ) - .to.emit(auctionManager, "PreferredNFTRecipientUpdated") - .withArgs(ethers.utils.formatBytes32String("id2"), editionsOwner.address, mintManager.address); - - await expect(auctionManager.fulfillAuction(ethers.utils.formatBytes32String("id2"))).to.be.revertedWith( - "Preferred nft recipient is an invalid receiver", - ); - }); - }); - - describe("Auctions for existing tokens", function () { - // in the beforeEach, create 2 auctions, one each via each method - // one in native gas token, other in erc20 - // in the beforeEach, do the validations on the created parameters - /* - await expect( - auctionManager.createAuctionForExistingToken(ethers.utils.formatBytes32String("id1"), defaultAuction), - ).to.be.revertedWith("ERC721: invalid token ID"); - */ - }); - - describe("Multiple auctions", function () { - // just validate that there's no logic mishap with multiple auctions (one new token, one existing), don't need to validate as much as previous 2 describes - }); - - describe("Admin", function () { - it("Only the owner can update the platform account", async function () { - auctionManager = auctionManager.connect(editionsOwner); - await expect(auctionManager.updatePlatform(editionsOwner.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - auctionManager = auctionManager.connect(mintManagerOwner); - await expect(auctionManager.updatePlatform(editionsOwner.address)) - .to.emit(auctionManager, "PlatformUpdated") - .withArgs(editionsOwner.address); - }); - - it("Only the owner can upgrade the auction manager", async function () { - // upgrade to mock contract - // validate - }); - }); -}); - -// need to validate behaviour with many auctions, for eg. -// to show that correct amount of tokens / native currency held by contract is retained (through auctions) diff --git a/test/ERC721BaseTest.ts b/test/ERC721BaseTest.ts deleted file mode 100644 index b4db6e8..0000000 --- a/test/ERC721BaseTest.ts +++ /dev/null @@ -1,920 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - ERC721SingleEdition, - EditionsMetadataRenderer, - InvalidRoyaltyManager, - InvalidTokenManager, - LockedRoyaltyManager, - LockedTokenManager, - MinimalForwarder, - MintManager, - Observability, - OwnerOnlyRoyaltyManager, - OwnerOnlyTokenManager, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupEditions, setupSingleEdition, setupSystem } from "./__utils__/helpers"; - -enum BaseEvents { - MinterRegistrationChanged = "MinterRegistrationChanged", - GranularTokenManagersSet = "GranularTokenManagersSet", - GranularTokenManagersRemoved = "GranularTokenManagersRemoved", - DefaultTokenManagerChanged = "DefaultTokenManagerChanged", - DefaultRoyaltySet = "DefaultRoyaltySet", - GranularRoyaltiesSet = "GranularRoyaltiesSet", - RoyaltyManagerChanged = "RoyaltyManagerChanged", - MintsFrozen = "MintsFrozen", -} - -const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [["name", "description", "imageUrl", "animationUrl", "externalUrl", "attributes"]], -); - -describe("ERC721 Base functionality", () => { - let invalidRoyaltyManager: InvalidRoyaltyManager; - let lockedRoyaltyManager: LockedRoyaltyManager; - let ownerOnlyRoyaltyManager: OwnerOnlyRoyaltyManager; - let invalidTokenManager: InvalidTokenManager; - let lockedTokenManager: LockedTokenManager; - let ownerOnlyTokenManager: OwnerOnlyTokenManager; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let observability: Observability; - let editionsImplementation: string; - let singleEditionImplementation: string; - - const zeroRoyalty = { - recipientAddress: ethers.constants.AddressZero, - royaltyPercentageBPS: 0, - }; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - - invalidRoyaltyManager = await (await ethers.getContractFactory("InvalidRoyaltyManager")).deploy(); - lockedRoyaltyManager = await (await ethers.getContractFactory("LockedRoyaltyManager")).deploy(); - ownerOnlyRoyaltyManager = await (await ethers.getContractFactory("OwnerOnlyRoyaltyManager")).deploy(); - - invalidTokenManager = await (await ethers.getContractFactory("InvalidTokenManager")).deploy(); - lockedTokenManager = await (await ethers.getContractFactory("LockedTokenManager")).deploy(); - ownerOnlyTokenManager = await (await ethers.getContractFactory("OwnerOnlyTokenManager")).deploy(); - }); - - describe("Base (using Editions)", function () { - let editions: ERC721Editions; - - beforeEach(async () => { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - ); - }); - - describe("Minter registration", function () { - it("Non-owners cannot register or unregister minters", async function () { - editions = editions.connect(fan1); - await expect(editions.registerMinter(fan1.address)).to.be.revertedWith("Ownable: caller is not the owner"); - - await expect(editions.unregisterMinter(fan1.address)).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can only register unregistered minters", async function () { - await expect(editions.registerMinter(fan1.address)) - .to.emit(editions, BaseEvents.MinterRegistrationChanged) - .withArgs(fan1.address, true) - .to.emit(observability, BaseEvents.MinterRegistrationChanged) - .withArgs(editions.address, fan1.address, true); - - await expect(editions.registerMinter(fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MinterRegistrationInvalid, - ); - }); - - it("Can only unregister registered minters", async function () { - await expect(editions.unregisterMinter(fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MinterRegistrationInvalid, - ); - - await editions.registerMinter(fan1.address); - - await expect(editions.unregisterMinter(fan1.address)) - .to.emit(editions, BaseEvents.MinterRegistrationChanged) - .withArgs(fan1.address, false) - .to.emit(observability, BaseEvents.MinterRegistrationChanged) - .withArgs(editions.address, fan1.address, false); - }); - }); - - describe("Granular token managers management", function () { - describe("Current token manager not existing", function () { - it("An invalid token manager cannot be set", async function () { - await expect( - editions.setGranularTokenManagers([0, 1], [invalidTokenManager.address, invalidTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.InvalidManager); - - await expect( - editions.setGranularTokenManagers([0, 1], [lockedTokenManager.address, invalidTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.InvalidManager); - - await expect( - editions.setGranularTokenManagers([0, 1], [invalidTokenManager.address, lockedTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.InvalidManager); - }); - - it("Non owners cannot call", async function () { - editions = editions.connect(fan1); - await expect( - editions.setGranularTokenManagers([0, 1], [lockedTokenManager.address, lockedTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.Unauthorized); - }); - - it("Owner can set granular token managers", async function () { - await expect( - editions.setGranularTokenManagers([0, 1], [lockedTokenManager.address, lockedTokenManager.address]), - ) - .to.be.emit(editions, BaseEvents.GranularTokenManagersSet) - .withArgs([0, 1], [lockedTokenManager.address, lockedTokenManager.address]); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - expect(await editions.tokenManager(1)).to.eql(lockedTokenManager.address); - }); - - it("Cannot remove non-existent token manager", async function () { - await expect(editions.removeGranularTokenManagers([0])).to.be.revertedWithCustomError( - editions, - Errors.ManagerDoesNotExist, - ); - }); - }); - - describe("Current token manager exists", async function () { - beforeEach(async function () { - await editions.setGranularTokenManagers( - [0, 1], - [ownerOnlyTokenManager.address, ownerOnlyTokenManager.address], - ); - - expect(await editions.tokenManager(0)).to.eql(ownerOnlyTokenManager.address); - expect(await editions.tokenManager(1)).to.eql(ownerOnlyTokenManager.address); - }); - - it("Swap attempts respect the wishes of current token managers", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setGranularTokenManagers([0, 1], [lockedTokenManager.address, lockedTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.ManagerSwapBlocked); - - editions = editions.connect(editionsOwner); - - await expect(editions.setGranularTokenManagers([0], [lockedTokenManager.address])) - .to.be.emit(editions, BaseEvents.GranularTokenManagersSet) - .withArgs([0], [lockedTokenManager.address]); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - - await expect( - editions.setGranularTokenManagers([0, 1], [ownerOnlyTokenManager.address, lockedTokenManager.address]), - ).to.be.revertedWithCustomError(editions, Errors.ManagerSwapBlocked); - - await expect(editions.setGranularTokenManagers([1], [lockedTokenManager.address])) - .to.be.emit(editions, BaseEvents.GranularTokenManagersSet) - .withArgs([1], [lockedTokenManager.address]); - - expect(await editions.tokenManager(1)).to.eql(lockedTokenManager.address); - }); - - it("Remove attempts respect the wishes of current token managers", async function () { - editions = editions.connect(fan1); - - await expect(editions.removeGranularTokenManagers([0, 1])).to.be.revertedWithCustomError( - editions, - Errors.ManagerRemoveBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.removeGranularTokenManagers([0])) - .to.emit(editions, BaseEvents.GranularTokenManagersRemoved) - .withArgs([0]) - .to.emit(observability, BaseEvents.GranularTokenManagersRemoved) - .withArgs(editions.address, [0]); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - }); - }); - }); - - describe("Default token manager management", function () { - describe("Current default token manager not existing", function () { - it("An invalid default token manager cannot be set", async function () { - await expect(editions.setDefaultTokenManager(invalidTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.InvalidManager, - ); - }); - - it("Non owners cannot call", async function () { - editions = editions.connect(fan1); - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.Unauthorized, - ); - }); - - it("Owner can set default token manager", async function () { - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)) - .to.be.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(lockedTokenManager.address); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - } - }); - - it("Cannot remove non-existent default token manager", async function () { - await expect(editions.removeDefaultTokenManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerDoesNotExist, - ); - }); - }); - - describe("Current default token manager existing", function () { - beforeEach(async function () { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - null, - null, - ownerOnlyTokenManager.address, - ); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(ownerOnlyTokenManager.address); - } - }); - - it("Swap attempts respect the wishes of current default token manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)) - .to.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(lockedTokenManager.address) - .to.emit(observability, BaseEvents.DefaultTokenManagerChanged) - .withArgs(editions.address, lockedTokenManager.address); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - } - - await expect(editions.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - }); - - it("Remove attempts respect the wishes of current default token", async function () { - editions = editions.connect(fan1); - - await expect(editions.removeDefaultTokenManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerRemoveBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.removeDefaultTokenManager()) - .to.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(ethers.constants.AddressZero); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - } - }); - }); - }); - - describe("Royalty manager management", function () { - describe("Current royalty manager not existing", function () { - it("An invalid royalty manager cannot be set", async function () { - await expect(editions.setRoyaltyManager(invalidRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.InvalidManager, - ); - }); - - it("Non owners cannot call", async function () { - editions = editions.connect(fan1); - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.Unauthorized, - ); - }); - - it("Owner can set royalty manager", async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(ownerOnlyRoyaltyManager.address); - }); - - it("Cannot remove non-existent royalty manager", async function () { - await expect(editions.removeRoyaltyManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerDoesNotExist, - ); - }); - }); - - describe("Current royalty manager exists", function () { - beforeEach(async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(ownerOnlyRoyaltyManager.address); - }); - - it("Swap attempts respect the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(lockedRoyaltyManager.address) - .to.be.emit(observability, BaseEvents.RoyaltyManagerChanged) - .withArgs(editions.address, lockedRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(lockedRoyaltyManager.address); - - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - }); - - it("Remove attempts respect the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.removeRoyaltyManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerRemoveBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.removeRoyaltyManager()) - .to.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ethers.constants.AddressZero); - - expect(await editions.royaltyManager()).to.eql(ethers.constants.AddressZero); - }); - }); - }); - - describe("Royalty management", function () { - describe("Current royalty manager does not exist", async function () { - it("Royalty perentage BPS cannot be greater than 10000 for setting default royalty", async function () { - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 10001 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltyBPSInvalid); - }); - - it("Non-owner cannot set default royalty", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.Unauthorized); - }); - - it("Owner can set default royalty", async function () { - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ) - .to.emit(editions, BaseEvents.DefaultRoyaltySet) - .withArgs(ethers.constants.AddressZero, 100) - .to.emit(observability, BaseEvents.DefaultRoyaltySet) - .withArgs(editions.address, ethers.constants.AddressZero, 100); - - // mint some tokens to test royalties - await expect( - editions.createEdition(defaultEditionInfo, 4, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.emit(editions, "EditionCreated"); - - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - - await expect(editions.mintAmountToRecipient(0, editionsOwner.address, 2)).to.emit(editions, "Transfer"); - - const royaltyInfo = await editions.royaltyInfo(1, 10000); - expect(royaltyInfo.receiver).to.eql(ethers.constants.AddressZero); - expect(royaltyInfo.royaltyAmount.toNumber()).to.eql(100); - }); - - it("Royalty perentage BPS cannot be greater than 10000 for setting granular royalties", async function () { - await expect( - editions.setGranularRoyalties( - [0, 1], - [ - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 10001 }, - ], - ), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltyBPSInvalid); - }); - - it("Non-owner cannot set granular royalties", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setGranularRoyalties( - [0, 1], - [ - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 1000 }, - ], - ), - ).to.be.revertedWithCustomError(editions, Errors.Unauthorized); - }); - - it("Owner can set granular royalties", async function () { - await expect( - editions.setGranularRoyalties( - [0, 1], - [ - { recipientAddress: fan1.address, royaltyPercentageBPS: 100 }, - { recipientAddress: fan1.address, royaltyPercentageBPS: 1000 }, - ], - ), - ) - .to.emit(editions, BaseEvents.GranularRoyaltiesSet) - .to.emit(observability, BaseEvents.GranularRoyaltiesSet); - - // mint some tokens to test royalties - await expect( - editions.createEdition(defaultEditionInfo, 1, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.emit(editions, "EditionCreated"); - - await expect( - editions.createEdition(defaultEditionInfo, 1, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.emit(editions, "EditionCreated"); - - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - - await expect(editions.mintOneToRecipient(0, editionsOwner.address)).to.emit(editions, "Transfer"); - - await expect(editions.mintOneToRecipient(1, editionsOwner.address)).to.emit(editions, "Transfer"); - - const royaltyInfo1 = await editions.royaltyInfo(1, 10000); - expect(royaltyInfo1.receiver).to.eql(fan1.address); - expect(royaltyInfo1.royaltyAmount.toNumber()).to.eql(100); - - const royaltyInfo2 = await editions.royaltyInfo(2, 10000); - expect(royaltyInfo2.receiver).to.eql(fan1.address); - expect(royaltyInfo2.royaltyAmount.toNumber()).to.eql(1000); - }); - }); - - describe("Current royalty manager does exist", async function () { - beforeEach(async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - }); - - it("Setting royalties respects the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - - await expect( - editions.setGranularRoyalties( - [0, 1], - [ - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 1000 }, - ], - ), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - - editions = editions.connect(editionsOwner); - - // mint some tokens to test royalties - await expect( - editions.createEdition(defaultEditionInfo, 2, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.emit(editions, "EditionCreated"); - - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - - await expect(editions.mintAmountToRecipient(0, editionsOwner.address, 2)).to.emit(editions, "Transfer"); - - await expect(editions.setDefaultRoyalty({ recipientAddress: fan1.address, royaltyPercentageBPS: 100 })) - .to.emit(editions, BaseEvents.DefaultRoyaltySet) - .withArgs(fan1.address, 100); - - const royaltyInfo1 = await editions.royaltyInfo(1, 10000); - expect(royaltyInfo1.receiver).to.eql(fan1.address); - expect(royaltyInfo1.royaltyAmount.toNumber()).to.eql(100); - - await expect( - editions.setGranularRoyalties( - [0], - [{ recipientAddress: editionsOwner.address, royaltyPercentageBPS: 1000 }], - ), - ).to.emit(editions, BaseEvents.GranularRoyaltiesSet); - - const royaltyInfo2 = await editions.royaltyInfo(1, 10000); - expect(royaltyInfo2.receiver).to.eql(editionsOwner.address); - expect(royaltyInfo2.royaltyAmount.toNumber()).to.eql(1000); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(lockedRoyaltyManager.address); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - - await expect( - editions.setGranularRoyalties( - [0, 1], - [ - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 1000 }, - ], - ), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - }); - }); - }); - }); - - describe("MinimizedBase (using SingleEdition)", function () { - let editions: ERC721SingleEdition; - - beforeEach(async () => { - editions = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 100, - "name", - "SYM", - ); - }); - - describe("Minter registration", function () { - it("Non-owners cannot register or unregister minters", async function () { - editions = editions.connect(fan1); - await expect(editions.registerMinter(fan1.address)).to.be.revertedWith("Ownable: caller is not the owner"); - - await expect(editions.unregisterMinter(fan1.address)).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can only register unregistered minters", async function () { - await expect(editions.registerMinter(fan1.address)) - .to.emit(editions, BaseEvents.MinterRegistrationChanged) - .withArgs(fan1.address, true); - - await expect(editions.registerMinter(fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MinterRegistrationInvalid, - ); - }); - - it("Can only unregister registered minters", async function () { - await expect(editions.unregisterMinter(fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MinterRegistrationInvalid, - ); - - await editions.registerMinter(fan1.address); - - await expect(editions.unregisterMinter(fan1.address)) - .to.emit(editions, BaseEvents.MinterRegistrationChanged) - .withArgs(fan1.address, false); - }); - }); - - describe("Default token manager management", function () { - describe("Current default token manager not existing", function () { - it("An invalid default token manager cannot be set", async function () { - await expect(editions.setDefaultTokenManager(invalidTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.InvalidManager, - ); - }); - - it("Non owners cannot call", async function () { - editions = editions.connect(fan1); - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.Unauthorized, - ); - }); - - it("Owner can set default token manager", async function () { - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)) - .to.be.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(lockedTokenManager.address); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - } - }); - - it("Cannot remove non-existent default token manager", async function () { - await expect(editions.removeDefaultTokenManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerDoesNotExist, - ); - }); - }); - - describe("Current default token manager existing", function () { - beforeEach(async function () { - editions = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 100, - "name", - "SYM", - null, - null, - false, - ownerOnlyTokenManager.address, - ); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(ownerOnlyTokenManager.address); - } - }); - - it("Swap attempts respect the wishes of current default token manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.setDefaultTokenManager(lockedTokenManager.address)) - .to.be.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(lockedTokenManager.address); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - } - - await expect(editions.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - }); - - it("Remove attempts respect the wishes of current default token", async function () { - editions = editions.connect(fan1); - - await expect(editions.removeDefaultTokenManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerRemoveBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.removeDefaultTokenManager()) - .to.emit(editions, BaseEvents.DefaultTokenManagerChanged) - .withArgs(ethers.constants.AddressZero); - - for (let i = 0; i < 5; i++) { - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - } - }); - }); - }); - - describe("Royalty manager management", function () { - describe("Current royalty manager not existing", function () { - it("An invalid royalty manager cannot be set", async function () { - await expect(editions.setRoyaltyManager(invalidRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.InvalidManager, - ); - }); - - it("Non owners cannot call", async function () { - editions = editions.connect(fan1); - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.Unauthorized, - ); - }); - - it("Owner can set royalty manager", async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(ownerOnlyRoyaltyManager.address); - }); - - it("Cannot remove non-existent royalty manager", async function () { - await expect(editions.removeRoyaltyManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerDoesNotExist, - ); - }); - }); - - describe("Current royalty manager exists", function () { - beforeEach(async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(ownerOnlyRoyaltyManager.address); - }); - - it("Swap attempts respect the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(lockedRoyaltyManager.address); - - expect(await editions.royaltyManager()).to.eql(lockedRoyaltyManager.address); - - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)).to.be.revertedWithCustomError( - editions, - Errors.ManagerSwapBlocked, - ); - }); - - it("Remove attempts respect the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect(editions.removeRoyaltyManager()).to.be.revertedWithCustomError( - editions, - Errors.ManagerRemoveBlocked, - ); - - editions = editions.connect(editionsOwner); - - await expect(editions.removeRoyaltyManager()) - .to.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ethers.constants.AddressZero); - - expect(await editions.royaltyManager()).to.eql(ethers.constants.AddressZero); - }); - }); - }); - - describe("Royalty management", function () { - describe("Current royalty manager does not exist", async function () { - it("Royalty perentage BPS cannot be greater than 10000 for setting default royalty", async function () { - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 10001 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltyBPSInvalid); - }); - - it("Non-owner cannot set default royalty", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.Unauthorized); - }); - - it("Owner can set default royalty", async function () { - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ) - .to.emit(editions, BaseEvents.DefaultRoyaltySet) - .withArgs(ethers.constants.AddressZero, 100); - - const royaltyInfo = await editions.royaltyInfo(1, 10000); - expect(royaltyInfo.receiver).to.eql(ethers.constants.AddressZero); - expect(royaltyInfo.royaltyAmount.toNumber()).to.eql(100); - }); - }); - - describe("Current royalty manager does exist", async function () { - beforeEach(async function () { - await expect(editions.setRoyaltyManager(ownerOnlyRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(ownerOnlyRoyaltyManager.address); - }); - - it("Setting royalties respects the wishes of current royalty manager", async function () { - editions = editions.connect(fan1); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - - editions = editions.connect(editionsOwner); - - await expect(editions.setDefaultRoyalty({ recipientAddress: fan1.address, royaltyPercentageBPS: 100 })) - .to.emit(editions, BaseEvents.DefaultRoyaltySet) - .withArgs(fan1.address, 100); - - const royaltyInfo1 = await editions.royaltyInfo(0, 10000); - expect(royaltyInfo1.receiver).to.eql(fan1.address); - expect(royaltyInfo1.royaltyAmount.toNumber()).to.eql(100); - - await expect(editions.setRoyaltyManager(lockedRoyaltyManager.address)) - .to.be.emit(editions, BaseEvents.RoyaltyManagerChanged) - .withArgs(lockedRoyaltyManager.address); - - await expect( - editions.setDefaultRoyalty({ recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 100 }), - ).to.be.revertedWithCustomError(editions, Errors.RoyaltySetBlocked); - }); - }); - }); - }); -}); diff --git a/test/ERC721EditionsDFSTest.ts b/test/ERC721EditionsDFSTest.ts deleted file mode 100644 index 1b9f021..0000000 --- a/test/ERC721EditionsDFSTest.ts +++ /dev/null @@ -1,733 +0,0 @@ -import { parseEther } from "@ethersproject/units"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721EditionsDFS, - LockedTokenManager, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { - DEFAULT_ONCHAIN_MINT_VECTOR, - setupEditionsDFS, - setupMultipleEditionDFS, - setupSystem, -} from "./__utils__/helpers"; -import { getValidClaimTimestamp } from "./__utils__/mint"; - -describe("ERC721EditionsDFS functionality", () => { - let lockedTokenManager: LockedTokenManager; - let editions: ERC721EditionsDFS; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let mintManager: MintManager; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let observability: Observability; - let editionsImplementation: string; - let auctionData: string; - - const zeroRoyalty = { - recipientAddress: ethers.constants.AddressZero, - royaltyPercentageBPS: 0, - }; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - auctionManagerProxy, - editionsDFSImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - auctionManager = auctionManagerProxy; - editionsImplementation = editionsDFSImplementationAddress; - - lockedTokenManager = await (await ethers.getContractFactory("LockedTokenManager")).deploy(); - }); - - beforeEach(async () => { - editions = await setupEditionsDFS( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - editionsOwner, - ); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId"), - ethers.constants.AddressZero, - editionsOwner.address, - getValidClaimTimestamp(), - ], - ); - }); - - describe("createEdition", async function () { - it("Edition size has to be greater than 0", async function () { - await expect( - editions.createEdition("editionUri", 0, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.be.revertedWithCustomError(editions, Errors.InvalidSize); - }); - - it("Non-owner cannot create edition", async function () { - editions = editions.connect(fan1); - await expect( - editions.createEdition("editionUri", 100, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can create edition without passing in edition token manager", async function () { - await expect(editions.createEdition("editionUri", 100, ethers.constants.AddressZero, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 100, ethers.constants.AddressZero); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - }); - - it("Can create edition with passing in edition token manager", async function () { - await expect(editions.createEdition("editionUri", 100, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 100, lockedTokenManager.address); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - }); - }); - - describe("createEditionWithAuction", async function () { - it("Non-owner cannot create edition/auction", async function () { - editions = editions.connect(fan1); - await expect( - editions.createEditionWithAuction("editionUri", auctionData, ethers.constants.AddressZero, zeroRoyalty), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can create edition without passing in edition token manager", async function () { - const timestamp = getValidClaimTimestamp(); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId1"), - ethers.constants.AddressZero, - editionsOwner.address, - timestamp, - ], - ); - - await expect( - editions.createEditionWithAuction("editionUri", auctionData, ethers.constants.AddressZero, zeroRoyalty), - ) - .to.emit(editions, "EditionCreated") - .withArgs(0, 1, ethers.constants.AddressZero); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("auctionId1")); - expect(res[0][0]).to.equal(editions.address); - expect(res[0][4]).to.equal(ethers.BigNumber.from(timestamp)); - expect(res[2][0]).to.equal(true); - expect(res[2][1]).to.equal(ethers.BigNumber.from(0)); - }); - - it("Can create edition with passing in edition token manager", async function () { - const timestamp = getValidClaimTimestamp(); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId2"), - ethers.constants.AddressZero, - editionsOwner.address, - timestamp, - ], - ); - - await expect( - editions.createEditionWithAuction("editionUri", auctionData, lockedTokenManager.address, zeroRoyalty), - ) - .to.emit(editions, "EditionCreated") - .withArgs(0, 1, lockedTokenManager.address); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("auctionId2")); - expect(res[0][0]).to.equal(editions.address); - expect(res[0][4]).to.equal(ethers.BigNumber.from(timestamp)); - expect(res[2][0]).to.equal(true); - expect(res[2][1]).to.equal(ethers.BigNumber.from(0)); - }); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(editions.createEdition("editionUri", 5, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 5, lockedTokenManager.address); - - await expect(editions.registerMinter(editionsOwner.address)); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - }); - - describe("mintOneToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipient(1, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until max supply", async function () { - for (let i = 1; i <= 5; i++) { - await expect(editions.mintOneToRecipient(0, fan1.address)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await editions.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionStartIds()).map(x => x.toNumber())).to.eql([1]); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, i, 1]); - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(i); - expect(res[0][0][3].toNumber()).to.equal(1); - } - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - }); - - describe("mintAmountToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipient(1, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 6)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - expect(res[1][0]).to.equal("editionUri"); - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await editions.ownerOf(j)).to.equal(fan1.address); - expect((await editions.getEditionId(j)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintOneToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipients(1, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()) - .to.emit(editions, "MintsFrozen") - .to.emit(observability, "MintsFrozen") - .withArgs(editions.address); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, editionsMetadataOwner.address, editionsOwner.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await editions.ownerOf(i)).to.equal(recipient); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - i += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, editionsOwner.address]; - for (let i = 0; i < 2; i++) { - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await editions.ownerOf(i * 2 + j)).to.equal(recipient); - expect((await editions.getEditionId(i * 2 + j)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintAmountToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipients(1, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 1), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (complex variation) (with multiple editions)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect(editions.createEdition("editionUri", 10, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(1, 10, lockedTokenManager.address); - - const recipientAddresses2 = [fan1.address, editionsOwner.address]; - - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipients(1, recipientAddresses2, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 3 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 4 + 5); - - let j = 0; - for (const recipient of recipientAddresses2) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2 + 4 * (1 - j)); // (1-j) encodes whether it's the fan or not (fan received 4 token prior already) - expect(await editions.ownerOf(i * 4 + j * 2 + 1 + 5)).to.equal(recipient); - expect(await editions.ownerOf(i * 4 + j * 2 + 2 + 5)).to.equal(recipient); - expect((await editions.getEditionId(i * 4 + j * 2 + 1 + 5)).toNumber()).to.equal(1); - expect((await editions.getEditionId(i * 4 + j * 2 + 2 + 5)).toNumber()).to.equal(1); - j += 1; - } - - expect( - (await editions.getEditionDetails(1)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 10, (i + 1) * 4, 6]); - - const res = await editions.getEditionsDetailsAndUri([1]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(10); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 4); - expect(res[0][0][3].toNumber()).to.equal(6); - } - - expect((await editions.getEditionStartIds()).map(x => x.toNumber())).to.eql([1, 6]); - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - editions = editions.connect(editionsOwner); - - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(editions.address, "new name", "new symbol", "new contract uri"); - - expect(await editions.name()).to.equal("new name"); - expect(await editions.symbol()).to.equal("new symbol"); - expect(await editions.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - editions = editions.connect(fan1); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - editions = editions.connect(editionsMetadataOwner); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint and create editions with direct mints after", async function () { - editions = await setupMultipleEditionDFS( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - editionsOwner, - 100, - "symbol", - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - editions.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - DEFAULT_ONCHAIN_MINT_VECTOR.editionId ?? 0, - true, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect(mintManager.vectorMint721(1, 2, editionsOwner.address, { value: parseEther("0.0008").mul(2) })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), editions.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, editionsOwner.address)).to.equal(2); - - const mintVectorData = ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManager.address, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 1, - DEFAULT_ONCHAIN_MINT_VECTOR.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ); - await expect( - editions.createEdition( - "editionUri", - 10, - ethers.constants.AddressZero, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 0 }, - mintVectorData, - ), - ) - .to.emit(editions, "EditionCreated") - .withArgs(1, 10, ethers.constants.AddressZero) - .to.emit(mintManager, "EditionVectorCreated") - .withArgs(2, 1, editions.address); - - await expect(mintManager.vectorMint721(2, 1, editionsOwner.address, { value: parseEther("0.0008") })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(2), 32), editions.address, true, 1); - - await expect(mintManager.vectorMint721(2, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(2, editionsOwner.address)).to.equal(1); - }); -}); diff --git a/test/ERC721EditionsTest.ts b/test/ERC721EditionsTest.ts deleted file mode 100644 index 8fc27bc..0000000 --- a/test/ERC721EditionsTest.ts +++ /dev/null @@ -1,747 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - EditionsMetadataRenderer, - LockedTokenManager, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupEditions, setupMultipleEdition, setupSystem } from "./__utils__/helpers"; -import { getValidClaimTimestamp } from "./__utils__/mint"; - -const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [["name", "description", "imageUrl", "animationUrl", "externalUrl", "attributes"]], -); - -describe("ERC721Editions functionality", () => { - let lockedTokenManager: LockedTokenManager; - let editions: ERC721Editions; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let observability: Observability; - let editionsImplementation: string; - let auctionData: string; - - const zeroRoyalty = { - recipientAddress: ethers.constants.AddressZero, - royaltyPercentageBPS: 0, - }; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - auctionManagerProxy, - editionsImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - auctionManager = auctionManagerProxy; - editionsImplementation = editionsImplementationAddress; - - lockedTokenManager = await (await ethers.getContractFactory("LockedTokenManager")).deploy(); - }); - - beforeEach(async () => { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - ); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId"), - ethers.constants.AddressZero, - editionsOwner.address, - getValidClaimTimestamp(), - ], - ); - }); - - describe("createEdition", async function () { - it("Edition size has to be greater than 0", async function () { - await expect( - editions.createEdition(defaultEditionInfo, 0, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.be.revertedWithCustomError(editions, Errors.InvalidSize); - }); - - it("Non-owner cannot create edition", async function () { - editions = editions.connect(fan1); - await expect( - editions.createEdition(defaultEditionInfo, 100, ethers.constants.AddressZero, zeroRoyalty, "0x"), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can create edition without passing in edition token manager", async function () { - await expect(editions.createEdition(defaultEditionInfo, 100, ethers.constants.AddressZero, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 100, ethers.constants.AddressZero); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - }); - - it("Can create edition with passing in edition token manager", async function () { - await expect(editions.createEdition(defaultEditionInfo, 100, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 100, lockedTokenManager.address); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - }); - }); - - describe("createEditionWithAuction", async function () { - it("Non-owner cannot create edition/auction", async function () { - editions = editions.connect(fan1); - await expect( - editions.createEditionWithAuction(defaultEditionInfo, auctionData, ethers.constants.AddressZero, zeroRoyalty), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Can create edition without passing in edition token manager", async function () { - const timestamp = getValidClaimTimestamp(); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId1"), - ethers.constants.AddressZero, - editionsOwner.address, - timestamp, - ], - ); - - await expect( - editions.createEditionWithAuction(defaultEditionInfo, auctionData, ethers.constants.AddressZero, zeroRoyalty), - ) - .to.emit(editions, "EditionCreated") - .withArgs(0, 1, ethers.constants.AddressZero); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("auctionId1")); - expect(res[0][0]).to.equal(editions.address); - expect(res[0][4]).to.equal(ethers.BigNumber.from(timestamp)); - expect(res[2][0]).to.equal(true); - expect(res[2][1]).to.equal(ethers.BigNumber.from(0)); - }); - - it("Can create edition with passing in edition token manager", async function () { - const timestamp = getValidClaimTimestamp(); - auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManager.address, - ethers.utils.formatBytes32String("auctionId2"), - ethers.constants.AddressZero, - editionsOwner.address, - timestamp, - ], - ); - - await expect( - editions.createEditionWithAuction(defaultEditionInfo, auctionData, lockedTokenManager.address, zeroRoyalty), - ) - .to.emit(editions, "EditionCreated") - .withArgs(0, 1, lockedTokenManager.address); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - const res = await auctionManager.getFullAuctionData(ethers.utils.formatBytes32String("auctionId2")); - expect(res[0][0]).to.equal(editions.address); - expect(res[0][4]).to.equal(ethers.BigNumber.from(timestamp)); - expect(res[2][0]).to.equal(true); - expect(res[2][1]).to.equal(ethers.BigNumber.from(0)); - }); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(editions.createEdition(defaultEditionInfo, 5, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 5, lockedTokenManager.address); - - await expect(editions.registerMinter(editionsOwner.address)); - - expect(await editions.tokenManager(0)).to.eql(lockedTokenManager.address); - }); - - describe("mintOneToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipient(1, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until max supply", async function () { - for (let i = 1; i <= 5; i++) { - await expect(editions.mintOneToRecipient(0, fan1.address)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await editions.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionStartIds()).map(x => x.toNumber())).to.eql([1]); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, i, 1]); - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(i); - expect(res[0][0][3].toNumber()).to.equal(1); - } - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - }); - - describe("mintAmountToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipient(1, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 6)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await editions.ownerOf(j)).to.equal(fan1.address); - expect((await editions.getEditionId(j)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintOneToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipients(1, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()) - .to.emit(editions, "MintsFrozen") - .to.emit(observability, "MintsFrozen") - .withArgs(editions.address); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, editionsMetadataOwner.address, editionsOwner.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await editions.ownerOf(i)).to.equal(recipient); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - i += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, editionsOwner.address]; - for (let i = 0; i < 2; i++) { - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await editions.ownerOf(i * 2 + j)).to.equal(recipient); - expect((await editions.getEditionId(i * 2 + j)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintAmountToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipients(1, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 1), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (complex variation) (with multiple editions)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect(editions.createEdition(defaultEditionInfo, 10, lockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(1, 10, lockedTokenManager.address); - - const recipientAddresses2 = [fan1.address, editionsOwner.address]; - - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipients(1, recipientAddresses2, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 3 + 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 4 + 5); - - let j = 0; - for (const recipient of recipientAddresses2) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2 + 4 * (1 - j)); // (1-j) encodes whether it's the fan or not (fan received 4 token prior already) - expect(await editions.ownerOf(i * 4 + j * 2 + 1 + 5)).to.equal(recipient); - expect(await editions.ownerOf(i * 4 + j * 2 + 2 + 5)).to.equal(recipient); - expect((await editions.getEditionId(i * 4 + j * 2 + 1 + 5)).toNumber()).to.equal(1); - expect((await editions.getEditionId(i * 4 + j * 2 + 2 + 5)).toNumber()).to.equal(1); - j += 1; - } - - expect( - (await editions.getEditionDetails(1)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 10, (i + 1) * 4, 6]); - - const res = await editions.getEditionsDetailsAndUri([1]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(10); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 4); - expect(res[0][0][3].toNumber()).to.equal(6); - } - - expect((await editions.getEditionStartIds()).map(x => x.toNumber())).to.eql([1, 6]); - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - editions = editions.connect(editionsOwner); - - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(editions.address, "new name", "new symbol", "new contract uri"); - - expect(await editions.name()).to.equal("new name"); - expect(await editions.symbol()).to.equal("new symbol"); - expect(await editions.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - editions = editions.connect(fan1); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - editions = editions.connect(editionsMetadataOwner); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint and create editions with direct mints after", async function () { - editions = await setupMultipleEdition( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 100, - "name", - "symbol", - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [["name", "description", "imageUrl", "animationUrl", "externalUrl", "attributes"]], - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - editions.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - DEFAULT_ONCHAIN_MINT_VECTOR.editionId ?? 0, - true, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect( - mintManager.vectorMint721(1, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), editions.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, editionsOwner.address)).to.equal(2); - - const mintVectorData = ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManager.address, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 1, - DEFAULT_ONCHAIN_MINT_VECTOR.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ); - await expect( - editions.createEdition( - defaultEditionInfo, - 10, - ethers.constants.AddressZero, - { recipientAddress: ethers.constants.AddressZero, royaltyPercentageBPS: 0 }, - mintVectorData, - ), - ) - .to.emit(editions, "EditionCreated") - .withArgs(1, 10, ethers.constants.AddressZero) - .to.emit(mintManager, "EditionVectorCreated") - .withArgs(2, 1, editions.address); - - await expect(mintManager.vectorMint721(2, 1, editionsOwner.address, { value: ethers.utils.parseEther("0.0008") })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(2), 32), editions.address, true, 1); - - await expect(mintManager.vectorMint721(2, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(2, editionsOwner.address)).to.equal(1); - }); -}); diff --git a/test/ERC721GeneralSequenceTest.ts b/test/ERC721GeneralSequenceTest.ts deleted file mode 100644 index aafb121..0000000 --- a/test/ERC721GeneralSequenceTest.ts +++ /dev/null @@ -1,608 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - ERC721General, - MinimalForwarder, - MintManager, - Observability, - OwnerOnlyTokenManager, - TotalLockedTokenManager, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupGeneral, setupSystem } from "./__utils__/helpers"; - -describe("ERC721GeneralSequence functionality", () => { - let totalLockedTokenManager: TotalLockedTokenManager; - let ownerOnlyTokenManager: OwnerOnlyTokenManager; - let general: ERC721General; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let mintManager: MintManager; - let trustedForwarder: MinimalForwarder; - let observability: Observability; - let generalImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - generalSequenceImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - generalImplementation = generalSequenceImplementationAddress; - - totalLockedTokenManager = await (await ethers.getContractFactory("TotalLockedTokenManager")).deploy(); - ownerOnlyTokenManager = await (await ethers.getContractFactory("OwnerOnlyTokenManager")).deploy(); - }); - - beforeEach(async () => { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - ); - }); - - describe("URIs", function () { - beforeEach(async () => { - // mint a couple tokens to validate uris - await expect(general.registerMinter(owner.address)).to.emit(general, "MinterRegistrationChanged"); - - await expect(general.mintSameAmountToMultipleRecipients([owner.address, fan1.address], 2)).to.emit( - general, - "Transfer", - ); - }); - - it("Base uri concatenation should be respected for tokens without overwritten uris", async function () { - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - describe("setBaseUri", function () { - it("Cannot set to empty string", async function () { - await expect(general.setBaseURI("")).to.be.revertedWithCustomError(general, Errors.EmptyString); - }); - - it("If default manager is non-existent, invocation from non-owner fails", async function () { - general = general.connect(fan1); - await expect(general.setBaseURI("testing")).to.be.revertedWithCustomError(general, Errors.Unauthorized); - }); - - it("If default manager is non-existent, invocation from owner succeeds", async function () { - await expect(general.setBaseURI("testing")) - .to.emit(general, "BaseURISet") - .withArgs("baseUri", "testing") - .to.emit(observability, "BaseUriSet") - .withArgs(general.address, "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - - it("If default manager exists, invocation respects token manager", async function () { - await expect(general.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - general = general.connect(fan1); - await expect(general.setBaseURI("testing")).to.be.revertedWithCustomError(general, Errors.Unauthorized); - - general = general.connect(owner); - await expect(general.setBaseURI("testing")).to.emit(general, "BaseURISet").withArgs("baseUri", "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - }); - - describe("setTokenUris", function () { - it("ids and uris length cannot mismatch", async function () { - await expect(general.setTokenURIs([1, 2], ["test"])).to.be.revertedWithCustomError( - general, - Errors.MismatchedArrayLengths, - ); - }); - - it("If token manager is non-existent, invocation from non-owner fails", async function () { - general = general.connect(fan1); - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - }); - - it("If tokens manager is non-existent, invocation owner succeeds", async function () { - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(general, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - it("If token manager exists either as a default or an overwriting token manager, invocation respects token manager", async function () { - await expect(general.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - general = general.connect(fan1); - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - general = general.connect(owner); - - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(general, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]) - .to.emit(observability, "TokenURIsSet") - .withArgs(general.address, [1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - - await expect( - general.setGranularTokenManagers([1, 2], [totalLockedTokenManager.address, totalLockedTokenManager.address]), - ) - .to.emit(general, "GranularTokenManagersSet") - .to.emit(observability, "GranularTokenManagersSet"); - - await expect( - general.setTokenURIs([1, 2, 3], ["testing1", "testing2", "testing3"]), - ).to.be.revertedWithCustomError(general, Errors.Unauthorized); - - await expect(general.setTokenURIs([2, 3], ["testing2", "testing3"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - await expect(general.setTokenURIs([1, 3], ["testing1", "testing3"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - await expect(general.setTokenURIs([3], ["testing3"])) - .to.emit(general, "TokenURIsSet") - .withArgs([3], ["testing3"]); - - for (let i = 1; i <= 3; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - expect(await general.tokenURI(4)).to.equal(`baseUri/4`); - }); - }); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(general.registerMinter(owner.address)); - - expect(await general.tokenManager(0)).to.eql(ethers.constants.AddressZero); - - await expect(general.setLimitSupply(4)).to.emit(general, "LimitSupplySet").withArgs(4); - }); - - describe("mintOneToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until limit supply", async function () { - for (let i = 1; i <= 4; i++) { - await expect(general.mintOneToOneRecipient(fan1.address)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await general.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - for (let i = 5; i <= 8; i++) { - await expect(general.mintOneToOneRecipient(fan1.address)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await general.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - }); - }); - - describe("mintAmountToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - await expect(general.mintAmountToOneRecipient(fan1.address, 6)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)) - .to.emit(general, "LimitSupplySet") - .withArgs(0) - .to.emit(observability, "LimitSupplySet") - .withArgs(general.address, 0); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await general.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(general.mintAmountToOneRecipient(fan1.address, 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await general.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await general.ownerOf(j)).to.equal(fan1.address); - } - } - }); - }); - - describe("mintOneToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(general.mintOneToMultipleRecipients(recipientAddresses)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, owner.address, editionsMetadataOwner.address]; - await expect(general.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await general.ownerOf(i)).to.equal(recipient); - i += 1; - } - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - for (let i = 0; i < 2; i++) { - await expect(general.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await general.ownerOf(i * 2 + j)).to.equal(recipient); - j += 1; - } - } - }); - }); - - describe("mintSameAmountToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 1), - ).to.be.revertedWithCustomError(general, Errors.OverLimitSupply); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 7) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 8); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2), - ).to.be.revertedWithCustomError(general, Errors.OverLimitSupply); - }); - - it("Minter can mint validly (complex variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - - for (let i = 0; i < 2; i++) { - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 4); - - let j = 0; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2); - expect(await general.ownerOf(i * 4 + j * 2 + 1)).to.equal(recipient); - expect(await general.ownerOf(i * 4 + j * 2 + 2)).to.equal(recipient); - j += 1; - } - - await expect(general.setLimitSupply(8)).to.emit(general, "LimitSupplySet").withArgs(8); - } - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - general = general.connect(owner); - - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(general.address, "new name", "new symbol", "new contract uri"); - - expect(await general.name()).to.equal("new name"); - expect(await general.symbol()).to.equal("new symbol"); - expect(await general.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - general = general.connect(fan1); - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - general = general.connect(editionsMetadataOwner); - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint", async function () { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - general.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - owner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - 0, - false, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect(mintManager.vectorMint721(1, 2, owner.address, { value: ethers.utils.parseEther("0.0008").mul(2) })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), general.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, owner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, owner.address)).to.equal(2); - }); -}); diff --git a/test/ERC721GeneralTest.ts b/test/ERC721GeneralTest.ts deleted file mode 100644 index ea3d122..0000000 --- a/test/ERC721GeneralTest.ts +++ /dev/null @@ -1,701 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - ERC721General, - MinimalForwarder, - MintManager, - Observability, - OwnerOnlyTokenManager, - TotalLockedTokenManager, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupGeneral, setupSystem } from "./__utils__/helpers"; - -describe("ERC721General functionality", () => { - let totalLockedTokenManager: TotalLockedTokenManager; - let ownerOnlyTokenManager: OwnerOnlyTokenManager; - let general: ERC721General; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let mintManager: MintManager; - let trustedForwarder: MinimalForwarder; - let observability: Observability; - let generalImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - generalImplementation = generalImplementationAddress; - - totalLockedTokenManager = await (await ethers.getContractFactory("TotalLockedTokenManager")).deploy(); - ownerOnlyTokenManager = await (await ethers.getContractFactory("OwnerOnlyTokenManager")).deploy(); - }); - - beforeEach(async () => { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - ); - }); - - describe("URIs", function () { - beforeEach(async () => { - // mint a couple tokens to validate uris - await expect(general.registerMinter(owner.address)).to.emit(general, "MinterRegistrationChanged"); - - await expect(general.mintSameAmountToMultipleRecipients([owner.address, fan1.address], 2)).to.emit( - general, - "Transfer", - ); - }); - - it("Base uri concatenation should be respected for tokens without overwritten uris", async function () { - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - describe("setBaseUri", function () { - it("Cannot set to empty string", async function () { - await expect(general.setBaseURI("")).to.be.revertedWithCustomError(general, Errors.EmptyString); - }); - - it("If default manager is non-existent, invocation from non-owner fails", async function () { - general = general.connect(fan1); - await expect(general.setBaseURI("testing")).to.be.revertedWithCustomError(general, Errors.Unauthorized); - }); - - it("If default manager is non-existent, invocation from owner succeeds", async function () { - await expect(general.setBaseURI("testing")) - .to.emit(general, "BaseURISet") - .withArgs("baseUri", "testing") - .to.emit(observability, "BaseUriSet") - .withArgs(general.address, "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - - it("If default manager exists, invocation respects token manager", async function () { - await expect(general.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - general = general.connect(fan1); - await expect(general.setBaseURI("testing")).to.be.revertedWithCustomError(general, Errors.Unauthorized); - - general = general.connect(owner); - await expect(general.setBaseURI("testing")).to.emit(general, "BaseURISet").withArgs("baseUri", "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - }); - - describe("setTokenUris", function () { - it("ids and uris length cannot mismatch", async function () { - await expect(general.setTokenURIs([1, 2], ["test"])).to.be.revertedWithCustomError( - general, - Errors.MismatchedArrayLengths, - ); - }); - - it("If token manager is non-existent, invocation from non-owner fails", async function () { - general = general.connect(fan1); - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - }); - - it("If tokens manager is non-existent, invocation owner succeeds", async function () { - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(general, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - it("If token manager exists either as a default or an overwriting token manager, invocation respects token manager", async function () { - await expect(general.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - general = general.connect(fan1); - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - general = general.connect(owner); - - await expect(general.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(general, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]) - .to.emit(observability, "TokenURIsSet") - .withArgs(general.address, [1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await general.tokenURI(i)).to.equal(`baseUri/${i}`); - } - - await expect( - general.setGranularTokenManagers([1, 2], [totalLockedTokenManager.address, totalLockedTokenManager.address]), - ) - .to.emit(general, "GranularTokenManagersSet") - .to.emit(observability, "GranularTokenManagersSet"); - - await expect( - general.setTokenURIs([1, 2, 3], ["testing1", "testing2", "testing3"]), - ).to.be.revertedWithCustomError(general, Errors.Unauthorized); - - await expect(general.setTokenURIs([2, 3], ["testing2", "testing3"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - await expect(general.setTokenURIs([1, 3], ["testing1", "testing3"])).to.be.revertedWithCustomError( - general, - Errors.Unauthorized, - ); - - await expect(general.setTokenURIs([3], ["testing3"])) - .to.emit(general, "TokenURIsSet") - .withArgs([3], ["testing3"]); - - for (let i = 1; i <= 3; i++) { - expect(await general.tokenURI(i)).to.equal(`testing${i}`); - } - expect(await general.tokenURI(4)).to.equal(`baseUri/4`); - }); - }); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(general.registerMinter(owner.address)); - - expect(await general.tokenManager(0)).to.eql(ethers.constants.AddressZero); - - await expect(general.setLimitSupply(4)).to.emit(general, "LimitSupplySet").withArgs(4); - }); - - describe("mintOneToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until limit supply", async function () { - for (let i = 1; i <= 4; i++) { - await expect(general.mintOneToOneRecipient(fan1.address)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await general.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - - await expect(general.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - for (let i = 5; i <= 8; i++) { - await expect(general.mintOneToOneRecipient(fan1.address)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await general.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - }); - }); - - describe("mintAmountToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - await expect(general.mintAmountToOneRecipient(fan1.address, 6)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)) - .to.emit(general, "LimitSupplySet") - .withArgs(0) - .to.emit(observability, "LimitSupplySet") - .withArgs(general.address, 0); - - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(general.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await general.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(general.mintAmountToOneRecipient(fan1.address, 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await general.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await general.ownerOf(j)).to.equal(fan1.address); - } - } - }); - }); - - describe("mintOneToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(general.mintOneToMultipleRecipients(recipientAddresses)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, owner.address, editionsMetadataOwner.address]; - await expect(general.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await general.ownerOf(i)).to.equal(recipient); - i += 1; - } - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - for (let i = 0; i < 2; i++) { - await expect(general.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await general.ownerOf(i * 2 + j)).to.equal(recipient); - j += 1; - } - } - }); - }); - - describe("mintSameAmountToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 1), - ).to.be.revertedWithCustomError(general, Errors.OverLimitSupply); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 7) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 8); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)).to.be.revertedWithCustomError( - general, - Errors.OverLimitSupply, - ); - - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - general.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2), - ).to.be.revertedWithCustomError(general, Errors.OverLimitSupply); - }); - - it("Minter can mint validly (complex variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - - for (let i = 0; i < 2; i++) { - await expect(general.mintSameAmountToMultipleRecipients(recipientAddresses, 2)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 4); - - let j = 0; - for (const recipient of recipientAddresses) { - expect((await general.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2); - expect(await general.ownerOf(i * 4 + j * 2 + 1)).to.equal(recipient); - expect(await general.ownerOf(i * 4 + j * 2 + 2)).to.equal(recipient); - j += 1; - } - - await expect(general.setLimitSupply(8)).to.emit(general, "LimitSupplySet").withArgs(8); - } - }); - }); - - describe("mintSpecificTokenToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 1)).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint token not in range, but can mint in-range ones", async function () { - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 1)).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 2)).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 5)).to.be.revertedWithCustomError( - general, - Errors.TokenNotInRange, - ); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 3)).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 4)).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 5)).to.be.revertedWithCustomError( - general, - Errors.TokenNotInRange, - ); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 5)).to.emit(general, "Transfer"); - }); - - it("Cannot mint already minted token", async function () { - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 4)).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokenToOneRecipient(fan1.address, 4)).to.be.revertedWith( - "ERC721: token minted", - ); - }); - }); - - describe("mintSpecificTokensToOneRecipient", function () { - it("Non minter cannot call", async function () { - general = general.connect(fan1); - - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [1, 2])).to.be.revertedWithCustomError( - general, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(general.freezeMints()).to.emit(general, "MintsFrozen"); - - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [2])).to.be.revertedWithCustomError( - general, - Errors.MintFrozen, - ); - }); - - it("Cannot mint token not in range, but can mint in-range ones", async function () { - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [1, 4])) - .to.emit(general, "Transfer") - .to.emit(general, "Transfer"); - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [2, 5])).to.be.revertedWithCustomError( - general, - Errors.TokenNotInRange, - ); - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [2, 3])) - .to.emit(general, "Transfer") - .to.emit(general, "Transfer"); - - await expect(general.setLimitSupply(0)).to.emit(general, "LimitSupplySet").withArgs(0); - - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [6, 19, 20])) - .to.emit(general, "Transfer") - .to.emit(general, "Transfer") - .to.emit(general, "Transfer"); - }); - - it("Cannot mint already minted token", async function () { - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [4, 1])).to.emit(general, "Transfer"); - await expect(general.mintSpecificTokensToOneRecipient(fan1.address, [2, 1, 3])).to.be.revertedWith( - "ERC721: token minted", - ); - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - general = general.connect(owner); - - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(general.address, "new name", "new symbol", "new contract uri"); - - expect(await general.name()).to.equal("new name"); - expect(await general.symbol()).to.equal("new symbol"); - expect(await general.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - general = general.connect(fan1); - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - general = general.connect(editionsMetadataOwner); - await expect(general.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint", async function () { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - general.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - owner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - 0, - false, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect(mintManager.vectorMint721(1, 2, owner.address, { value: ethers.utils.parseEther("0.0008").mul(2) })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), general.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, owner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, owner.address)).to.equal(2); - }); -}); diff --git a/test/ERC721GenerativeTest.ts b/test/ERC721GenerativeTest.ts deleted file mode 100644 index 8070b35..0000000 --- a/test/ERC721GenerativeTest.ts +++ /dev/null @@ -1,886 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - BitRotGenerative, - BitRotGenerativeTest, - BitRotGenerativeTest__factory, - BitRotGenerative__factory, - ERC721GeneralSequence, - ERC721GenerativeOnchain, - ERC721GenerativeOnchain__factory, - FileDeployer, - MinimalForwarder, - MintManager, - Observability, - OwnerOnlyTokenManager, - TestHighlightRenderer, - TotalLockedTokenManager, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupGenerative, setupSystem } from "./__utils__/helpers"; - -describe("ERC721Generative functionality", () => { - let totalLockedTokenManager: TotalLockedTokenManager; - let ownerOnlyTokenManager: OwnerOnlyTokenManager; - let generative: ERC721GeneralSequence; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let mintManager: MintManager; - let observability: Observability; - let trustedForwarder: MinimalForwarder; - let generativeImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - generativeImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - generativeImplementation = generativeImplementationAddress; - - totalLockedTokenManager = await (await ethers.getContractFactory("TotalLockedTokenManager")).deploy(); - ownerOnlyTokenManager = await (await ethers.getContractFactory("OwnerOnlyTokenManager")).deploy(); - }); - - beforeEach(async () => { - generative = await setupGenerative( - observability.address, - generativeImplementation, - trustedForwarder.address, - mintManager.address, - owner, - ); - }); - - describe("URIs", function () { - beforeEach(async () => { - // mint a couple tokens to validate uris - await expect(generative.registerMinter(owner.address)).to.emit(generative, "MinterRegistrationChanged"); - - await expect(generative.mintSameAmountToMultipleRecipients([owner.address, fan1.address], 2)).to.emit( - generative, - "Transfer", - ); - }); - - it("Base uri concatenation should be respected for tokens without overwritten uris", async function () { - for (let i = 1; i <= 4; i++) { - expect(await generative.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - describe("setBaseUri", function () { - it("Cannot set to empty string", async function () { - await expect(generative.setBaseURI("")).to.be.revertedWithCustomError(generative, Errors.EmptyString); - }); - - it("If default manager is non-existent, invocation from non-owner fails", async function () { - generative = generative.connect(fan1); - await expect(generative.setBaseURI("testing")).to.be.revertedWithCustomError(generative, Errors.Unauthorized); - }); - - it("If default manager is non-existent, invocation from owner succeeds", async function () { - await expect(generative.setBaseURI("testing")).to.emit(generative, "BaseURISet").withArgs("baseUri", "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await generative.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - - it("If default manager exists, invocation respects token manager", async function () { - await expect(generative.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - generative, - "DefaultTokenManagerChanged", - ); - - generative = generative.connect(fan1); - await expect(generative.setBaseURI("testing")).to.be.revertedWithCustomError(generative, Errors.Unauthorized); - - generative = generative.connect(owner); - await expect(generative.setBaseURI("testing")).to.emit(generative, "BaseURISet").withArgs("baseUri", "testing"); - - for (let i = 1; i <= 4; i++) { - expect(await generative.tokenURI(i)).to.equal(`testing/${i}`); - } - }); - }); - - describe("setTokenUris", function () { - it("ids and uris length cannot mismatch", async function () { - await expect(generative.setTokenURIs([1, 2], ["test"])).to.be.revertedWithCustomError( - generative, - Errors.MismatchedArrayLengths, - ); - }); - - it("If token manager is non-existent, invocation from non-owner fails", async function () { - generative = generative.connect(fan1); - await expect(generative.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - generative, - Errors.Unauthorized, - ); - }); - - it("If tokens manager is non-existent, invocation owner succeeds", async function () { - await expect(generative.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(generative, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]) - .to.emit(observability, "TokenURIsSet") - .withArgs(generative.address, [1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await generative.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await generative.tokenURI(i)).to.equal(`baseUri/${i}`); - } - }); - - it("If token manager exists either as a default or an overwriting token manager, invocation respects token manager", async function () { - await expect(generative.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - generative, - "DefaultTokenManagerChanged", - ); - - generative = generative.connect(fan1); - await expect(generative.setTokenURIs([1, 2], ["testing1", "testing2"])).to.be.revertedWithCustomError( - generative, - Errors.Unauthorized, - ); - - generative = generative.connect(owner); - - await expect(generative.setTokenURIs([1, 2], ["testing1", "testing2"])) - .to.emit(generative, "TokenURIsSet") - .withArgs([1, 2], ["testing1", "testing2"]); - - for (let i = 1; i <= 2; i++) { - expect(await generative.tokenURI(i)).to.equal(`testing${i}`); - } - for (let i = 3; i <= 4; i++) { - expect(await generative.tokenURI(i)).to.equal(`baseUri/${i}`); - } - - await expect( - generative.setGranularTokenManagers( - [1, 2], - [totalLockedTokenManager.address, totalLockedTokenManager.address], - ), - ).to.emit(generative, "GranularTokenManagersSet"); - - await expect( - generative.setTokenURIs([1, 2, 3], ["testing1", "testing2", "testing3"]), - ).to.be.revertedWithCustomError(generative, Errors.Unauthorized); - - await expect(generative.setTokenURIs([2, 3], ["testing2", "testing3"])).to.be.revertedWithCustomError( - generative, - Errors.Unauthorized, - ); - - await expect(generative.setTokenURIs([1, 3], ["testing1", "testing3"])).to.be.revertedWithCustomError( - generative, - Errors.Unauthorized, - ); - - await expect(generative.setTokenURIs([3], ["testing3"])) - .to.emit(generative, "TokenURIsSet") - .withArgs([3], ["testing3"]); - - for (let i = 1; i <= 3; i++) { - expect(await generative.tokenURI(i)).to.equal(`testing${i}`); - } - expect(await generative.tokenURI(4)).to.equal(`baseUri/4`); - }); - }); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(generative.registerMinter(owner.address)); - - expect(await generative.tokenManager(0)).to.eql(ethers.constants.AddressZero); - - await expect(generative.setLimitSupply(4)).to.emit(generative, "LimitSupplySet").withArgs(4); - }); - - describe("mintOneToOneRecipient", function () { - it("Non minter cannot call", async function () { - generative = generative.connect(fan1); - - await expect(generative.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - generative, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(generative.freezeMints()).to.emit(generative, "MintsFrozen"); - - await expect(generative.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - generative, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until limit supply", async function () { - for (let i = 1; i <= 4; i++) { - await expect(generative.mintOneToOneRecipient(fan1.address)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await generative.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await generative.ownerOf(i)).to.equal(fan1.address); - } - - await expect(generative.mintOneToOneRecipient(fan1.address)).to.be.revertedWithCustomError( - generative, - Errors.OverLimitSupply, - ); - - await expect(generative.setLimitSupply(0)).to.emit(generative, "LimitSupplySet").withArgs(0); - - for (let i = 5; i <= 8; i++) { - await expect(generative.mintOneToOneRecipient(fan1.address)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await generative.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await generative.ownerOf(i)).to.equal(fan1.address); - } - }); - }); - - describe("mintAmountToOneRecipient", function () { - it("Non minter cannot call", async function () { - generative = generative.connect(fan1); - - await expect(generative.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - generative, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(generative.freezeMints()).to.emit(generative, "MintsFrozen"); - - await expect(generative.mintAmountToOneRecipient(fan1.address, 2)).to.be.revertedWithCustomError( - generative, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - await expect(generative.mintAmountToOneRecipient(fan1.address, 6)).to.be.revertedWithCustomError( - generative, - Errors.OverLimitSupply, - ); - - await expect(generative.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(generative.mintAmountToOneRecipient(fan1.address, 3)).to.be.revertedWithCustomError( - generative, - Errors.OverLimitSupply, - ); - - await expect(generative.setLimitSupply(0)).to.emit(generative, "LimitSupplySet").withArgs(0); - - await expect(generative.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(generative.mintAmountToOneRecipient(fan1.address, 3)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await generative.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await generative.ownerOf(i)).to.equal(fan1.address); - } - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(generative.mintAmountToOneRecipient(fan1.address, 2)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await generative.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await generative.ownerOf(j)).to.equal(fan1.address); - } - } - }); - }); - - describe("mintOneToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - generative = generative.connect(fan1); - - await expect(generative.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - generative, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(generative.freezeMints()).to.emit(generative, "MintsFrozen"); - - await expect(generative.mintOneToMultipleRecipients([fan1.address])).to.be.revertedWithCustomError( - generative, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(generative.mintOneToMultipleRecipients(recipientAddresses)).to.be.revertedWithCustomError( - generative, - Errors.OverLimitSupply, - ); - - await expect(generative.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(generative.mintOneToMultipleRecipients(recipientAddresses.slice(3))).to.be.revertedWithCustomError( - generative, - Errors.OverLimitSupply, - ); - - await expect(generative.setLimitSupply(0)).to.emit(generative, "LimitSupplySet").withArgs(0); - - await expect(generative.mintOneToMultipleRecipients(recipientAddresses.slice(3))) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, owner.address, editionsMetadataOwner.address]; - await expect(generative.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await generative.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await generative.ownerOf(i)).to.equal(recipient); - i += 1; - } - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - for (let i = 0; i < 2; i++) { - await expect(generative.mintOneToMultipleRecipients(recipientAddresses)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await generative.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await generative.ownerOf(i * 2 + j)).to.equal(recipient); - j += 1; - } - } - }); - }); - - describe("mintSameAmountToMultipleRecipients", function () { - it("Non minter cannot call", async function () { - generative = generative.connect(fan1); - - await expect(generative.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - generative, - Errors.NotMinter, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(generative.freezeMints()).to.emit(generative, "MintsFrozen"); - - await expect(generative.mintSameAmountToMultipleRecipients([fan1.address], 2)).to.be.revertedWithCustomError( - generative, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than limitSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect( - generative.mintSameAmountToMultipleRecipients(recipientAddresses, 2), - ).to.be.revertedWithCustomError(generative, Errors.OverLimitSupply); - - await expect(generative.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - generative.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 1), - ).to.be.revertedWithCustomError(generative, Errors.OverLimitSupply); - - await expect(generative.setLimitSupply(0)) - .to.emit(generative, "LimitSupplySet") - .withArgs(0) - .to.emit(observability, "LimitSupplySet") - .withArgs(generative.address, 0); - - await expect(generative.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 7) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 8); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect( - generative.mintSameAmountToMultipleRecipients(recipientAddresses, 2), - ).to.be.revertedWithCustomError(generative, Errors.OverLimitSupply); - - await expect(generative.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - generative.mintSameAmountToMultipleRecipients(recipientAddresses.slice(1), 2), - ).to.be.revertedWithCustomError(generative, Errors.OverLimitSupply); - }); - - it("Minter can mint validly (complex variation)", async function () { - const recipientAddresses = [fan1.address, owner.address]; - - for (let i = 0; i < 2; i++) { - await expect(generative.mintSameAmountToMultipleRecipients(recipientAddresses, 2)) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 3) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, owner.address, i * 4 + 4); - - let j = 0; - for (const recipient of recipientAddresses) { - expect((await generative.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2); - expect(await generative.ownerOf(i * 4 + j * 2 + 1)).to.equal(recipient); - expect(await generative.ownerOf(i * 4 + j * 2 + 2)).to.equal(recipient); - j += 1; - } - - await expect(generative.setLimitSupply(8)).to.emit(generative, "LimitSupplySet").withArgs(8); - } - }); - }); - }); - - it("Can deploy with direct mint", async function () { - generative = await setupGenerative( - observability.address, - generativeImplementation, - trustedForwarder.address, - mintManager.address, - owner, - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - null, - false, - 0, - ethers.constants.AddressZero, - fan1.address, - 1000, - ); - - expect((await generative.royaltyInfo(1, 10000)).royaltyAmount.toNumber()).to.equal(1000); - expect((await generative.royaltyInfo(1, 10000)).receiver).to.equal(fan1.address); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - generative.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - owner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - 0, - false, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect(mintManager.vectorMint721(1, 2, owner.address, { value: ethers.utils.parseEther("0.0008").mul(2) })) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), generative.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, owner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, owner.address)).to.equal(2); - }); - - describe("OCS onchain contracts", function () { - let ocsERC721: ERC721GenerativeOnchain; - let fileDeployer: FileDeployer; - const file = ` - function _readBytecode( - address pointer, - uint256 start, - uint256 size - ) private view returns (bytes memory data) { - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - data := mload(0x40) - - // Update the free memory pointer to prevent overriding our data. - // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). - // Adding 31 to size and running the result through the logic above ensures - // the memory pointer remains word-aligned, following the Solidity convention. - mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) - - // Store the size of the data in the first 32 byte chunk of free memory. - mstore(data, size) - - // Copy the code into memory right after the 32 bytes we used to store the size. - extcodecopy(pointer, add(data, 32), start, size) - } - } - `; - const secondFile = ` - function _revert(bytes4 errorSelector) internal pure virtual { - assembly { - mstore(0x00, errorSelector) - revert(0x00, 0x04) - } - } - `; - const fileAddresses1: string[] = []; - const fileAddresses2: string[] = []; - - before(async () => { - // deploy FileDeployer - const FileDeployer = await ethers.getContractFactory("FileDeployer"); - fileDeployer = await FileDeployer.deploy(); - await fileDeployer.deployed(); - - const OCSERC721Implementation = await ethers.getContractFactory("ERC721GenerativeOnchain"); - const ocsERC721Implementation = await OCSERC721Implementation.deploy(); - await ocsERC721Implementation.deployed(); - - // deploy instance of ocs721 - ocsERC721 = ERC721GenerativeOnchain__factory.connect( - ( - await setupGenerative( - observability.address, - ocsERC721Implementation.address, - trustedForwarder.address, - mintManager.address, - owner, - ) - ).address, - owner, - ); - }); - - it("Can deploy files via the file deployer", async function () { - const filePart1 = file.slice(0, file.length / 3); - const filePart2 = file.slice(file.length / 3, (file.length * 2) / 3); - const filePart3 = file.slice((file.length * 2) / 3); - - const tx1 = await fileDeployer.deploy( - ["1", "2"].map(name => { - return ethers.utils.formatBytes32String(name); - }), - [filePart1, filePart2], - ); - const receipt1 = await tx1.wait(); - fileAddresses1.push("0x" + receipt1.logs[0].topics[2].slice(26)); - fileAddresses1.push("0x" + receipt1.logs[1].topics[2].slice(26)); - - const tx3 = await fileDeployer.deploy( - ["3"].map(name => { - return ethers.utils.formatBytes32String(name); - }), - [filePart3], - ); - const receipt3 = await tx3.wait(); - fileAddresses1.push("0x" + receipt3.logs[0].topics[2].slice(26)); - - const secondFilePart1 = secondFile.slice(0, secondFile.length / 2); - const secondFilePart2 = secondFile.slice(secondFile.length / 2); - const tx4 = await fileDeployer.deploy( - ["4"].map(name => { - return ethers.utils.formatBytes32String(name); - }), - [secondFilePart1], - ); - const receipt4 = await tx4.wait(); - fileAddresses2.push("0x" + receipt4.logs[0].topics[2].slice(26)); - - const tx5 = await fileDeployer.deploy( - ["5"].map(name => { - return ethers.utils.formatBytes32String(name); - }), - [secondFilePart2], - ); - const receipt5 = await tx5.wait(); - fileAddresses2.push("0x" + receipt5.logs[0].topics[2].slice(26)); - }); - - it("Owner can register files and view their contents + bytecode addresses", async function () { - await expect(ocsERC721.addFile("readBytecodeSnippet.sol", fileAddresses1)).to.not.be.reverted; - await expect(ocsERC721.addFile("revertSnippet.sol", fileAddresses2)).to.not.be.reverted; - - expect(await ocsERC721.files()).to.eql(["readBytecodeSnippet.sol", "revertSnippet.sol"]); - expect((await ocsERC721.fileStorage("readBytecodeSnippet.sol")).map(address => address.toLowerCase())).to.eql( - fileAddresses1.map(address => address.toLowerCase()), - ); - expect((await ocsERC721.fileStorage("revertSnippet.sol")).map(address => address.toLowerCase())).to.eql( - fileAddresses2.map(address => address.toLowerCase()), - ); - - // viewing contents - expect(await ocsERC721.fileContents("readBytecodeSnippet.sol")).to.eql(file); - expect(await ocsERC721.fileContents("revertSnippet.sol")).to.eql(secondFile); - }); - - it("Cannot register an already registered file", async function () { - await expect(ocsERC721.addFile("readBytecodeSnippet.sol", fileAddresses1)).to.be.revertedWithCustomError( - ocsERC721, - "FileAlreadyRegistered", - ); - }); - - it("Non-owner cannot register a file", async function () { - ocsERC721 = ocsERC721.connect(fan1); - await expect(ocsERC721.addFile("readBytecodeSnippet3.sol", fileAddresses1)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - ocsERC721 = ocsERC721.connect(owner); - }); - - it("Cannot remove a non-registered file", async function () { - await expect(ocsERC721.removeFile("readBytecodeSnippet3.sol")).to.be.revertedWithCustomError( - ocsERC721, - "FileNotRegistered", - ); - }); - - it("Non-owner cannot remove a file", async function () { - ocsERC721 = ocsERC721.connect(fan1); - await expect(ocsERC721.removeFile("readBytecodeSnippet.sol")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - ocsERC721 = ocsERC721.connect(owner); - }); - - it("Owner can remove a file in any position", async function () { - await expect(ocsERC721.addFile("readBytecodeSnippet2.sol", fileAddresses1)).to.not.be.reverted; - expect(await ocsERC721.files()).to.eql([ - "readBytecodeSnippet.sol", - "revertSnippet.sol", - "readBytecodeSnippet2.sol", - ]); - - await expect(ocsERC721.removeFile("readBytecodeSnippet.sol")).to.not.be.reverted; - expect(await ocsERC721.files()).to.eql(["revertSnippet.sol", "readBytecodeSnippet2.sol"]); - - await expect(ocsERC721.removeFile("readBytecodeSnippet2.sol")).to.not.be.reverted; - expect(await ocsERC721.files()).to.eql(["revertSnippet.sol"]); - - await expect(ocsERC721.addFile("readBytecodeSnippet.sol", fileAddresses1)).to.not.be.reverted; - await expect(ocsERC721.addFile("readBytecodeSnippet2.sol", fileAddresses1)).to.not.be.reverted; - expect(await ocsERC721.files()).to.eql([ - "revertSnippet.sol", - "readBytecodeSnippet.sol", - "readBytecodeSnippet2.sol", - ]); - - await expect(ocsERC721.removeFile("readBytecodeSnippet.sol")).to.not.be.reverted; - expect(await ocsERC721.files()).to.eql(["revertSnippet.sol", "readBytecodeSnippet2.sol"]); - }); - }); - - describe("Custom", function () { - let bitRotTest: BitRotGenerativeTest; - let bitRotGenerative: BitRotGenerative; - - before(async () => { - const bitRotGenerativeImpl = await (await ethers.getContractFactory("BitRotGenerative")).deploy(); - bitRotGenerative = BitRotGenerative__factory.connect( - ( - await setupGenerative( - observability.address, - bitRotGenerativeImpl.address, - trustedForwarder.address, - mintManager.address, - owner, - ) - ).address, - owner, - ); - bitRotTest = BitRotGenerativeTest__factory.connect( - (await (await ethers.getContractFactory("BitRotGenerativeTest")).deploy(bitRotGenerative.address)).address, - owner, - ); - await expect(bitRotGenerative.registerMinter(bitRotTest.address)).to.not.be.reverted; - }); - - it("BitRot test passes", async function () { - await expect(bitRotTest.test()).to.not.be.reverted; - }); - }); - - describe("Custom renderer", function () { - let testHighlightRenderer: TestHighlightRenderer; - - beforeEach(async () => { - await expect(generative.registerMinter(owner.address)).to.emit(generative, "MinterRegistrationChanged"); - const TestHighlightRenderer = await ethers.getContractFactory("TestHighlightRenderer"); - - testHighlightRenderer = await TestHighlightRenderer.deploy(); - await testHighlightRenderer.deployed(); - }); - - it("Only owner can set custom renderer config", async function () { - generative = generative.connect(fan1); - await expect( - generative.setCustomRenderer({ - renderer: testHighlightRenderer.address, - processMintDataOnRenderer: true, - }), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - - it("Invalid custom renderer config not allowed", async function () { - generative = generative.connect(owner); - await expect( - generative.setCustomRenderer({ - renderer: ethers.constants.AddressZero, - processMintDataOnRenderer: true, - }), - ).to.be.revertedWith("Invalid input"); - }); - - it("Custom renderer can process mint data, then re-use it on tokenURI query", async function () { - await expect( - generative.setCustomRenderer({ - renderer: testHighlightRenderer.address, - processMintDataOnRenderer: true, - }), - ).to.not.be.reverted; - - await expect(generative.mintOneToOneRecipient(owner.address)).to.not.be.reverted; - await expect(generative.mintAmountToOneRecipient(owner.address, 2)).to.not.be.reverted; - await expect(generative.mintOneToMultipleRecipients([owner.address, fan1.address])).to.not.be.reverted; - await expect(generative.mintSameAmountToMultipleRecipients([owner.address, fan1.address], 2)).to.not.be.reverted; - - const tokenIds = Array.from({ length: 9 }, (_, i) => i + 1); - const seedDetailsPerToken = await Promise.all( - tokenIds.map(async tokenId => { - const seedDetails = await testHighlightRenderer.getSeedDetails(tokenId, 10, generative.address); - expect(seedDetails.blockTimestamp.eq(0)).to.be.false; - - return { ...seedDetails, tokenId }; - }), - ); - - await expect( - generative.setCustomRenderer({ - renderer: testHighlightRenderer.address, - processMintDataOnRenderer: false, - }), - ).to.not.be.reverted; - - await Promise.all( - seedDetailsPerToken.map(async seedDetails => { - const uri = await generative.tokenURI(seedDetails.tokenId); - const predictedUri = await testHighlightRenderer.concatenateSeedDetails( - { - previousBlockHash: seedDetails.previousBlockHash, - blockTimestamp: seedDetails.blockTimestamp, - }, - seedDetails.tokenId, - ); - expect(uri).to.equal(predictedUri); - }), - ); - }); - }); -}); diff --git a/test/ERC721SingleEditionDFSTest.ts b/test/ERC721SingleEditionDFSTest.ts deleted file mode 100644 index 46149c1..0000000 --- a/test/ERC721SingleEditionDFSTest.ts +++ /dev/null @@ -1,551 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { ERC721SingleEditionDFS, MinimalForwarder, MintManager, Observability } from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupSingleEditionDFS, setupSystem } from "./__utils__/helpers"; - -describe("ERC721SingleEdition functionality", () => { - let editions: ERC721SingleEditionDFS; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let observability: Observability; - let mintManager: MintManager; - let trustedForwarder: MinimalForwarder; - let singleEditionImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - singleEditionDFSImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - mintManager = mintManagerProxy; - observability = observabilityInstance; - trustedForwarder = minimalForwarder; - singleEditionImplementation = singleEditionDFSImplementationAddress; - }); - - beforeEach(async () => { - editions = await setupSingleEditionDFS( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - editionsOwner, - 5, - "", - "NM", - ); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(editions.registerMinter(editionsOwner.address)); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - }); - - describe("mintOneToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipient(1, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until max supply", async function () { - for (let i = 1; i <= 5; i++) { - await expect(editions.mintOneToRecipient(0, fan1.address)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await editions.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, i, 1]); - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(i); - expect(res[0][0][3].toNumber()).to.equal(1); - } - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - }); - - describe("mintAmountToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipient(1, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 6)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await editions.ownerOf(j)).to.equal(fan1.address); - expect((await editions.getEditionId(j)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintOneToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipients(1, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, editionsMetadataOwner.address, editionsOwner.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await editions.ownerOf(i)).to.equal(recipient); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - i += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, editionsOwner.address]; - for (let i = 0; i < 2; i++) { - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await editions.ownerOf(i * 2 + j)).to.equal(recipient); - expect((await editions.getEditionId(i * 2 + j)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintAmountToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipients(1, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 1), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (complex variation)", async function () { - editions = await setupSingleEditionDFS( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - editionsOwner, - 8, - "", - "NM", - ); - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - const recipientAddresses = [fan1.address, editionsOwner.address]; - - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 4); - - let j = 0; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2); - expect(await editions.ownerOf(i * 4 + j * 2 + 1)).to.equal(recipient); - expect(await editions.ownerOf(i * 4 + j * 2 + 2)).to.equal(recipient); - expect((await editions.getEditionId(i * 4 + j * 2 + 1)).toNumber()).to.equal(0); - expect((await editions.getEditionId(i * 4 + j * 2 + 2)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["", 8, (i + 1) * 4, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal(""); - expect(res[0][0][1].toNumber()).to.equal(8); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 4); - expect(res[0][0][3].toNumber()).to.equal(1); - expect(res[1][0]).to.equal("editionUri"); - } - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - editions = editions.connect(editionsOwner); - - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(editions.address, "new name", "new symbol", "new contract uri"); - - expect(await editions.name()).to.equal("new name"); - expect(await editions.symbol()).to.equal("new symbol"); - expect(await editions.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - editions = editions.connect(fan1); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - editions = editions.connect(editionsMetadataOwner); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint", async function () { - editions = await setupSingleEditionDFS( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - editionsOwner, - 100, - "name", - "symbol", - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - editions.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - 0, - true, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect( - mintManager.vectorMint721(1, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), editions.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, editionsOwner.address)).to.equal(2); - }); -}); diff --git a/test/ERC721SingleEditionTest.ts b/test/ERC721SingleEditionTest.ts deleted file mode 100644 index 547b328..0000000 --- a/test/ERC721SingleEditionTest.ts +++ /dev/null @@ -1,556 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { ERC721SingleEdition, EditionsMetadataRenderer, MinimalForwarder, MintManager, Observability } from "../types"; -import { Errors } from "./__utils__/data"; -import { DEFAULT_ONCHAIN_MINT_VECTOR, setupSingleEdition, setupSystem } from "./__utils__/helpers"; - -describe("ERC721SingleEdition functionality", () => { - let editions: ERC721SingleEdition; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let observability: Observability; - let mintManager: MintManager; - let trustedForwarder: MinimalForwarder; - let singleEditionImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - singleEditionImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - observability = observabilityInstance; - trustedForwarder = minimalForwarder; - singleEditionImplementation = singleEditionImplementationAddress; - }); - - beforeEach(async () => { - editions = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 5, - "name", - "NM", - ); - }); - - describe("Minting", function () { - beforeEach(async function () { - await expect(editions.registerMinter(editionsOwner.address)); - - expect(await editions.tokenManager(0)).to.eql(ethers.constants.AddressZero); - }); - - describe("mintOneToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipient(1, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Can mint validly up until max supply", async function () { - for (let i = 1; i <= 5; i++) { - await expect(editions.mintOneToRecipient(0, fan1.address)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i); - - expect(await editions.balanceOf(fan1.address)).to.equal(ethers.BigNumber.from(i)); - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, i, 1]); - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(i); - expect(res[0][0][3].toNumber()).to.equal(1); - } - - await expect(editions.mintOneToRecipient(0, fan1.address)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - }); - - describe("mintAmountToRecipient", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipient(1, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 6)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - await expect(editions.mintAmountToRecipient(0, fan1.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal(3); - - for (let i = 1; i <= 3; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipient(0, fan1.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2 * i + 2); - - expect((await editions.balanceOf(fan1.address)).toNumber()).to.equal((i + 1) * 2); - - for (let j = 1; j <= (i + 1) * 2; j++) { - expect(await editions.ownerOf(j)).to.equal(fan1.address); - expect((await editions.getEditionId(j)).toNumber()).to.equal(0); - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintOneToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintOneToRecipients(1, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintOneToRecipients(0, [fan1.address])).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address, fan1.address, fan1.address, fan1.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3); - - await expect(editions.mintOneToRecipients(0, recipientAddresses.slice(0, 3))).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, editionsMetadataOwner.address, editionsOwner.address]; - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsMetadataOwner.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 3); - - let i = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(1); - expect(await editions.ownerOf(i)).to.equal(recipient); - expect((await editions.getEditionId(i)).toNumber()).to.equal(0); - i += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, 3, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal(3); - expect(res[0][0][3].toNumber()).to.equal(1); - }); - - it("Minter can mint validly (running variation)", async function () { - const recipientAddresses = [fan1.address, editionsOwner.address]; - for (let i = 0; i < 2; i++) { - await expect(editions.mintOneToRecipients(0, recipientAddresses)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 2 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 2 + 2); - - let j = 1; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal(i + 1); - expect(await editions.ownerOf(i * 2 + j)).to.equal(recipient); - expect((await editions.getEditionId(i * 2 + j)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 5, (i + 1) * 2, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(5); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 2); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("mintAmountToRecipients", function () { - it("Non minter cannot call", async function () { - editions = editions.connect(fan1); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.NotMinter, - ); - }); - - it("Cannot mint on non-existent edition", async function () { - await expect(editions.mintAmountToRecipients(1, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.EditionDoesNotExist, - ); - }); - - it("Cannot mint if mint frozen", async function () { - await expect(editions.freezeMints()).to.emit(editions, "MintsFrozen"); - - await expect(editions.mintAmountToRecipients(0, [fan1.address], 2)).to.be.revertedWithCustomError( - editions, - Errors.MintFrozen, - ); - }); - - it("Cannot mint more than maxSupply, in multiple variations", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 1), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (simple variation)", async function () { - const recipientAddresses = [fan1.address, fan1.address, fan1.address]; - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)).to.be.revertedWithCustomError( - editions, - Errors.SoldOut, - ); - - await expect(editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect( - editions.mintAmountToRecipients(0, recipientAddresses.slice(0, 2), 2), - ).to.be.revertedWithCustomError(editions, Errors.SoldOut); - }); - - it("Minter can mint validly (complex variation)", async function () { - editions = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 8, - "name", - "NM", - ); - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - const recipientAddresses = [fan1.address, editionsOwner.address]; - - for (let i = 0; i < 2; i++) { - await expect(editions.mintAmountToRecipients(0, recipientAddresses, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, i * 4 + 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, i * 4 + 4); - - let j = 0; - for (const recipient of recipientAddresses) { - expect((await editions.balanceOf(recipient)).toNumber()).to.equal((i + 1) * 2); - expect(await editions.ownerOf(i * 4 + j * 2 + 1)).to.equal(recipient); - expect(await editions.ownerOf(i * 4 + j * 2 + 2)).to.equal(recipient); - expect((await editions.getEditionId(i * 4 + j * 2 + 1)).toNumber()).to.equal(0); - expect((await editions.getEditionId(i * 4 + j * 2 + 2)).toNumber()).to.equal(0); - j += 1; - } - - expect( - (await editions.getEditionDetails(0)).map(x => { - if (typeof x != "string") { - return x.toNumber(); - } else { - return x; - } - }), - ).to.eql(["name", 8, (i + 1) * 4, 1]); - - const res = await editions.getEditionsDetailsAndUri([0]); - expect(res[0][0][0]).to.equal("name"); - expect(res[0][0][1].toNumber()).to.equal(8); - expect(res[0][0][2].toNumber()).to.equal((i + 1) * 4); - expect(res[0][0][3].toNumber()).to.equal(1); - } - }); - }); - - describe("Contract metadata updates", function () { - it("Owner can change the contract level metadata", async function () { - editions = editions.connect(editionsOwner); - - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")) - .to.emit(observability, "ContractMetadataSet") - .withArgs(editions.address, "new name", "new symbol", "new contract uri"); - - expect(await editions.name()).to.equal("new name"); - expect(await editions.symbol()).to.equal("new symbol"); - expect(await editions.contractURI()).to.equal("new contract uri"); - }); - - it("Non-owners cannot change the contract level metadata", async function () { - editions = editions.connect(fan1); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - editions = editions.connect(editionsMetadataOwner); - await expect(editions.setContractMetadata("new name", "new symbol", "new contract uri")).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - }); - - it("Can deploy with direct mint", async function () { - editions = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 100, - "name", - "symbol", - { ...DEFAULT_ONCHAIN_MINT_VECTOR, maxUserClaimableViaVector: 2 }, - ); - - expect((await mintManager.getAbridgedVector(1)).slice(0, 14)).to.deep.equal([ - editions.address, - DEFAULT_ONCHAIN_MINT_VECTOR.startTimestamp, - DEFAULT_ONCHAIN_MINT_VECTOR.endTimestamp, - editionsOwner.address, - DEFAULT_ONCHAIN_MINT_VECTOR.maxTotalClaimableViaVector, - 0, - ethers.constants.AddressZero, - DEFAULT_ONCHAIN_MINT_VECTOR.tokenLimitPerTx, - 2, - DEFAULT_ONCHAIN_MINT_VECTOR.pricePerToken, - 0, - true, - false, - DEFAULT_ONCHAIN_MINT_VECTOR.allowlistRoot, - ]); - - await expect( - mintManager.vectorMint721(1, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ) - .to.emit(mintManager, "NumTokenMint") - .withArgs(ethers.utils.hexZeroPad(ethers.utils.hexlify(1), 32), editions.address, true, 2); - - await expect(mintManager.vectorMint721(1, 1, editionsOwner.address)).to.be.revertedWithCustomError( - mintManager, - "OnchainVectorMintGuardFailed", - ); - - expect(await mintManager.userClaims(1, editionsOwner.address)).to.equal(2); - }); -}); diff --git a/test/ERC721StandardTest.ts b/test/ERC721StandardTest.ts deleted file mode 100644 index 5e6edea..0000000 --- a/test/ERC721StandardTest.ts +++ /dev/null @@ -1,1285 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ConsensualNonTransferableTokenManager, - ERC721Editions, - ERC721General, - ERC721SingleEdition, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - NonTransferableTokenManager, - Observability, - TotalLockedTokenManager, - TransferAndBurnLockedTokenManager, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { - DEFAULT_ONCHAIN_MINT_VECTOR, - setupEditions, - setupGeneral, - setupSingleEdition, - setupSystem, -} from "./__utils__/helpers"; - -const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [["name", "description", "imageUrl", "animationUrl", "externalUrl", "attributes"]], -); - -describe("ERC721 Standard with token managers functionality", () => { - let totalLockedTokenManager: TotalLockedTokenManager; - let transferAndBurnLockedTokenManager: TransferAndBurnLockedTokenManager; - let nonTransferableTokenManager: NonTransferableTokenManager; - let consensualNonTransferableTokenManager: ConsensualNonTransferableTokenManager; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let observability: Observability; - let trustedForwarder: MinimalForwarder; - let editionsImplementation: string; - let singleEditionImplementation: string; - let generalImplementation: string; - - const zeroRoyalty = { - recipientAddress: ethers.constants.AddressZero, - royaltyPercentageBPS: 0, - }; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - - totalLockedTokenManager = await (await ethers.getContractFactory("TotalLockedTokenManager")).deploy(); - transferAndBurnLockedTokenManager = await ( - await ethers.getContractFactory("TransferAndBurnLockedTokenManager") - ).deploy(); - - nonTransferableTokenManager = await (await ethers.getContractFactory("NonTransferableTokenManager")).deploy(); - - consensualNonTransferableTokenManager = await ( - await ethers.getContractFactory("ConsensualNonTransferableTokenManager") - ).deploy(); - }); - - describe("Testing 721 standard on ERC721General", function () { - let general: ERC721General; - - beforeEach(async function () { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - ); - - await expect(general.registerMinter(owner.address)).to.emit(general, "MinterRegistrationChanged"); - - expect(await general.tokenManager(0)).to.eql(ethers.constants.AddressZero); - - await expect(general.mintAmountToOneRecipient(fan1.address, 4)) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - for (let i = 1; i <= 4; i++) { - expect(await general.ownerOf(i)).to.equal(fan1.address); - } - }); - - describe("Without a token manager", function () { - it("safeTransferFrom works as expected", async function () { - general = general.connect(fan1); - - await expect(general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 1) - .to.emit(observability, "Transfer") - .withArgs(general.address, fan1.address, owner.address, 1); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await general.ownerOf(1)).to.equal(owner.address); - expect(await general.ownerOf(2)).to.equal(owner.address); - - await expect(general.approve(owner.address, 3)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(general.approve(owner.address, 4)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 4); - - general = general.connect(owner); - - await expect(general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await general.ownerOf(3)).to.equal(owner.address); - expect(await general.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - general = general.connect(fan1); - - expect(await general.ownerOf(1)).to.equal(fan1.address); - expect(await general.ownerOf(2)).to.equal(fan1.address); - - await expect(general.transferFrom(fan1.address, owner.address, 1)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 1) - .to.emit(observability, "Transfer") - .withArgs(general.address, fan1.address, owner.address, 1); - - expect(await general.ownerOf(1)).to.equal(owner.address); - - await expect(general.approve(owner.address, 2)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 2); - - general = general.connect(owner); - - await expect(general.transferFrom(fan1.address, owner.address, 2)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await general.ownerOf(2)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - general = general.connect(fan1); - - expect(await general.ownerOf(1)).to.equal(fan1.address); - expect(await general.ownerOf(2)).to.equal(fan1.address); - - await expect(general.burn(1)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1) - .to.emit(observability, "Transfer") - .withArgs(general.address, fan1.address, ethers.constants.AddressZero, 1); - - await expect(general.approve(owner.address, 2)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 2); - - general = general.connect(owner); - - await expect(general.burn(2)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - }); - - describe("With a default token manager", function () { - beforeEach(async function () { - await expect(general.setDefaultTokenManager(transferAndBurnLockedTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - for (let i = 1; i <= 4; i++) { - expect(await general.tokenManager(1)).to.equal(transferAndBurnLockedTokenManager.address); - } - }); - - it("safeTransferFrom works as expected", async function () { - general = general.connect(fan1); - - await expect( - general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(general.approve(owner.address, 3)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(general.approve(owner.address, 4)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 4); - - general = general.connect(owner); - - await expect( - general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(general.removeDefaultTokenManager()).to.emit(general, "DefaultTokenManagerChanged"); - - await expect(general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - expect(await general.ownerOf(3)).to.equal(owner.address); - - await expect(general.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await general.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - general = general.connect(fan1); - - await expect(general.transferFrom(fan1.address, owner.address, 1)).to.be.revertedWith("Transfers disallowed"); - - await expect(general.transferFrom(fan1.address, owner.address, 2)).to.be.revertedWith("Transfers disallowed"); - - await expect(general.approve(owner.address, 3)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(general.approve(owner.address, 4)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 4); - - general = general.connect(owner); - - await expect(general.transferFrom(fan1.address, owner.address, 3)).to.be.revertedWith("Transfers disallowed"); - - await expect(general.removeDefaultTokenManager()).to.emit(general, "DefaultTokenManagerChanged"); - - await expect(general.transferFrom(fan1.address, owner.address, 3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - expect(await general.ownerOf(3)).to.equal(owner.address); - - await expect(general.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - await expect(general.transferFrom(fan1.address, owner.address, 4)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await general.ownerOf(4)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - general = general.connect(fan1); - - expect(await general.ownerOf(1)).to.equal(fan1.address); - expect(await general.ownerOf(2)).to.equal(fan1.address); - - await expect(general.burn(1)).to.be.revertedWith("Burns disallowed"); - - await expect(general.approve(owner.address, 1)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 1); - - await expect(general.approve(owner.address, 2)) - .to.emit(general, "Approval") - .withArgs(fan1.address, owner.address, 2); - - general = general.connect(owner); - - await expect(general.removeDefaultTokenManager()).to.emit(general, "DefaultTokenManagerChanged"); - - await expect(general.burn(1)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1); - - await expect(general.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - await expect(general.burn(2)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - }); - - describe("With multiple overwriting token managers per token", function () { - beforeEach(async function () { - await expect(general.setDefaultTokenManager(transferAndBurnLockedTokenManager.address)).to.emit( - general, - "DefaultTokenManagerChanged", - ); - - await expect( - general.setGranularTokenManagers([3, 4], [totalLockedTokenManager.address, totalLockedTokenManager.address]), - ).to.emit(general, "GranularTokenManagersSet"); - - for (let i = 1; i <= 2; i++) { - expect(await general.tokenManager(i)).to.equal(transferAndBurnLockedTokenManager.address); - } - - for (let i = 3; i <= 4; i++) { - expect(await general.tokenManager(i)).to.equal(totalLockedTokenManager.address); - } - }); - - it("safeTransferFrom works as expected", async function () { - general = general.connect(fan1); - - await expect( - general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(general["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - await expect( - general["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await general.ownerOf(3)).to.equal(owner.address); - expect(await general.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - general = general.connect(fan1); - - await expect(general.transferFrom(fan1.address, owner.address, 1)).to.be.revertedWith("Transfers disallowed"); - - await expect(general.transferFrom(fan1.address, owner.address, 2)).to.be.revertedWith("Transfers disallowed"); - - await expect(general.transferFrom(fan1.address, owner.address, 3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - await expect(general.transferFrom(fan1.address, owner.address, 4)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await general.ownerOf(3)).to.equal(owner.address); - expect(await general.ownerOf(4)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - general = general.connect(fan1); - - await expect(general.burn(1)).to.be.revertedWith("Burns disallowed"); - - await expect(general.burn(2)).to.be.revertedWith("Burns disallowed"); - - await expect(general.burn(3)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 3); - - await expect(general.burn(4)) - .to.emit(general, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 4); - }); - - it("ConsensualNonTransferableTokenManager burns properly", async function () { - general = general.connect(owner); - // burn allowed - await expect( - general.setGranularTokenManagers( - [1, 2], - [consensualNonTransferableTokenManager.address, nonTransferableTokenManager.address], - ), - ).to.emit(general, "GranularTokenManagersSet"); - - general = general.connect(fan1); - - await expect(general.burn(1)).to.emit(general, "Transfer"); - - general = general.connect(editionsMetadataOwner); - - await expect(general.burn(2)).to.be.revertedWithCustomError(general, Errors.Unauthorized); - }); - }); - }); - - describe("Testing 721 standard on ERC721Editions", function () { - let editions: ERC721Editions; - - beforeEach(async function () { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - owner, - ); - - await expect(editions.registerMinter(owner.address)).to.emit(editions, "MinterRegistrationChanged"); - - await expect(editions.createEdition(defaultEditionInfo, 4, ethers.constants.AddressZero, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(0, 4, ethers.constants.AddressZero); - - await expect(editions.createEdition(defaultEditionInfo, 2, totalLockedTokenManager.address, zeroRoyalty, "0x")) - .to.emit(editions, "EditionCreated") - .withArgs(1, 2, totalLockedTokenManager.address); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - await expect(editions.mintAmountToRecipient(1, fan1.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 5) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 6); - - for (let i = 1; i <= 6; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - } - }); - - describe("Without a token manager", function () { - it("safeTransferFrom works as expected", async function () { - editions = editions.connect(fan1); - - await expect(editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 1) - .to.emit(observability, "Transfer") - .withArgs(editions.address, fan1.address, owner.address, 1); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await editions.ownerOf(1)).to.equal(owner.address); - expect(await editions.ownerOf(2)).to.equal(owner.address); - - await expect(editions.approve(owner.address, 3)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(editions.approve(owner.address, 4)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 4); - - editions = editions.connect(owner); - - await expect(editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await editions.ownerOf(3)).to.equal(owner.address); - expect(await editions.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - editions = editions.connect(fan1); - - expect(await editions.ownerOf(1)).to.equal(fan1.address); - expect(await editions.ownerOf(2)).to.equal(fan1.address); - - await expect(editions.transferFrom(fan1.address, owner.address, 1)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 1) - .to.emit(observability, "Transfer") - .withArgs(editions.address, fan1.address, owner.address, 1); - - expect(await editions.ownerOf(1)).to.equal(owner.address); - - await expect(editions.approve(owner.address, 2)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 2); - - editions = editions.connect(owner); - - await expect(editions.transferFrom(fan1.address, owner.address, 2)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await editions.ownerOf(2)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - editions = editions.connect(fan1); - - expect(await editions.ownerOf(1)).to.equal(fan1.address); - expect(await editions.ownerOf(2)).to.equal(fan1.address); - - await expect(editions.burn(1)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1) - .to.emit(observability, "Transfer") - .withArgs(editions.address, fan1.address, ethers.constants.AddressZero, 1); - - await expect(editions.approve(owner.address, 2)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 2); - - editions = editions.connect(owner); - - await expect(editions.burn(2)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - }); - - describe("With a default token manager", function () { - beforeEach(async function () { - await expect(await editions.setDefaultTokenManager(transferAndBurnLockedTokenManager.address)).to.emit( - editions, - "DefaultTokenManagerChanged", - ); - - // TODO: use tokenManagerByTokenId - expect(await editions.tokenManager(0)).to.equal(transferAndBurnLockedTokenManager.address); - expect(await editions.tokenManager(1)).to.equal(totalLockedTokenManager.address); - }); - - it("safeTransferFrom works as expected", async function () { - editions = editions.connect(fan1); - - await expect( - editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.approve(owner.address, 3)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(editions.approve(owner.address, 4)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 4); - - editions = editions.connect(owner); - - await expect( - editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.removeDefaultTokenManager()).to.emit(editions, "DefaultTokenManagerChanged"); - - await expect(editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - expect(await editions.ownerOf(3)).to.equal(owner.address); - - await expect(editions.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - editions, - "DefaultTokenManagerChanged", - ); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await editions.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - editions = editions.connect(fan1); - - await expect(editions.transferFrom(fan1.address, owner.address, 1)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.transferFrom(fan1.address, owner.address, 2)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.approve(owner.address, 3)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(editions.approve(owner.address, 4)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 4); - - editions = editions.connect(owner); - - await expect(editions.transferFrom(fan1.address, owner.address, 3)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.removeDefaultTokenManager()).to.emit(editions, "DefaultTokenManagerChanged"); - - await expect(editions.transferFrom(fan1.address, owner.address, 3)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - expect(await editions.ownerOf(3)).to.equal(owner.address); - - await expect(editions.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - editions, - "DefaultTokenManagerChanged", - ); - - await expect(editions.transferFrom(fan1.address, owner.address, 4)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await editions.ownerOf(4)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - editions = editions.connect(fan1); - - expect(await editions.ownerOf(1)).to.equal(fan1.address); - expect(await editions.ownerOf(2)).to.equal(fan1.address); - - await expect(editions.burn(1)).to.be.revertedWith("Burns disallowed"); - - await expect(editions.approve(owner.address, 1)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 1); - - await expect(editions.approve(owner.address, 2)) - .to.emit(editions, "Approval") - .withArgs(fan1.address, owner.address, 2); - - editions = editions.connect(owner); - - await expect(editions.removeDefaultTokenManager()).to.emit(editions, "DefaultTokenManagerChanged"); - - await expect(editions.burn(1)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1); - - await expect(editions.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - editions, - "DefaultTokenManagerChanged", - ); - - await expect(editions.burn(2)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - }); - - describe("With multiple overwriting token managers per edition", function () { - beforeEach(async function () { - await expect(await editions.setDefaultTokenManager(transferAndBurnLockedTokenManager.address)).to.emit( - editions, - "DefaultTokenManagerChanged", - ); - - // TODO: use tokenManagerByTokenId - expect(await editions.tokenManager(0)).to.equal(transferAndBurnLockedTokenManager.address); - expect(await editions.tokenManager(1)).to.equal(totalLockedTokenManager.address); - }); - - it("safeTransferFrom works as expected", async function () { - editions = editions.connect(fan1); - - await expect( - editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 4), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(editions["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 5)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 5); - - await expect( - editions["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 6, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 6); - - expect(await editions.ownerOf(5)).to.equal(owner.address); - expect(await editions.ownerOf(6)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - editions = editions.connect(fan1); - - await expect(editions.transferFrom(fan1.address, owner.address, 1)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.transferFrom(fan1.address, owner.address, 2)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.transferFrom(fan1.address, owner.address, 3)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.transferFrom(fan1.address, owner.address, 4)).to.be.revertedWith("Transfers disallowed"); - - await expect(editions.transferFrom(fan1.address, owner.address, 5)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 5); - - await expect(editions.transferFrom(fan1.address, owner.address, 6)) - .to.emit(editions, "Transfer") - .withArgs(fan1.address, owner.address, 6); - - expect(await editions.ownerOf(5)).to.equal(owner.address); - expect(await editions.ownerOf(6)).to.equal(owner.address); - }); - }); - }); - - describe("Testing 721 standard on ERC721SingleEdition", function () { - let singleEdition: ERC721SingleEdition; - - beforeEach(async function () { - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - owner, - 4, - "name", - "SYM", - ); - - await expect(singleEdition.registerMinter(owner.address)).to.emit(singleEdition, "MinterRegistrationChanged"); - - await expect(singleEdition.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - for (let i = 1; i <= 4; i++) { - expect(await singleEdition.ownerOf(i)).to.equal(fan1.address); - } - }); - - describe("Without a token manager", function () { - it("safeTransferFrom works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - await expect(singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 1); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await singleEdition.ownerOf(1)).to.equal(owner.address); - expect(await singleEdition.ownerOf(2)).to.equal(owner.address); - - await expect(singleEdition.approve(owner.address, 3)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(singleEdition.approve(owner.address, 4)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 4); - - singleEdition = singleEdition.connect(owner); - - await expect(singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await singleEdition.ownerOf(3)).to.equal(owner.address); - expect(await singleEdition.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - expect(await singleEdition.ownerOf(1)).to.equal(fan1.address); - expect(await singleEdition.ownerOf(2)).to.equal(fan1.address); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 1)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 1) - .to.emit(observability, "Transfer") - .withArgs(singleEdition.address, fan1.address, owner.address, 1); - - expect(await singleEdition.ownerOf(1)).to.equal(owner.address); - - await expect(singleEdition.approve(owner.address, 2)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 2); - - singleEdition = singleEdition.connect(owner); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 2)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 2); - - expect(await singleEdition.ownerOf(2)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - expect(await singleEdition.ownerOf(1)).to.equal(fan1.address); - expect(await singleEdition.ownerOf(2)).to.equal(fan1.address); - - await expect(singleEdition.burn(1)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1) - .to.emit(observability, "Transfer") - .withArgs(singleEdition.address, fan1.address, ethers.constants.AddressZero, 1); - - await expect(singleEdition.approve(owner.address, 2)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 2); - - singleEdition = singleEdition.connect(owner); - - await expect(singleEdition.burn(2)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - }); - - describe("With a default token manager", function () { - beforeEach(async function () { - await expect(await singleEdition.setDefaultTokenManager(transferAndBurnLockedTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - // TODO: use tokenManagerByTokenId - expect(await singleEdition.tokenManager(0)).to.equal(transferAndBurnLockedTokenManager.address); - }); - - it("safeTransferFrom works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 1), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 2, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(singleEdition.approve(owner.address, 3)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(singleEdition.approve(owner.address, 4)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 4); - - singleEdition = singleEdition.connect(owner); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(singleEdition.removeDefaultTokenManager()).to.emit(singleEdition, "DefaultTokenManagerChanged"); - - await expect(singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, 3)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 3) - .to.emit(observability, "Transfer") - .withArgs(singleEdition.address, fan1.address, owner.address, 3); - - expect(await singleEdition.ownerOf(3)).to.equal(owner.address); - - await expect(singleEdition.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - 4, - ethers.utils.arrayify("0x"), - ), - ) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await singleEdition.ownerOf(4)).to.equal(owner.address); - }); - - it("transferFrom works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 1)).to.be.revertedWith( - "Transfers disallowed", - ); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 2)).to.be.revertedWith( - "Transfers disallowed", - ); - - await expect(singleEdition.approve(owner.address, 3)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 3); - - await expect(singleEdition.approve(owner.address, 4)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 4); - - singleEdition = singleEdition.connect(owner); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 3)).to.be.revertedWith( - "Transfers disallowed", - ); - - await expect(singleEdition.removeDefaultTokenManager()).to.emit(singleEdition, "DefaultTokenManagerChanged"); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 3)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 3); - - expect(await singleEdition.ownerOf(3)).to.equal(owner.address); - - await expect(singleEdition.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, 4)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, owner.address, 4); - - expect(await singleEdition.ownerOf(4)).to.equal(owner.address); - }); - - it("burn works as expected", async function () { - singleEdition = singleEdition.connect(fan1); - - expect(await singleEdition.ownerOf(1)).to.equal(fan1.address); - expect(await singleEdition.ownerOf(2)).to.equal(fan1.address); - - await expect(singleEdition.burn(1)).to.be.revertedWith("Burns disallowed"); - - await expect(singleEdition.approve(owner.address, 1)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 1); - - await expect(singleEdition.approve(owner.address, 2)) - .to.emit(singleEdition, "Approval") - .withArgs(fan1.address, owner.address, 2); - - singleEdition = singleEdition.connect(owner); - - await expect(singleEdition.removeDefaultTokenManager()).to.emit(singleEdition, "DefaultTokenManagerChanged"); - - await expect(singleEdition.burn(1)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 1); - - await expect(singleEdition.setDefaultTokenManager(totalLockedTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - await expect(singleEdition.burn(2)) - .to.emit(singleEdition, "Transfer") - .withArgs(fan1.address, ethers.constants.AddressZero, 2); - }); - - it("NonTransferable token manager works properly", async function () { - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - owner, - 4, - "name", - "SYM", - null, - null, - false, - nonTransferableTokenManager.address, - ); - - await expect(singleEdition.registerMinter(owner.address)).to.emit(singleEdition, "MinterRegistrationChanged"); - - await expect(singleEdition.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - singleEdition = singleEdition.connect(fan1); - for (let i = 1; i <= 4; i++) { - expect(await singleEdition.ownerOf(i)).to.equal(fan1.address); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, i), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - i, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(singleEdition.transferFrom(fan1.address, owner.address, i)).to.be.revertedWith( - "Transfers disallowed", - ); - } - }); - - it("ConsensualNonTransferableTokenManager token manager works properly", async function () { - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - owner, - 4, - "name", - "SYM", - null, - null, - false, - consensualNonTransferableTokenManager.address, - ); - - await expect(singleEdition.registerMinter(owner.address)).to.emit(singleEdition, "MinterRegistrationChanged"); - - await expect(singleEdition.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - singleEdition = singleEdition.connect(fan1); - for (let i = 1; i <= 4; i++) { - expect(await singleEdition.ownerOf(i)).to.equal(fan1.address); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, editionsMetadataOwner.address, i), - ).to.be.revertedWith("Transfers disallowed"); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - editionsMetadataOwner.address, - i, - ethers.utils.arrayify("0x"), - ), - ).to.be.revertedWith("Transfers disallowed"); - - await expect(singleEdition.transferFrom(fan1.address, editionsMetadataOwner.address, i)).to.be.revertedWith( - "Transfers disallowed", - ); - - if (i == 1 || i == 2) { - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, owner.address, i), - ).to.emit(singleEdition, "Transfer"); - } else if (i == 3) { - await expect( - singleEdition["safeTransferFrom(address,address,uint256,bytes)"]( - fan1.address, - owner.address, - i, - ethers.utils.arrayify("0x"), - ), - ).to.emit(singleEdition, "Transfer"); - } else { - await expect(singleEdition.transferFrom(fan1.address, owner.address, i)).to.emit(singleEdition, "Transfer"); - } - } - }); - }); - }); -}); diff --git a/test/EditionsMetadataRendererTest.ts b/test/EditionsMetadataRendererTest.ts deleted file mode 100644 index e598950..0000000 --- a/test/EditionsMetadataRendererTest.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - ERC721General, - ERC721SingleEdition, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; - -/* eslint-disable prefer-const */ -import { - DEFAULT_ONCHAIN_MINT_VECTOR, - generateClaim, - setupGeneral, - setupMultipleEdition, - setupSingleEdition, - setupSystem, -} from "./__utils__/helpers"; - -//TODO: Token URI - -describe("Editions Metadata Renderer", () => { - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - generalOwner: SignerWithAddress, - editionsOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - fan1: SignerWithAddress; - - let mintFeeWei = ethers.BigNumber.from("800000000000000"); - - before(async () => { - [ - initialPlatformExecutor, - mintManagerOwner, - editionsMetadataOwner, - platformPaymentAddress, - generalOwner, - editionsOwner, - fan1, - ] = await ethers.getSigners(); - }); - - async function setup() { - const size = 10, - name = "Test 1", - symbol = "T1"; - - let auctionManager: AuctionManager; - let mintManager: MintManager; - let emr: EditionsMetadataRenderer; - let minimalForwarder: MinimalForwarder; - let observability: Observability; - - const { - mintManagerProxy, - auctionManagerProxy, - emrProxy, - observability: observabilityInstance, - minimalForwarder: minimalForwarderContract, - generalImplementationAddress, - editionsImplementationAddress, - singleEditionImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - auctionManager = auctionManagerProxy; - mintManager = mintManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - minimalForwarder = minimalForwarderContract; - - const general: ERC721General = await setupGeneral( - observability.address, - generalImplementationAddress, - minimalForwarder.address, - emr.address, - generalOwner, - null, - null, - false, - false, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - name, - symbol, - ); - const singleEdition: ERC721SingleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementationAddress, - mintManager.address, - minimalForwarder.address, - emr.address, - editionsOwner, - size, - name, - symbol, - ); - const editions: ERC721Editions = await setupMultipleEdition( - observability.address, - editionsImplementationAddress, - mintManager.address, - auctionManager.address, - minimalForwarder.address, - emr.address, - editionsOwner, - size, - name, - symbol, - ); - - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - ); - - const ownerOnlyTokenManager = await (await ethers.getContractFactory("OwnerOnlyTokenManager")).deploy(); - await ownerOnlyTokenManager.deployed(); - - const checkersTokenManager = await ( - await ethers.getContractFactory("CheckerboardTokenManager") - ).deploy(initialPlatformExecutor.address, emr.address, "allowed 1"); - await checkersTokenManager.deployed(); - - return { - auctionManager, - mintManager, - emr, - minimalForwarder, - general, - singleEdition, - editions, - ownerOnlyTokenManager, - checkersTokenManager, - name, - symbol, - size, - singleEditionClaim: { claim, signature }, - }; - } - - it("Should generate correct uri for editions uri", async () => { - let { emr, singleEdition } = await setup(); - const fakeContractSigner = await ethers.getSigner(singleEdition.address); - emr = emr.connect(fakeContractSigner); - const uri = (await emr.editionURI(0)).replace("data:application/json;base64,", ""); - const buff = Buffer.from(uri, "base64"); - const metadata: Record = JSON.parse(buff.toString()); - expect(Object.keys(metadata)).to.include.members(["name", "size", "description", "external_url", "attributes"]); - }); - it("Should generate correct uri for token uri", async () => { - let { emr, mintManager, singleEdition, singleEditionClaim } = await setup(); - const mintManagerForFan1 = mintManager.connect(fan1); - const tx = await mintManagerForFan1.gatedMintEdition721( - singleEditionClaim.claim, - singleEditionClaim.signature, - fan1.address, - { value: mintFeeWei.mul(singleEditionClaim.claim.numTokensToMint) }, - ); - await tx.wait(); - const emrImp = await ethers.getSigner(singleEdition.address); - emr = emr.connect(emrImp); - const uri = (await emr.tokenURI(1)).replace("data:application/json;base64,", ""); - const buff = Buffer.from(uri, "base64"); - const metadata: Record = JSON.parse(buff.toString()); - expect(Object.keys(metadata)).to.include.members(["name", "description", "external_url", "attributes"]); - }); - it("Should return correct token edition info", async () => { - const { emr, singleEdition, name } = await setup(); - const editionInfo = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo.name).to.be.equal(name); - expect(editionInfo.description).to.be.equal(""); - expect(editionInfo.imageUrl).to.be.equal(""); - expect(editionInfo.animationUrl).to.be.equal(""); - expect(editionInfo.attributes).to.be.equal(""); - }); - - it("Updating metadata fields without a token manager is restricted to the owner", async () => { - const { emr, singleEdition } = await setup(); - - await expect(emr.updateName(singleEdition.address, 0, "new name")) - .to.emit(emr, "NameUpdated") - .withArgs(singleEdition.address, 0, "new name"); - - await expect(emr.updateDescription(singleEdition.address, 0, "new description")) - .to.emit(emr, "DescriptionUpdated") - .withArgs(singleEdition.address, 0, "new description"); - - await expect(emr.updateImageUrl(singleEdition.address, 0, "new image url")) - .to.emit(emr, "ImageUrlUpdated") - .withArgs(singleEdition.address, 0, "new image url"); - - await expect(emr.updateAnimationUrl(singleEdition.address, 0, "new animation url")) - .to.emit(emr, "AnimationUrlUpdated") - .withArgs(singleEdition.address, 0, "new animation url"); - - await expect(emr.updateExternalUrl(singleEdition.address, 0, "new external url")) - .to.emit(emr, "ExternalUrlUpdated") - .withArgs(singleEdition.address, 0, "new external url"); - - await expect(emr.updateAttributes(singleEdition.address, 0, "new attributes")) - .to.emit(emr, "AttributesUpdated") - .withArgs(singleEdition.address, 0, "new attributes"); - - const editionInfo = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo.name).to.be.equal("new name"); - expect(editionInfo.description).to.be.equal("new description"); - expect(editionInfo.imageUrl).to.be.equal("new image url"); - expect(editionInfo.animationUrl).to.be.equal("new animation url"); - expect(editionInfo.externalUrl).to.be.equal("new external url"); - expect(editionInfo.attributes).to.be.equal("new attributes"); - }); - - it("Updating metadata with a non-conforming (to ITokenManagerEditions) token manager uses the ITokenManager standard", async () => { - let { emr, singleEdition, ownerOnlyTokenManager } = await setup(); - emr = emr.connect(editionsMetadataOwner); - await expect(singleEdition.setDefaultTokenManager(ownerOnlyTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - emr = emr.connect(editionsOwner); - await expect(emr.updateName(singleEdition.address, 0, "new name")).to.be.revertedWith("Can't update metadata"); - - emr = emr.connect(editionsMetadataOwner); - await expect(emr.updateName(singleEdition.address, 0, "new name")).to.emit(emr, "NameUpdated"); - }); - - it("Updating metadata with a checkerboard token manager works as expected", async () => { - let { emr, singleEdition, mintManager, checkersTokenManager, singleEditionClaim } = await setup(); - emr = emr.connect(editionsMetadataOwner); - await expect(singleEdition.setDefaultTokenManager(checkersTokenManager.address)).to.emit( - singleEdition, - "DefaultTokenManagerChanged", - ); - - // editions metadata owner not allowed + name update not allowed - await expect(emr.updateName(singleEdition.address, 0, "allowed 1")).to.be.revertedWith("Can't update metadata"); - - // name update not allowed - emr = emr.connect(editionsOwner); - await expect(emr.updateName(singleEdition.address, 0, "allowed 1")).to.be.revertedWith("Can't update metadata"); - - // editions metadata owner not alowed - emr = emr.connect(editionsMetadataOwner); - await expect(emr.updateImageUrl(singleEdition.address, 0, "new image")).to.be.revertedWith("Can't update metadata"); - - // invalid name update not allowed - emr = emr.connect(editionsOwner); - await expect(emr.updateImageUrl(singleEdition.address, 0, "new image")).to.be.revertedWith("Can't update metadata"); - - await expect(emr.updateImageUrl(singleEdition.address, 0, "allowed 1")).to.emit(emr, "ImageUrlUpdated"); - - const editionInfo = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo.imageUrl).to.be.equal("allowed 1"); - - checkersTokenManager = checkersTokenManager.connect(editionsOwner); - // this is working as expected - //await expect(checkersTokenManager.setAllowedMoveForTheDay("allowed 2")) - // .to.be.revertedWith("Ownable: caller is not the owner") - - checkersTokenManager = checkersTokenManager.connect(initialPlatformExecutor); - await expect(checkersTokenManager.setAllowedMoveForTheDay("allowed 2")) - .to.emit(checkersTokenManager, "SetAllowedMove") - .withArgs("allowed 2"); - - emr = emr.connect(fan1); - await expect(emr.updateImageUrl(singleEdition.address, 0, "allowed 2")).to.be.revertedWith("Can't update metadata"); - - const mintManagerForFan1 = mintManager.connect(fan1); - const tx = await mintManagerForFan1.gatedMintEdition721( - singleEditionClaim.claim, - singleEditionClaim.signature, - fan1.address, - { value: mintFeeWei.mul(singleEditionClaim.claim.numTokensToMint) }, - ); - await tx.wait(); - - // now fan holds nft from single edition - await expect(emr.updateImageUrl(singleEdition.address, 0, "allowed 2")).to.emit(emr, "ImageUrlUpdated"); - }); - - it("Can update all metadata fields at once", async () => { - const { emr, singleEdition } = await setup(); - - const newEditionMetadata = { - name: "new name", - description: "new description", - imageUrl: "new image url", - animationUrl: "new animation url", - externalUrl: "new external url", - attributes: "new attributes", - }; - - await expect(emr.updateMetadata(singleEdition.address, 0, newEditionMetadata, [1, 3])) - .to.emit(emr, "NameUpdated") - .withArgs(singleEdition.address, 0, "new name") - .to.emit(emr, "ImageUrlUpdated") - .withArgs(singleEdition.address, 0, "new image url"); - - const editionInfo = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo.name).to.be.equal("new name"); - expect(editionInfo.imageUrl).to.be.equal("new image url"); - expect(editionInfo.description).to.be.equal(""); - expect(editionInfo.animationUrl).to.be.equal(""); - expect(editionInfo.externalUrl).to.be.equal(""); - expect(editionInfo.attributes).to.be.equal(""); - - await expect(emr.updateMetadata(singleEdition.address, 0, newEditionMetadata, [2, 4])) - .to.emit(emr, "DescriptionUpdated") - .withArgs(singleEdition.address, 0, "new description") - .to.emit(emr, "AnimationUrlUpdated") - .withArgs(singleEdition.address, 0, "new animation url"); - - const editionInfo2 = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo2.name).to.be.equal("new name"); - expect(editionInfo2.imageUrl).to.be.equal("new image url"); - expect(editionInfo2.description).to.be.equal("new description"); - expect(editionInfo2.animationUrl).to.be.equal("new animation url"); - expect(editionInfo2.externalUrl).to.be.equal(""); - expect(editionInfo2.attributes).to.be.equal(""); - - await expect(emr.updateMetadata(singleEdition.address, 0, newEditionMetadata, [5, 6])) - .to.emit(emr, "ExternalUrlUpdated") - .withArgs(singleEdition.address, 0, "new external url") - .to.emit(emr, "AttributesUpdated") - .withArgs(singleEdition.address, 0, "new attributes"); - - const editionInfo3 = await emr.editionInfo(singleEdition.address, 0); - expect(editionInfo3.name).to.be.equal("new name"); - expect(editionInfo3.imageUrl).to.be.equal("new image url"); - expect(editionInfo3.description).to.be.equal("new description"); - expect(editionInfo3.animationUrl).to.be.equal("new animation url"); - expect(editionInfo3.externalUrl).to.be.equal("new external url"); - expect(editionInfo3.attributes).to.be.equal("new attributes"); - }); -}); diff --git a/test/MarketplaceFiltererTest.ts b/test/MarketplaceFiltererTest.ts deleted file mode 100644 index 8fa8162..0000000 --- a/test/MarketplaceFiltererTest.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - ERC721EditionsDFS, - ERC721SingleEdition, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - Observability, - OperatorFilterRegistry, -} from "../types"; -import { Errors } from "./__utils__/data"; -import { setupMultipleEdition, setupMultipleEditionDFS, setupSingleEdition, setupSystem } from "./__utils__/helpers"; - -describe("MarketplaceFilterer functionality", () => { - let singleEdition: ERC721SingleEdition; - let operatorRegistry: OperatorFilterRegistry; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let observability: Observability; - let trustedForwarder: MinimalForwarder; - let singleEditionImplementation: string; - let editionsImplementation: string; - let editionsDFSImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, editionsOwner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - singleEditionImplementationAddress, - editionsImplementationAddress, - editionsDFSImplementationAddress, - auctionManagerProxy, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - trustedForwarder = minimalForwarder; - singleEditionImplementation = singleEditionImplementationAddress; - editionsImplementation = editionsImplementationAddress; - editionsDFSImplementation = editionsDFSImplementationAddress; - - operatorRegistry = await (await ethers.getContractFactory("OperatorFilterRegistry")).deploy(); - }); - - beforeEach(async () => { - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 10, - "Test 1", - "T1", - null, - true, - ); - - expect(await singleEdition.operatorFiltererRegistry()).to.equal("0x000000000000AAeB6D7670E522A718067333cd4E"); - - const tx = await singleEdition.removeMarketplaceFiltererRegistryAndUnregister(); - await tx.wait(); - - expect(await singleEdition.operatorFiltererRegistry()).to.equal(ethers.constants.AddressZero); - - // setup some minted nfts to transfer later - await expect(singleEdition.registerMinter(editionsOwner.address)).to.emit( - singleEdition, - "MinterRegistrationChanged", - ); - - await expect(singleEdition.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(singleEdition, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - for (let i = 1; i <= 4; i++) { - expect(await singleEdition.ownerOf(i)).to.equal(fan1.address); - } - }); - - describe("Registering/unregistering filterer registry", async function () { - it("Registering/unregistering filterer registry sets data properly", async function () { - // register and subscribe should fail due to non registration of default registrant - await expect( - singleEdition.setCustomMarketplaceFiltererRegistryAndRegisterDefaultSubscription(operatorRegistry.address), - ).to.emit(operatorRegistry, "RegistrationUpdated"); - }); - - it("Existence of filterer restricts/unrestricts transfers/approvals", async function () { - // manually register subscription and seed it direclty via registry restricted address - operatorRegistry = operatorRegistry.connect(initialPlatformExecutor); - await expect(operatorRegistry.register(initialPlatformExecutor.address)) - .to.emit(operatorRegistry, "RegistrationUpdated") - .withArgs(initialPlatformExecutor.address, true); - - expect(await operatorRegistry.isRegistered(initialPlatformExecutor.address)).to.equal(true); - - await expect(operatorRegistry.updateOperator(initialPlatformExecutor.address, editionsOwner.address, true)) - .to.emit(operatorRegistry, "OperatorUpdated") - .withArgs(initialPlatformExecutor.address, editionsOwner.address, true); - - // register and subscribe collection manually to initialPlatformExecutor subscription - singleEdition = singleEdition.connect(editionsOwner); - await expect( - singleEdition.setCustomMarketplaceFiltererRegistryAndRegisterDefaultSubscription(operatorRegistry.address), - ) - .to.emit(operatorRegistry, "RegistrationUpdated") - .to.emit(operatorRegistry, "SubscriptionUpdated"); - - expect(await operatorRegistry.subscriptionOf(singleEdition.address)).to.equal( - "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6", - ); - - operatorRegistry = operatorRegistry.connect(editionsOwner); - await expect(operatorRegistry.subscribe(singleEdition.address, initialPlatformExecutor.address)).to.emit( - operatorRegistry, - "SubscriptionUpdated", - ); - - expect(await operatorRegistry.subscriptionOf(singleEdition.address)).to.equal(initialPlatformExecutor.address); - - // all 4 transfer / approval functions should fail for editions owner now, and succeed for others - - singleEdition = singleEdition.connect(fan1); - await expect(singleEdition.approve(editionsOwner.address, 1)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(singleEdition.setApprovalForAll(editionsOwner.address, true)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(singleEdition.approve(editionsMetadataOwner.address, 1)).to.emit(singleEdition, "Approval"); - - await expect(singleEdition.setApprovalForAll(editionsMetadataOwner.address, true)).to.emit( - singleEdition, - "ApprovalForAll", - ); - - singleEdition = singleEdition.connect(editionsOwner); - await expect(singleEdition.transferFrom(fan1.address, editionsOwner.address, 1)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, editionsOwner.address, 1), - ).to.be.revertedWithCustomError(operatorRegistry, "AddressFiltered"); - - singleEdition = singleEdition.connect(editionsMetadataOwner); - await expect(singleEdition.transferFrom(fan1.address, editionsOwner.address, 1)).to.emit( - singleEdition, - "Transfer", - ); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, editionsOwner.address, 2), - ).to.emit(singleEdition, "Transfer"); - - // remove / unregister (validate event as well) - singleEdition = singleEdition.connect(editionsOwner); - await expect(singleEdition.removeMarketplaceFiltererRegistryAndUnregister()) - .to.emit(operatorRegistry, "RegistrationUpdated") - .withArgs(singleEdition.address, false); - - // try all 4 transfer / approval with formerly restricted address - singleEdition = singleEdition.connect(fan1); - await expect(singleEdition.approve(editionsOwner.address, 3)).to.emit(singleEdition, "Approval"); - - await expect(singleEdition.setApprovalForAll(editionsOwner.address, true)).to.emit( - singleEdition, - "ApprovalForAll", - ); - - singleEdition = singleEdition.connect(editionsOwner); - await expect(singleEdition.transferFrom(fan1.address, editionsOwner.address, 1)).to.be.revertedWithCustomError( - singleEdition, - Errors.TransferFromIncorrectOwner, - ); - - await expect( - singleEdition["safeTransferFrom(address,address,uint256)"](fan1.address, editionsOwner.address, 1), - ).to.be.revertedWithCustomError(singleEdition, Errors.TransferFromIncorrectOwner); - }); - }); - - describe("MarketplaceFiltererAbridged", function () { - let editions: ERC721Editions; - let editionsDFS: ERC721EditionsDFS; - - beforeEach(async () => { - editions = await setupMultipleEdition( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 10, - "Test 1", - "T1", - ); - - editionsDFS = await setupMultipleEditionDFS( - observability.address, - editionsDFSImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - editionsOwner, - 10, - "Test 2", - ); - - // setup some minted nfts to transfer later - await expect(editions.registerMinter(editionsOwner.address)).to.emit(editions, "MinterRegistrationChanged"); - - await expect(editions.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - for (let i = 1; i <= 4; i++) { - expect(await editions.ownerOf(i)).to.equal(fan1.address); - } - - await expect(editionsDFS.registerMinter(editionsOwner.address)).to.emit(editionsDFS, "MinterRegistrationChanged"); - - await expect(editionsDFS.mintAmountToRecipient(0, fan1.address, 4)) - .to.emit(editionsDFS, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 1) - .to.emit(editionsDFS, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 2) - .to.emit(editionsDFS, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 3) - .to.emit(editionsDFS, "Transfer") - .withArgs(ethers.constants.AddressZero, fan1.address, 4); - - for (let i = 1; i <= 4; i++) { - expect(await editionsDFS.ownerOf(i)).to.equal(fan1.address); - } - }); - - it("Registering/unregistering filterer registry sets data properly", async function () { - await expect( - editions.setRegistryAndSubscription(operatorRegistry.address, "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6"), - ).to.emit(operatorRegistry, "RegistrationUpdated"); - - await expect( - editionsDFS.setRegistryAndSubscription(operatorRegistry.address, "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6"), - ).to.emit(operatorRegistry, "RegistrationUpdated"); - - expect(await editions.operatorFiltererRegistry()).to.equal(operatorRegistry.address); - expect(await editionsDFS.operatorFiltererRegistry()).to.equal(operatorRegistry.address); - - await expect(editions.setRegistryAndSubscription(ethers.constants.AddressZero, ethers.constants.AddressZero)).to - .not.be.reverted; - await expect(editionsDFS.setRegistryAndSubscription(ethers.constants.AddressZero, ethers.constants.AddressZero)) - .to.not.be.reverted; - expect(await editions.operatorFiltererRegistry()).to.equal(ethers.constants.AddressZero); - expect(await editionsDFS.operatorFiltererRegistry()).to.equal(ethers.constants.AddressZero); - }); - - it("Existence of filterer restricts/unrestricts transfers/approvals (MultipleEditions)", async function () { - expect(await operatorRegistry.isRegistered(initialPlatformExecutor.address)).to.equal(true); - - // register and subscribe collection manually to initialPlatformExecutor subscription - editions = editions.connect(editionsOwner); - await expect( - editions.setRegistryAndSubscription(operatorRegistry.address, "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6"), - ) - .to.emit(operatorRegistry, "RegistrationUpdated") - .to.emit(operatorRegistry, "SubscriptionUpdated"); - - expect(await operatorRegistry.subscriptionOf(editions.address)).to.equal( - "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6", - ); - - operatorRegistry = operatorRegistry.connect(editionsOwner); - await expect(operatorRegistry.subscribe(editions.address, initialPlatformExecutor.address)).to.emit( - operatorRegistry, - "SubscriptionUpdated", - ); - - expect(await operatorRegistry.subscriptionOf(editions.address)).to.equal(initialPlatformExecutor.address); - - // all 4 transfer / approval functions should fail for editions owner now, and succeed for others - // TODO: transfer functions - - editions = editions.connect(fan1); - await expect(editions.approve(editionsOwner.address, 1)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(editions.setApprovalForAll(editionsOwner.address, true)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(editions.approve(editionsMetadataOwner.address, 1)).to.emit(editions, "Approval"); - - await expect(editions.setApprovalForAll(editionsMetadataOwner.address, true)).to.emit(editions, "ApprovalForAll"); - }); - - it("Existence of filterer restricts/unrestricts transfers/approvals (MultipleEditionsDFS)", async function () { - expect(await operatorRegistry.isRegistered(initialPlatformExecutor.address)).to.equal(true); - - // register and subscribe collection manually to initialPlatformExecutor subscription - editionsDFS = editionsDFS.connect(editionsOwner); - await expect( - editionsDFS.setRegistryAndSubscription(operatorRegistry.address, "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6"), - ) - .to.emit(operatorRegistry, "RegistrationUpdated") - .to.emit(operatorRegistry, "SubscriptionUpdated"); - - expect(await operatorRegistry.subscriptionOf(editionsDFS.address)).to.equal( - "0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6", - ); - - operatorRegistry = operatorRegistry.connect(editionsOwner); - await expect(operatorRegistry.subscribe(editionsDFS.address, initialPlatformExecutor.address)).to.emit( - operatorRegistry, - "SubscriptionUpdated", - ); - - expect(await operatorRegistry.subscriptionOf(editionsDFS.address)).to.equal(initialPlatformExecutor.address); - - // all 4 transfer / approval functions should fail for editionsDFS owner now, and succeed for others - // TODO: transfer functions - - editionsDFS = editionsDFS.connect(fan1); - await expect(editionsDFS.approve(editionsOwner.address, 1)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(editionsDFS.setApprovalForAll(editionsOwner.address, true)).to.be.revertedWithCustomError( - operatorRegistry, - "AddressFiltered", - ); - - await expect(editionsDFS.approve(editionsMetadataOwner.address, 1)).to.emit(editionsDFS, "Approval"); - - await expect(editionsDFS.setApprovalForAll(editionsMetadataOwner.address, true)).to.emit( - editionsDFS, - "ApprovalForAll", - ); - }); - }); -}); diff --git a/test/MechanicMintVectorsTest.ts b/test/MechanicMintVectorsTest.ts deleted file mode 100644 index 0131ffa..0000000 --- a/test/MechanicMintVectorsTest.ts +++ /dev/null @@ -1,1139 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - DiscreteDutchAuctionMechanic, - ERC721EditionsDFS, - ERC721General, - ERC721Generative, - ERC721SingleEditionDFS, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; -import { SAMPLE_DA_VECTOR } from "./__utils__/data"; -import { Errors } from "./__utils__/data"; -import { - dutchAuctionUpdateArgs, - encodeDAVectorData, - encodeMechanicVectorData, - produceMechanicVectorId, - setupGeneral, - setupGenerative, - setupMultipleEditionDFS, - setupSingleEditionDFS, - setupSystem, -} from "./__utils__/helpers"; - -describe("Mechanic mint vectors", () => { - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - generalOwner: SignerWithAddress, - fan1: SignerWithAddress; - - let observability: Observability; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let dutchAuction: DiscreteDutchAuctionMechanic; - let editionsDFSImplementation: string; - let singleEditionDFSImplementation: string; - let generalImplementation: string; - let generativeImplementation: string; - - let generative: ERC721Generative; - let editions: ERC721EditionsDFS; - let singleEdition: ERC721SingleEditionDFS; - let general: ERC721General; - - let generativeVectorId: string; - let editionsVectorId: string; - let singleEditionVectorId: string; - let generalVectorId: string; - - const prices1 = ["0.001", "0.0001"]; - const prices2 = ["100", "0.189", "0.09", "0.08", "0.07", "0.06", "0.05", "0.00001"]; - const prices3 = ["0.00000000001", "0.0000000000000001"]; - const prices4: string[] = []; - - const mintFeeWei = ethers.BigNumber.from("800000000000000"); - - before(async () => { - [ - initialPlatformExecutor, - mintManagerOwner, - editionsMetadataOwner, - platformPaymentAddress, - editionsOwner, - generalOwner, - fan1, - ] = await ethers.getSigners(); - - const { - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsDFSImplementationAddress, - singleEditionDFSImplementationAddress, - generalImplementationAddress, - generativeImplementationAddress, - daMechanic, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - editionsDFSImplementation = editionsDFSImplementationAddress; - singleEditionDFSImplementation = singleEditionDFSImplementationAddress; - generalImplementation = generalImplementationAddress; - generativeImplementation = generativeImplementationAddress; - dutchAuction = daMechanic; - }); - - // in this, validate that contract deployments with mechanic vector registration works - beforeEach(async function () { - for (let i = 0; i < 30; i++) { - prices4[i] = (1 - i * ((1 - 0.08) / 30)).toString(); - } - - const vector1 = SAMPLE_DA_VECTOR(dutchAuction.address, {}); - const vector2 = SAMPLE_DA_VECTOR(dutchAuction.address, { prices: prices2 }); - const vector3 = SAMPLE_DA_VECTOR(dutchAuction.address, { prices: prices3 }); - const vector4 = SAMPLE_DA_VECTOR(dutchAuction.address, { prices: prices4, periodDuration: 10000 }); - - singleEdition = await setupSingleEditionDFS( - observability.address, - singleEditionDFSImplementation, - mintManager.address, - trustedForwarder.address, - editionsOwner, - 5, - "", - "NM", - null, - vector1, - ); - - editions = await setupMultipleEditionDFS( - observability.address, - editionsDFSImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - editionsOwner, - 100, - "symbol", - null, - vector2, - ); - - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - null, - vector3, - true, - ); - - generative = await setupGenerative( - observability.address, - generativeImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - null, - vector4, - ); - - singleEditionVectorId = produceMechanicVectorId( - singleEdition.address, - dutchAuction.address, - parseInt(vector1.seed), - 0, - ); - editionsVectorId = produceMechanicVectorId(editions.address, dutchAuction.address, parseInt(vector2.seed), 0); - generalVectorId = produceMechanicVectorId(general.address, dutchAuction.address, parseInt(vector3.seed)); - generativeVectorId = produceMechanicVectorId(generative.address, dutchAuction.address, parseInt(vector4.seed)); - - const vectorMeta1 = await mintManager.mechanicVectorMetadata(singleEditionVectorId); - const vectorMeta2 = await mintManager.mechanicVectorMetadata(editionsVectorId); - const vectorMeta3 = await mintManager.mechanicVectorMetadata(generalVectorId); - const vectorMeta4 = await mintManager.mechanicVectorMetadata(generativeVectorId); - expect(ethers.utils.getAddress(vectorMeta1.contractAddress)).to.equal( - ethers.utils.getAddress(singleEdition.address), - ); - expect(ethers.utils.getAddress(vectorMeta2.contractAddress)).to.equal(ethers.utils.getAddress(editions.address)); - expect(ethers.utils.getAddress(vectorMeta3.contractAddress)).to.equal(ethers.utils.getAddress(general.address)); - expect(ethers.utils.getAddress(vectorMeta4.contractAddress)).to.equal(ethers.utils.getAddress(generative.address)); - - const daState1 = await dutchAuction.getVectorState(singleEditionVectorId); - const daState2 = await dutchAuction.getVectorState(editionsVectorId); - const daState3 = await dutchAuction.getVectorState(generalVectorId); - const daState4 = await dutchAuction.getVectorState(generativeVectorId); - - expect(daState1._vector.numPrices.toString()).to.equal(prices1.length.toString()); - expect( - daState1.prices.map(price => { - return parseFloat(ethers.utils.formatEther(price)); - }), - ).to.eql( - prices1.map(price => { - return parseFloat(price); - }), - ); - expect(daState2._vector.numPrices.toString()).to.equal(prices2.length.toString()); - expect( - daState2.prices.map(price => { - return parseFloat(ethers.utils.formatEther(price)); - }), - ).to.eql( - prices2.map(price => { - return parseFloat(price); - }), - ); - expect(daState3._vector.numPrices.toString()).to.equal(prices3.length.toString()); - expect( - daState3.prices.map(price => { - return parseFloat(ethers.utils.formatEther(price)); - }), - ).to.eql( - prices3.map(price => { - return parseFloat(price); - }), - ); - expect(daState4._vector.numPrices.toString()).to.equal(prices4.length.toString()); - expect( - daState4.prices.map(price => { - return parseFloat(ethers.utils.formatEther(price)); - }), - ).to.eql( - prices4.map(price => { - return parseFloat(price); - }), - ); - }); - - describe("Mechanic vector management", function () { - it("Only the owner of a collection can register mechanic mint vectors", async function () { - const seed = Math.floor(Date.now() / 1000); - const vectorData = encodeMechanicVectorData( - mintManager.address, - fan1.address, - SAMPLE_DA_VECTOR(dutchAuction.address, {}), - ); - mintManager = mintManager.connect(fan1); - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId: 1, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - vectorData, - ), - ).to.be.revertedWithCustomError(mintManager, Errors.Unauthorized); - - mintManager = mintManager.connect(generalOwner); - }); - - it("Only the owner can pause/unpause mechanic mint vectors, which cause the mints to be paused/unpaused", async function () { - // do with both mechanicMintNum and mechanicMintChoose - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 2, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[0])).mul(2), - }), - ) - .to.emit(mintManager, "NumTokenMint") - .withArgs(generativeVectorId, generative.address, true, 2); - await expect( - mintManager.mechanicMintChoose(generalVectorId, fan1.address, [1, 2], "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices3[0])).mul(2), - }), - ) - .to.emit(mintManager, "ChooseTokenMint") - .withArgs(generalVectorId, general.address, true, [1, 2]); - - await expect(mintManager.setPauseOnMechanicMintVector(generativeVectorId, true)).to.be.not.reverted; - await expect(mintManager.setPauseOnMechanicMintVector(generalVectorId, true)).to.be.not.reverted; - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 2, "0x", { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.MechanicPaused); - await expect( - mintManager.mechanicMintChoose(generalVectorId, fan1.address, [3], "0x", { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.MechanicPaused); - }); - - it("Cannot try the wrong mint style", async function () { - await expect( - mintManager.mechanicMintNum(generalVectorId, fan1.address, 2, "0x", { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidMechanic); - await expect( - mintManager.mechanicMintChoose(generativeVectorId, fan1.address, [3], "0x", { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidMechanic); - }); - - describe("Dutch auction mechanic vector management", function () { - it("Can register/create dutch auction mechanic mint vectors with different configurations", async function () { - mintManager = mintManager.connect(editionsOwner); - const editionId = 0; - const seed = 1; - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - prices: ["0.001", "0.0001", "0.00009"], - periodDuration: 10, - maxTotalClaimableViaVector: 20, - startTimestamp: Math.floor(Date.now() / 1000) + 1000, - endTimestamp: Math.floor(Date.now() / 1000) + 1021, // 21 sec dutch auction / 2 periods of 10 sec each + 1 last period of 1 sec - }), - editionsOwner.address, - ), - ), - ) - .to.emit(dutchAuction, "DiscreteDutchAuctionCreated") - .withArgs(produceMechanicVectorId(editions.address, dutchAuction.address, seed, editionId)); - }); - - it("Cannot register/create dutch auction mechanic mint vectors with invalid configurations", async function () { - const editionId = 0; - const seed = 1; - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - prices: ["0.001", "0.0001", "0.00009"], - periodDuration: 10, - maxTotalClaimableViaVector: 20, - startTimestamp: Math.floor(Date.now() / 1000) + 1000, - endTimestamp: Math.floor(Date.now() / 1000) + 1020, // invalid, no time for last period - }), - editionsOwner.address, - ), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData(SAMPLE_DA_VECTOR(dutchAuction.address, {}), ethers.constants.AddressZero), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - periodDuration: 0, - }), - ethers.constants.AddressZero, - ), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - prices: ["0.001"], - }), - ethers.constants.AddressZero, - ), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - prices: ["0.001", "0.0001", "0.01"], - }), - ethers.constants.AddressZero, - ), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - - await expect( - mintManager.registerMechanicVector( - { - contractAddress: editions.address, - editionId, - isChoose: false, - paused: false, - mechanic: dutchAuction.address, - isEditionBased: true, - }, - seed, - encodeDAVectorData( - SAMPLE_DA_VECTOR(dutchAuction.address, { - prices: ["0.001", "0.0001", "0.0001"], - }), - ethers.constants.AddressZero, - ), - ), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - }); - - it("Non-owner of collection cannot update dutch auction", async function () { - dutchAuction = dutchAuction.connect(fan1); - const { - dutchAuction: dutchAuction1, - updateConfig: updateConfig1, - packedPrices: packedPrices1, - } = dutchAuctionUpdateArgs({ - prices: ["0.1", "0.0001", "0.00001"], - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuction1, packedPrices1, updateConfig1), - ).to.be.revertedWithCustomError(dutchAuction, Errors.Unauthorized); - }); - - it("Can update auction mechanic mint vectors with different configurations", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const { - numPrices: da1NumPrices, - bytesPerPrice: da1BytesPerPrice, - periodDuration: da1PeriodDuration, - tokenLimitPerTx: da1TokenLimitPerTx, - endTimestamp: da1EndTimestamp, - } = (await dutchAuction.getRawVector(generativeVectorId))._vector; - const { - dutchAuction: dutchAuction1, - updateConfig: updateConfig1, - packedPrices: packedPrices1, - } = dutchAuctionUpdateArgs({ - prices: ["1000", "0.0001", "0.00001"], - }); - // none of periodDuration, tokenLimitPerTx, endTimestamp should update - await expect( - dutchAuction.updateVector( - generativeVectorId, - { ...dutchAuction1, periodDuration: 5, tokenLimitPerTx: 10, endTimestamp: 100 }, - packedPrices1, - updateConfig1, - ), - ) - .to.emit(dutchAuction, "DiscreteDutchAuctionUpdated") - .withArgs(generativeVectorId); - const { - numPrices: da1NewNumPrices, - bytesPerPrice: da1NewBytesPerPrice, - periodDuration: da1NewPeriodDuration, - tokenLimitPerTx: da1NewTokenLimitPerTx, - endTimestamp: da1NewEndTimestamp, - } = (await dutchAuction.getRawVector(generativeVectorId))._vector; - const newPackedPrices = (await dutchAuction.getRawVector(generativeVectorId)).packedPrices; - expect(da1NumPrices.toString()).to.not.equal(da1NewNumPrices.toString()); - expect(da1NewNumPrices.toString()).to.equal("3"); - expect(da1BytesPerPrice.toString()).to.not.equal(da1NewBytesPerPrice.toString()); - expect(da1PeriodDuration.toString()).to.equal(da1NewPeriodDuration.toString()); - expect(da1TokenLimitPerTx.toString()).to.equal(da1NewTokenLimitPerTx.toString()); - expect(da1EndTimestamp.toString()).to.equal(da1NewEndTimestamp.toString()); - expect(packedPrices1).to.eql(newPackedPrices); - expect( - (await dutchAuction.getVectorState(generativeVectorId)).prices.map(price => { - return ethers.utils.formatEther(price); - }), - ).to.eql(["1000.0", "0.0001", "0.00001"]); - - const { - dutchAuction: dutchAuction2, - updateConfig: updateConfig2, - packedPrices: packedPrices2, - } = dutchAuctionUpdateArgs({ - startTimestamp: 10000, - endTimestamp: 20000, - periodDuration: 334, // 30 periods, so 334 x 30 = 10020, on limit - maxUserClaimableViaVector: 5, - maxTotalClaimableViaVector: 10, - tokenLimitPerTx: 5, - paymentRecipient: fan1.address, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuction2, packedPrices2, updateConfig2)) - .to.emit(dutchAuction, "DiscreteDutchAuctionUpdated") - .withArgs(generativeVectorId); - const { - startTimestamp: da2NewStartTimestamp, - endTimestamp: da2NewEndTimestamp, - periodDuration: da2NewPeriodDuration, - maxUserClaimableViaVector: da2NewMaxUserClaimableViaVector, - maxTotalClaimableViaVector: da2NewMaxTotalClaimableViaVector, - tokenLimitPerTx: da2NewTokenLimitPerTx, - paymentRecipient: da2NewPaymentRecipient, - } = (await dutchAuction.getRawVector(generativeVectorId))._vector; - - expect(da2NewStartTimestamp.toString()).to.equal("10000"); - expect(da2NewEndTimestamp.toString()).to.equal("20000"); - expect(da2NewPeriodDuration.toString()).to.equal("334"); - expect(da2NewMaxUserClaimableViaVector.toString()).to.equal("5"); - expect(da2NewMaxTotalClaimableViaVector.toString()).to.equal("10"); - expect(da2NewTokenLimitPerTx.toString()).to.equal("5"); - expect(ethers.utils.getAddress(da2NewPaymentRecipient)).to.equal(ethers.utils.getAddress(fan1.address)); - }); - - it("Cannot update dutch auction to set a time range that exceeds or equals (numPrices - 1) * periodDuration", async function () { - // cannot set invalid times, given there are 30 prices - const { - dutchAuction: dutchAuction1, - updateConfig: updateConfig1, - packedPrices: packedPrices1, - } = dutchAuctionUpdateArgs({ - periodDuration: 10, - startTimestamp: 20, - endTimestamp: 310, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuction1, packedPrices1, updateConfig1), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - }); - - it("Cannot update dutch auction with non-decreasing prices", async function () { - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - prices: ["0.001", "0.001"], - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - }); - - it("Cannot update dutch auction with the payment recipient as the zero address", async function () { - const { - dutchAuction: dutchAuction3, - updateConfig: updateConfig3, - packedPrices: packedPrices3, - } = dutchAuctionUpdateArgs({ - paymentRecipient: ethers.constants.AddressZero, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuction3, packedPrices3, updateConfig3), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - }); - - it("Cannot update dutch auction to make period duration 0", async function () { - const { - dutchAuction: dutchAuction3, - updateConfig: updateConfig3, - packedPrices: packedPrices3, - } = dutchAuctionUpdateArgs({ - periodDuration: 0, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuction3, packedPrices3, updateConfig3), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidVectorConfig); - }); - - describe("Cannot update certain fields on dutch auction after first token is minted", function () { - beforeEach(async function () { - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 2, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(2), - }), - ).to.emit(mintManager, "NumTokenMint"); - - const { _vector, payeePotentialEscrowedFunds, currentPrice } = await dutchAuction.getVectorState( - generativeVectorId, - ); - expect(_vector.lowestPriceSoldAtIndex).to.equal(0); - expect(_vector.currentSupply).to.equal(2); - expect(ethers.utils.formatEther(_vector.totalSales)).to.equal("2.0"); - expect(ethers.utils.formatEther(currentPrice)).to.equal("1.0"); - expect(ethers.utils.formatEther(payeePotentialEscrowedFunds)).to.equal("2.0"); - }); - - it("maxTotalClaimableViaVector", async function () { - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 30, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidUpdate); - }); - - it("prices", async function () { - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - prices: ["0.008", "0.007"], - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidUpdate); - }); - - it("periodDuration", async function () { - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - periodDuration: 1001, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidUpdate); - }); - - it("startTimestamp", async function () { - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - startTimestamp: 100, - }); - await expect( - dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidUpdate); - }); - }); - }); - }); - - describe("Dutch auctions", function () { - describe("Mints + rebates + escrow funds withdrawal (logic / state / errors)", function () { - it("Cannot send too low of a mint fee", async function () { - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x"), - ).to.be.revertedWithCustomError(mintManager, Errors.MintFeeTooLow); - }); - - it("Cannot send too low of a fee for the auction", async function () { - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - }); - - it("Can only mint within the time bounds of the auction", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - const startTimestamp = currTime + 1000; - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - startTimestamp, - endTimestamp: startTimestamp + 300000, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - await ethers.provider.send("evm_mine", [currTime + 200000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")), - }), - ).not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 400000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - - const currentPrice = (await dutchAuction.getVectorState(generativeVectorId)).currentPrice; - const userInfo = await dutchAuction.getUserInfo(generativeVectorId, fan1.address); - const rebate = userInfo[0]; - const { totalPosted } = userInfo[1]; - expect(ethers.utils.formatEther(totalPosted)).to.equal("1.0"); - expect(totalPosted.sub(currentPrice).eq(rebate)).to.equal(true); - }); - - it("Cannot mint over maxUser, maxTotal, and tokenLimitPerTx bounds", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 10, - maxUserClaimableViaVector: 5, - tokenLimitPerTx: 3, - startTimestamp: currTime + 1000, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - - // tokenLimitPerTx - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 4, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(4), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(3), - }), - ).to.not.be.reverted; - // maxUserClaimableViaVector - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(3), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 2, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(2), - }), - ).to.not.be.reverted; - await expect( - mintManager.mechanicMintNum(generativeVectorId, generalOwner.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(3), - }), - ).to.not.be.reverted; - // maxTotalClaimableViaVector - await expect( - mintManager.mechanicMintNum(generativeVectorId, editionsOwner.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(3), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - await expect( - mintManager.mechanicMintNum(generativeVectorId, editionsOwner.address, 2, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther("1")).mul(2), - }), - ).to.not.be.reverted; - }); - - it("State updates properly through multiple mints at different prices", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 15, - startTimestamp: currTime + 1000000, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - - await ethers.provider.send("evm_mine", [currTime + 1000000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[0])).mul(3), - }), - ).to.not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 1010000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[1])).mul(3), - }), - ).to.not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 1030000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 3, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[3])).mul(3), - }), - ).to.not.be.reverted; - - const { _vector, currentPrice, collectionSupply, payeePotentialEscrowedFunds, escrowedFundsAmountFinalized } = - await dutchAuction.getVectorState(generativeVectorId); - expect(_vector.currentSupply).to.equal(9); - expect(ethers.utils.formatEther(currentPrice)).to.equal(prices4[3]); - expect(collectionSupply.toString()).to.equal("9"); - expect(payeePotentialEscrowedFunds.toString()).to.eql(ethers.utils.parseEther(prices4[3]).mul(9).toString()); - expect(escrowedFundsAmountFinalized).to.equal(false); - expect(_vector.totalSales.toString()).to.equal( - ethers.utils - .parseEther(prices4[0]) - .add(ethers.utils.parseEther(prices4[1])) - .add(ethers.utils.parseEther(prices4[3])) - .mul(3) - .toString(), - ); - await ethers.provider.send("evm_mine", [currTime + 1040000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, generalOwner.address, 5, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[4])).mul(5), - }), - ).to.not.be.reverted; - - expect((await dutchAuction.getVectorState(generativeVectorId))._vector.lowestPriceSoldAtIndex).to.equal(4); - - await ethers.provider.send("evm_mine", [currTime + 1090000]); - - await expect( - mintManager.mechanicMintNum(generativeVectorId, generalOwner.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[9])).mul(1), - }), - ).to.not.be.reverted; - - expect((await dutchAuction.getVectorState(generativeVectorId))._vector.lowestPriceSoldAtIndex).to.equal(9); - - const contractBalance = await ethers.provider.getBalance(dutchAuction.address); - const collectorBalance = await ethers.provider.getBalance(generalOwner.address); - const userInfo = await dutchAuction.getUserInfo(generativeVectorId, generalOwner.address); - expect(userInfo[0].toString()).to.equal( - ethers.utils.parseEther(prices4[4]).sub(ethers.utils.parseEther(prices4[9])).mul(5).toString(), - ); - const generalOwnerTotalPosted = ethers.utils - .parseEther(prices4[4]) - .mul(5) - .add(ethers.utils.parseEther(prices4[9])); - expect(userInfo[1].totalPosted.toString()).to.equal(generalOwnerTotalPosted.toString()); - - // collect rebate - await expect(dutchAuction.rebateCollector(generativeVectorId, generalOwner.address)) - .to.emit(dutchAuction, "DiscreteDutchAuctionCollectorRebate") - .withArgs( - generativeVectorId, - generalOwner.address, - userInfo[0], - ( - await dutchAuction.getVectorState(generativeVectorId) - ).currentPrice, - ); - - // validate 2 balances difference - expect((await ethers.provider.getBalance(dutchAuction.address)).eq(contractBalance.sub(userInfo[0]))).to.equal( - true, - ); - // over 90% of the rebate (consider ether lost to gas) - const newCollectorBalance = await ethers.provider.getBalance(generalOwner.address); - expect(newCollectorBalance.lt(collectorBalance.add(userInfo[0]))).to.equal(true); - expect(newCollectorBalance.gt(collectorBalance.add(userInfo[0]).mul(9).div(10))).to.equal(true); - - const state = await dutchAuction.getVectorState(generativeVectorId); - const [rebateGeneralOwner, newUserInfoGeneralOwner] = await dutchAuction.getUserInfo( - generativeVectorId, - generalOwner.address, - ); - const [rebateFan1, newUserInfoFan1] = await dutchAuction.getUserInfo(generativeVectorId, fan1.address); - expect(state.escrowedFundsAmountFinalized).to.equal(true); - expect(state.payeePotentialEscrowedFunds.toString()).to.equal( - ethers.utils.parseEther(prices4[9]).mul(15).toString(), - ); - expect(rebateGeneralOwner.eq(0)).to.equal(true); - expect(newUserInfoGeneralOwner.totalPosted.eq(generalOwnerTotalPosted.sub(userInfo[0]))).to.equal(true); // new totalPosted = old totalPosted - rebate paid out - expect(newUserInfoGeneralOwner.numRebates).to.equal(1); - expect(newUserInfoGeneralOwner.numTokensBought).to.equal(6); - const fan1TotalPosted = ethers.utils - .parseEther(prices4[0]) - .add(ethers.utils.parseEther(prices4[1])) - .add(ethers.utils.parseEther(prices4[3])) - .mul(3); - expect(newUserInfoFan1.totalPosted.eq(fan1TotalPosted)).to.equal(true); - expect(rebateFan1.eq(fan1TotalPosted.sub(ethers.utils.parseEther(prices4[9]).mul(9)))).to.equal(true); - expect(newUserInfoFan1.numRebates).to.equal(0); - expect(newUserInfoFan1.numTokensBought).to.equal(9); - - expect(state._vector.auctionExhausted).to.equal(true); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(state.currentPrice), - }), - ).to.be.revertedWithCustomError(dutchAuction, Errors.InvalidMint); - }); - - it("Underlying collection is exhausted and we validly withdraws escrowed funds", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - generative = generative.connect(generalOwner); - await expect(generative.setLimitSupply(1)).to.not.be.reverted; - - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 15, - startTimestamp: currTime + 2000000, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - - await ethers.provider.send("evm_mine", [currTime + 2000000]); - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[0])), - }), - ).to.not.be.reverted; - - // cannot trigger a rebate if user isn't eligible - expect((await dutchAuction.getUserInfo(generativeVectorId, fan1.address))[0].eq(0)).to.equal(true); - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)).to.be.revertedWithCustomError( - dutchAuction, - Errors.CollectorNotOwedRebate, - ); - - const state = await dutchAuction.getVectorState(generativeVectorId); - expect(state.auctionExhausted).to.equal(true); - expect(state.escrowedFundsAmountFinalized).to.equal(true); - expect(state.payeePotentialEscrowedFunds.eq(ethers.utils.parseEther("1"))).to.equal(true); - expect(state._vector.lowestPriceSoldAtIndex).eq(0); - - dutchAuction = dutchAuction.connect(fan1); - const contractBalance = await ethers.provider.getBalance(dutchAuction.address); - const payeeBalance = await ethers.provider.getBalance(state._vector.paymentRecipient); - await expect(dutchAuction.withdrawDPPFunds(generativeVectorId)) - .to.emit(dutchAuction, "DiscreteDutchAuctionDPPFundsWithdrawn") - .withArgs(generativeVectorId, state._vector.paymentRecipient, ethers.utils.parseEther("1.0"), 1); - expect( - (await ethers.provider.getBalance(dutchAuction.address)).eq( - contractBalance.sub(ethers.utils.parseEther("1")), - ), - ).to.equal(true); - expect( - (await ethers.provider.getBalance(state._vector.paymentRecipient)).eq( - payeeBalance.add(ethers.utils.parseEther("1")), - ), - ).to.equal(true); - - // cannot re-trigger a withdrawal - expect((await dutchAuction.getVectorState(generativeVectorId))._vector.payeeRevenueHasBeenWithdrawn).to.equal( - true, - ); - await expect(dutchAuction.withdrawDPPFunds(generativeVectorId)).to.be.revertedWithCustomError( - dutchAuction, - Errors.InvalidDPPFundsWithdrawl, - ); - }); - - it("Cannot trigger a rebate for a vector with no tokens minted through it", async function () { - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)).to.be.revertedWithCustomError( - dutchAuction, - Errors.InvalidRebate, - ); - }); - - it("Cannot trigger a withdrawal when no tokens have been minted through the vector", async function () { - await expect(dutchAuction.withdrawDPPFunds(generativeVectorId)).to.be.revertedWithCustomError( - dutchAuction, - Errors.InvalidDPPFundsWithdrawl, - ); - }); - - it("Cannot trigger a withdrawal if an auction isn't exhausted or in the FPP", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 15, - startTimestamp: currTime + 3000000, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - await ethers.provider.send("evm_mine", [currTime + 3000000]); - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices4[0])), - }), - ).to.not.be.reverted; - - await expect(dutchAuction.withdrawDPPFunds(generativeVectorId)).to.be.revertedWithCustomError( - dutchAuction, - Errors.InvalidDPPFundsWithdrawl, - ); - }); - - it("Keep rebating as price drops, down to FPP (with excess amounts sent), withdraw funds, then payments go straight to payee", async function () { - dutchAuction = dutchAuction.connect(generalOwner); - const currTime = Math.floor(Date.now() / 1000); - - const prices = ["1", "0.8", "0.6", "0.4"]; - const { - dutchAuction: dutchAuctionData, - updateConfig, - packedPrices, - } = dutchAuctionUpdateArgs({ - maxTotalClaimableViaVector: 4, - startTimestamp: currTime + 4000000, - prices, - }); - await expect(dutchAuction.updateVector(generativeVectorId, dutchAuctionData, packedPrices, updateConfig)).to.not - .be.reverted; - await ethers.provider.send("evm_mine", [currTime + 4000000]); - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices[0])).add(ethers.utils.parseEther("0.5")), - }), - ).to.not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 4010000]); - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)) - .to.emit(dutchAuction, "DiscreteDutchAuctionCollectorRebate") - .withArgs( - generativeVectorId, - fan1.address, - ethers.utils.parseEther("0.7"), - ethers.utils.parseEther(prices[1]), - ); - expect((await dutchAuction.getUserInfo(generativeVectorId, fan1.address))[0].eq(0)).to.equal(true); - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices[1])).add(ethers.utils.parseEther("0.5")), - }), - ).to.not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 4020000]); - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)) - .to.emit(dutchAuction, "DiscreteDutchAuctionCollectorRebate") - .withArgs( - generativeVectorId, - fan1.address, - ethers.utils.parseEther("0.9"), - ethers.utils.parseEther(prices[2]), - ); - expect((await dutchAuction.getUserInfo(generativeVectorId, fan1.address))[0].eq(0)).to.equal(true); - - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices[2])).add(ethers.utils.parseEther("0.5")), - }), - ).to.not.be.reverted; - await ethers.provider.send("evm_mine", [currTime + 4030000]); - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)) - .to.emit(dutchAuction, "DiscreteDutchAuctionCollectorRebate") - .withArgs( - generativeVectorId, - fan1.address, - ethers.utils.parseEther("1.1"), - ethers.utils.parseEther(prices[3]), - ); - expect((await dutchAuction.getUserInfo(generativeVectorId, fan1.address))[0].eq(0)).to.equal(true); - - const { _vector, auctionInFPP, auctionExhausted, escrowedFundsAmountFinalized, payeePotentialEscrowedFunds } = - await dutchAuction.getVectorState(generativeVectorId); - expect(_vector.currentSupply).to.equal(3); - expect(escrowedFundsAmountFinalized).to.equal(true); - expect(auctionExhausted).to.equal(false); - expect(auctionInFPP).to.equal(true); - expect(payeePotentialEscrowedFunds.eq(ethers.utils.parseEther("0.4").mul(3))); - - dutchAuction = dutchAuction.connect(fan1); - mintManager = mintManager.connect(fan1); - const payeeBalance = await ethers.provider.getBalance(_vector.paymentRecipient); - await expect(dutchAuction.withdrawDPPFunds(generativeVectorId)) - .to.emit(dutchAuction, "DiscreteDutchAuctionDPPFundsWithdrawn") - .withArgs(generativeVectorId, _vector.paymentRecipient, ethers.utils.parseEther("0.4"), 3); - const intermediaryPayeeBalance = await ethers.provider.getBalance(_vector.paymentRecipient); - expect(intermediaryPayeeBalance.sub(payeePotentialEscrowedFunds).eq(payeeBalance)); - - // payments now go straight to payee - await expect( - mintManager.mechanicMintNum(generativeVectorId, fan1.address, 1, "0x", { - value: mintFeeWei.add(ethers.utils.parseEther(prices[3])).add(ethers.utils.parseEther("0.5")), - }), - ).to.not.be.reverted; - expect( - (await ethers.provider.getBalance(_vector.paymentRecipient)).eq( - intermediaryPayeeBalance.add(ethers.utils.parseEther("0.4")), - ), - ); - - // can still collect rebate from overpay in FPP - expect( - (await dutchAuction.getUserInfo(generativeVectorId, fan1.address))[0].eq(ethers.utils.parseEther("0.5")), - ); - await expect(dutchAuction.rebateCollector(generativeVectorId, fan1.address)) - .to.emit(dutchAuction, "DiscreteDutchAuctionCollectorRebate") - .withArgs(generativeVectorId, fan1.address, ethers.utils.parseEther("0.5"), ethers.utils.parseEther("0.4")); - }); - }); - }); -}); diff --git a/test/MetaTransactionsTest.ts b/test/MetaTransactionsTest.ts deleted file mode 100644 index 5843dd4..0000000 --- a/test/MetaTransactionsTest.ts +++ /dev/null @@ -1,647 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - ERC721General, - ERC721SingleEdition, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - Observability, -} from "../types"; -import { - DEFAULT_ONCHAIN_MINT_VECTOR, - setupEditions, - setupGeneral, - setupSingleEdition, - setupSystem, -} from "./__utils__/helpers"; -import { sign2771MetaTxRequest } from "./__utils__/metaTx"; - -// have to import this here to not import OpenZeppelin minimal forwarder -const MinimalForwarderData = { - _format: "hh-sol-artifact-1", - contractName: "MinimalForwarder", - sourceName: "contracts/metatx/MinimalForwarder.sol", - abi: [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [ - { - components: [ - { - internalType: "address", - name: "from", - type: "address", - }, - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "uint256", - name: "gas", - type: "uint256", - }, - { - internalType: "uint256", - name: "nonce", - type: "uint256", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", - }, - ], - internalType: "struct MinimalForwarder.ForwardRequest", - name: "req", - type: "tuple", - }, - { - internalType: "bytes", - name: "signature", - type: "bytes", - }, - ], - name: "execute", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - { - internalType: "bytes", - name: "", - type: "bytes", - }, - ], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address", - }, - ], - name: "getNonce", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - components: [ - { - internalType: "address", - name: "from", - type: "address", - }, - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "uint256", - name: "gas", - type: "uint256", - }, - { - internalType: "uint256", - name: "nonce", - type: "uint256", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", - }, - ], - internalType: "struct MinimalForwarder.ForwardRequest", - name: "req", - type: "tuple", - }, - { - internalType: "bytes", - name: "signature", - type: "bytes", - }, - ], - name: "verify", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - ], - bytecode: - "0x61014060405234801561001157600080fd5b50604080518082018252601081526f26b4b734b6b0b62337b93bb0b93232b960811b602080830191825283518085019094526005845264302e302e3160d81b908401528151902060e08190527fae209a0b48f21c054280f2455d32cf309387644879d9acbd8ffc1991638118856101008190524660a0529192917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6100fb8184846040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6080523060c052610120525061011092505050565b60805160a05160c05160e0516101005161012051610b6261015f600039600061050c0152600061055b015260006105360152600061048f015260006104b9015260006104e30152610b626000f3fe6080604052600436106100345760003560e01c80632d0335ab1461003957806347153f8214610082578063bf5d3bdb146100a3575b600080fd5b34801561004557600080fd5b5061006f6100543660046108fd565b6001600160a01b031660009081526020819052604090205490565b6040519081526020015b60405180910390f35b61009561009036600461092d565b6100d3565b604051610079929190610a28565b3480156100af57600080fd5b506100c36100be36600461092d565b61028c565b6040519015158152602001610079565b600060606100e285858561028c565b61014e5760405162461bcd60e51b815260206004820152603260248201527f4d696e696d616c466f727761726465723a207369676e617475726520646f6573604482015271081b9bdd081b585d18da081c995c5d595cdd60721b60648201526084015b60405180910390fd5b61015d60808601356001610a4b565b60008061016d60208901896108fd565b6001600160a01b03166001600160a01b03168152602001908152602001600020819055506000808660200160208101906101a791906108fd565b6001600160a01b0316606088013560408901356101c760a08b018b610a71565b6101d460208d018d6108fd565b6040516020016101e693929190610ab8565b60408051601f198184030181529082905261020091610ade565b600060405180830381858888f193505050503d806000811461023e576040519150601f19603f3d011682016040523d82523d6000602084013e610243565b606091505b50915091508181906102685760405162461bcd60e51b81526004016101459190610afa565b50610278603f6060890135610b0d565b5a1161028057fe5b90969095509350505050565b60008061039f84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061039992507fdd8f4b70b0f4393e889bd39128a30628a78b61816a9eb8199759e7a349657e4891506102fc905060208a018a6108fd565b61030c60408b0160208c016108fd565b60408b013560608c013560808d013561032860a08f018f610a71565b604051610336929190610b2f565b6040805191829003822060208301989098526001600160a01b0396871690820152949093166060850152608084019190915260a083015260c082015260e0810191909152610100016040516020818303038152906040528051906020012061040a565b9061045e565b905060808501356000806103b660208901896108fd565b6001600160a01b03166001600160a01b031681526020019081526020016000205414801561040157506103ec60208601866108fd565b6001600160a01b0316816001600160a01b0316145b95945050505050565b6000610458610417610482565b8360405161190160f01b6020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b92915050565b600080600061046d85856105a9565b9150915061047a81610619565b509392505050565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156104db57507f000000000000000000000000000000000000000000000000000000000000000046145b1561050557507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b6000808251604114156105e05760208301516040840151606085015160001a6105d4878285856107d7565b94509450505050610612565b82516040141561060a57602083015160408401516105ff8683836108c4565b935093505050610612565b506000905060025b9250929050565b600081600481111561062d5761062d610b3f565b14156106365750565b600181600481111561064a5761064a610b3f565b14156106985760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401610145565b60028160048111156106ac576106ac610b3f565b14156106fa5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610145565b600381600481111561070e5761070e610b3f565b14156107675760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610145565b600481600481111561077b5761077b610b3f565b14156107d45760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610145565b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561080e57506000905060036108bb565b8460ff16601b1415801561082657508460ff16601c14155b1561083757506000905060046108bb565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561088b573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166108b4576000600192509250506108bb565b9150600090505b94509492505050565b6000806001600160ff1b038316816108e160ff86901c601b610a4b565b90506108ef878288856107d7565b935093505050935093915050565b60006020828403121561090f57600080fd5b81356001600160a01b038116811461092657600080fd5b9392505050565b60008060006040848603121561094257600080fd5b833567ffffffffffffffff8082111561095a57600080fd5b9085019060c0828803121561096e57600080fd5b9093506020850135908082111561098457600080fd5b818601915086601f83011261099857600080fd5b8135818111156109a757600080fd5b8760208285010111156109b957600080fd5b6020830194508093505050509250925092565b60005b838110156109e75781810151838201526020016109cf565b838111156109f6576000848401525b50505050565b60008151808452610a148160208601602086016109cc565b601f01601f19169290920160200192915050565b8215158152604060208201526000610a4360408301846109fc565b949350505050565b60008219821115610a6c57634e487b7160e01b600052601160045260246000fd5b500190565b6000808335601e19843603018112610a8857600080fd5b83018035915067ffffffffffffffff821115610aa357600080fd5b60200191503681900382131561061257600080fd5b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251610af08184602087016109cc565b9190910192915050565b60208152600061092660208301846109fc565b600082610b2a57634e487b7160e01b600052601260045260246000fd5b500490565b8183823760009101908152919050565b634e487b7160e01b600052602160045260246000fdfea164736f6c634300080a000a", - deployedBytecode: - "0x6080604052600436106100345760003560e01c80632d0335ab1461003957806347153f8214610082578063bf5d3bdb146100a3575b600080fd5b34801561004557600080fd5b5061006f6100543660046108fd565b6001600160a01b031660009081526020819052604090205490565b6040519081526020015b60405180910390f35b61009561009036600461092d565b6100d3565b604051610079929190610a28565b3480156100af57600080fd5b506100c36100be36600461092d565b61028c565b6040519015158152602001610079565b600060606100e285858561028c565b61014e5760405162461bcd60e51b815260206004820152603260248201527f4d696e696d616c466f727761726465723a207369676e617475726520646f6573604482015271081b9bdd081b585d18da081c995c5d595cdd60721b60648201526084015b60405180910390fd5b61015d60808601356001610a4b565b60008061016d60208901896108fd565b6001600160a01b03166001600160a01b03168152602001908152602001600020819055506000808660200160208101906101a791906108fd565b6001600160a01b0316606088013560408901356101c760a08b018b610a71565b6101d460208d018d6108fd565b6040516020016101e693929190610ab8565b60408051601f198184030181529082905261020091610ade565b600060405180830381858888f193505050503d806000811461023e576040519150601f19603f3d011682016040523d82523d6000602084013e610243565b606091505b50915091508181906102685760405162461bcd60e51b81526004016101459190610afa565b50610278603f6060890135610b0d565b5a1161028057fe5b90969095509350505050565b60008061039f84848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061039992507fdd8f4b70b0f4393e889bd39128a30628a78b61816a9eb8199759e7a349657e4891506102fc905060208a018a6108fd565b61030c60408b0160208c016108fd565b60408b013560608c013560808d013561032860a08f018f610a71565b604051610336929190610b2f565b6040805191829003822060208301989098526001600160a01b0396871690820152949093166060850152608084019190915260a083015260c082015260e0810191909152610100016040516020818303038152906040528051906020012061040a565b9061045e565b905060808501356000806103b660208901896108fd565b6001600160a01b03166001600160a01b031681526020019081526020016000205414801561040157506103ec60208601866108fd565b6001600160a01b0316816001600160a01b0316145b95945050505050565b6000610458610417610482565b8360405161190160f01b6020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b92915050565b600080600061046d85856105a9565b9150915061047a81610619565b509392505050565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156104db57507f000000000000000000000000000000000000000000000000000000000000000046145b1561050557507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b6000808251604114156105e05760208301516040840151606085015160001a6105d4878285856107d7565b94509450505050610612565b82516040141561060a57602083015160408401516105ff8683836108c4565b935093505050610612565b506000905060025b9250929050565b600081600481111561062d5761062d610b3f565b14156106365750565b600181600481111561064a5761064a610b3f565b14156106985760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401610145565b60028160048111156106ac576106ac610b3f565b14156106fa5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610145565b600381600481111561070e5761070e610b3f565b14156107675760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610145565b600481600481111561077b5761077b610b3f565b14156107d45760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610145565b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561080e57506000905060036108bb565b8460ff16601b1415801561082657508460ff16601c14155b1561083757506000905060046108bb565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561088b573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166108b4576000600192509250506108bb565b9150600090505b94509492505050565b6000806001600160ff1b038316816108e160ff86901c601b610a4b565b90506108ef878288856107d7565b935093505050935093915050565b60006020828403121561090f57600080fd5b81356001600160a01b038116811461092657600080fd5b9392505050565b60008060006040848603121561094257600080fd5b833567ffffffffffffffff8082111561095a57600080fd5b9085019060c0828803121561096e57600080fd5b9093506020850135908082111561098457600080fd5b818601915086601f83011261099857600080fd5b8135818111156109a757600080fd5b8760208285010111156109b957600080fd5b6020830194508093505050509250925092565b60005b838110156109e75781810151838201526020016109cf565b838111156109f6576000848401525b50505050565b60008151808452610a148160208601602086016109cc565b601f01601f19169290920160200192915050565b8215158152604060208201526000610a4360408301846109fc565b949350505050565b60008219821115610a6c57634e487b7160e01b600052601160045260246000fd5b500190565b6000808335601e19843603018112610a8857600080fd5b83018035915067ffffffffffffffff821115610aa357600080fd5b60200191503681900382131561061257600080fd5b8284823760609190911b6bffffffffffffffffffffffff19169101908152601401919050565b60008251610af08184602087016109cc565b9190910192915050565b60208152600061092660208301846109fc565b600082610b2a57634e487b7160e01b600052601260045260246000fd5b500490565b8183823760009101908152919050565b634e487b7160e01b600052602160045260246000fdfea164736f6c634300080a000a", - linkReferences: {}, - deployedLinkReferences: {}, -}; - -describe("MetaTransactions functionality", () => { - let minimalForwarderFanSigner: MinimalForwarder; - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let observability: Observability; - let trustedForwarder: MinimalForwarder; - let editionsImplementation: string; - let singleEditionImplementation: string; - let generalImplementation: string; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - emr = emrProxy; - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - auctionManager = auctionManagerProxy; - - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - - mintManager = mintManager.connect(mintManagerOwner); - minimalForwarderFanSigner = minimalForwarder.connect(fan1); - }); - - describe("MinimalForwarder", function () { - describe("ERC721Editions", function () { - let editions: ERC721Editions; - - beforeEach(async function () { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - owner, - ); - }); - - it("Not encoding enough gas for an operation fails the operation", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: editions.address, - gas: 30000, - data: await editions.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith(""); - }); - - it("Mismatched signers from the from address of a request are not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: editionsMetadataOwner.address, - to: editions.address, - gas: 60000, - data: await editions.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Mismatched data in a request is not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: editions.address, - gas: 60000, - data: await editions.interface.encodeFunctionData("freezeMints"), - }); - - request.gas = 60001; - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Sending a request from a non-trusted forwarder does not invoke meta transaction functionality", async function () { - // have to do this to not import OpenZeppelin minimal forwarder - const newMinimalForwarder = (await new ethers.ContractFactory( - MinimalForwarderData.abi, - MinimalForwarderData.bytecode, - fan1, - ).deploy()) as MinimalForwarder; - await newMinimalForwarder.deployed(); - - // this operation would have succeeded from the trusted forwarder, but since it's not trusted, it fails - const { signature, request } = await sign2771MetaTxRequest(owner, newMinimalForwarder, { - from: owner.address, - to: editions.address, - gas: 60000, - data: await editions.interface.encodeFunctionData("freezeMints"), - }); - - expect(await newMinimalForwarder.verify(request, signature)).to.equal(true); - expect(await editions.isTrustedForwarder(newMinimalForwarder.address)).to.equal(false); - expect(await editions.isTrustedForwarder(minimalForwarderFanSigner.address)).to.equal(true); - - // TODO: fix HardhatChaiMatchersDecodingError issue. This reverts with Ownable message as expected - let reverted = false; - try { - await expect(newMinimalForwarder.execute(request, signature)).to.emit(editions, "MintsFrozen"); - } catch (error) { - reverted = true; - } - - expect(reverted).to.equal(true); - }); - - it("A validly encoded msgSender is allowed to perform operations as expected", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: editions.address, - gas: 60000, - data: await editions.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.emit(editions, "MintsFrozen").withArgs(); - }); - }); - - describe("ERC721SingleEdition", function () { - let singleEdition: ERC721SingleEdition; - - beforeEach(async function () { - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - owner, - 4, - "name", - "SYM", - ); - }); - - it("Not encoding enough gas for an operation fails the operation", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: singleEdition.address, - gas: 30000, - data: await singleEdition.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith(""); - }); - - it("Mismatched signers from the from address of a request are not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: editionsMetadataOwner.address, - to: singleEdition.address, - gas: 60000, - data: await singleEdition.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Mismatched data in a request is not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: singleEdition.address, - gas: 60000, - data: await singleEdition.interface.encodeFunctionData("freezeMints"), - }); - - request.gas = 60001; - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Sending a request from a non-trusted forwarder does not invoke meta transaction functionality", async function () { - // have to do this to not import OpenZeppelin minimal forwarder - const newMinimalForwarder = (await new ethers.ContractFactory( - MinimalForwarderData.abi, - MinimalForwarderData.bytecode, - fan1, - ).deploy()) as MinimalForwarder; - await newMinimalForwarder.deployed(); - - // this operation would have succeeded from the trusted forwarder, but since it's not trusted, it fails - const { signature, request } = await sign2771MetaTxRequest(owner, newMinimalForwarder, { - from: owner.address, - to: singleEdition.address, - gas: 60000, - data: await singleEdition.interface.encodeFunctionData("freezeMints"), - }); - - expect(await newMinimalForwarder.verify(request, signature)).to.equal(true); - expect(await singleEdition.isTrustedForwarder(newMinimalForwarder.address)).to.equal(false); - expect(await singleEdition.isTrustedForwarder(minimalForwarderFanSigner.address)).to.equal(true); - - // TODO: fix HardhatChaiMatchersDecodingError issue. This reverts with Ownable message as expected - let reverted = false; - try { - await expect(newMinimalForwarder.execute(request, signature)).to.emit(singleEdition, "MintsFrozen"); - } catch (error) { - reverted = true; - } - - expect(reverted).to.equal(true); - }); - - it("A validly encoded msgSender is allowed to perform operations as expected", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: singleEdition.address, - gas: 60000, - data: await singleEdition.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)) - .to.emit(singleEdition, "MintsFrozen") - .withArgs(); - }); - }); - - describe("ERC721General", function () { - let general: ERC721General; - - beforeEach(async function () { - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - owner, - ); - }); - - it("Not encoding enough gas for an operation fails the operation", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: general.address, - gas: 30000, - data: await general.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith(""); - }); - - it("Mismatched signers from the from address of a request are not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: editionsMetadataOwner.address, - to: general.address, - gas: 60000, - data: await general.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Mismatched data in a request is not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: general.address, - gas: 60000, - data: await general.interface.encodeFunctionData("freezeMints"), - }); - - request.gas = 60001; - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Sending a request from a non-trusted forwarder does not invoke meta transaction functionality", async function () { - // have to do this to not import OpenZeppelin minimal forwarder - const newMinimalForwarder = (await new ethers.ContractFactory( - MinimalForwarderData.abi, - MinimalForwarderData.bytecode, - fan1, - ).deploy()) as MinimalForwarder; - await newMinimalForwarder.deployed(); - - // this operation would have succeeded from the trusted forwarder, but since it's not trusted, it fails - const { signature, request } = await sign2771MetaTxRequest(owner, newMinimalForwarder, { - from: owner.address, - to: general.address, - gas: 60000, - data: await general.interface.encodeFunctionData("freezeMints"), - }); - - expect(await newMinimalForwarder.verify(request, signature)).to.equal(true); - expect(await general.isTrustedForwarder(newMinimalForwarder.address)).to.equal(false); - expect(await general.isTrustedForwarder(minimalForwarderFanSigner.address)).to.equal(true); - - // TODO: fix HardhatChaiMatchersDecodingError issue. This reverts with Ownable message as expected - let reverted = false; - try { - await expect(newMinimalForwarder.execute(request, signature)).to.emit(general, "MintsFrozen"); - } catch (error) { - reverted = true; - } - - expect(reverted).to.equal(true); - }); - - it("A validly encoded msgSender is allowed to perform operations as expected", async function () { - const { signature, request } = await sign2771MetaTxRequest(owner, minimalForwarderFanSigner, { - from: owner.address, - to: general.address, - gas: 60000, - data: await general.interface.encodeFunctionData("freezeMints"), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.emit(general, "MintsFrozen").withArgs(); - }); - }); - - describe("MintManager", function () { - it("Not encoding enough gas for an operation fails the operation", async function () { - const { signature, request } = await sign2771MetaTxRequest(mintManagerOwner, minimalForwarderFanSigner, { - from: mintManagerOwner.address, - to: mintManager.address, - gas: 2000, - data: await mintManager.interface.encodeFunctionData("transferOwnership", [fan1.address]), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith(""); - }); - - it("Mismatched signers from the from address of a request are not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(mintManagerOwner, minimalForwarderFanSigner, { - from: editionsMetadataOwner.address, - to: mintManager.address, - gas: 60000, - data: await mintManager.interface.encodeFunctionData("transferOwnership", [fan1.address]), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Mismatched data in a request is not allowed", async function () { - const { signature, request } = await sign2771MetaTxRequest(mintManagerOwner, minimalForwarderFanSigner, { - from: mintManagerOwner.address, - to: mintManager.address, - gas: 60000, - data: await mintManager.interface.encodeFunctionData("transferOwnership", [fan1.address]), - }); - - request.gas = 60001; - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(false); - - await expect(minimalForwarderFanSigner.execute(request, signature)).to.be.revertedWith( - "MinimalForwarder: signature does not match request", - ); - }); - - it("Sending a request from a non-trusted forwarder does not invoke meta transaction functionality", async function () { - // have to do this to not import OpenZeppelin minimal forwarder - const newMinimalForwarder = (await new ethers.ContractFactory( - MinimalForwarderData.abi, - MinimalForwarderData.bytecode, - fan1, - ).deploy()) as MinimalForwarder; - await newMinimalForwarder.deployed(); - - // this operation would have succeeded from the trusted forwarder, but since it's not trusted, it fails - const { signature, request } = await sign2771MetaTxRequest(mintManagerOwner, newMinimalForwarder, { - from: mintManagerOwner.address, - to: mintManager.address, - gas: 60000, - data: await mintManager.interface.encodeFunctionData("transferOwnership", [fan1.address]), - }); - - expect(await newMinimalForwarder.verify(request, signature)).to.equal(true); - expect(await mintManager.isTrustedForwarder(newMinimalForwarder.address)).to.equal(false); - expect(await mintManager.isTrustedForwarder(minimalForwarderFanSigner.address)).to.equal(true); - - // TODO: fix HardhatChaiMatchersDecodingError issue. This reverts with Ownable message as expected - let reverted = false; - try { - await expect(newMinimalForwarder.execute(request, signature)).to.emit(mintManager, "OwnershipTransferred"); - } catch (error) { - reverted = true; - } - - expect(reverted).to.equal(true); - }); - - it("A validly encoded msgSender is allowed to perform operations as expected", async function () { - const { signature, request } = await sign2771MetaTxRequest(mintManagerOwner, minimalForwarderFanSigner, { - from: mintManagerOwner.address, - to: mintManager.address, - gas: 60000, - data: await mintManager.interface.encodeFunctionData("transferOwnership", [fan1.address]), - }); - - expect(await minimalForwarderFanSigner.verify(request, signature)).to.equal(true); - - await expect(minimalForwarderFanSigner.execute(request, signature)) - .to.emit(mintManager, "OwnershipTransferred") - .withArgs(mintManagerOwner.address, fan1.address); - }); - }); - }); -}); diff --git a/test/MintManagerTest.ts b/test/MintManagerTest.ts deleted file mode 100644 index 84b6a69..0000000 --- a/test/MintManagerTest.ts +++ /dev/null @@ -1,2838 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - ERC721Editions, - ERC721EditionsDFS, - ERC721General, - ERC721GeneralSequence, - ERC721GeneralSequence__factory, - ERC721Generative, - ERC721SingleEdition, - ERC721SingleEditionDFS, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - MintManager__factory, - Observability, -} from "../types"; -import { SAMPLE_ABRIDGED_VECTOR, SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG } from "./__utils__/data"; -import { Errors } from "./__utils__/data"; -import { - generateClaim, - generateClaimWithMetaTxPackets, - generateSeriesClaim, - setupEditionsDFS, - setupGeneral, - setupGenerative, - setupMultipleEdition, - setupSingleEdition, - setupSingleEditionDFS, - setupSystem, -} from "./__utils__/helpers"; -import { getExpiredClaimTimestamp, getValidClaimTimestamp } from "./__utils__/mint"; - -describe("Mint Manager", () => { - let initialPlatformExecutor: SignerWithAddress, - additionalPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - eWETHOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - editionsOwner: SignerWithAddress, - generalOwner: SignerWithAddress, - fan1: SignerWithAddress, - randomEOA: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let observability: Observability; - let mintManager: MintManager; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let editionsImplementation: string; - let singleEditionImplementation: string; - let singleEditionDFSImplementation: string; - let generalImplementation: string; - let generalSequenceImplementation: string; - let generativeImplementation: string; - let editionsDFSImplementation: string; - - const mintFeeWei = ethers.BigNumber.from("800000000000000"); - - before(async () => { - [ - initialPlatformExecutor, - additionalPlatformExecutor, - mintManagerOwner, - editionsMetadataOwner, - eWETHOwner, - platformPaymentAddress, - editionsOwner, - generalOwner, - fan1, - randomEOA, - ] = await ethers.getSigners(); - }); - - describe("Platform Executor", function () { - it("Should be able to add a new platform executor as Owner", async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - generalSequenceImplementationAddress, - generativeImplementationAddress, - singleEditionDFSImplementationAddress, - editionsDFSImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - editionsDFSImplementation = editionsDFSImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - generalSequenceImplementation = generalSequenceImplementationAddress; - generativeImplementation = generativeImplementationAddress; - singleEditionDFSImplementation = singleEditionDFSImplementationAddress; - - const mintManagerOwnerBased = mintManager.connect(mintManagerOwner); - - await expect(mintManagerOwnerBased.addOrDeprecatePlatformExecutor(additionalPlatformExecutor.address)).to.not.be - .reverted; - expect(await mintManagerOwnerBased.isPlatformExecutor(additionalPlatformExecutor.address)).to.be.true; - }); - it("Should be able to deprecate platform executor as Owner", async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - - //Add platform executor - await expect(mintManager.addOrDeprecatePlatformExecutor(additionalPlatformExecutor.address)).to.not.be.reverted; - expect(await mintManager.isPlatformExecutor(additionalPlatformExecutor.address)).to.be.true; - - //deprecate platform executor - await expect(mintManager.addOrDeprecatePlatformExecutor(additionalPlatformExecutor.address)).to.not.be.reverted; - expect(await mintManager.isPlatformExecutor(additionalPlatformExecutor.address)).to.be.false; - }); - it("Should not be able to add Zero address as platform executor", async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - editionsImplementationAddress, - observability: observabilityInstance, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - await expect( - mintManager.addOrDeprecatePlatformExecutor(ethers.constants.AddressZero), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidExecutorChanged); - expect(await mintManager.isPlatformExecutor(ethers.constants.AddressZero)).to.be.false; - }); - it("Should not be able to add a platform executor that already exists", async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - await expect(mintManager.addOrDeprecatePlatformExecutor(additionalPlatformExecutor.address)).to.not.be.reverted; - expect(await mintManager.isPlatformExecutor(additionalPlatformExecutor.address)).to.be.true; - }); - it("Should reject all platform executor changes from non owner", async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - const mintManagerForFan1 = await mintManager.connect(fan1); - - //Add/deprecate - await expect( - mintManagerForFan1.addOrDeprecatePlatformExecutor(additionalPlatformExecutor.address), - ).to.be.revertedWith("Ownable: caller is not the owner"); - expect(await mintManager.isPlatformExecutor(additionalPlatformExecutor.address)).be.false; - }); - }); - - describe("Off-Chain Claims", function () { - let singleEdition: ERC721SingleEdition; - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - observability = observabilityInstance; - emr = emrProxy; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 10, - "Test 1", - "T1", - ); - }); - - it("Should return true for valid claim", async function () { - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.true; - }); - it("should return false for expired timestamp claim", async function () { - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getExpiredClaimTimestamp(), - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.false; - }); - it("Should return false if not PlatformExecutor", async function () { - const { signature, claim } = await generateClaim( - randomEOA, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.false; - }); - it("Should return false if maxPerVector reached", async function () { - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 1, - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.true; - const mintManagerForFan1 = mintManager.connect(fan1); - const tx = await mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }); - await tx.wait(); - const { signature: signature2, claim: claim2 } = await generateClaim( - initialPlatformExecutor, - mintManagerForFan1.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 1, - ); - expect(await mintManagerForFan1.verifyClaim(claim2, signature2, fan1.address)).to.be.false; - }); - it("Should return false if maxPerUser reached", async function () { - const offChainVectorId = "maxPerUserTestVectorId"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 1, - 0, - offChainVectorId, - "maxPerUserClaimNonce", - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.true; - const mintManagerForFan1 = mintManager.connect(fan1); - const tx = await mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }); - await tx.wait(); - const { signature: signature2, claim: claim2 } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 1, - 0, - offChainVectorId, - "maxPerUserClaimNonce1", - ); - expect(await mintManager.verifyClaim(claim2, signature2, fan1.address)).to.be.false; - }); - it("Should return false if claimNonce already used", async function () { - const claimNonce = "claimNonceUsedClaimNonce"; - const offChainVectorId = "claimNonceUsedVectorId"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - expect(await mintManager.verifyClaim(claim, signature, fan1.address)).to.be.true; - const mintManagerForFan1 = mintManager.connect(fan1); - const tx = await mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }); - await tx.wait(); - const { signature: signature2, claim: claim2 } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - expect(await mintManager.verifyClaim(claim2, signature2, fan1.address)).to.be.false; - }); - }); - - describe("Gated Mints", function () { - async function deployMetaTxnFixture() { - const maticWETHFactory = await ethers.getContractFactory("MaticWETH", fan1); - const maticWETH = await maticWETHFactory.deploy(initialPlatformExecutor.address); - await maticWETH.deployed(); - - const eWETHFactory = await ethers.getContractFactory("EthereumWETH", eWETHOwner); - const eWETH = await eWETHFactory.deploy(); - await eWETH.deployed(); - - const tx = await eWETH.mint(fan1.address, 100); - await tx.wait(); - - return { eWETH, maticWETH }; - } - describe("General721", function () { - let general: ERC721General; - const offChainVectorId = "gatedMintGeneral721VectorId"; - const offchainVectorId2 = "gatedMintGeneral721VectorId2"; - const maxPerVector = 10; - const maxPerUser = 10; - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - null, - null, - false, - false, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - "Test 1", - "T1", - ); - }); - it("Should be able to mint one to one recipient", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce1"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect(mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address)).to.be.revertedWithCustomError( - mintManager, - Errors.MintFeeTooLow, - ); - await expect( - mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(general, "Transfer"); - }); - - it("User limit is based on claimer address, and others can ferry mints", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonceA"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - 1, - 0, - offchainVectorId2, - claimNonce, - ); - const mintManagerNotForFan = mintManager.connect(generalOwner); - await expect( - mintManagerNotForFan.gatedSeriesMint(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(general, "Transfer"); - - const claimNonce2 = "gatedMintGeneral721ClaimNonceB"; - const { signature: signature2, claim: claim2 } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - 1, - 0, - offchainVectorId2, - claimNonce2, - ); - const mintManagerForFan = mintManager.connect(fan1); - await expect( - mintManagerForFan.gatedSeriesMint(claim2, signature2, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidClaim); - }); - - it("Cannot mint with an unsafe mint recipient (tx not sent by claimer and to a recipient that's not the claimer)", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonceC"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerNotForFan = mintManager.connect(generalOwner); - await expect( - mintManagerNotForFan.gatedSeriesMint(claim, signature, generalOwner.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.UnsafeMintRecipient); - }); - - it("Should be able to mint multiple to one recipient", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce2"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - getValidClaimTimestamp(), - "0.01", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address, { - value: ethers.utils.parseEther("0.09"), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect(mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address)).to.be.revertedWithCustomError( - mintManager, - Errors.InvalidPaymentAmount, - ); - await expect( - mintManagerForFan1.gatedSeriesMint(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint).add(ethers.utils.parseEther("0.09")), - }), - ).to.emit(general, "Transfer"); - }); - }); - describe("General721 Series claims (choose token)", function () { - let general: ERC721General; - const offChainVectorId = "gatedSeriesMintVectorId"; - const maxPerVector = 10; - const maxPerUser = 10; - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - mintManagerOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - null, - null, - false, - false, - 10, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - "Test 1", - "T1", - ); - }); - it("Should be able to mint a chosen token to a recipient", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce1"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 1, - getValidClaimTimestamp(), - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - // expect(await mintManager.verifySeriesClaim(claim, signature, fan1.address, [1])).to.be.true; - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [1]), - ).to.be.revertedWithCustomError(mintManager, Errors.MintFeeTooLow); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [1], { value: mintFeeWei }), - ).to.emit(general, "Transfer"); - }); - it("Should be able to mint multiple chosen tokens to one recipient", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce2"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 2, - getValidClaimTimestamp(), - "0.01", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [2, 3], { - value: mintFeeWei.mul(2), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [2, 3], { - value: ethers.utils.parseEther("0.02"), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [2, 3]), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [2, 3], { - value: mintFeeWei.mul(2).add(ethers.utils.parseEther("0.02")), - }), - ).to.emit(general, "Transfer"); - }); - - it("Should not be able to mint chosen token that's already minted", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce3"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 2, - getValidClaimTimestamp(), - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4, 2])).to.be.false; - await expect( - mintManagerForFan1.gatedSeriesMintChooseToken(claim, signature, fan1.address, [4, 2], { - value: mintFeeWei.mul(2), - }), - ).to.be.revertedWith("ERC721: token minted"); - }); - - /* - it("Invalid claim signer should fail", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce4"; - const { signature, claim } = await generateSeriesClaim( - generalOwner, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 2, - getValidClaimTimestamp(), - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4])).to.be.false; - }); - - it("Hitting the max per user limit should fail", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce5"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 3, - getValidClaimTimestamp(), - "0", - maxPerVector, - 4, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4, 5])).to.be.false; - }); - - it("Hitting the max per vector limit should fail", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce6"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 5, - getValidClaimTimestamp(), - "0", - 4, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4, 5])).to.be.false; - }); - - it("Expired claim should fail", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce7"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 1, - "100", - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4])).to.be.false; - }); - - it("Claim with taken nonce should fail", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce1"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 1, - getValidClaimTimestamp(), - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4])).to.be.false; - }); - it("Cannot mint more tokens than the maxPerTxn", async function () { - const claimNonce = "gatedMintGeneral721ClaimNonce9"; - const { signature, claim } = await generateSeriesClaim( - initialPlatformExecutor, - mintManager.address, - general.address, - fan1.address, - generalOwner.address, - 0, - getValidClaimTimestamp(), - "0", - maxPerVector, - maxPerUser, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - // expect(await mintManagerForFan1.verifySeriesClaim(claim, signature, fan1.address, [4])).to.be.false; - }); - */ - }); - describe("Edition721", function () { - describe("Single Edition", function () { - let singleEdition: ERC721SingleEdition, mintManager: MintManager; - const offChainVectorId = "gatedMintEditions721VectorId"; - const maxPerVector = 10; - const maxPerUser = 10; - const editionSize = 10; - - async function deploySingleEditionFixture() { - //Deploy Minimal Forwarder - const minimalForwarderFactory = await ethers.getContractFactory("MinimalForwarder"); - const minimalForwarder = await minimalForwarderFactory.deploy(); - await minimalForwarder.deployed(); - - //Deploy Mint Manager - const mintManagerFactory = await ethers.getContractFactory("MintManager"); - const mintManager = await mintManagerFactory.deploy(); - await mintManager.deployed(); - const encodedFn = mintManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - mintManagerOwner.address, - minimalForwarder.address, - initialPlatformExecutor.address, - mintFeeWei, - ]); - - const mintManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const mintManagerProxy = await mintManagerProxyFactory.deploy(mintManager.address, encodedFn); - await mintManagerProxy.deployed(); - - // deploy AuctionManager - const auctionManagerFactory = await ethers.getContractFactory("AuctionManager"); - const auctionManager = await auctionManagerFactory.deploy(); - await auctionManager.deployed(); - const amEncodedFn = auctionManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - minimalForwarder.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - ]); - - const auctionManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const auctionManagerProxy = await auctionManagerProxyFactory.deploy(auctionManager.address, amEncodedFn); - await auctionManagerProxy.deployed(); - - //Deploy EMR - const emrFactory = await ethers.getContractFactory("EditionsMetadataRenderer"); - const emr = await emrFactory.deploy(); - await emr.deployed(); - const emrEncodedFn = emr.interface.encodeFunctionData("initialize", [editionsMetadataOwner.address]); - - const emrProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const emrProxy = await emrProxyFactory.deploy(emr.address, emrEncodedFn); - await emrProxy.deployed(); - - //Deploy Editions - const editionsFactory = await ethers.getContractFactory("ERC721Editions"); - const editionsImpl = await editionsFactory.deploy(); - await editionsImpl.deployed(); - - //Deploy Single Edition - const singleEditionFactory = await ethers.getContractFactory("ERC721SingleEdition"); - const singleEditionImpl = await singleEditionFactory.deploy(); - await singleEditionImpl.deployed(); - - //Deploy General - const generalFactory = await ethers.getContractFactory("ERC721General"); - const generalImpl = await generalFactory.deploy(); - await generalImpl.deployed(); - - const mintManagerWithOwner = MintManager__factory.connect(mintManagerProxy.address, mintManagerOwner); - - const singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManagerProxy.address, - minimalForwarder.address, - emrProxy.address, - editionsOwner, - editionSize, - "name", - "SYM", - ); - - const maticWETHFactory = await ethers.getContractFactory("MaticWETH", fan1); - const maticWETH = await maticWETHFactory.deploy(initialPlatformExecutor.address); - await maticWETH.deployed(); - - return { mintManagerWithOwner, singleEdition, maticWETH }; - } - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - editionSize, - "name", - "SYM", - ); - }); - it("Should be able to mint one to one recipient", async function () { - const claimNonce = "gatedMintEdition721ClaimNonce1"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(singleEdition, "Transfer"); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(1); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(1); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint multiple to one recipient", async function () { - const claimNonce = "gatedMintEdition721ClaimNonce2"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(singleEdition, "Transfer"); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint with Native Currency", async function () { - const { mintManagerWithOwner, singleEdition } = await deploySingleEditionFixture(); - const offChainVectorId = "gatedMintNativeVectorId"; - const claimNonce = "gatedMintNativePay"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManagerWithOwner.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0.00001", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManagerWithOwner.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: claim.pricePerToken, - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address), - ).to.be.revertedWithCustomError(mintManagerForFan1, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint).add(claim.pricePerToken), - }), - ).to.emit(singleEdition, "Transfer"); - expect((await mintManagerWithOwner.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal( - 1, - ); - expect( - ( - await mintManagerWithOwner.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address) - ).toNumber(), - ).to.be.equal(1); - expect(await mintManagerWithOwner.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint with ERC20", async function () { - const { mintManagerWithOwner, singleEdition, maticWETH } = await deploySingleEditionFixture(); - const offChainVectorId = "gatedMintERC20VectorId"; - const claimNonce = "gatedMintERC20"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManagerWithOwner.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - maticWETH.address, - ); - const mintManagerForFan1 = mintManagerWithOwner.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(singleEdition, "Transfer"); - expect((await mintManagerForFan1.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal( - 9, - ); - expect( - ( - await mintManagerForFan1.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address) - ).toNumber(), - ).to.be.equal(9); - expect(await mintManagerForFan1.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it.skip("Should be able to mint with meta-tx payment", async function () { - const { maticWETH } = await deployMetaTxnFixture(); - const claimNonce = "gatedMintPacketClaimNonce2"; - const { signature, claim } = await generateClaimWithMetaTxPackets( - initialPlatformExecutor, - fan1, - mintManager.address, - singleEdition.address, - editionsOwner.address, - maticWETH.address, - getValidClaimTimestamp(), - "1", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - console.log({ signature, claim, mintManagerForFan1 }); - /* - await expect(mintManagerForFan1.gatedMintPaymentPacketEdition721(claim, signature, fan1.address)).to.emit( - singleEdition, - "Transfer", - ); - */ - // expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - // expect( - // (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - // ).to.be.equal(10); - // expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - // claim.claimNonce, - // ); - }); - it("Should not be able to mint if claim nonce already used", async function () { - const claimNonce = "gatedMintEdition721ClaimNonce2"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManagerForFan1, Errors.InvalidClaim); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - }); - it("Should not be be able to mint after max per vector reached", async function () { - const claimNonce = "gatedMintEdition721ClaimNonce3"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManagerForFan1, Errors.InvalidClaim); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.not.include( - claim.claimNonce, - ); - }); - it("Should not be be able to mint after edition size is reached", async function () { - const claimNonce = "gatedMintEdition721ClaimNonce4"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - singleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(singleEdition, Errors.SoldOut); - }); - }); - describe("Multiple Editions", function () { - let multipleEdition: ERC721Editions, mintManager: MintManager; - const offChainVectorId = "gatedMintEdition721VectorId"; - const maxPerVector = 10; - const maxPerUser = 10; - const editionSize = 10; - - async function deployMultipleEditionFixture() { - //Deploy Minimal Forwarder - const minimalForwarderFactory = await ethers.getContractFactory("MinimalForwarder"); - const minimalForwarder = await minimalForwarderFactory.deploy(); - await minimalForwarder.deployed(); - - //Deploy Mint Manager - const mintManagerFactory = await ethers.getContractFactory("MintManager"); - const mintManager = await mintManagerFactory.deploy(); - await mintManager.deployed(); - const encodedFn = mintManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - mintManagerOwner.address, - minimalForwarder.address, - initialPlatformExecutor.address, - mintFeeWei, - ]); - - const mintManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const mintManagerProxy = await mintManagerProxyFactory.deploy(mintManager.address, encodedFn); - await mintManagerProxy.deployed(); - - // deploy AuctionManager - const auctionManagerFactory = await ethers.getContractFactory("AuctionManager"); - const auctionManager = await auctionManagerFactory.deploy(); - await auctionManager.deployed(); - const amEncodedFn = auctionManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - minimalForwarder.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - ]); - - const auctionManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const auctionManagerProxy = await auctionManagerProxyFactory.deploy(auctionManager.address, amEncodedFn); - await auctionManagerProxy.deployed(); - - //Deploy EMR - const emrFactory = await ethers.getContractFactory("EditionsMetadataRenderer"); - const emr = await emrFactory.deploy(); - await emr.deployed(); - const emrEncodedFn = emr.interface.encodeFunctionData("initialize", [editionsMetadataOwner.address]); - - const emrProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const emrProxy = await emrProxyFactory.deploy(emr.address, emrEncodedFn); - await emrProxy.deployed(); - - //Deploy Editions - const editionsFactory = await ethers.getContractFactory("ERC721Editions"); - const editionsImpl = await editionsFactory.deploy(); - await editionsImpl.deployed(); - - //Deploy Single Edition - const singleEditionFactory = await ethers.getContractFactory("ERC721SingleEdition"); - const singleEditionImpl = await singleEditionFactory.deploy(); - await singleEditionImpl.deployed(); - - //Deploy General - const generalFactory = await ethers.getContractFactory("ERC721General"); - const generalImpl = await generalFactory.deploy(); - await generalImpl.deployed(); - - const mintManagerWithOwner = MintManager__factory.connect(mintManagerProxy.address, mintManagerOwner); - - const multipleEdition = await setupMultipleEdition( - observability.address, - editionsImplementation, - mintManagerProxy.address, - auctionManagerProxy.address, - minimalForwarder.address, - emrProxy.address, - editionsOwner, - editionSize, - "Test 1", - "T1", - ); - - const maticWETHFactory = await ethers.getContractFactory("MaticWETH", fan1); - const maticWETH = await maticWETHFactory.deploy(initialPlatformExecutor.address); - await maticWETH.deployed(); - - return { mintManagerWithOwner, multipleEdition, maticWETH }; - } - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - multipleEdition = await setupMultipleEdition( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 10, - "Test 1", - "T1", - ); - }); - it("Should be able to mint one to one recipient", async function () { - const claimNonce = "gatedMintEditions721ClaimNonce1"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(multipleEdition, "Transfer"); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(1); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(1); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint multiple to one recipient", async function () { - const claimNonce = "gatedMintEditions721ClaimNonce2"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(multipleEdition, "Transfer"); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint with Native Currency", async function () { - const { mintManagerWithOwner, multipleEdition } = await deployMultipleEditionFixture(); - const offChainVectorId = "gatedMintNativeVectorId"; - const claimNonce = "gatedMintNativePay"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManagerWithOwner.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0.00001", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManagerWithOwner.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint).add(claim.pricePerToken), - }), - ).to.emit(multipleEdition, "Transfer"); - expect((await mintManagerWithOwner.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal( - 1, - ); - expect( - ( - await mintManagerWithOwner.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address) - ).toNumber(), - ).to.be.equal(1); - expect(await mintManagerWithOwner.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should be able to mint with ERC20", async function () { - const { mintManagerWithOwner, multipleEdition, maticWETH } = await deployMultipleEditionFixture(); - const offChainVectorId = "gatedMintERC20VectorId"; - const claimNonce = "gatedMintERC20"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManagerWithOwner.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 9, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - maticWETH.address, - ); - const mintManagerForFan1 = mintManagerWithOwner.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.emit(multipleEdition, "Transfer"); - expect((await mintManagerForFan1.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal( - 9, - ); - expect( - ( - await mintManagerForFan1.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address) - ).toNumber(), - ).to.be.equal(9); - expect(await mintManagerForFan1.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - }); - it("Should not be able to mint if claim nonce already used", async function () { - const claimNonce = "gatedMintEditions721ClaimNonce2"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.include( - claim.claimNonce, - ); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManagerForFan1, Errors.InvalidClaim); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal(10); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - }); - it("Should not be able to mint after max per vector reached", async function () { - const claimNonce = "gatedMintEditions721ClaimNonce3"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - maxPerVector, - maxPerUser, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - expect((await mintManager.offchainVectorsClaimState(claim.offchainVectorId)).toNumber()).to.be.equal( - claim.maxClaimableViaVector, - ); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(mintManagerForFan1, Errors.InvalidClaim); - expect( - (await mintManager.getNumClaimedPerUserOffchainVector(claim.offchainVectorId, fan1.address)).toNumber(), - ).to.be.equal(10); - expect(await mintManager.getClaimNoncesUsedForOffchainVector(claim.offchainVectorId)).to.not.include( - claim.claimNonce, - ); - }); - it("Should be not be able to mint after edition size is reached", async function () { - const claimNonce = "gatedMintEditions721ClaimNonce4"; - const { signature, claim } = await generateClaim( - initialPlatformExecutor, - mintManager.address, - multipleEdition.address, - fan1.address, - editionsOwner.address, - getValidClaimTimestamp(), - "0", - 1, - 0, - 0, - 0, - offChainVectorId, - claimNonce, - ); - const mintManagerForFan1 = mintManager.connect(fan1); - await expect( - mintManagerForFan1.gatedMintEdition721(claim, signature, fan1.address, { - value: mintFeeWei.mul(claim.numTokensToMint), - }), - ).to.be.revertedWithCustomError(multipleEdition, Errors.SoldOut); - }); - }); - }); - }); - - describe("Vectors", function () { - let singleEdition: ERC721SingleEdition, mintManager: MintManager; - const editionSize = 10; - let vectorId = 1; - before(async () => { - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - auctionManagerProxy, - observability: observabilityInstance, - editionsImplementationAddress, - singleEditionImplementationAddress, - generalImplementationAddress, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - editionsOwner, - ); - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - auctionManager = auctionManagerProxy; - emr = emrProxy; - observability = observabilityInstance; - editionsImplementation = editionsImplementationAddress; - singleEditionImplementation = singleEditionImplementationAddress; - generalImplementation = generalImplementationAddress; - singleEdition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - editionSize, - "Test 1", - "T1", - ); - }); - it("Should be able to create new vector for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await expect(mintManagerForEditionOwner.createAbridgedVector(vector)).to.emit( - mintManagerForEditionOwner, - "EditionVectorCreated", - ); - vectorId += 1; - }); - it.skip("Should not be able to update vector when frozen", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - /* - await expect(mintManagerForEditionOwner.updateAbridgedVector(vectorId, vector)).to.be.revertedWithCustomError( - mintManagerForEditionOwner, - Errors.VectorUpdateActionFrozen, - ); - */ - vectorId += 1; - }); - it("Should be able to update vector for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true, 0, 100); - const vectorUpdateConfig = SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG({ - updateMaxTotalClaimableViaVector: true, - updateTokenLimitPerTx: true, - }); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - await expect( - mintManagerForEditionOwner.updateAbridgedVector( - vectorId, - { ...vector, maxUserClaimableViaVector: 57, tokenLimitPerTx: 32938 }, - vectorUpdateConfig, - true, - 10009, - ), - ).to.emit(mintManagerForEditionOwner, "VectorUpdated"); - expect((await mintManagerForEditionOwner.getAbridgedVector(vectorId)).tokenLimitPerTx).to.equal(32938); - expect((await mintManagerForEditionOwner.getAbridgedVector(vectorId)).maxTotalClaimableViaVector).to.equal(100); - expect((await mintManagerForEditionOwner.getAbridgedVector(vectorId)).maxUserClaimableViaVector).to.not.equal(57); - expect((await mintManagerForEditionOwner.getAbridgedVector(vectorId)).maxUserClaimableViaVector).to.equal(0); - expect(await mintManagerForEditionOwner.getAbridgedVectorMetadata(vectorId)).to.eql([ - false, - ethers.BigNumber.from(0), - ]); - vectorId += 1; - }); - it.skip("Should not be able to delete vector when frozen for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - await expect(mintManagerForEditionOwner.deleteAbridgedVector(vectorId)).to.be.revertedWithCustomError( - mintManagerForEditionOwner, - Errors.VectorUpdateActionFrozen, - ); - vectorId += 1; - }); - it("Should be able to delete vector for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - await expect(mintManagerForEditionOwner.deleteAbridgedVector(vectorId)).to.emit( - mintManagerForEditionOwner, - "VectorDeleted", - ); - vectorId += 1; - }); - it.skip("Should not be able to pause vector when frozen for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - /* - await expect(mintManagerForEditionOwner.pauseVector(vectorId)).to.be.revertedWithCustomError( - mintManagerForEditionOwner, - Errors.VectorUpdateActionFrozen, - ); - */ - vectorId += 1; - }); - it.skip("Should be able to pause vector for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - /* - await expect(mintManagerForEditionOwner.pauseVector(vectorId)).to.emit( - mintManagerForEditionOwner, - "VectorPausedOrUnpaused", - ); - */ - vectorId += 1; - }); - it.skip("Should be able to pause vector for contract by Owner", async () => { - const mintManagerForEditionOwner = await mintManager.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - /* - await expect(mintManagerForEditionOwner.unpauseVector(vectorId)).to.emit( - mintManagerForEditionOwner, - "VectorPausedOrUnpaused", - ); - */ - vectorId += 1; - }); - it("Should reject all vector interactions for contract by non Owner", async () => { - const vector = SAMPLE_ABRIDGED_VECTOR(singleEdition.address, editionsOwner.address, true, 0, 100); - const vectorUpdateConfig = SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG({ updateMaxTotalClaimableViaVector: true }); - const mintManagerForEditionOwner = mintManager.connect(editionsOwner); - await (await mintManagerForEditionOwner.createAbridgedVector(vector)).wait(); - mintManager = mintManager.connect(fan1); - await expect(mintManager.createAbridgedVector(vector)).to.be.revertedWithCustomError( - mintManager, - Errors.Unauthorized, - ); - await expect( - mintManager.updateAbridgedVector(vectorId, vector, vectorUpdateConfig, false, 0), - ).to.be.revertedWithCustomError(mintManager, Errors.Unauthorized); - await expect(mintManager.deleteAbridgedVector(vectorId)).to.be.revertedWithCustomError( - mintManager, - Errors.Unauthorized, - ); - - mintManager = mintManager.connect(editionsOwner); - }); - }); - - describe("Vector Mints", function () { - async function vectorMintsFixture() { - //Deploy Minimal Forwarder - const minimalForwarderFactory = await ethers.getContractFactory("MinimalForwarder"); - const minimalForwarder = await minimalForwarderFactory.deploy(); - await minimalForwarder.deployed(); - - //Deploy Mint Manager - const mintManagerFactory = await ethers.getContractFactory("MintManager"); - const mintManager = await mintManagerFactory.deploy(); - await mintManager.deployed(); - const encodedFn = mintManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - mintManagerOwner.address, - minimalForwarder.address, - initialPlatformExecutor.address, - mintFeeWei, - ]); - - const mintManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const mintManagerProxy = await mintManagerProxyFactory.deploy(mintManager.address, encodedFn); - await mintManagerProxy.deployed(); - - // deploy AuctionManager - const auctionManagerFactory = await ethers.getContractFactory("AuctionManager"); - const auctionManager = await auctionManagerFactory.deploy(); - await auctionManager.deployed(); - const amEncodedFn = auctionManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress.address, - minimalForwarder.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - ]); - - const auctionManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const auctionManagerProxy = await auctionManagerProxyFactory.deploy(auctionManager.address, amEncodedFn); - await auctionManagerProxy.deployed(); - - //Deploy EMR - const emrFactory = await ethers.getContractFactory("EditionsMetadataRenderer"); - const emr = await emrFactory.deploy(); - await emr.deployed(); - const emrEncodedFn = emr.interface.encodeFunctionData("initialize", [editionsMetadataOwner.address]); - - const emrProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const emrProxy = await emrProxyFactory.deploy(emr.address, emrEncodedFn); - await emrProxy.deployed(); - - //Deploy Editions - const editionsFactory = await ethers.getContractFactory("ERC721Editions"); - const editionsImpl = await editionsFactory.deploy(); - await editionsImpl.deployed(); - - //Deploy Single Edition - const singleEditionFactory = await ethers.getContractFactory("ERC721SingleEdition"); - const singleEditionImpl = await singleEditionFactory.deploy(); - await singleEditionImpl.deployed(); - - //Deploy General - const generalFactory = await ethers.getContractFactory("ERC721General"); - const generalImpl = await generalFactory.deploy(); - await generalImpl.deployed(); - - const mintManagerWithOwner = MintManager__factory.connect(mintManagerProxy.address, mintManagerOwner); - - const observabilityFactory = await ethers.getContractFactory("Observability"); - const observability = await observabilityFactory.deploy(); - await observability.deployed(); - - const generalERC721 = await setupGeneral( - observability.address, - generalImplementation, - minimalForwarder.address, - mintManagerProxy.address, - generalOwner, - null, - null, - false, - false, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - "Test 1", - "T1", - ); - - const singleEditionERC721 = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManagerProxy.address, - minimalForwarder.address, - emr.address, - editionsOwner, - 10, - "Test 1", - "T1", - ); - - const multipleEditionERC721 = await setupMultipleEdition( - observability.address, - editionsImplementation, - mintManagerProxy.address, - auctionManagerProxy.address, - minimalForwarder.address, - emrProxy.address, - editionsOwner, - 10, - "Test 1", - "T1", - ); - - return { mintManagerWithOwner, generalERC721, singleEditionERC721, multipleEditionERC721 }; - } - describe("Edition721", function () { - describe("Single Edition", function () { - it("Should be able to mint one to one recipient", async function () { - const { mintManagerWithOwner, singleEditionERC721 } = await vectorMintsFixture(); - const mintManagerForEditionOwner = await mintManagerWithOwner.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR( - singleEditionERC721.address, - editionsOwner.address, - true, - 0, - 5, - 5, - 0, - 0, - 5, - ethers.utils.parseEther("0.00000001"), - ); - await expect(mintManagerForEditionOwner.createAbridgedVector(vector)).to.emit( - mintManagerForEditionOwner, - "EditionVectorCreated", - ); - const mintManagerForFan1 = await mintManagerWithOwner.connect(fan1); - await expect(mintManagerForFan1.vectorMint721(1, 1, fan1.address)).to.be.revertedWithCustomError( - mintManagerForFan1, - Errors.InvalidPaymentAmount, - ); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: ethers.utils.parseEther("0.00000001"), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.emit(singleEditionERC721, "Transfer"); - }); - }); - describe("Multiple Edition", function () { - let mintManagerForFan1: MintManager; - let meERC721: ERC721Editions; - - it("Sending invalid mint fee should fail", async function () { - const { mintManagerWithOwner, multipleEditionERC721 } = await vectorMintsFixture(); - const mintManagerForEditionOwner = mintManagerWithOwner.connect(editionsOwner); - const vector = SAMPLE_ABRIDGED_VECTOR(multipleEditionERC721.address, editionsOwner.address, true); - await expect(mintManagerForEditionOwner.createAbridgedVector(vector)).to.emit( - mintManagerForEditionOwner, - "EditionVectorCreated", - ); - mintManagerForFan1 = await mintManagerWithOwner.connect(fan1); - meERC721 = multipleEditionERC721; - await expect(mintManagerForFan1.vectorMint721(1, 1, fan1.address)).to.be.revertedWithCustomError( - mintManager, - Errors.MintFeeTooLow, - ); - }); - - it("Should be able to mint one to one recipient", async function () { - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { value: mintFeeWei.sub(1) }), - ).to.be.revertedWithCustomError(mintManager, Errors.MintFeeTooLow); - - const mintManagerForPlatform = mintManagerForFan1.connect(mintManagerOwner); - await expect(mintManagerForPlatform.updatePlatformAndMintFee(mintManagerOwner.address, mintFeeWei.sub(1))).to - .not.be.reverted; - - await expect(mintManagerForFan1.vectorMint721(1, 1, fan1.address, { value: mintFeeWei.sub(1) })).to.emit( - meERC721, - "Transfer", - ); - }); - }); - - describe("Mint fee updates", function () { - it("Non-platform accounts can't update the mint fee", async function () { - const { mintManagerWithOwner } = await vectorMintsFixture(); - - let mintManagerUnauthorized = mintManagerWithOwner.connect(fan1); - await expect( - mintManagerUnauthorized.updatePlatformAndMintFee(mintManagerOwner.address, 1), - ).to.be.revertedWith("Ownable: caller is not the owner"); - - mintManagerUnauthorized = mintManagerUnauthorized.connect(editionsOwner); - await expect( - mintManagerUnauthorized.updatePlatformAndMintFee(mintManagerOwner.address, 1), - ).to.be.revertedWith("Ownable: caller is not the owner"); - - mintManagerUnauthorized = mintManagerUnauthorized.connect(editionsMetadataOwner); - await expect( - mintManagerUnauthorized.updatePlatformAndMintFee(mintManagerOwner.address, 1), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - }); - }); - - describe("Series vector mint", async function () { - it("Should be able to mint one to one recipient", async function () { - const { mintManagerWithOwner, generalERC721 } = await vectorMintsFixture(); - const mintManagerForGeneralOwner = await mintManagerWithOwner.connect(generalOwner); - const vector = SAMPLE_ABRIDGED_VECTOR( - generalERC721.address, - generalOwner.address, - false, - 0, - 10, - 5, - 0, - 0, - 5, - ethers.utils.parseEther("0.00000001"), - ); - await expect(mintManagerForGeneralOwner.createAbridgedVector(vector)).to.emit( - mintManagerForGeneralOwner, - "SeriesVectorCreated", - ); - const mintManagerForFan1 = await mintManagerWithOwner.connect(fan1); - await expect(mintManagerForFan1.vectorMint721(1, 1, fan1.address)).to.be.revertedWithCustomError( - mintManagerForFan1, - Errors.InvalidPaymentAmount, - ); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: ethers.utils.parseEther("0.00000001"), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.emit(generalERC721, "Transfer"); - await expect( - mintManagerForFan1.vectorMint721(1, 4, fan1.address, { - value: mintFeeWei.mul(4).add(ethers.utils.parseEther("0.00000001").mul(4)), - }), - ) - .to.emit(generalERC721, "Transfer") - .to.emit(generalERC721, "Transfer") - .to.emit(generalERC721, "Transfer") - .to.emit(generalERC721, "Transfer"); - - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.OnchainVectorMintGuardFailed); - }); - - it("User limit is based on mint recipent", async function () { - const { mintManagerWithOwner, generalERC721 } = await vectorMintsFixture(); - const mintManagerForGeneralOwner = await mintManagerWithOwner.connect(generalOwner); - const vector = SAMPLE_ABRIDGED_VECTOR( - generalERC721.address, - generalOwner.address, - false, - 0, - 10, - 1, - 0, - 0, - 5, - ethers.utils.parseEther("0.00000001"), - ); - await expect(mintManagerForGeneralOwner.createAbridgedVector(vector)).to.emit( - mintManagerForGeneralOwner, - "SeriesVectorCreated", - ); - await expect( - mintManagerForGeneralOwner.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.emit(generalERC721, "Transfer"); - await expect( - mintManagerForGeneralOwner.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.OnchainVectorMintGuardFailed); - await expect( - mintManagerForGeneralOwner.vectorMint721(1, 2, generalOwner.address, { - value: mintFeeWei.mul(2).add(ethers.utils.parseEther("0.00000002")), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.OnchainVectorMintGuardFailed); - }); - - it("Should be able to mint one to one recipient", async function () { - const { mintManagerWithOwner, generalERC721 } = await vectorMintsFixture(); - const mintManagerForGeneralOwner = await mintManagerWithOwner.connect(generalOwner); - - /* - const allowlistedAddresses = [ - fan1.address, - editionsOwner.address, - generalOwner.address, - editionsMetadataOwner.address, - ]; - const leaves = allowlistedAddresses.map(x => ethers.utils.keccak256(x)); - const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); - const root = tree.getRoot().toString("hex"); - const hashedFan1Address = keccak256(fan1.address); - const proof = tree.getHexProof(hashedFan1Address); - */ - - const vector = SAMPLE_ABRIDGED_VECTOR( - generalERC721.address, - generalOwner.address, - false, - 0, - 5, - 5, - 0, - 0, - 5, - ethers.utils.parseEther("0.00000001"), - ethers.constants.HashZero, - ); - await expect(mintManagerForGeneralOwner.createAbridgedVector(vector)).to.emit( - mintManagerForGeneralOwner, - "SeriesVectorCreated", - ); - const mintManagerForFan1 = await mintManagerWithOwner.connect(fan1); - await expect(mintManagerForFan1.vectorMint721(1, 1, fan1.address)).to.be.revertedWithCustomError( - mintManagerForFan1, - Errors.InvalidPaymentAmount, - ); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: ethers.utils.parseEther("0.00000001"), - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - await expect( - mintManagerForFan1.vectorMint721(1, 1, fan1.address, { - value: mintFeeWei.add(ethers.utils.parseEther("0.00000001")), - }), - ).to.emit(generalERC721, "Transfer"); - - const mintManagerForNonAllowlistedAccount = mintManagerForFan1.connect(platformPaymentAddress); - await expect( - mintManagerForNonAllowlistedAccount.vectorMint721(1, 1, platformPaymentAddress.address, { - value: mintFeeWei, - }), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - }); - }); - - describe("Direct mint vectors metadata", function () { - let mintManagerForEditionOwner: MintManager; - let mintManagerForGeneralOwner: MintManager; - let sampleVector: any; - - beforeEach(async function () { - const { mintManagerWithOwner, generalERC721, singleEditionERC721 } = await vectorMintsFixture(); - mintManagerForEditionOwner = await mintManagerWithOwner.connect(editionsOwner); - mintManagerForGeneralOwner = await mintManagerWithOwner.connect(generalOwner); - - const vector1 = SAMPLE_ABRIDGED_VECTOR(generalERC721.address, generalOwner.address, false); - await expect(mintManagerForGeneralOwner.createAbridgedVector(vector1)).to.emit( - mintManagerForGeneralOwner, - "SeriesVectorCreated", - ); - sampleVector = vector1; - - const vector2 = SAMPLE_ABRIDGED_VECTOR(singleEditionERC721.address, editionsOwner.address, true, 0); - await expect(mintManagerForEditionOwner.createAbridgedVector(vector2)).to.emit( - mintManagerForEditionOwner, - "EditionVectorCreated", - ); - - const vector3 = SAMPLE_ABRIDGED_VECTOR( - generalERC721.address, - generalOwner.address, - false, - 0, - 5, - 5, - 0, - 0, - 5, - ethers.utils.parseEther("0"), - ethers.constants.HashZero, - ); - await expect(mintManagerForGeneralOwner.createAbridgedVector(vector3)).to.emit( - mintManagerForGeneralOwner, - "SeriesVectorCreated", - ); - - const vector4 = SAMPLE_ABRIDGED_VECTOR( - singleEditionERC721.address, - editionsOwner.address, - true, - 0, - 5, - 5, - 0, - 0, - 5, - ethers.utils.parseEther("0"), - ethers.constants.HashZero, - ); - await expect(mintManagerForEditionOwner.createAbridgedVector(vector4)).to.emit( - mintManagerForEditionOwner, - "EditionVectorCreated", - ); - }); - - it("Direct mint vector metadata cannot be set by non contract owners", async function () { - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(1, true, 0)).to.be.revertedWithCustomError( - mintManagerForEditionOwner, - Errors.Unauthorized, - ); - - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(3, true, 0)).to.be.revertedWithCustomError( - mintManagerForEditionOwner, - Errors.Unauthorized, - ); - - await expect(mintManagerForGeneralOwner.setAbridgedVectorMetadata(2, true, 0)).to.be.revertedWithCustomError( - mintManagerForGeneralOwner, - Errors.Unauthorized, - ); - - await expect(mintManagerForGeneralOwner.setAbridgedVectorMetadata(4, true, 0)).to.be.revertedWithCustomError( - mintManagerForGeneralOwner, - Errors.Unauthorized, - ); - }); - - it("Direct mint vector metadata can be set (composed) and read (decomposed) correctly", async function () { - await expect(mintManagerForGeneralOwner.setAbridgedVectorMetadata(1, true, 589384)) - .to.emit(mintManagerForGeneralOwner, "VectorMetadataSet") - .withArgs(1, true, 589384); - - expect(await mintManagerForGeneralOwner.getAbridgedVectorMetadata(1)).to.eql([ - true, - ethers.BigNumber.from(589384), - ]); - }); - - it("Pausing a direct mint vector pauses all types of direct mints", async function () { - // mints paused - const vectorUpdateConfig = SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG({ - updateMetadata: true, - updateMaxUserClaimableViaVector: true, - }); - await expect( - mintManagerForGeneralOwner.updateAbridgedVector( - 1, - { ...sampleVector, maxUserClaimableViaVector: 57 }, - vectorUpdateConfig, - true, - 1908, - ), - ) - .to.emit(mintManagerForGeneralOwner, "VectorMetadataSet") - .withArgs(1, true, 1908); - - await expect(mintManagerForGeneralOwner.setAbridgedVectorMetadata(3, true, 0)) - .to.emit(mintManagerForGeneralOwner, "VectorMetadataSet") - .withArgs(3, true, 0); - - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(2, true, 0)) - .to.emit(mintManagerForEditionOwner, "VectorMetadataSet") - .withArgs(2, true, 0); - - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(4, true, 0)) - .to.emit(mintManagerForEditionOwner, "VectorMetadataSet") - .withArgs(4, true, 0); - - await expect( - mintManagerForGeneralOwner.vectorMint721(1, 1, fan1.address, { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManagerForGeneralOwner, Errors.MintPaused); - - await expect( - mintManagerForGeneralOwner.vectorMint721(3, 1, fan1.address, { - value: mintFeeWei, - }), - ).to.be.revertedWithCustomError(mintManagerForGeneralOwner, Errors.MintPaused); - - await expect( - mintManagerForEditionOwner.vectorMint721(2, 1, fan1.address, { value: mintFeeWei }), - ).to.be.revertedWithCustomError(mintManagerForEditionOwner, Errors.MintPaused); - - /* - vectorMint721WithAllowlist DEPRECATED - await expect( - mintManagerForEditionOwner.vectorMint721WithAllowlist(4, 1, fan1.address, proofForFan, { - value: mintFeeWei, - }), - ).to.be.revertedWithCustomError(mintManagerForEditionOwner, Errors.MintPaused); - */ - - // mints unpaused - await expect(mintManagerForGeneralOwner.setAbridgedVectorMetadata(1, false, 0)) - .to.emit(mintManagerForGeneralOwner, "VectorMetadataSet") - .withArgs(1, false, 0); - - await expect( - mintManagerForGeneralOwner.updateAbridgedVector( - 3, - sampleVector, - SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG({ updateMetadata: true }), - false, - 2023, - ), - ) - .to.emit(mintManagerForGeneralOwner, "VectorMetadataSet") - .withArgs(3, false, 2023); - - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(2, false, 0)) - .to.emit(mintManagerForEditionOwner, "VectorMetadataSet") - .withArgs(2, false, 0); - - await expect(mintManagerForEditionOwner.setAbridgedVectorMetadata(4, false, 0)) - .to.emit(mintManagerForEditionOwner, "VectorMetadataSet") - .withArgs(4, false, 0); - - await expect(mintManagerForGeneralOwner.vectorMint721(1, 1, fan1.address, { value: mintFeeWei })).to.not.be - .reverted; - - await expect( - mintManagerForGeneralOwner.vectorMint721(3, 1, fan1.address, { - value: mintFeeWei, - }), - ).to.not.be.reverted; - - await expect(mintManagerForEditionOwner.vectorMint721(2, 1, fan1.address, { value: mintFeeWei })).to.not.be - .reverted; - - /* - vectorMint721WithAllowlist DEPRECATED - await expect( - mintManagerForEditionOwner.vectorMint721WithAllowlist(4, 1, fan1.address, proofForFan, { - value: mintFeeWei, - }), - ).to.not.be.reverted; - */ - }); - }); - }); - - describe("721a bug", function () { - describe("Generative", function () { - let generative: ERC721Generative; - - before(async function () { - generative = await setupGenerative( - observability.address, - generativeImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - SAMPLE_ABRIDGED_VECTOR(ethers.constants.AddressZero, generalOwner.address, false), - null, - false, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - "Test 1", - "T1", - ); - }); - - it("Transfer bug is non-existent", async function () { - mintManager = mintManager.connect(fan1); - await expect(mintManager.vectorMint721(1, 1, fan1.address, { value: ethers.utils.parseEther("0.0008") })).to.not - .be.reverted; - await expect( - mintManager.vectorMint721(1, 2, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await generative.ownerOf(1)).to.equal(fan1.address); - expect(await generative.ownerOf(2)).to.equal(generalOwner.address); - expect(await generative.ownerOf(3)).to.equal(generalOwner.address); - - mintManager = mintManager.connect(generalOwner); - await expect(generative.transferFrom(generalOwner.address, fan1.address, 3)).to.not.be.reverted; - - expect(await generative.ownerOf(3)).to.equal(fan1.address); - - // can still mint after the last transfer - await expect( - mintManager.vectorMint721(1, 2, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await generative.ownerOf(4)).to.equal(generalOwner.address); - }); - - it("Parallel minting bug is non-existent", async function () { - const signers = (await ethers.getSigners()).filter( - signer => signer.address != fan1.address && signer.address != generalOwner.address, - ); - - await Promise.all( - signers.map(async signer => { - for (let i = 1; i <= 5; i++) { - await expect( - mintManager.vectorMint721(1, i, signer.address, { - value: ethers.utils.parseEther("0.0008").mul(i), - }), - ).to.not.be.reverted; - } - }), - ); - - await Promise.all( - signers.map(async signer => { - expect((await generative.balanceOf(signer.address)).toNumber()).to.equal(15); - }), - ); - }); - }); - - describe("General Sequence", function () { - let general: ERC721GeneralSequence; - - before(async function () { - general = ERC721GeneralSequence__factory.connect( - ( - await setupGeneral( - observability.address, - generalSequenceImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - SAMPLE_ABRIDGED_VECTOR(ethers.constants.AddressZero, generalOwner.address, false), - ) - ).address, - generalOwner, - ); - }); - - it("Transfer bug is non-existent", async function () { - mintManager = mintManager.connect(fan1); - await expect(mintManager.vectorMint721(2, 1, fan1.address, { value: ethers.utils.parseEther("0.0008") })).to.not - .be.reverted; - await expect( - mintManager.vectorMint721(2, 2, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await general.ownerOf(1)).to.equal(fan1.address); - expect(await general.ownerOf(2)).to.equal(generalOwner.address); - expect(await general.ownerOf(3)).to.equal(generalOwner.address); - - mintManager = mintManager.connect(generalOwner); - await expect(general.transferFrom(generalOwner.address, fan1.address, 3)).to.not.be.reverted; - - expect(await general.ownerOf(3)).to.equal(fan1.address); - - // can still mint after the last transfer - await expect( - mintManager.vectorMint721(2, 2, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await general.ownerOf(4)).to.equal(generalOwner.address); - }); - - it("Parallel minting bug is non-existent", async function () { - const signers = (await ethers.getSigners()).filter( - signer => signer.address != fan1.address && signer.address != generalOwner.address, - ); - - await Promise.all( - signers.map(async signer => { - for (let i = 1; i <= 5; i++) { - await expect( - mintManager.vectorMint721(2, i, signer.address, { - value: ethers.utils.parseEther("0.0008").mul(i), - }), - ).to.not.be.reverted; - } - }), - ); - - await Promise.all( - signers.map(async signer => { - expect((await general.balanceOf(signer.address)).toNumber()).to.equal(15); - }), - ); - }); - }); - - describe("Open Edition", function () { - let edition: ERC721SingleEdition; - - before(async function () { - edition = await setupSingleEdition( - observability.address, - singleEditionImplementation, - mintManager.address, - trustedForwarder.address, - emr.address, - editionsOwner, - 0, - "Test 1", - "T1", - SAMPLE_ABRIDGED_VECTOR(ethers.constants.AddressZero, editionsOwner.address, true), - ); - }); - - it("Transfer bug is non-existent", async function () { - mintManager = mintManager.connect(fan1); - await expect(mintManager.vectorMint721(3, 1, fan1.address, { value: ethers.utils.parseEther("0.0008") })).to.not - .be.reverted; - await expect( - mintManager.vectorMint721(3, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await edition.ownerOf(1)).to.equal(fan1.address); - expect(await edition.ownerOf(2)).to.equal(editionsOwner.address); - expect(await edition.ownerOf(3)).to.equal(editionsOwner.address); - - mintManager = mintManager.connect(editionsOwner); - await expect(edition.transferFrom(editionsOwner.address, fan1.address, 3)).to.not.be.reverted; - - expect(await edition.ownerOf(3)).to.equal(fan1.address); - - // can still mint after the last transfer - await expect( - mintManager.vectorMint721(3, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await edition.ownerOf(4)).to.equal(editionsOwner.address); - }); - - it("Parallel minting bug is non-existent", async function () { - const signers = (await ethers.getSigners()).filter( - signer => signer.address != fan1.address && signer.address != editionsOwner.address, - ); - - await Promise.all( - signers.map(async signer => { - for (let i = 1; i <= 5; i++) { - await expect( - mintManager.vectorMint721(3, i, signer.address, { - value: ethers.utils.parseEther("0.0008").mul(i), - }), - ).to.not.be.reverted; - } - }), - ); - - await Promise.all( - signers.map(async signer => { - expect((await edition.balanceOf(signer.address)).toNumber()).to.equal(15); - }), - ); - }); - }); - - describe("Open Edition DFS", function () { - let edition: ERC721SingleEditionDFS; - - before(async function () { - edition = await setupSingleEditionDFS( - observability.address, - singleEditionDFSImplementation, - mintManager.address, - trustedForwarder.address, - editionsOwner, - 0, - "Test 1", - "T1", - SAMPLE_ABRIDGED_VECTOR(ethers.constants.AddressZero, editionsOwner.address, true), - ); - }); - - it("Transfer bug is non-existent", async function () { - mintManager = mintManager.connect(fan1); - await expect(mintManager.vectorMint721(4, 1, fan1.address, { value: ethers.utils.parseEther("0.0008") })).to.not - .be.reverted; - await expect( - mintManager.vectorMint721(4, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await edition.ownerOf(1)).to.equal(fan1.address); - expect(await edition.ownerOf(2)).to.equal(editionsOwner.address); - expect(await edition.ownerOf(3)).to.equal(editionsOwner.address); - - mintManager = mintManager.connect(editionsOwner); - await expect(edition.transferFrom(editionsOwner.address, fan1.address, 3)).to.not.be.reverted; - - expect(await edition.ownerOf(3)).to.equal(fan1.address); - - // can still mint after the last transfer - await expect( - mintManager.vectorMint721(4, 2, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ).to.not.be.reverted; - - expect(await edition.ownerOf(4)).to.equal(editionsOwner.address); - }); - - it("Parallel minting bug is non-existent", async function () { - const signers = (await ethers.getSigners()).filter( - signer => signer.address != fan1.address && signer.address != editionsOwner.address, - ); - - await Promise.all( - signers.map(async signer => { - for (let i = 1; i <= 5; i++) { - await expect( - mintManager.vectorMint721(4, i, signer.address, { - value: ethers.utils.parseEther("0.0008").mul(i), - }), - ).to.not.be.reverted; - } - }), - ); - - await Promise.all( - signers.map(async signer => { - expect((await edition.balanceOf(signer.address)).toNumber()).to.equal(15); - }), - ); - }); - }); - }); - - describe("Creator reserve mints", function () { - describe("Series based", function () { - let generative: ERC721Generative; - let general: ERC721General; - - before(async function () { - generative = await setupGenerative( - observability.address, - generativeImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - SAMPLE_ABRIDGED_VECTOR(ethers.constants.AddressZero, generalOwner.address, false), - null, - false, - 0, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - 0, - "Test 1", - "T1", - ); - - general = await setupGeneral( - observability.address, - generalImplementation, - trustedForwarder.address, - mintManager.address, - generalOwner, - ); - }); - - it("Non-owner cannot mint creator reserves", async function () { - mintManager = mintManager.connect(fan1); - await expect( - mintManager.creatorReservesMint(generative.address, false, 0, 3, [], false, generalOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.Unauthorized); - - await expect( - mintManager.creatorReservesMint(general.address, false, 0, 0, [4, 7], true, generalOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.Unauthorized); - }); - - it("Cannot mint creator reserves with invalid mint fee", async function () { - mintManager = mintManager.connect(generalOwner); - await expect( - mintManager.creatorReservesMint(generative.address, false, 0, 3, [], false, generalOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - - await expect( - mintManager.creatorReservesMint(general.address, false, 0, 0, [4, 7], true, generalOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - }); - - it("Owner can validly mint creator reserves multiple times", async function () { - mintManager = mintManager.connect(generalOwner); - await expect( - mintManager.creatorReservesMint(generative.address, false, 0, 3, [], false, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(3), - }), - ) - .to.emit(mintManager, "CreatorReservesNumMint") - .withArgs(generative.address, false, 0, 3) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, generalOwner.address, 1) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, generalOwner.address, 2) - .to.emit(generative, "Transfer") - .withArgs(ethers.constants.AddressZero, generalOwner.address, 3); - - await expect( - mintManager.creatorReservesMint(general.address, false, 0, 0, [4, 7], true, generalOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(2), - }), - ) - .to.emit(mintManager, "CreatorReservesChooseMint") - .withArgs(general.address, [4, 7]) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, generalOwner.address, 4) - .to.emit(general, "Transfer") - .withArgs(ethers.constants.AddressZero, generalOwner.address, 7); - }); - }); - - describe("Editions based", function () { - let editions: ERC721EditionsDFS; - - before(async function () { - editions = await setupEditionsDFS( - observability.address, - editionsDFSImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - editionsOwner, - ); - editions = editions.connect(editionsOwner); - await expect( - editions.createEdition( - "uri", - 100, - ethers.constants.AddressZero, - { - royaltyPercentageBPS: 0, - recipientAddress: ethers.constants.AddressZero, - }, - "0x", - ), - ).to.not.be.reverted; - - await expect( - editions.createEdition( - "uri", - 100, - ethers.constants.AddressZero, - { - royaltyPercentageBPS: 0, - recipientAddress: ethers.constants.AddressZero, - }, - "0x", - ), - ).to.not.be.reverted; - }); - - it("Non-owner cannot mint creator reserves", async function () { - mintManager = mintManager.connect(fan1); - await expect( - mintManager.creatorReservesMint(editions.address, true, 0, 3, [], false, editionsOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.Unauthorized); - }); - - it("Cannot mint creator reserves with invalid mint fee", async function () { - mintManager = mintManager.connect(editionsOwner); - await expect( - mintManager.creatorReservesMint(editions.address, true, 0, 3, [], false, editionsOwner.address), - ).to.be.revertedWithCustomError(mintManager, Errors.InvalidPaymentAmount); - }); - - it("Owner can validly mint creator reserves multiple times on multiple editions on a contract", async function () { - mintManager = mintManager.connect(editionsOwner); - await expect( - mintManager.creatorReservesMint(editions.address, true, 0, 3, [], false, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(3), - }), - ) - .to.emit(mintManager, "CreatorReservesNumMint") - .withArgs(editions.address, true, 0, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 1) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 2) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 3); - - await expect( - mintManager.creatorReservesMint(editions.address, true, 1, 3, [], false, editionsOwner.address, { - value: ethers.utils.parseEther("0.0008").mul(3), - }), - ) - .to.emit(mintManager, "CreatorReservesNumMint") - .withArgs(editions.address, true, 1, 3) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 101) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 102) - .to.emit(editions, "Transfer") - .withArgs(ethers.constants.AddressZero, editionsOwner.address, 103); - }); - }); - }); -}); diff --git a/test/Test.ts b/test/Test.ts new file mode 100644 index 0000000..c0985bf --- /dev/null +++ b/test/Test.ts @@ -0,0 +1,7 @@ +// import helpful utilities from __utils__ + +describe("Test", () => { + it("Test 1", async function () { + console.log("Starting test 1..."); + }) +}); \ No newline at end of file diff --git a/test/UpgradesTest.ts b/test/UpgradesTest.ts deleted file mode 100644 index a786989..0000000 --- a/test/UpgradesTest.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { - AuctionManager, - DiscreteDutchAuctionMechanic, - ERC721Editions, - EditionsMetadataRenderer, - MinimalForwarder, - MintManager, - Observability, - TestDiscreteDutchAuctionMechanic, - TestEditionsMetadataRenderer, - TestMintManager, -} from "../types"; -import { SAMPLE_ABRIDGED_VECTOR } from "./__utils__/data"; -import { setupEditions, setupSystem } from "./__utils__/helpers"; - -const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [["name", "description", "imageUrl", "animationUrl", "externalUrl", "attributes"]], -); - -describe("Upgrades functionality", () => { - let initialPlatformExecutor: SignerWithAddress, - mintManagerOwner: SignerWithAddress, - editionsMetadataOwner: SignerWithAddress, - platformPaymentAddress: SignerWithAddress, - owner: SignerWithAddress, - fan1: SignerWithAddress; - - let emr: EditionsMetadataRenderer; - let mintManager: MintManager; - let dutchAuction: DiscreteDutchAuctionMechanic; - let observability: Observability; - let auctionManager: AuctionManager; - let trustedForwarder: MinimalForwarder; - let editionsImplementation: string; - - const zeroRoyalty = { - recipientAddress: ethers.constants.AddressZero, - royaltyPercentageBPS: 0, - }; - - before(async () => { - [initialPlatformExecutor, mintManagerOwner, editionsMetadataOwner, platformPaymentAddress, owner, fan1] = - await ethers.getSigners(); - const { - emrProxy, - mintManagerProxy, - minimalForwarder, - observability: observabilityInstance, - auctionManagerProxy, - editionsImplementationAddress, - daMechanic, - } = await setupSystem( - platformPaymentAddress.address, - mintManagerOwner.address, - initialPlatformExecutor.address, - editionsMetadataOwner.address, - owner, - ); - - emr = emrProxy; - dutchAuction = daMechanic; - mintManager = mintManagerProxy; - trustedForwarder = minimalForwarder; - observability = observabilityInstance; - auctionManager = auctionManagerProxy; - editionsImplementation = editionsImplementationAddress; - - mintManager = mintManager.connect(mintManagerOwner); - emr = emr.connect(editionsMetadataOwner); - }); - - describe("MintManager", function () { - let testMintManager: TestMintManager; - let editions: ERC721Editions; - - beforeEach(async function () { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - mintManagerOwner, - ); - }); - - it("Non owner cannot upgrade MintManager", async function () { - testMintManager = await (await ethers.getContractFactory("TestMintManager")).deploy(); - await testMintManager.deployed(); - - mintManager = mintManager.connect(owner); - - await expect(mintManager.upgradeTo(testMintManager.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - mintManager = mintManager.connect(editionsMetadataOwner); - - await expect(mintManager.upgradeTo(testMintManager.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - mintManager = mintManager.connect(fan1); - - await expect(mintManager.upgradeTo(testMintManager.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - - it("Upgrade to TestMintManager retains original data and introduces new functionality", async function () { - mintManager = mintManager.connect(mintManagerOwner); - - await expect( - mintManager.createAbridgedVector(SAMPLE_ABRIDGED_VECTOR(editions.address, owner.address, true)), - ).to.emit(mintManager, "EditionVectorCreated"); - - // data before upgrade - const vectorOnOldImpl = await mintManager.vectors(1); - const ownerOnOldImpl = await mintManager.owner(); - - testMintManager = await (await ethers.getContractFactory("TestMintManager")).deploy(); - await testMintManager.deployed(); - - await expect(mintManager.upgradeTo(testMintManager.address)) - .to.emit(mintManager, "Upgraded") - .withArgs(testMintManager.address); - - const newMintManager = new ethers.Contract(mintManager.address, testMintManager.interface, mintManagerOwner); - expect(await newMintManager.test()).to.equal(true); - - // data after upgrade - expect(await newMintManager.vectors(1)).to.eql(vectorOnOldImpl); - expect(await newMintManager.owner()).to.equal(ownerOnOldImpl); - }); - }); - - describe("EditionsMetadataRenderer", function () { - let testEditionsMetadataRenderer: TestEditionsMetadataRenderer; - let editions: ERC721Editions; - - beforeEach(async function () { - editions = await setupEditions( - observability.address, - editionsImplementation, - mintManager.address, - auctionManager.address, - trustedForwarder.address, - emr.address, - mintManagerOwner, - ); - await expect(editions.createEdition(defaultEditionInfo, 4, ethers.constants.AddressZero, zeroRoyalty, "0x")); - }); - - it("Non owner cannot upgrade EditionsMetadataRenderer", async function () { - testEditionsMetadataRenderer = await (await ethers.getContractFactory("TestEditionsMetadataRenderer")).deploy(); - await testEditionsMetadataRenderer.deployed(); - - mintManager = mintManager.connect(owner); - - await expect(mintManager.upgradeTo(testEditionsMetadataRenderer.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - mintManager = mintManager.connect(editionsMetadataOwner); - - await expect(mintManager.upgradeTo(testEditionsMetadataRenderer.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - mintManager = mintManager.connect(fan1); - - await expect(mintManager.upgradeTo(testEditionsMetadataRenderer.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - - it("Upgrade to TestEditionsMetadataRenderer retains original data and introduces new functionality", async function () { - emr = emr.connect(editionsMetadataOwner); - // data before upgrade - // const editionOnOldEMR = await emr.editionInfo(editions.address, 0); - const ownerOnOldEMR = await emr.owner(); - - testEditionsMetadataRenderer = await (await ethers.getContractFactory("TestEditionsMetadataRenderer")).deploy(); - await testEditionsMetadataRenderer.deployed(); - - await expect(emr.upgradeTo(testEditionsMetadataRenderer.address)) - .to.emit(emr, "Upgraded") - .withArgs(testEditionsMetadataRenderer.address); - - const newEditionsMetadataRenderer = new ethers.Contract( - emr.address, - testEditionsMetadataRenderer.interface, - editionsMetadataOwner, - ); - expect(await newEditionsMetadataRenderer.test()).to.equal("test"); - - // data after upgrade - // expect(await newEditionsMetadataRenderer.editionInfo(editions.address, 0)).to.equal(editionOnOldEMR); - expect(await newEditionsMetadataRenderer.owner()).to.equal(ownerOnOldEMR); - }); - }); - - describe("DiscreteDutchAuctionMechanic", function () { - let testDiscreteDutchAuctionMechanic: TestDiscreteDutchAuctionMechanic; - - it("Non owner cannot upgrade DiscreteDutchAuctionMechanic", async function () { - testDiscreteDutchAuctionMechanic = await ( - await ethers.getContractFactory("TestDiscreteDutchAuctionMechanic") - ).deploy(); - await testDiscreteDutchAuctionMechanic.deployed(); - - dutchAuction = dutchAuction.connect(owner); - - await expect(dutchAuction.upgradeTo(testDiscreteDutchAuctionMechanic.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - dutchAuction = dutchAuction.connect(editionsMetadataOwner); - - await expect(dutchAuction.upgradeTo(testDiscreteDutchAuctionMechanic.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - - dutchAuction = dutchAuction.connect(fan1); - - await expect(dutchAuction.upgradeTo(testDiscreteDutchAuctionMechanic.address)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - - it("Upgrade to TestDiscreteDutchAuctionMechanic retains original data and introduces new functionality", async function () { - dutchAuction = dutchAuction.connect(mintManagerOwner); - // data before upgrade - const ownerOnOldMechanic = await dutchAuction.owner(); - - testDiscreteDutchAuctionMechanic = await ( - await ethers.getContractFactory("TestDiscreteDutchAuctionMechanic") - ).deploy(); - await testDiscreteDutchAuctionMechanic.deployed(); - - await expect(dutchAuction.upgradeTo(testDiscreteDutchAuctionMechanic.address)) - .to.emit(dutchAuction, "Upgraded") - .withArgs(testDiscreteDutchAuctionMechanic.address); - - const newDiscreteDutchAuctionMechanic = new ethers.Contract( - dutchAuction.address, - testDiscreteDutchAuctionMechanic.interface, - mintManagerOwner, - ); - expect(await newDiscreteDutchAuctionMechanic.test()).to.equal(true); - - // data after upgrade - expect(await newDiscreteDutchAuctionMechanic.owner()).to.equal(ownerOnOldMechanic); - }); - }); -}); diff --git a/test/__utils__/EIP712.ts b/test/__utils__/EIP712.ts deleted file mode 100644 index 8f3c6e5..0000000 --- a/test/__utils__/EIP712.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { TypedDataDomain, TypedDataField, ethers } from "ethers"; - -export type EIP712Types = Record>; - -export class EIP712 { - contract: ethers.Contract; - signer: SignerWithAddress; - message: Record; - types: EIP712Types; - constructor(contract: ethers.Contract, signer: SignerWithAddress, message: Record, types: EIP712Types) { - this.contract = contract; - this.signer = signer; - this.message = message; - this.types = types; - } - - static buildDomain( - name: string, - version: string, - verifyingContract: string, - chainId?: number, - salt?: string, - ): TypedDataDomain { - const domain: TypedDataDomain = { - name, - version, - verifyingContract, - }; - if (salt) domain.salt = salt; - if (chainId) domain.chainId = chainId; - return domain; - } - - buildTypedData(domain: TypedDataDomain) { - return { - types: this.types, - domain, - message: this.message, - }; - } - - async sign(domain: TypedDataDomain): Promise { - const typedData = this.buildTypedData(domain); - return await this.signer._signTypedData(typedData.domain, typedData.types, typedData.message); - } -} diff --git a/test/__utils__/auction.ts b/test/__utils__/auction.ts deleted file mode 100644 index ea32cad..0000000 --- a/test/__utils__/auction.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ethers } from "ethers"; - -import { EIP712 } from "./EIP712"; - -export type Claim = { - auctionId: string; - bidPrice: string; - reservePrice: string; - maxClaimsPerAccount: number; - claimExpiryTimestamp: string; - buffer: number; - minimumIncrementPerBidPctBPS: number; - claimer: string; -}; - -const Claim = [ - { name: "auctionId", type: "bytes32" }, - { name: "bidPrice", type: "uint256" }, - { name: "reservePrice", type: "uint256" }, - { name: "maxClaimsPerAccount", type: "uint256" }, - { name: "claimExpiryTimestamp", type: "uint256" }, - { name: "buffer", type: "uint256" }, - { name: "minimumIncrementPerBidPctBPS", type: "uint256" }, - { name: "claimer", type: "address" }, -]; - -function buildClaim(input: Claim): Claim { - input.auctionId = ethers.utils.formatBytes32String(input.auctionId); - input.bidPrice = ethers.utils.parseEther(input.bidPrice).toString(); - input.reservePrice = ethers.utils.parseEther(input.reservePrice).toString(); - if (input.claimExpiryTimestamp == "0") { - input.claimExpiryTimestamp = getValidClaimTimestamp(); - } - - return input; -} - -export async function signGatedBid(signer: SignerWithAddress, auctionManager: ethers.Contract, claimInput: Claim) { - const chainId = await auctionManager.provider.getNetwork().then(n => n.chainId); - const claim = buildClaim(claimInput) as Claim; - const eip712 = new EIP712(auctionManager, signer, claim, { Claim }); - const signature = await eip712.sign(EIP712.buildDomain("AuctionManager", "1.0.0", auctionManager.address, chainId)); - return { signature, claim }; -} - -export const getValidClaimTimestamp = () => (Math.floor(Date.now() / 1000) + 360000).toString(); diff --git a/test/__utils__/data.ts b/test/__utils__/data.ts deleted file mode 100644 index 00f91fa..0000000 --- a/test/__utils__/data.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { BigNumber } from "@ethersproject/contracts/node_modules/@ethersproject/bignumber"; -import { ethers } from "hardhat"; - -import { OnchainDutchAuctionParams } from "./helpers"; - -export const SAMPLE_VECTOR_1 = ( - address: string, - paymentRecipient: string, - maxTotalClaimableViaVector = 10, - maxUserClaimableViaVector = 5, - pricePerToken = 0, - tokenLimitPerTx = 1, - start = 0, - end = 0, - paused = 0, - allowlistRoot = "", -) => { - return { - contractAddress: address, - currency: ethers.constants.AddressZero, - paymentRecipient: paymentRecipient, - startTimestamp: start, - endTimestamp: end, - pricePerToken, - tokenLimitPerTx, - maxTotalClaimableViaVector, - maxUserClaimableViaVector, - totalClaimedViaVector: 0, - allowlistRoot: ethers.utils.formatBytes32String(allowlistRoot), - paused, - }; -}; - -export const SAMPLE_ABRIDGED_VECTOR = ( - contractAddress: string, - paymentRecipient: string, - editionBasedCollection: boolean, - editionId: number = 0, - maxTotalClaimableViaVector: number = 0, - maxUserClaimableViaVector: number = 0, - startTimestamp: number = 0, - endTimestamp: number = 0, - tokenLimitPerTx: number = 0, - pricePerToken: BigNumber = ethers.utils.parseEther("0"), - allowlistRoot: string = ethers.constants.HashZero, - requireDirectEOA: boolean = false, -) => { - return { - contractAddress, - currency: ethers.constants.AddressZero, - totalClaimedViaVector: 0, - startTimestamp, - endTimestamp, - paymentRecipient, - maxTotalClaimableViaVector, - tokenLimitPerTx, - maxUserClaimableViaVector, - pricePerToken, - editionId, - editionBasedCollection, - requireDirectEOA, - allowlistRoot, - }; -}; - -export const SAMPLE_ABRIDGED_VECTOR_UPDATE_CONFIG = ({ - updateMaxTotalClaimableViaVector, - updateStartTimestamp, - updateEndTimestamp, - updatePaymentRecipient, - updateTokenLimitPerTx, - updateMaxUserClaimableViaVector, - updatePricePerToken, - updateAllowlistRoot, - updateRequireDirectEOA, - updateMetadata, -}: { - updateMaxTotalClaimableViaVector?: boolean; - updateStartTimestamp?: boolean; - updateEndTimestamp?: boolean; - updatePaymentRecipient?: boolean; - updateTokenLimitPerTx?: boolean; - updateMaxUserClaimableViaVector?: boolean; - updatePricePerToken?: boolean; - updateAllowlistRoot?: boolean; - updateRequireDirectEOA?: boolean; - updateMetadata?: boolean; -}) => { - return { - updateStartTimestamp: updateStartTimestamp ? 1 : 0, - updateEndTimestamp: updateEndTimestamp ? 1 : 0, - updatePaymentRecipient: updatePaymentRecipient ? 1 : 0, - updateMaxTotalClaimableViaVector: updateMaxTotalClaimableViaVector ? 1 : 0, - updateTokenLimitPerTx: updateTokenLimitPerTx ? 1 : 0, - updateMaxUserClaimableViaVector: updateMaxUserClaimableViaVector ? 1 : 0, - updatePricePerToken: updatePricePerToken ? 1 : 0, - updateAllowlistRoot: updateAllowlistRoot ? 1 : 0, - updateRequireDirectEOA: updateRequireDirectEOA ? 1 : 0, - updateMetadata: updateMetadata ? 1 : 0, - }; -}; - -export const SAMPLE_DA_VECTOR = ( - mechanicAddress: string, - input: { - prices?: string[]; - periodDuration?: number; - maxTotalClaimableViaVector?: number; - maxUserClaimableViaVector?: number; - startTimestamp?: number; - endTimestamp?: number; - tokenLimitPerTx?: number; - seed?: string; - }, -): OnchainDutchAuctionParams => { - return { - mechanicAddress, - prices: input.prices ?? ["0.001", "0.0001"], - periodDuration: input.periodDuration ?? 100, - maxTotalClaimableViaVector: input.maxTotalClaimableViaVector ?? 0, - maxUserClaimableViaVector: input.maxUserClaimableViaVector ?? 0, - startTimestamp: input.startTimestamp ?? Math.floor(Date.now() / 1000), - endTimestamp: input.endTimestamp ?? 0, - tokenLimitPerTx: input.tokenLimitPerTx ?? 0, - seed: input.seed ?? Math.floor(Date.now() / 1000).toString(), - }; -}; - -export type DutchAuctionUpdateValues = { - prices?: string[]; - periodDuration?: number; - maxTotalClaimableViaVector?: number; - maxUserClaimableViaVector?: number; - startTimestamp?: number; - endTimestamp?: number; - tokenLimitPerTx?: number; - paymentRecipient?: string; -}; - -export const SAMPLE_VECTOR_MUTABILITY_1 = (deleteFrozen = 0, pausesFrozen = 0, updatesFrozen = 0) => { - return { - deleteFrozen, - pausesFrozen, - updatesFrozen, - }; -}; - -export enum Errors { - InvalidManager = "InvalidManager", - ManagerDoesNotExist = "ManagerDoesNotExist", - Unauthorized = "Unauthorized", - NotMinter = "NotMinter", - ManagerSwapBlocked = "ManagerSwapBlocked", - ManagerRemoveBlocked = "ManagerRemoveBlocked", - RoyaltySetBlocked = "RoyaltySetBlocked", - RoyaltyBPSInvalid = "RoyaltyBPSInvalid", - MinterRegistrationInvalid = "MinterRegistrationInvalid", - EditionDoesNotExist = "EditionDoesNotExist", - TokenDoesNotExist = "TokenDoesNotExist", - MintFrozen = "MintFrozen", - SoldOut = "SoldOut", - InvalidSize = "InvalidSize", - InvalidEditionIdsLength = "InvalidEditionIdsLength", - TokenNotInRange = "TokenNotInRange", - OverLimitSupply = "OverLimitSupply", - MismatchedArrayLengths = "MismatchedArrayLengths", - MetadataUpdateBlocked = "MetadataUpdateBlocked", - InvalidEditionId = "InvalidEditionId", - InvalidExecutorChanged = "InvalidExecutorChanged", - VectorUpdateActionFrozen = "VectorUpdateActionFrozen", - InvalidTotalClaimed = "InvalidTotalClaimed", - AllowlistInvalid = "AllowlistInvalid", - CurrencyTypeInvalid = "CurrencyTypeInvalid", - MintFeeTooLow = "MintFeeTooLow", - EtherSendFailed = "EtherSendFailed", - SenderNotClaimer = "SenderNotClaimer", - InvalidClaim = "InvalidClaim", - InvalidPaymentAmount = "InvalidPaymentAmount", - OnchainVectorMintGuardFailed = "OnchainVectorMintGuardFailed", - EmptyString = "EmptyString", - OwnerQueryForNonexistentToken = "OwnerQueryForNonexistentToken", - TransferFromIncorrectOwner = "TransferFromIncorrectOwner", - TokenMintedAlready = "TokenMintedAlready", - UnsafeMintRecipient = "UnsafeMintRecipient", - MintPaused = "MintPaused", - MechanicPaused = "MechanicPaused", - InvalidMechanic = "InvalidMechanic", - InvalidVectorConfig = "InvalidVectorConfig", - InvalidUpdate = "InvalidUpdate", - InvalidMint = "InvalidMint", - InvalidRebate = "InvalidRebate", - CollectorNotOwedRebate = "CollectorNotOwedRebate", - InvalidDPPFundsWithdrawl = "InvalidDPPFundsWithdrawl", -} diff --git a/test/__utils__/helpers.ts b/test/__utils__/helpers.ts deleted file mode 100644 index 5fd559b..0000000 --- a/test/__utils__/helpers.ts +++ /dev/null @@ -1,1167 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { BigNumber, BytesLike } from "ethers"; -import { ethers } from "hardhat"; - -import { - AuctionManager__factory, - DiscreteDutchAuctionMechanic, - DiscreteDutchAuctionMechanic__factory, - ERC721Editions, - ERC721EditionsDFS, - ERC721EditionsDFS__factory, - ERC721Editions__factory, - ERC721General, - ERC721General__factory, - ERC721Generative__factory, - ERC721SingleEdition, - ERC721SingleEditionDFS, - ERC721SingleEditionDFS__factory, - ERC721SingleEdition__factory, - EditionsMetadataRenderer__factory, - IERC20__factory, - MinimalForwarder__factory, - MintManager__factory, - NativeMetaTransaction__factory, - Observability__factory, -} from "../../types"; -import { DutchAuctionUpdateValues } from "./data"; -import { signGatedMint, signGatedMintWithMetaTxPacket, signGatedSeriesMint, signWETHMetaTxRequest } from "./mint"; - -export type OnchainMintVectorParams = { - startTimestamp: number; - endTimestamp: number; - pricePerToken: BigNumber; - tokenLimitPerTx: number; - maxTotalClaimableViaVector: number; - maxUserClaimableViaVector: number; - allowlistRoot: string; - editionId?: number; -}; - -export type OnchainDutchAuctionParams = { - startTimestamp: number; - endTimestamp: number; - prices: string[]; - periodDuration: number; - tokenLimitPerTx: number; - maxTotalClaimableViaVector: number; - maxUserClaimableViaVector: number; - mechanicAddress: string; - seed: string; -}; - -export const DEFAULT_ONCHAIN_MINT_VECTOR: OnchainMintVectorParams = { - startTimestamp: 0, - endTimestamp: 0, - pricePerToken: ethers.utils.parseEther("0"), - tokenLimitPerTx: 0, - maxTotalClaimableViaVector: 0, - maxUserClaimableViaVector: 0, - allowlistRoot: ethers.constants.HashZero, -}; - -export const setupSingleEdition = async ( - observabilityAddress: string, - singleImplementationAddress: string, - mintManagerAddress: string, - trustedForwarderAddress: string, - emrAddress: string, - creator: SignerWithAddress, - size: number, - name: string, - symbol: string, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - useMarketplaceFilter = false, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - contractUri = "", - description = "", - imageUrl = "", - animationUrl = "", - externalUrl = "", - attributes = "", -): Promise => { - const editionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [[name, description, imageUrl, animationUrl, externalUrl, attributes]], - ); - - const initializeData = ethers.utils.defaultAbiCoder.encode( - [ - "address", - "tuple(address, uint16)", - "address", - "string", - "string", - "string", - "uint256", - "address", - "address", - "address", - "bool", - "bytes", - ], - [ - creator.address, - [royaltyRecipient, royaltyPercentage], - defaultTokenManager, - contractUri, - name, - symbol, - size, - emrAddress, - trustedForwarderAddress, - mintManagerAddress, - useMarketplaceFilter, - editionInfo, - ], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const SingleEdition = await ( - await ethers.getContractFactory("SingleEdition") - ).deploy( - singleImplementationAddress, - initializeData, - mintVectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - observabilityAddress, - ); - const singleEdition = await SingleEdition.deployed(); - return ERC721SingleEdition__factory.connect(singleEdition.address, creator); -}; - -export const setupSingleEditionDFS = async ( - observabilityAddress: string, - singleEditionDFSImplementationAddress: string, - mintManagerAddress: string, - trustedForwarderAddress: string, - creator: SignerWithAddress, - size: number, - name: string, - symbol: string, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - useMarketplaceFilter = false, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - contractUri = "", - editionUri = "editionUri", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - [ - "address", - "tuple(address, uint16)", - "address", - "string", - "string", - "string", - "uint256", - "address", - "address", - "bool", - "string", - ], - [ - creator.address, - [royaltyRecipient, royaltyPercentage], - defaultTokenManager, - contractUri, - name, - symbol, - size, - trustedForwarderAddress, - mintManagerAddress, - useMarketplaceFilter, - editionUri, - ], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const SingleEditionDFS = await ( - await ethers.getContractFactory("SingleEditionDFS") - ).deploy( - singleEditionDFSImplementationAddress, - initializeData, - mintVectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - observabilityAddress, - ); - const singleEditionDFS = await SingleEditionDFS.deployed(); - return ERC721SingleEditionDFS__factory.connect(singleEditionDFS.address, creator); -}; - -// sets up MultipleEditions without first edition -export const setupEditions = async ( - observabilityAddress: string, - editionsImplementationAddress: string, - mintManagerAddress: string, - auctionManagerAddress: string, - trustedForwarderAddress: string, - emrAddress: string, - creator: SignerWithAddress, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - useMarketplaceFilter = false, - name = "dummy", - symbol = "DMY", - contractUri = "dummyContractMetadata", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - ["address", "string", "string", "string", "address", "address", "address[]", "bool", "address"], - [ - creator.address, - contractUri, - name, - symbol, - emrAddress, - trustedForwarderAddress, - [mintManagerAddress, auctionManagerAddress], - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const MultipleEditions = await ( - await ethers.getContractFactory("MultipleEditions", creator) - ).deploy( - editionsImplementationAddress, - initializeData, - ethers.utils.arrayify("0x"), - 0, - ethers.constants.AddressZero, - { - recipientAddress: royaltyRecipient, - royaltyPercentageBPS: royaltyPercentage, - }, - ethers.utils.arrayify("0x"), - mintVectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - ); - const multipleEditions = await MultipleEditions.deployed(); - - const multipleEditionsCreator = ERC721Editions__factory.connect(multipleEditions.address, creator); - - if (defaultTokenManager != ethers.constants.AddressZero) { - const tx = await multipleEditionsCreator.setDefaultTokenManager(defaultTokenManager); - await tx.wait(); - } - - return multipleEditionsCreator; -}; - -// sets up MultipleEditionsDFS without first edition -export const setupEditionsDFS = async ( - observabilityAddress: string, - editionsDFSImplementationAddress: string, - mintManagerAddress: string, - auctionManagerAddress: string, - trustedForwarderAddress: string, - creator: SignerWithAddress, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - editionUri = "", - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - useMarketplaceFilter = false, - name = "dummy", - symbol = "DMY", - contractUri = "dummyContractMetadata", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - ["address", "string", "string", "string", "address", "address[]", "bool", "address"], - [ - creator.address, - contractUri, - name, - symbol, - trustedForwarderAddress, - [mintManagerAddress, auctionManagerAddress], - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const MultipleEditionsDFS = await ( - await ethers.getContractFactory("MultipleEditionsDFS", creator) - ).deploy( - editionsDFSImplementationAddress, - initializeData, - editionUri, - 0, - ethers.constants.AddressZero, - { - recipientAddress: royaltyRecipient, - royaltyPercentageBPS: royaltyPercentage, - }, - ethers.utils.arrayify("0x"), - mintVectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - ); - const multipleEditionsDFS = await MultipleEditionsDFS.deployed(); - - const multipleEditionsCreator = ERC721EditionsDFS__factory.connect(multipleEditionsDFS.address, creator); - - if (defaultTokenManager != ethers.constants.AddressZero) { - const tx = await multipleEditionsCreator.setDefaultTokenManager(defaultTokenManager); - await tx.wait(); - } - - return multipleEditionsCreator; -}; - -// sets up MultipleEditions with first edition -export const setupMultipleEdition = async ( - observabilityAddress: string, - editionsImplementationAddress: string, - mintVectorAddress: string, - auctionManagerAddress: string, - trustedForwarderAddress: string, - emrAddress: string, - creator: SignerWithAddress, - size: number, - name: string, - symbol: string, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - useMarketplaceFilter = false, - contractName = "contractName", - royaltyPercentage = 0, - royaltyRecipient = ethers.constants.AddressZero, - contractUri = "", - description = "", - imageUrl = "", - animationUrl = "", - externalUrl = "", - attributes = "", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - ["address", "string", "string", "string", "address", "address", "address[]", "bool", "address"], - [ - creator.address, - contractUri, - contractName, - symbol, - emrAddress, - trustedForwarderAddress, - [mintVectorAddress, auctionManagerAddress], - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [[name, description, imageUrl, animationUrl, externalUrl, attributes]], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintVectorAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const MultipleEditions = await ( - await ethers.getContractFactory("MultipleEditions", creator) - ).deploy( - editionsImplementationAddress, - initializeData, - defaultEditionInfo, - size, - ethers.constants.AddressZero, - { - recipientAddress: royaltyRecipient, - royaltyPercentageBPS: royaltyPercentage, - }, - ethers.utils.arrayify("0x"), - mintVectorData, - encodeMechanicVectorData(mintVectorAddress, creator.address, mechanicMint), - ); - const multipleEditions = await MultipleEditions.deployed(); - - return ERC721Editions__factory.connect(multipleEditions.address, creator); -}; - -// sets up MultipleEditionsDFS with first edition -export const setupMultipleEditionDFS = async ( - observabilityAddress: string, - editionsDFSImplementationAddress: string, - mintVectorAddress: string, - auctionManagerAddress: string, - trustedForwarderAddress: string, - creator: SignerWithAddress, - size: number, - symbol: string, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - editionUri: string = "uri", - useMarketplaceFilter = false, - contractName = "contractName", - royaltyPercentage = 0, - royaltyRecipient = ethers.constants.AddressZero, - contractUri = "", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - ["address", "string", "string", "string", "address", "address[]", "bool", "address"], - [ - creator.address, - contractUri, - contractName, - symbol, - trustedForwarderAddress, - [mintVectorAddress, auctionManagerAddress], - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const mintVectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintVectorAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const MultipleEditionsDFS = await ( - await ethers.getContractFactory("MultipleEditionsDFS", creator) - ).deploy( - editionsDFSImplementationAddress, - initializeData, - editionUri, - size, - ethers.constants.AddressZero, - { - recipientAddress: royaltyRecipient, - royaltyPercentageBPS: royaltyPercentage, - }, - ethers.utils.arrayify("0x"), - mintVectorData, - encodeMechanicVectorData(mintVectorAddress, creator.address, mechanicMint), - ); - const multipleEditionsDFS = await MultipleEditionsDFS.deployed(); - - return ERC721EditionsDFS__factory.connect(multipleEditionsDFS.address, creator); -}; - -export const generateClaim = async ( - mintManagerOwner: SignerWithAddress, - mintManagerAddress: string, - contractAddress: string, - claimer: string, - paymentRecipient: string, - claimExpiryTimestamp: string = (Math.floor(Date.now() / 1000) + 360000).toString(), - pricePerToken = "0", - numTokensToMint = 1, - maxClaimableViaVector = 0, - maxClaimablePerUser = 0, - editionId = 0, - offchainVectorId = "randomVectorId", - claimNonce = "randomClaimNonce", - currency: string = ethers.constants.AddressZero, -) => { - const mintManager = await MintManager__factory.connect(mintManagerAddress, mintManagerOwner); - - return await signGatedMint( - mintManagerOwner, - mintManager, - { - currency, - contractAddress, - claimer, - paymentRecipient, - pricePerToken: ethers.utils.parseEther(pricePerToken).toString(), - numTokensToMint, - maxClaimableViaVector, - maxClaimablePerUser, - editionId, - }, - offchainVectorId, - claimNonce, - claimExpiryTimestamp, - ); -}; - -export const generateSeriesClaim = async ( - mintManagerOwner: SignerWithAddress, - mintManagerAddress: string, - contractAddress: string, - claimer: string, - paymentRecipient: string, - maxPerTxn: number, - claimExpiryTimestamp: string = (Math.floor(Date.now() / 1000) + 360000).toString(), - pricePerToken = "0", - maxClaimableViaVector = 0, - maxClaimablePerUser = 0, - offchainVectorId = "randomVectorId", - claimNonce = "randomClaimNonce", - currency: string = ethers.constants.AddressZero, -) => { - const mintManager = await MintManager__factory.connect(mintManagerAddress, mintManagerOwner); - - return await signGatedSeriesMint( - mintManagerOwner, - mintManager, - { - currency, - contractAddress, - claimer, - paymentRecipient, - pricePerToken: ethers.utils.parseEther(pricePerToken).toString(), - maxPerTxn, - maxClaimableViaVector, - maxClaimablePerUser, - }, - offchainVectorId, - claimNonce, - claimExpiryTimestamp, - ); -}; - -export const generateClaimWithMetaTxPackets = async ( - mintManagerOwner: SignerWithAddress, - claimer: SignerWithAddress, - mintManagerAddress: string, - contractAddress: string, - paymentRecipient: string, - currency: string, - claimExpiryTimestamp: string = (Math.floor(Date.now() / 1000) + 3600).toString(), - pricePerToken = "0", - numTokensToMint = 1, - maxClaimableViaVector = 10, - maxClaimablePerUser = 10, - editionId = 0, - offchainVectorId = "randomVectorId", - claimNonce = "randomClaimNonce", -) => { - const wETHWei = ethers.utils.parseUnits(pricePerToken, 18).mul(BigNumber.from(numTokensToMint)); - const wETHWeiToCreator = wETHWei.mul(95).div(100); - const wETHWeiToPlatform = wETHWei.mul(5).div(100); - - const erc20 = await IERC20__factory.connect(currency, mintManagerOwner); - const transferToCreatorData = erc20.interface.encodeFunctionData("transfer", [paymentRecipient, wETHWeiToCreator]); - const transferToPlatformData = erc20.interface.encodeFunctionData("transfer", [ - mintManagerAddress, - wETHWeiToPlatform, - ]); - const metaTxContract = await NativeMetaTransaction__factory.connect(currency, mintManagerOwner); - const nonce = await metaTxContract.getNonce(claimer.address); - const purchaseToCreatorPacket = await signWETHMetaTxRequest(claimer, erc20, { - from: claimer.address, - functionSignature: transferToCreatorData, - nonce: nonce.toString(), - }); - const purchaseToPlatformPacket = await signWETHMetaTxRequest(claimer, erc20, { - from: claimer.address, - functionSignature: transferToPlatformData, - nonce: nonce.add(1).toString(), - }); - - const mintManager = await MintManager__factory.connect(mintManagerAddress, mintManagerOwner); - - return await signGatedMintWithMetaTxPacket( - mintManagerOwner, - mintManager, - { - currency, - contractAddress, - claimer: claimer.address, - purchaseToCreatorPacket, - purchaseToPlatformPacket, - pricePerToken: ethers.utils.parseEther(pricePerToken).toString(), - numTokensToMint, - maxClaimableViaVector, - maxClaimablePerUser, - editionId, - }, - offchainVectorId, - claimNonce, - claimExpiryTimestamp, - ); -}; - -export const setupGeneral = async ( - observabilityAddress: string, - generalImplementationAddress: string, - trustedForwarderAddress: string, - mintManagerAddress: string, - creator: SignerWithAddress, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - isCollectorsChoice: boolean = false, - useMarketplaceFilter = false, - limitSupply = 0, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - name = "dummy", - symbol = "DMY", - baseUri = "baseUri", - contractUri = "dummyContractMetadata", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - [ - "address", - "string", - "tuple(address, uint16)", - "address", - "string", - "string", - "address", - "address", - "string", - "uint256", - "bool", - "address", - ], - [ - creator.address, - contractUri, - [royaltyRecipient, royaltyPercentage], - defaultTokenManager, - name, - symbol, - trustedForwarderAddress, - mintManagerAddress, - baseUri, - limitSupply, - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const vectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - ethers.constants.HashZero, - ], - ) - : "0x"; - - const Series = await ( - await ethers.getContractFactory("Series", creator) - ).deploy( - generalImplementationAddress, - initializeData, - vectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - isCollectorsChoice, - ); - const series = await Series.deployed(); - - return ERC721General__factory.connect(series.address, creator); -}; - -export const setupGenerative = async ( - observabilityAddress: string, - generalImplementationAddress: string, - trustedForwarderAddress: string, - mintManagerAddress: string, - creator: SignerWithAddress, - directMint: OnchainMintVectorParams | null = null, - mechanicMint: OnchainDutchAuctionParams | null = null, - useMarketplaceFilter = false, - limitSupply = 0, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - name = "dummy", - symbol = "DMY", - baseUri = "baseUri", - contractUri = "dummyContractMetadata", - codeUri = "codeUri", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - [ - "address", - "string", - "tuple(address, uint16)", - "address", - "string", - "string", - "address", - "address", - "string", - "string", - "uint256", - "bool", - ], - [ - creator.address, - contractUri, - [royaltyRecipient, royaltyPercentage], - defaultTokenManager, - name, - symbol, - trustedForwarderAddress, - mintManagerAddress, - codeUri, - baseUri, - limitSupply, - useMarketplaceFilter, - ], - ); - - const vectorData = directMint - ? ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint48", "uint48", "uint192", "uint48", "uint48", "uint48", "bytes32"], - [ - mintManagerAddress, - creator.address, - directMint.startTimestamp, - directMint.endTimestamp, - directMint.pricePerToken, - directMint.tokenLimitPerTx, - directMint.maxTotalClaimableViaVector, - directMint.maxUserClaimableViaVector, - directMint.allowlistRoot, - ], - ) - : "0x"; - - const GenerativeSeries = await ( - await ethers.getContractFactory("GenerativeSeries", creator) - ).deploy( - generalImplementationAddress, - initializeData, - vectorData, - encodeMechanicVectorData(mintManagerAddress, creator.address, mechanicMint), - observabilityAddress, - ); - const generativeSeries = await GenerativeSeries.deployed(); - - return ERC721Generative__factory.connect(generativeSeries.address, creator); -}; - -export const setupEtherAuctionWithNewToken = async ( - observabilityAddress: string, - editionsImplementationAddress: string, - mintManagerAddress: string, - auctionManagerAddress: string, - emrAddress: string, - trustedForwarderAddress: string, - creator: SignerWithAddress, - auctionId: string, - auctionEndTime: number, - auctionPaymentRecipient: string, - useMarketplaceFilter: boolean = false, - auctionCurrency: string = ethers.constants.AddressZero, - defaultTokenManager = ethers.constants.AddressZero, - royaltyRecipient = ethers.constants.AddressZero, - royaltyPercentage = 0, - contractName = "contractName", - symbol = "DMY", - name = "dummy", - description = "description", - imageUrl = "imageUrl", - animationUrl = "animationUrl", - externalUrl = "externalUrl", - attributes = "attributes", - contractUri = "dummyContractMetadata", -): Promise => { - const initializeData = ethers.utils.defaultAbiCoder.encode( - ["address", "string", "string", "string", "address", "address", "address[]", "bool", "address"], - [ - creator.address, - contractUri, - contractName, - symbol, - emrAddress, - trustedForwarderAddress, - [mintManagerAddress, auctionManagerAddress], - useMarketplaceFilter, - observabilityAddress, - ], - ); - - const defaultEditionInfo = ethers.utils.defaultAbiCoder.encode( - ["tuple(string, string, string, string, string, string)"], - [[name, description, imageUrl, animationUrl, externalUrl, attributes]], - ); - - const auctionData = ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "address", "address", "uint256"], - [ - auctionManagerAddress, - ethers.utils.formatBytes32String(auctionId), - auctionCurrency, - auctionPaymentRecipient, - auctionEndTime, - ], - ); - - const mintVectorData = "0x"; - - const MultipleEditions = await ( - await ethers.getContractFactory("MultipleEditions", creator) - ).deploy( - editionsImplementationAddress, - initializeData, - defaultEditionInfo, - 1, - defaultTokenManager, - { recipientAddress: royaltyRecipient, royaltyPercentageBPS: royaltyPercentage }, - auctionData, - mintVectorData, - "0x", - ); - const multipleEditions = await MultipleEditions.deployed(); - - return ERC721Editions__factory.connect(multipleEditions.address, creator); -}; - -export async function setupSystem( - platformPaymentAddress: string, - mintManagerOwnerAddress: string, - initialPlatformExecutorAddress: string, - editionsMetadataOwnerAddress: string, - signer: SignerWithAddress, - mintFee: string = "0.0008", -) { - const minimalForwarderFactory = await ethers.getContractFactory("MinimalForwarder"); - const minimalForwarder = await minimalForwarderFactory.deploy(); - await minimalForwarder.deployed(); - - const mintManagerFactory = await ethers.getContractFactory("MintManager"); - const mintManager = await mintManagerFactory.deploy(); - await mintManager.deployed(); - const encodedFn = mintManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress, - mintManagerOwnerAddress, - minimalForwarder.address, - initialPlatformExecutorAddress, - ethers.utils.parseEther(mintFee), - ]); - - const mintManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const mintManagerProxy = await mintManagerProxyFactory.deploy(mintManager.address, encodedFn); - await mintManagerProxy.deployed(); - - // deploy AuctionManager - const auctionManagerFactory = await ethers.getContractFactory("AuctionManager"); - const auctionManager = await auctionManagerFactory.deploy(); - await auctionManager.deployed(); - const amEncodedFn = auctionManager.interface.encodeFunctionData("initialize", [ - platformPaymentAddress, - minimalForwarder.address, - mintManagerOwnerAddress, - initialPlatformExecutorAddress, - ]); - - const auctionManagerProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const auctionManagerProxy = await auctionManagerProxyFactory.deploy(auctionManager.address, amEncodedFn); - await auctionManagerProxy.deployed(); - - //Deploy EMR - const emrFactory = await ethers.getContractFactory("EditionsMetadataRenderer"); - const emr = await emrFactory.deploy(); - await emr.deployed(); - const emrEncodedFn = emr.interface.encodeFunctionData("initialize", [editionsMetadataOwnerAddress]); - - const emrProxyFactory = await ethers.getContractFactory("ERC1967Proxy"); - const emrProxy = await emrProxyFactory.deploy(emr.address, emrEncodedFn); - await emrProxy.deployed(); - - //Deploy Editions - const editionsFactory = await ethers.getContractFactory("ERC721Editions"); - const editions = await editionsFactory.deploy(); - await editions.deployed(); - - //Deploy EditionsDFS - const editionsDFSFactory = await ethers.getContractFactory("ERC721EditionsDFS"); - const editionsDFS = await editionsDFSFactory.deploy(); - await editionsDFS.deployed(); - - //Deploy Single Edition - const singleEditionFactory = await ethers.getContractFactory("ERC721SingleEdition"); - const singleEdition = await singleEditionFactory.deploy(); - await singleEdition.deployed(); - - //Deploy Single Edition DFS - const singleEditionDFSFactory = await ethers.getContractFactory("ERC721SingleEditionDFS"); - const singleEditionDFS = await singleEditionDFSFactory.deploy(); - await singleEditionDFS.deployed(); - - //Deploy General - const generalFactory = await ethers.getContractFactory("ERC721General"); - const general = await generalFactory.deploy(); - await general.deployed(); - - //Deploy GeneralSequence - const generalSequenceFactory = await ethers.getContractFactory("ERC721GeneralSequence"); - const generalSequence = await generalSequenceFactory.deploy(); - await generalSequence.deployed(); - - //Deploy Editions - const generativeFactory = await ethers.getContractFactory("ERC721GenerativeOnchain"); - const generative = await generativeFactory.deploy(); - await generative.deployed(); - - const observabilityFactory = await ethers.getContractFactory("Observability"); - const observability = await observabilityFactory.deploy(); - await observability.deployed(); - - const dutchAuctionImplFactory = await ethers.getContractFactory("DiscreteDutchAuctionMechanic"); - const dutchAuctionImpl = await dutchAuctionImplFactory.deploy(); - await dutchAuctionImpl.deployed(); - - const dutchAuctionEncodedFn = dutchAuctionImpl.interface.encodeFunctionData("initialize", [ - mintManagerProxy.address, - mintManagerOwnerAddress, - ]); - const dutchAuctionFactory = await ethers.getContractFactory("ERC1967Proxy"); - const dutchAuction = await dutchAuctionFactory.deploy(dutchAuctionImpl.address, dutchAuctionEncodedFn); - await dutchAuction.deployed(); - - return { - emrProxy: EditionsMetadataRenderer__factory.connect(emrProxy.address, signer), - mintManagerProxy: MintManager__factory.connect(mintManagerProxy.address, signer), - auctionManagerProxy: AuctionManager__factory.connect(auctionManagerProxy.address, signer), - minimalForwarder: MinimalForwarder__factory.connect(minimalForwarder.address, signer), - observability: Observability__factory.connect(observability.address, signer), - daMechanic: DiscreteDutchAuctionMechanic__factory.connect(dutchAuction.address, signer), - generalImplementationAddress: general.address, - generalSequenceImplementationAddress: generalSequence.address, - generativeImplementationAddress: generative.address, - editionsImplementationAddress: editions.address, - editionsDFSImplementationAddress: editionsDFS.address, - singleEditionImplementationAddress: singleEdition.address, - singleEditionDFSImplementationAddress: singleEditionDFS.address, - }; -} - -export const encodeMechanicVectorData = ( - mintManagerAddress: string, - paymentRecipient: string, - mechanicMint: OnchainDutchAuctionParams | null, -): BytesLike => { - let mechanicVectorData = "0x"; - if (mechanicMint) { - const dutchAuctionData = encodeDAVectorData(mechanicMint, paymentRecipient); - - mechanicVectorData = ethers.utils.defaultAbiCoder.encode( - ["uint96", "address", "address", "bytes"], - [mechanicMint.seed, mechanicMint.mechanicAddress, mintManagerAddress, dutchAuctionData], - ); - } - - return mechanicVectorData; -}; - -export const encodeDAVectorData = (mechanicMint: OnchainDutchAuctionParams, paymentRecipient: string): BytesLike => { - const { packedPrices, numPrices, bytesPerPrice } = encodeDutchAuctionPriceData(mechanicMint.prices); - - return ethers.utils.defaultAbiCoder.encode( - ["uint48", "uint48", "uint32", "uint32", "uint48", "uint32", "uint32", "uint8", "address", "bytes"], - [ - mechanicMint.startTimestamp, - mechanicMint.endTimestamp, - mechanicMint.periodDuration, - mechanicMint.maxUserClaimableViaVector, - mechanicMint.maxTotalClaimableViaVector, - mechanicMint.tokenLimitPerTx, - numPrices, - bytesPerPrice, - paymentRecipient, - packedPrices, - ], - ); -}; - -export const encodeDutchAuctionPriceData = ( - prices: string[], -): { packedPrices: BytesLike; numPrices: number; bytesPerPrice: number } => { - if (prices.length == 0) { - return { packedPrices: "0x", numPrices: 0, bytesPerPrice: 0 }; - } - - // expect in ether, expect 10^18, convert to wei - let biggestPrice = ethers.utils.parseEther(prices[0]); - for (const price of prices) { - if (ethers.utils.parseEther(price).gt(biggestPrice)) { - biggestPrice = ethers.utils.parseEther(price); - } - } - - const bytesPerPrice = numBytesNeeded(biggestPrice); - const packedPrices = ethers.utils.solidityPack( - new Array(prices.length).fill(`uint${bytesPerPrice * 8}`), - prices.map(price => { - return ethers.utils.parseEther(price); - }), - ); - - return { - packedPrices, - numPrices: prices.length, - bytesPerPrice, - }; -}; - -export const dutchAuctionUpdateArgs = ( - updateValues: DutchAuctionUpdateValues, -): { - dutchAuction: DiscreteDutchAuctionMechanic.DutchAuctionVectorStruct; - updateConfig: DiscreteDutchAuctionMechanic.DutchAuctionVectorUpdateConfigStruct; - packedPrices: BytesLike; -} => { - // if prices isn't updated, this returns 0 values for each field - const { numPrices, bytesPerPrice, packedPrices } = encodeDutchAuctionPriceData(updateValues.prices ?? []); - - const dutchAuction = { - startTimestamp: updateValues.startTimestamp ?? 0, - endTimestamp: updateValues.endTimestamp ?? 0, - periodDuration: updateValues.periodDuration ?? 0, - maxUserClaimableViaVector: updateValues.maxUserClaimableViaVector ?? 0, - maxTotalClaimableViaVector: updateValues.maxTotalClaimableViaVector ?? 0, - currentSupply: 0, - lowestPriceSoldAtIndex: 0, - tokenLimitPerTx: updateValues.tokenLimitPerTx ?? 0, - numPrices, - paymentRecipient: updateValues.paymentRecipient ?? ethers.constants.AddressZero, - totalSales: 0, - bytesPerPrice, - auctionExhausted: false, - payeeRevenueHasBeenWithdrawn: false, - }; - const updateConfig = { - updateStartTimestamp: updateValues.startTimestamp != undefined, - updateEndTimestamp: updateValues.endTimestamp != undefined, - updatePaymentRecipient: updateValues.paymentRecipient != undefined, - updateMaxTotalClaimableViaVector: updateValues.maxTotalClaimableViaVector != undefined, - updateTokenLimitPerTx: updateValues.tokenLimitPerTx != undefined, - updateMaxUserClaimableViaVector: updateValues.maxUserClaimableViaVector != undefined, - updatePrices: updateValues.prices != undefined, - updatePeriodDuration: updateValues.periodDuration != undefined, - }; - - return { - dutchAuction, - updateConfig, - packedPrices, - }; -}; - -const numBytesNeeded = (num: BigNumber) => { - const log10 = num.toString().length - 1; - const log2 = log10 / Math.log10(2); // convert log10 to log2 using the base change formula - return Math.floor(log2 / 8) + 1; -}; - -export const produceMechanicVectorId = ( - contractAddress: string, - mechanicAddress: string, - seed: number, - editionId?: number, -): string => { - return ethers.utils.solidityKeccak256( - ["address", "uint96", "address", "bool", "uint96"], - [contractAddress, editionId ?? 0, mechanicAddress, editionId != undefined, seed], - ); -}; - -export const hourFromNow = () => { - return Math.floor(Date.now() / 1000 + 3600); -}; diff --git a/test/__utils__/metaTx.ts b/test/__utils__/metaTx.ts deleted file mode 100644 index 6b72198..0000000 --- a/test/__utils__/metaTx.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; - -import { MinimalForwarder } from "../../types"; - -type Input2771 = { - from: string; // signer address - to: string; // contract being called - gas: number; // expected gas units for operation - data: string; // encoded function call on contract with arguments -}; - -type ForwardRequest = { - from: string; - to: string; - value: number; - gas: number; - nonce: string; - data: string; -}; - -const MINIMAL_FORWARDER_GAS_UNIT_CONSUMPTION = 60000; - -const ForwardRequest = [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "value", type: "uint256" }, - { name: "gas", type: "uint256" }, - { name: "nonce", type: "uint256" }, - { name: "data", type: "bytes" }, -]; - -function get2771MetaTxTypeData(chainId: number, verifyingContract: string) { - return { - types: { - // EIP712Domain, do not pass EIP712Domain type into ethers, it will pre-compute for us - ForwardRequest, - }, - domain: { - name: "MinimalForwarder", - version: "0.0.1", - chainId, - verifyingContract, - }, - primaryType: "ForwardRequest", - }; -} - -async function build2771Request(forwarder: MinimalForwarder, input: Input2771, overrideNonce?: number) { - const nonce = await forwarder.getNonce(input.from); - return { value: 0, nonce: overrideNonce == null ? nonce.toString() : overrideNonce.toString(), ...input }; -} - -async function build2771TypedData(forwarder: MinimalForwarder, request: ForwardRequest) { - const chainId = await forwarder.provider.getNetwork().then(n => n.chainId); - const typeData = get2771MetaTxTypeData(chainId, forwarder.address); - return { ...typeData, message: request }; -} - -export async function sign2771MetaTxRequest( - signer: SignerWithAddress, - forwarder: MinimalForwarder, - input: Input2771, - overrideNonce?: number, -) { - const request = await build2771Request(forwarder, input, overrideNonce); - const toSign = await build2771TypedData(forwarder, request); - const signature = await signer._signTypedData(toSign.domain, toSign.types, toSign.message); - return { signature, request }; -} diff --git a/test/__utils__/mint.ts b/test/__utils__/mint.ts deleted file mode 100644 index 2718665..0000000 --- a/test/__utils__/mint.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ethers } from "ethers"; - -import { EIP712 } from "./EIP712"; - -type ClaimInput = { - currency: string; - contractAddress: string; - claimer: string; - paymentRecipient: string; - pricePerToken: string; - numTokensToMint: number; - maxClaimableViaVector: number; - maxClaimablePerUser: number; - editionId: number; -}; - -type SeriesClaimInput = { - currency: string; - contractAddress: string; - claimer: string; - paymentRecipient: string; - pricePerToken: string; - maxPerTxn: number; - maxClaimableViaVector: number; - maxClaimablePerUser: number; -}; - -type ClaimWithMetaTxPacketInput = { - currency: string; - contractAddress: string; - claimer: string; - pricePerToken: string; - numTokensToMint: number; - purchaseToCreatorPacket: PurchaserMetaTxPacket; - purchaseToPlatformPacket: PurchaserMetaTxPacket; - maxClaimableViaVector: number; - maxClaimablePerUser: number; - editionId: number; -}; - -type PurchaserMetaTxPacket = { - functionSignature: string; - sigR: string; - sigS: string; - sigV: number; -}; - -const MetaTransaction = [ - { name: "nonce", type: "uint256" }, - { name: "from", type: "address" }, - { name: "functionSignature", type: "bytes" }, -]; - -export type Claim = ClaimInput & { - claimExpiryTimestamp: string; - claimNonce: string; - offchainVectorId: string; -}; - -export type ClaimWithMetaTxPacket = ClaimWithMetaTxPacketInput & { - claimExpiryTimestamp: string; - claimNonce: string; - offchainVectorId: string; -}; - -export type SeriesClaim = SeriesClaimInput & { - claimExpiryTimestamp: string; - claimNonce: string; - offchainVectorId: string; -}; - -const Claim = [ - { name: "currency", type: "address" }, - { name: "contractAddress", type: "address" }, - { name: "claimer", type: "address" }, - { name: "paymentRecipient", type: "address" }, - { name: "pricePerToken", type: "uint256" }, - { name: "numTokensToMint", type: "uint64" }, - { name: "maxClaimableViaVector", type: "uint256" }, - { name: "maxClaimablePerUser", type: "uint256" }, - { name: "editionId", type: "uint256" }, - { name: "claimExpiryTimestamp", type: "uint256" }, - { name: "claimNonce", type: "bytes32" }, - { name: "offchainVectorId", type: "bytes32" }, -]; - -const SeriesClaim = [ - { name: "currency", type: "address" }, - { name: "contractAddress", type: "address" }, - { name: "claimer", type: "address" }, - { name: "paymentRecipient", type: "address" }, - { name: "pricePerToken", type: "uint256" }, - { name: "maxPerTxn", type: "uint64" }, - { name: "maxClaimableViaVector", type: "uint64" }, - { name: "maxClaimablePerUser", type: "uint64" }, - { name: "claimExpiryTimestamp", type: "uint64" }, - { name: "claimNonce", type: "bytes32" }, - { name: "offchainVectorId", type: "bytes32" }, -]; - -const ClaimWithMetaTxPacket = [ - { name: "currency", type: "address" }, - { name: "contractAddress", type: "address" }, - { name: "claimer", type: "address" }, - { name: "pricePerToken", type: "uint256" }, - { name: "numTokensToMint", type: "uint64" }, - { name: "purchaseToCreatorPacket", type: "PurchaserMetaTxPacket" }, - { name: "purchaseToPlatformPacket", type: "PurchaserMetaTxPacket" }, - { name: "maxClaimableViaVector", type: "uint256" }, - { name: "maxClaimablePerUser", type: "uint256" }, - { name: "editionId", type: "uint256" }, - { name: "claimExpiryTimestamp", type: "uint256" }, - { name: "claimNonce", type: "bytes32" }, - { name: "offchainVectorId", type: "bytes32" }, -]; - -const PurchaserMetaTxPacket = [ - { name: "functionSignature", type: "bytes" }, - { name: "sigR", type: "bytes32" }, - { name: "sigS", type: "bytes32" }, - { name: "sigV", type: "uint8" }, -]; - -function getWETHMetaTxTypeData(salt: string, verifyingContract: string) { - return { - types: { - // EIP712Domain, do not pass EIP712Domain type into ethers, it will pre-compute for us - MetaTransaction, - }, - domain: { - name: "Wrapped Ether", - version: "1", - verifyingContract, - salt, - }, - primaryType: "MetaTransaction", - }; -} - -function buildClaim( - input: ClaimInput | SeriesClaimInput | ClaimWithMetaTxPacketInput, - offchainVectorId: string, - claimNonce: string, - claimExpiryTimestamp?: string, -): Claim | SeriesClaim | ClaimWithMetaTxPacket { - offchainVectorId = ethers.utils.formatBytes32String(offchainVectorId); - claimNonce = ethers.utils.formatBytes32String(claimNonce); - - return { - ...input, - claimExpiryTimestamp: claimExpiryTimestamp ?? (Math.floor(Date.now() / 1000) + 3600).toString(), - claimNonce, - offchainVectorId, - }; // give users 1 hr to use -} - -async function buildWETHTypedData(metaTx: any, contract: ethers.Contract) { - const chainIdBytes = ethers.utils.hexZeroPad( - ethers.utils.hexlify(await contract.provider.getNetwork().then(n => n.chainId)), - 32, - ); - const typeData = getWETHMetaTxTypeData(chainIdBytes, contract.address); - return { ...typeData, message: metaTx }; -} - -export async function signGatedMint( - signer: SignerWithAddress, - contract: ethers.Contract, - value: ClaimInput, - offchainVectorId: string, - claimNonce: string, - claimExpiryTimestamp?: string, -) { - const chainId = await contract.provider.getNetwork().then(n => n.chainId); - const claim = buildClaim(value, offchainVectorId, claimNonce, claimExpiryTimestamp) as Claim; - const eip712 = new EIP712(contract, signer, claim, { Claim }); - const signature = await eip712.sign(EIP712.buildDomain("MintManager", "1.0.0", contract.address, chainId)); - return { signature, claim }; -} - -export async function signGatedSeriesMint( - signer: SignerWithAddress, - contract: ethers.Contract, - value: SeriesClaimInput, - offchainVectorId: string, - claimNonce: string, - claimExpiryTimestamp?: string, -) { - const chainId = await contract.provider.getNetwork().then(n => n.chainId); - const claim = buildClaim(value, offchainVectorId, claimNonce, claimExpiryTimestamp) as SeriesClaim; - const eip712 = new EIP712(contract, signer, claim, { SeriesClaim }); - const signature = await eip712.sign(EIP712.buildDomain("MintManager", "1.0.0", contract.address, chainId)); - return { signature, claim }; -} - -export async function signGatedMintWithMetaTxPacket( - signer: SignerWithAddress, - contract: ethers.Contract, - value: ClaimWithMetaTxPacketInput, - offchainVectorId: string, - claimNonce: string, - claimExpiryTimestamp?: string, -) { - const chainId = await contract.provider.getNetwork().then(n => n.chainId); - const claim = buildClaim(value, offchainVectorId, claimNonce, claimExpiryTimestamp) as ClaimWithMetaTxPacket; - const eip712 = new EIP712(contract, signer, claim, { PurchaserMetaTxPacket, ClaimWithMetaTxPacket }); - const signature = await eip712.sign(EIP712.buildDomain("MintManager", "1.0.0", contract.address, chainId)); - return { signature, claim }; -} - -export async function signWETHMetaTxRequest( - signer: SignerWithAddress, - contract: ethers.Contract, - value: Record, -) { - const toSign = await buildWETHTypedData(value, contract); - const signature = await signer._signTypedData(toSign.domain, toSign.types, toSign.message); - const { r, s, v } = ethers.utils.splitSignature(signature); - return { functionSignature: value.functionSignature, sigR: r, sigS: s, sigV: v }; -} - -export const getValidClaimTimestamp = () => (Math.floor(Date.now() / 1000) + 360000).toString(); -export const getExpiredClaimTimestamp = () => (Math.floor(Date.now() / 1000) - 360000).toString(); diff --git a/yarn.lock b/yarn.lock index 2e0363a..285ea39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@ampproject/remapping@^2.1.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -174,6 +179,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.11.tgz#becf8ee33aad2a35ed5607f521fe6e72a615f905" integrity sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g== +"@babel/runtime@^7.4.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.16.7", "@babel/template@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -232,6 +244,16 @@ "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" +"@chainlink/contracts@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@chainlink/contracts/-/contracts-0.8.0.tgz#4050c83c8b1603ffb0fd6ab99f1d9ea9db2c37de" + integrity sha512-nUv1Uxw5Mn92wgLs2bgPYmo8hpdQ3s9jB/lcbdU0LmNOVu0hbfmouVnqwRLa28Ll50q6GczUA+eO0ikNIKLZsA== + dependencies: + "@eth-optimism/contracts" "^0.5.21" + "@openzeppelin/contracts" "~4.3.3" + "@openzeppelin/contracts-upgradeable-4.7.3" "npm:@openzeppelin/contracts-upgradeable@v4.7.3" + "@openzeppelin/contracts-v0.7" "npm:@openzeppelin/contracts@v3.4.2" + "@chainsafe/as-sha256@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" @@ -441,6 +463,49 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@ensdomains/address-encoder@^0.1.7": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz#f948c485443d9ef7ed2c0c4790e931c33334d02d" + integrity sha512-E2d2gP4uxJQnDu2Kfg1tHNspefzbLT8Tyjrm5sEuim32UkU2sm5xL4VXtgc2X33fmPEw9+jUMpGs4veMbf+PYg== + dependencies: + bech32 "^1.1.3" + blakejs "^1.1.0" + bn.js "^4.11.8" + bs58 "^4.0.1" + crypto-addr-codec "^0.1.7" + nano-base32 "^1.0.1" + ripemd160 "^2.0.2" + +"@ensdomains/ens@0.4.5": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc" + integrity sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw== + dependencies: + bluebird "^3.5.2" + eth-ens-namehash "^2.0.8" + solc "^0.4.20" + testrpc "0.0.1" + web3-utils "^1.0.0-beta.31" + +"@ensdomains/ensjs@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@ensdomains/ensjs/-/ensjs-2.1.0.tgz#0a7296c1f3d735ef019320d863a7846a0760c460" + integrity sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog== + dependencies: + "@babel/runtime" "^7.4.4" + "@ensdomains/address-encoder" "^0.1.7" + "@ensdomains/ens" "0.4.5" + "@ensdomains/resolver" "0.2.4" + content-hash "^2.5.2" + eth-ens-namehash "^2.0.8" + ethers "^5.0.13" + js-sha3 "^0.8.0" + +"@ensdomains/resolver@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@ensdomains/resolver/-/resolver-0.2.4.tgz#c10fe28bf5efbf49bff4666d909aed0265efbc89" + integrity sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -473,6 +538,37 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb" integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw== +"@eth-optimism/contracts@^0.5.21": + version "0.5.40" + resolved "https://registry.yarnpkg.com/@eth-optimism/contracts/-/contracts-0.5.40.tgz#d13a04a15ea947a69055e6fc74d87e215d4c936a" + integrity sha512-MrzV0nvsymfO/fursTB7m/KunkPsCndltVgfdHaT1Aj5Vi6R/doKIGGkOofHX+8B6VMZpuZosKCMQ5lQuqjt8w== + dependencies: + "@eth-optimism/core-utils" "0.12.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + +"@eth-optimism/core-utils@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.12.0.tgz#6337e4599a34de23f8eceb20378de2a2de82b0ea" + integrity sha512-qW+7LZYCz7i8dRa7SRlUKIo1VBU8lvN0HeXCxJR+z+xtMzMQpPds20XJNCMclszxYQHkXY00fOT6GvFw9ZL6nw== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/providers" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bufio "^1.0.7" + chai "^4.3.4" + "@ethereumjs/common@2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.5.0.tgz#ec61551b31bef7a69d1dc634d8932468866a4268" @@ -481,7 +577,7 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.1" -"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.4": +"@ethereumjs/common@2.6.5", "@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.4": version "2.6.5" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA== @@ -502,7 +598,7 @@ "@ethereumjs/common" "^2.5.0" ethereumjs-util "^7.1.2" -"@ethereumjs/tx@^3.3.2": +"@ethereumjs/tx@3.5.2", "@ethereumjs/tx@^3.3.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.5.2.tgz#197b9b6299582ad84f9527ca961466fce2296c1c" integrity sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw== @@ -607,7 +703,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -710,7 +806,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.6.8", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.10", "@ethersproject/providers@^5.6.8", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -920,6 +1016,25 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@manifoldxyz/creator-core-solidity@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@manifoldxyz/creator-core-solidity/-/creator-core-solidity-3.0.0.tgz#c7898e3e91d30a32067a505659be5491a2865ac4" + integrity sha512-bVKxdOJ6sLF2u20mxe1ltwKBBf+aVxyFzZEnFobxuBI04cly1SQ88KxrSJB/DuFKzCeimAfA5qyCY7NZoNhUCQ== + dependencies: + "@manifoldxyz/libraries-solidity" "^1.0.4" + "@openzeppelin/contracts" "^4.8.1" + "@openzeppelin/contracts-upgradeable" "^4.8.1" + "@openzeppelin/test-helpers" "^0.5.16" + truffle-contract-size "^2.0.1" + +"@manifoldxyz/libraries-solidity@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@manifoldxyz/libraries-solidity/-/libraries-solidity-1.0.4.tgz#d2b7d7ead11e8d85a27b0f59a8ff0e7b32479866" + integrity sha512-ibU4TgJ6DBp27j/P4KHKb3Sd1kILp1Q7awhU3To3RlL7ytK3G59B8C5YCUIH4sVzUFghs94FWZYmHvWjzW6f5Q== + dependencies: + "@openzeppelin/contracts" "4.7.3" + "@openzeppelin/contracts-upgradeable" "4.7.3" + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" @@ -931,6 +1046,13 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@mongodb-js/saslprep@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz#022fa36620a7287d17acd05c4aae1e5f390d250d" + integrity sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw== + dependencies: + sparse-bitfield "^3.0.3" + "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -938,6 +1060,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -948,7 +1077,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== -"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": +"@noble/hashes@1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== @@ -1124,6 +1253,14 @@ deep-eql "^4.0.1" ordinal "^1.0.3" +"@nomicfoundation/hardhat-ethers@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.6.tgz#e8ba7f9719de360c03501b85dae4999bb3a7e1c5" + integrity sha512-/xzkFQAaHQhmIAYOQmvHBPwL+NkwLzT9gRZBsgWUYeV+E6pzXsBQsHfRYbAZ3XEYare+T7S+5Tg/1KDJgepSkA== + dependencies: + debug "^4.1.1" + lodash.isequal "^4.5.0" + "@nomicfoundation/hardhat-network-helpers@^1.0.9": version "1.0.9" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.9.tgz#767449e8a2acda79306ac84626117583d95d25aa" @@ -1223,16 +1360,71 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contract-loader@^0.6.2": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contract-loader/-/contract-loader-0.6.3.tgz#61a7b44de327e40b7d53f39e0fb59bbf847335c3" + integrity sha512-cOFIjBjwbGgZhDZsitNgJl0Ye1rd5yu/Yx5LMgeq3u0ZYzldm4uObzHDFq4gjDdoypvyORjjJa3BlFA7eAnVIg== + dependencies: + find-up "^4.1.0" + fs-extra "^8.1.0" + +"@openzeppelin/contracts-upgradeable-4.7.3@npm:@openzeppelin/contracts-upgradeable@v4.7.3", "@openzeppelin/contracts-upgradeable@4.7.3": + name "@openzeppelin/contracts-upgradeable-4.7.3" + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" + integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== + "@openzeppelin/contracts-upgradeable@^4.7.3": version "4.9.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.3.tgz#ff17a80fb945f5102571f8efecb5ce5915cc4811" integrity sha512-jjaHAVRMrE4UuZNfDwjlLGDxTHWIOwTJS2ldnc278a0gevfXfPr8hxKEVBGFBE96kl2G3VHDZhUimw/+G3TG2A== +"@openzeppelin/contracts-upgradeable@^4.8.1": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" + integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== + +"@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" + integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== + +"@openzeppelin/contracts@4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" + integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== + "@openzeppelin/contracts@^4.7.2": version "4.9.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== +"@openzeppelin/contracts@^4.8.1": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" + integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== + +"@openzeppelin/contracts@~4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.3.tgz#ff6ee919fc2a1abaf72b22814bfb72ed129ec137" + integrity sha512-tDBopO1c98Yk7Cv/PZlHqrvtVjlgK5R4J6jxLwoO7qxK4xqOiZG+zSkIvGFpPZ0ikc3QOED3plgdqjgNTnBc7g== + +"@openzeppelin/test-helpers@^0.5.16": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz#2c9054f85069dfbfb5e8cef3ed781e8caf241fb3" + integrity sha512-T1EvspSfH1qQO/sgGlskLfYVBbqzJR23SZzYl/6B2JnT4EhThcI85UpvDk0BkLWKaDScQTabGHt4GzHW+3SfZg== + dependencies: + "@openzeppelin/contract-loader" "^0.6.2" + "@truffle/contract" "^4.0.35" + ansi-colors "^3.2.3" + chai "^4.2.0" + chai-bn "^0.2.1" + ethjs-abi "^0.2.1" + lodash.flatten "^4.4.0" + semver "^5.6.0" + web3 "^1.2.5" + web3-utils "^1.2.5" + "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -1399,11 +1591,94 @@ javascript-natural-sort "0.7.1" lodash "4.17.21" +"@truffle/abi-utils@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@truffle/abi-utils/-/abi-utils-1.0.3.tgz#9f0df7a8aaf5e815bee47e0ad26bd4c91e4045f2" + integrity sha512-AWhs01HCShaVKjml7Z4AbVREr/u4oiWxCcoR7Cktm0mEvtT04pvnxW5xB/cI4znRkrbPdFQlFt67kgrAjesYkw== + dependencies: + change-case "3.0.2" + fast-check "3.1.1" + web3-utils "1.10.0" + +"@truffle/blockchain-utils@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@truffle/blockchain-utils/-/blockchain-utils-0.1.9.tgz#d9b55bd23a134578e4217bae55a6dfbbb038d6dc" + integrity sha512-RHfumgbIVo68Rv9ofDYfynjnYZIfP/f1vZy4RoqkfYAO+fqfc58PDRzB1WAGq2U6GPuOnipOJxQhnqNnffORZg== + +"@truffle/codec@^0.17.3": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@truffle/codec/-/codec-0.17.3.tgz#94057e56e1a947594b35eba498d96915df3861d2" + integrity sha512-Ko/+dsnntNyrJa57jUD9u4qx9nQby+H4GsUO6yjiCPSX0TQnEHK08XWqBSg0WdmCH2+h0y1nr2CXSx8gbZapxg== + dependencies: + "@truffle/abi-utils" "^1.0.3" + "@truffle/compile-common" "^0.9.8" + big.js "^6.0.3" + bn.js "^5.1.3" + cbor "^5.2.0" + debug "^4.3.1" + lodash "^4.17.21" + semver "^7.5.4" + utf8 "^3.0.0" + web3-utils "1.10.0" + +"@truffle/compile-common@^0.9.8": + version "0.9.8" + resolved "https://registry.yarnpkg.com/@truffle/compile-common/-/compile-common-0.9.8.tgz#f91507c895852289a17bf401eefebc293c4c69f0" + integrity sha512-DTpiyo32t/YhLI1spn84D3MHYHrnoVqO+Gp7ZHrYNwDs86mAxtNiH5lsVzSb8cPgiqlvNsRCU9nm9R0YmKMTBQ== + dependencies: + "@truffle/error" "^0.2.2" + colors "1.4.0" + +"@truffle/contract-schema@^3.4.16": + version "3.4.16" + resolved "https://registry.yarnpkg.com/@truffle/contract-schema/-/contract-schema-3.4.16.tgz#c529c3f230db407b2f03290373b20b7366f2d37e" + integrity sha512-g0WNYR/J327DqtJPI70ubS19K1Fth/1wxt2jFqLsPmz5cGZVjCwuhiie+LfBde4/Mc9QR8G+L3wtmT5cyoBxAg== + dependencies: + ajv "^6.10.0" + debug "^4.3.1" + +"@truffle/contract@^4.0.35": + version "4.6.31" + resolved "https://registry.yarnpkg.com/@truffle/contract/-/contract-4.6.31.tgz#75cb059689ce73b365675d9650718908c01b6b58" + integrity sha512-s+oHDpXASnZosiCdzu+X1Tx5mUJUs1L1CYXIcgRmzMghzqJkaUFmR6NpNo7nJYliYbO+O9/aW8oCKqQ7rCHfmQ== + dependencies: + "@ensdomains/ensjs" "^2.1.0" + "@truffle/blockchain-utils" "^0.1.9" + "@truffle/contract-schema" "^3.4.16" + "@truffle/debug-utils" "^6.0.57" + "@truffle/error" "^0.2.2" + "@truffle/interface-adapter" "^0.5.37" + bignumber.js "^7.2.1" + debug "^4.3.1" + ethers "^4.0.32" + web3 "1.10.0" + web3-core-helpers "1.10.0" + web3-core-promievent "1.10.0" + web3-eth-abi "1.10.0" + web3-utils "1.10.0" + +"@truffle/debug-utils@^6.0.57": + version "6.0.57" + resolved "https://registry.yarnpkg.com/@truffle/debug-utils/-/debug-utils-6.0.57.tgz#4e9a1051221c5f467daa398b0ca638d8b6408a82" + integrity sha512-Q6oI7zLaeNLB69ixjwZk2UZEWBY6b2OD1sjLMGDKBGR7GaHYiw96GLR2PFgPH1uwEeLmV4N78LYaQCrDsHbNeA== + dependencies: + "@truffle/codec" "^0.17.3" + "@trufflesuite/chromafi" "^3.0.0" + bn.js "^5.1.3" + chalk "^2.4.2" + debug "^4.3.1" + highlightjs-solidity "^2.0.6" + "@truffle/error@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.1.1.tgz#e52026ac8ca7180d83443dca73c03e07ace2a301" integrity sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA== +"@truffle/error@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.2.2.tgz#1b4c4237c14dda792f20bd4f19ff4e4585b47796" + integrity sha512-TqbzJ0O8DHh34cu8gDujnYl4dUl6o2DE4PR6iokbybvnIm/L2xl6+Gv1VC+YJS45xfH83Yo3/Zyg/9Oq8/xZWg== + "@truffle/interface-adapter@^0.5.25": version "0.5.35" resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.5.35.tgz#f0eb1c4a2803190ca249143f545029a8b641fe96" @@ -1413,6 +1688,15 @@ ethers "^4.0.32" web3 "1.10.0" +"@truffle/interface-adapter@^0.5.37": + version "0.5.37" + resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.5.37.tgz#95d249c1912d2baaa63c54e8a138d3f476a1181a" + integrity sha512-lPH9MDgU+7sNDlJSClwyOwPCfuOimqsCx0HfGkznL3mcFRymc1pukAR1k17zn7ErHqBwJjiKAZ6Ri72KkS+IWw== + dependencies: + bn.js "^5.1.3" + ethers "^4.0.32" + web3 "1.10.0" + "@truffle/provider@^0.2.24": version "0.2.64" resolved "https://registry.yarnpkg.com/@truffle/provider/-/provider-0.2.64.tgz#7dd55117307fd019dcf81d08db5dc2bc5728f51c" @@ -1423,6 +1707,20 @@ debug "^4.3.1" web3 "1.7.4" +"@trufflesuite/chromafi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz#f6956408c1af6a38a6ed1657783ce59504a1eb8b" + integrity sha512-oqWcOqn8nT1bwlPPfidfzS55vqcIDdpfzo3HbU9EnUmcSTX+I8z0UyUFI3tZQjByVJulbzxHxUGS3ZJPwK/GPQ== + dependencies: + camelcase "^4.1.0" + chalk "^2.3.2" + cheerio "^1.0.0-rc.2" + detect-indent "^5.0.0" + highlight.js "^10.4.1" + lodash.merge "^4.6.2" + strip-ansi "^4.0.0" + strip-indent "^2.0.0" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1451,6 +1749,14 @@ lodash "^4.17.15" ts-essentials "^7.0.1" +"@typechain/ethers-v6@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz#42fe214a19a8b687086c93189b301e2b878797ea" + integrity sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + "@typechain/hardhat@^6.1.2": version "6.1.6" resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.6.tgz#1a749eb35e5054c80df531cf440819cb347c62ea" @@ -1572,6 +1878,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.6.tgz#5e9aaa86be03a09decafd61b128d6cec64a5fe40" integrity sha512-Gi5wRGPbbyOTX+4Y2iULQ27oUPrefaB0PxGQJnfyWN3kvEDGM3mIB5M/gQLmitZf7A9FmLeaqxD3L1CXpm3VKQ== +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + "@types/node@20.4.7": version "20.4.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.7.tgz#74d323a93f1391a63477b27b9aec56669c98b2ab" @@ -1653,6 +1964,19 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/webidl-conversions@*": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" + integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@typescript-eslint/eslint-plugin@^5.30.7": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" @@ -1737,6 +2061,11 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +"@uniswap/v3-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + "@vue/compiler-core@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" @@ -1813,7 +2142,7 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== -abortcontroller-polyfill@^1.7.3: +abortcontroller-polyfill@^1.7.3, abortcontroller-polyfill@^1.7.5: version "1.7.5" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== @@ -1869,6 +2198,11 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1884,7 +2218,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.6: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1919,6 +2253,11 @@ ansi-colors@4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + ansi-colors@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -1938,6 +2277,11 @@ ansi-escapes@^5.0.0: dependencies: type-fest "^1.0.2" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + ansi-regex@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" @@ -2188,16 +2532,31 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bech32@1.1.4: +bech32@1.1.4, bech32@^1.1.3: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +big-integer@1.6.36: + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" + integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + +big.js@^6.0.3: + version "6.2.1" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" + integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== + bigint-crypto-utils@^3.0.23: version "3.3.0" resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz#72ad00ae91062cf07f2b1def9594006c279c1d77" integrity sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg== +bignumber.js@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" + integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== + bignumber.js@^9.0.0, bignumber.js@^9.0.1: version "9.1.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" @@ -2222,7 +2581,7 @@ blakejs@^1.1.0: resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== -bluebird@^3.5.0, bluebird@^3.7.2: +bluebird@^3.5.0, bluebird@^3.5.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -2278,6 +2637,11 @@ body-parser@^1.16.0: type-is "~1.6.18" unpipe "1.0.0" +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2384,7 +2748,7 @@ browserslist@^4.21.9: node-releases "^2.0.13" update-browserslist-db "^1.0.11" -bs58@^4.0.0: +bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== @@ -2400,6 +2764,11 @@ bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" +bson@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-5.4.0.tgz#0eea77276d490953ad8616b483298dbff07384c6" + integrity sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2420,6 +2789,14 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== +buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.0.5, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2428,14 +2805,6 @@ buffer@^5.0.5, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.7" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" @@ -2443,6 +2812,11 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +bufio@^1.0.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.2.1.tgz#8d4ab3ddfcd5faa90f996f922f9397d41cbaf2de" + integrity sha512-9oR3zNdupcg/Ge2sSHQF3GX+kmvL/fTPvD0nd5AGLq8SjUYnTz+SlFjK/GXidndbZtIj+pVKXiWeR9w6e9wKCA== + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -2509,6 +2883,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -2518,6 +2900,16 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -2548,6 +2940,14 @@ catering@^2.1.0, catering@^2.1.1: resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== +cbor@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c" + integrity sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A== + dependencies: + bignumber.js "^9.0.1" + nofilter "^1.0.4" + cbor@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5" @@ -2562,6 +2962,37 @@ chai-as-promised@^7.1.1: dependencies: check-error "^1.0.2" +chai-bn@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/chai-bn/-/chai-bn-0.2.2.tgz#4dcf30dbc79db2378a00781693bc749c972bf34f" + integrity sha512-MzjelH0p8vWn65QKmEq/DLBG1Hle4WeyqT79ANhXZhn/UxRWO0OogkAxi5oGGtfzwU9bZR8mvbvYdoqNVWQwFg== + +chai@^4.2.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + +chai@^4.3.4: + version "4.3.10" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" + integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chai@^4.3.6: version "4.3.8" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.8.tgz#40c59718ad6928da6629c70496fe990b2bb5b17c" @@ -2580,7 +3011,7 @@ chalk@5.3.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== -chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2597,6 +3028,30 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +change-case@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" + integrity sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA== + dependencies: + camel-case "^3.0.0" + constant-case "^2.0.0" + dot-case "^2.1.0" + header-case "^1.0.0" + is-lower-case "^1.1.0" + is-upper-case "^1.1.0" + lower-case "^1.1.1" + lower-case-first "^1.0.0" + no-case "^2.3.2" + param-case "^2.1.0" + pascal-case "^2.0.0" + path-case "^2.1.0" + sentence-case "^2.1.0" + snake-case "^2.1.0" + swap-case "^1.1.0" + title-case "^2.1.0" + upper-case "^1.1.1" + upper-case-first "^1.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -2612,6 +3067,38 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + chokidar@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" @@ -2741,6 +3228,13 @@ cli-table3@^0.6.0: optionalDependencies: "@colors/colors" "1.5.0" +cli-table@^0.3.1: + version "0.3.11" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" + integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ== + dependencies: + colors "1.0.3" + cli-truncate@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" @@ -2754,6 +3248,15 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -2763,6 +3266,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -2793,6 +3305,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2822,6 +3339,11 @@ colorette@^2.0.20: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw== + colors@1.4.0, colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -2929,6 +3451,14 @@ console-table-printer@^2.9.0: dependencies: simple-wcswidth "^1.0.1" +constant-case@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" + integrity sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ== + dependencies: + snake-case "^2.1.0" + upper-case "^1.1.1" + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -3092,6 +3622,13 @@ cross-fetch@^3.1.4: dependencies: node-fetch "^2.6.12" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -3106,6 +3643,19 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +crypto-addr-codec@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/crypto-addr-codec/-/crypto-addr-codec-0.1.8.tgz#45c4b24e2ebce8e24a54536ee0ca25b65787b016" + integrity sha512-GqAK90iLLgP3FvhNmHbpT3wR6dEdaM8hZyZtLX29SPardh3OA13RFLHDR6sntGCgRWOfiHqW6sIyohpNqOtV/g== + dependencies: + base-x "^3.0.8" + big-integer "1.6.36" + blakejs "^1.1.0" + bs58 "^4.0.1" + ripemd160-min "0.0.6" + safe-buffer "^5.2.0" + sha3 "^2.1.1" + crypto-browserify@3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3128,6 +3678,22 @@ crypto-js@^3.1.9-1: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + csv-parse@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.1.tgz#ed08dc538c1b009c77428087470356830e6bbb41" @@ -3201,7 +3767,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -3235,7 +3801,7 @@ dedent@0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== -deep-eql@^4.0.1, deep-eql@^4.1.2: +deep-eql@^4.0.1, deep-eql@^4.1.2, deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== @@ -3310,6 +3876,11 @@ detect-indent@6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== + detect-port@^1.3.0: version "1.5.1" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" @@ -3356,11 +3927,48 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee" + integrity sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug== + dependencies: + no-case "^2.2.0" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -3449,12 +4057,17 @@ enquirer@^2.3.0: ansi-colors "^4.1.1" strip-ansi "^6.0.1" +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -error-ex@^1.3.1: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== @@ -3734,7 +4347,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -eth-ens-namehash@2.0.8: +eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw== @@ -3832,6 +4445,14 @@ ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethereum-multicall@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/ethereum-multicall/-/ethereum-multicall-2.24.0.tgz#b6ff351a632cae7882ca24224bd3ea2426b6e213" + integrity sha512-+xWNmtWdpL4zOdAF2zeLYa1ZsrHLeU7KLmBAsc0el140qdQmF9ubyM9SwKHzy+xztTHi+eCnmCjknqhEU25gFA== + dependencies: + "@ethersproject/providers" "^5.0.10" + ethers "^5.0.15" + ethereumjs-abi@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" @@ -3879,7 +4500,7 @@ ethers@^4.0.32, ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.7.1: +ethers@^5.0.13, ethers@^5.0.15, ethers@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3915,6 +4536,28 @@ ethers@^5.7.1: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.0.tgz#f342958d0f622cf06559f59fbccdc1d30fa64f50" + integrity sha512-+yyQQQWEntY5UVbCv++guA14RRVFm1rSnO1GoLFdrK7/XRWMoktNgyG9UjwxrQqGBfGyFKknNZ81YpUS2emCgg== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + +ethjs-abi@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.2.1.tgz#e0a7a93a7e81163a94477bad56ede524ab6de533" + integrity sha512-g2AULSDYI6nEJyJaEVEXtTimRY2aPC2fi7ddSy0W+LXvEVL8Fe1y76o43ecbgdUKwZD+xsmEgX1yJr1Ia3r1IA== + dependencies: + bn.js "4.11.6" + js-sha3 "0.5.5" + number-to-bn "1.7.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -4062,6 +4705,13 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== +fast-check@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.1.1.tgz#72c5ae7022a4e86504762e773adfb8a5b0b01252" + integrity sha512-3vtXinVyuUKCKFKYcwXhGE6NtGWkqF8Yh3rvMZNzmwz8EPrgoc/v4pDdLHyLnCyCI5MZpZZkDEwFyXyEONOxpA== + dependencies: + pure-rand "^5.0.1" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4169,6 +4819,14 @@ find-up@5.0.0, find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4416,6 +5074,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -4426,6 +5089,11 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -4939,6 +5607,24 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +header-case@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d" + integrity sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.3" + +highlight.js@^10.4.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +highlightjs-solidity@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/highlightjs-solidity/-/highlightjs-solidity-2.0.6.tgz#e7a702a2b05e0a97f185e6ba39fd4846ad23a990" + integrity sha512-DySXWfQghjm2l6a/flF+cteroJqD4gI8GSdL4PtvxZSsAHie8m3yVe2JFoRg03ROKT6hp2Lc/BxXkqerNmtQYg== + hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4967,6 +5653,16 @@ hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-basic@^8.1.1: version "8.1.3" resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" @@ -5153,6 +5849,11 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== + io-ts@1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" @@ -5160,6 +5861,11 @@ io-ts@1.10.4: dependencies: fp-ts "^1.0.0" +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -5238,6 +5944,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -5282,8 +5995,15 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-negative-zero@^2.0.2: - version "2.0.2" +is-lower-case@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" + integrity sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA== + dependencies: + lower-case "^1.1.0" + +is-negative-zero@^2.0.2: + version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== @@ -5387,7 +6107,14 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-utf8@^0.2.1: +is-upper-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" + integrity sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw== + dependencies: + upper-case "^1.1.0" + +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== @@ -5434,6 +6161,11 @@ js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847" integrity sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w== +js-sha3@0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" + integrity sha512-yLLwn44IVeunwjpDVTDZmQeVbB0h+dZpY2eO68B/Zik8hu6dH+rKeLxwua79GGIvW6xr8NBAcrtiUbYrTjEFTA== + js-sha3@0.5.7, js-sha3@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" @@ -5614,6 +6346,13 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw== + dependencies: + invert-kv "^1.0.0" + level-supports@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" @@ -5689,6 +6428,17 @@ listr2@6.6.1: rfdc "^1.3.0" wrap-ansi "^8.1.0" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5719,11 +6469,26 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.assign@^4.0.3, lodash.assign@^4.0.6: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -5822,6 +6587,25 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + +lower-case-first@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" + integrity sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA== + dependencies: + lower-case "^1.1.2" + +lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -5932,6 +6716,11 @@ memory-level@^1.0.0: functional-red-black-tree "^1.0.1" module-error "^1.0.1" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -6237,6 +7026,25 @@ module-error@^1.0.1, module-error@^1.0.2: resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== +mongodb-connection-string-url@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf" + integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^5.7.0: + version "5.8.1" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.8.1.tgz#dc201adfbd6c6d73401cdcf12ebdb75f14771faf" + integrity sha512-wKyh4kZvm6NrCPH8AxyzXm3JBoEf4Xulo0aUWh3hCgwgYJxyQ1KLST86ZZaSWdj6/kxYUA3+YZuyADCE61CMSg== + dependencies: + bson "^5.4.0" + mongodb-connection-string-url "^2.6.0" + socks "^2.7.1" + optionalDependencies: + "@mongodb-js/saslprep" "^1.1.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -6302,6 +7110,11 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +nano-base32@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nano-base32/-/nano-base32-1.0.1.tgz#ba548c879efcfb90da1c4d9e097db4a46c9255ef" + integrity sha512-sxEtoTqAPdjWVGv71Q17koMFGsOMSiHsIFEvzOM7cNp8BXB4AnEwmDabm5dorusJf/v1z7QxaZYxUorU9RKaAw== + nano-json-stream-parser@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" @@ -6347,6 +7160,13 @@ next-tick@1, next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== +no-case@^2.2.0, no-case@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -6384,6 +7204,11 @@ node-releases@^2.0.13: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +nofilter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e" + integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA== + nofilter@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" @@ -6396,7 +7221,7 @@ nopt@3.x: dependencies: abbrev "1" -normalize-package-data@^2.5.0: +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -6445,6 +7270,18 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== + number-to-bn@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" @@ -6588,6 +7425,13 @@ ordinal@^1.0.3: resolved "https://registry.yarnpkg.com/ordinal/-/ordinal-1.0.3.tgz#1a3c7726a61728112f50944ad7c35c06ae3a0d4d" integrity sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ== +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g== + dependencies: + lcid "^1.0.0" + os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -6674,6 +7518,13 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +param-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== + dependencies: + no-case "^2.2.0" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6702,6 +7553,13 @@ parse-headers@^2.0.0: resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" + parse-json@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -6717,11 +7575,48 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascal-case@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e" + integrity sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ== + dependencies: + camel-case "^3.0.0" + upper-case-first "^1.1.0" + +path-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" + integrity sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q== + dependencies: + no-case "^2.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== + dependencies: + pinkie-promise "^2.0.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6757,6 +7652,15 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -6798,11 +7702,28 @@ pidtree@0.6.0: resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + pinst@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pinst/-/pinst-3.0.0.tgz#80dec0a85f1f993c6084172020f3dbf512897eec" @@ -6923,6 +7844,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +pure-rand@^5.0.1: + version "5.0.5" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-5.0.5.tgz#bda2a7f6a1fc0f284d78d78ca5902f26f2ad35cf" + integrity sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw== + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -7006,6 +7932,14 @@ raw-body@2.5.2, raw-body@^2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" @@ -7015,6 +7949,15 @@ read-pkg-up@^7.0.1: read-pkg "^5.2.0" type-fest "^0.8.1" +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" @@ -7088,6 +8031,11 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" @@ -7158,11 +8106,21 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + integrity sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q== + require-from-string@^2.0.0, require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -7278,7 +8236,12 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: +ripemd160-min@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62" + integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A== + +ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -7397,7 +8360,7 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -7414,6 +8377,13 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -7433,6 +8403,14 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +sentence-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-2.1.1.tgz#1f6e2dda39c168bf92d13f86d4a918933f667ed4" + integrity sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ== + dependencies: + no-case "^2.2.0" + upper-case-first "^1.1.2" + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -7497,6 +8475,13 @@ sha1@^1.1.1: charenc ">= 0.0.1" crypt ">= 0.0.1" +sha3@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f" + integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg== + dependencies: + buffer "6.0.3" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7581,6 +8566,26 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q== + dependencies: + no-case "^2.2.0" + +socks@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + sol-merger@^4.1.1: version "4.4.0" resolved "https://registry.yarnpkg.com/sol-merger/-/sol-merger-4.4.0.tgz#d637cb50d1553dde1f9fb19833bf6ea01ba2da11" @@ -7609,6 +8614,17 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +solc@^0.4.20: + version "0.4.26" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.26.tgz#5390a62a99f40806b86258c737c1cf653cc35cb5" + integrity sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA== + dependencies: + fs-extra "^0.30.0" + memorystream "^0.3.1" + require-from-string "^1.1.0" + semver "^5.3.0" + yargs "^4.7.1" + solhint-plugin-prettier@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz#e3b22800ba435cd640a9eca805a7f8bc3e3e6a6b" @@ -7700,6 +8716,13 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -7797,6 +8820,15 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + "string-width@^1.0.2 || 2", string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -7873,6 +8905,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -7906,6 +8945,13 @@ strip-bom@4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== + dependencies: + is-utf8 "^0.2.0" + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -7923,6 +8969,11 @@ strip-hex-prefix@1.0.0: dependencies: is-hex-prefixed "1.0.0" +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -7980,6 +9031,14 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swap-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" + integrity sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ== + dependencies: + lower-case "^1.1.1" + upper-case "^1.1.1" + swarm-js@^0.1.40: version "0.1.42" resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.42.tgz#497995c62df6696f6e22372f457120e43e727979" @@ -8047,6 +9106,11 @@ tar@^4.0.2: safe-buffer "^5.2.1" yallist "^3.1.1" +testrpc@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" + integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== + text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" @@ -8099,6 +9163,14 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" +title-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" + integrity sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q== + dependencies: + no-case "^2.2.0" + upper-case "^1.0.3" + tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8136,6 +9208,13 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -8151,6 +9230,14 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +truffle-contract-size@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/truffle-contract-size/-/truffle-contract-size-2.0.1.tgz#9ec1b078b0b3221cc1612400bd6f5df83c7a3a09" + integrity sha512-AIKPwHPC/1pZwtVjgUcgcK23k6gWxKhn4ZnKLr339uieb94UgAUeIwGUkfc87T+0lqgC6ePY7YhsFeoZK2YEsA== + dependencies: + cli-table "^0.3.1" + yargs "^15.3.1" + ts-command-line-args@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" @@ -8205,6 +9292,11 @@ ts-node@^10.8.1, ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -8263,7 +9355,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -8458,6 +9550,18 @@ update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" +upper-case-first@^1.1.0, upper-case-first@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" + integrity sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ== + dependencies: + upper-case "^1.1.1" + +upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1, upper-case@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -8484,7 +9588,7 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "^4.3.0" -utf8@3.0.0: +utf8@3.0.0, utf8@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== @@ -8583,6 +9687,15 @@ web3-bzz@1.10.0: got "12.1.0" swarm-js "^0.1.40" +web3-bzz@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.4.tgz#dcc787970767d9004c73d11d0eeef774ce16b880" + integrity sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw== + dependencies: + "@types/node" "^12.12.6" + got "12.1.0" + swarm-js "^0.1.40" + web3-bzz@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.7.4.tgz#9419e606e38a9777443d4ce40506ebd796e06075" @@ -8600,6 +9713,14 @@ web3-core-helpers@1.10.0: web3-eth-iban "1.10.0" web3-utils "1.10.0" +web3-core-helpers@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz#bd2b4140df2016d5dd3bb2b925fc29ad8678677c" + integrity sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g== + dependencies: + web3-eth-iban "1.10.4" + web3-utils "1.10.4" + web3-core-helpers@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.7.4.tgz#f8f808928560d3e64e0c8d7bdd163aa4766bcf40" @@ -8619,6 +9740,17 @@ web3-core-method@1.10.0: web3-core-subscriptions "1.10.0" web3-utils "1.10.0" +web3-core-method@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.10.4.tgz#566b52f006d3cbb13b21b72b8d2108999bf5d6bf" + integrity sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA== + dependencies: + "@ethersproject/transactions" "^5.6.2" + web3-core-helpers "1.10.4" + web3-core-promievent "1.10.4" + web3-core-subscriptions "1.10.4" + web3-utils "1.10.4" + web3-core-method@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.7.4.tgz#3873c6405e1a0a8a1efc1d7b28de8b7550b00c15" @@ -8637,6 +9769,13 @@ web3-core-promievent@1.10.0: dependencies: eventemitter3 "4.0.4" +web3-core-promievent@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz#629b970b7934430b03c5033c79f3bb3893027e22" + integrity sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ== + dependencies: + eventemitter3 "4.0.4" + web3-core-promievent@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.7.4.tgz#80a75633fdfe21fbaae2f1e38950edb2f134868c" @@ -8655,6 +9794,17 @@ web3-core-requestmanager@1.10.0: web3-providers-ipc "1.10.0" web3-providers-ws "1.10.0" +web3-core-requestmanager@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz#eb1f147e6b9df84e3a37e602162f8925bdb4bb9a" + integrity sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg== + dependencies: + util "^0.12.5" + web3-core-helpers "1.10.4" + web3-providers-http "1.10.4" + web3-providers-ipc "1.10.4" + web3-providers-ws "1.10.4" + web3-core-requestmanager@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.7.4.tgz#2dc8a526dab8183dca3fef54658621801b1d0469" @@ -8674,6 +9824,14 @@ web3-core-subscriptions@1.10.0: eventemitter3 "4.0.4" web3-core-helpers "1.10.0" +web3-core-subscriptions@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz#2f4dcb404237e92802a563265d11a33934dc38e6" + integrity sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.4" + web3-core-subscriptions@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.7.4.tgz#cfbd3fa71081a8c8c6f1a64577a1a80c5bd9826f" @@ -8695,6 +9853,19 @@ web3-core@1.10.0: web3-core-requestmanager "1.10.0" web3-utils "1.10.0" +web3-core@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.4.tgz#639de68b8b9871d2dc8892e0dd4e380cb1361a98" + integrity sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww== + dependencies: + "@types/bn.js" "^5.1.1" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-requestmanager "1.10.4" + web3-utils "1.10.4" + web3-core@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.7.4.tgz#943fff99134baedafa7c65b4a0bbd424748429ff" @@ -8716,6 +9887,14 @@ web3-eth-abi@1.10.0: "@ethersproject/abi" "^5.6.3" web3-utils "1.10.0" +web3-eth-abi@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz#16c19d0bde0aaf8c1a56cb7743a83156d148d798" + integrity sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ== + dependencies: + "@ethersproject/abi" "^5.6.3" + web3-utils "1.10.4" + web3-eth-abi@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.7.4.tgz#3fee967bafd67f06b99ceaddc47ab0970f2a614a" @@ -8740,6 +9919,22 @@ web3-eth-accounts@1.10.0: web3-core-method "1.10.0" web3-utils "1.10.0" +web3-eth-accounts@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz#df30e85a7cd70e475f8cf52361befba408829e34" + integrity sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg== + dependencies: + "@ethereumjs/common" "2.6.5" + "@ethereumjs/tx" "3.5.2" + "@ethereumjs/util" "^8.1.0" + eth-lib "0.2.8" + scrypt-js "^3.0.1" + uuid "^9.0.0" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-utils "1.10.4" + web3-eth-accounts@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.7.4.tgz#7a24a4dfe947f7e9d1bae678529e591aa146167a" @@ -8771,6 +9966,20 @@ web3-eth-contract@1.10.0: web3-eth-abi "1.10.0" web3-utils "1.10.0" +web3-eth-contract@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz#22d39f04e11d9ff4e726e8025a56d78e843a2c3d" + integrity sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A== + dependencies: + "@types/bn.js" "^5.1.1" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-promievent "1.10.4" + web3-core-subscriptions "1.10.4" + web3-eth-abi "1.10.4" + web3-utils "1.10.4" + web3-eth-contract@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.7.4.tgz#e5761cfb43d453f57be4777b2e5e7e1082078ff7" @@ -8799,6 +10008,20 @@ web3-eth-ens@1.10.0: web3-eth-contract "1.10.0" web3-utils "1.10.0" +web3-eth-ens@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz#3d991adac52bc8e598f1f1b8528337fa6291004c" + integrity sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-promievent "1.10.4" + web3-eth-abi "1.10.4" + web3-eth-contract "1.10.4" + web3-utils "1.10.4" + web3-eth-ens@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.7.4.tgz#346720305379c0a539e226141a9602f1da7bc0c8" @@ -8821,6 +10044,14 @@ web3-eth-iban@1.10.0: bn.js "^5.2.1" web3-utils "1.10.0" +web3-eth-iban@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz#bc61b4a1930d19b1df8762c606d669902558e54d" + integrity sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw== + dependencies: + bn.js "^5.2.1" + web3-utils "1.10.4" + web3-eth-iban@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.7.4.tgz#711fb2547fdf0f988060027331b2b6c430505753" @@ -8841,6 +10072,18 @@ web3-eth-personal@1.10.0: web3-net "1.10.0" web3-utils "1.10.0" +web3-eth-personal@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz#e2ee920f47e84848288e03442659cdbb2c4deea2" + integrity sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-net "1.10.4" + web3-utils "1.10.4" + web3-eth-personal@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.7.4.tgz#22c399794cb828a75703df8bb4b3c1331b471546" @@ -8871,6 +10114,24 @@ web3-eth@1.10.0: web3-net "1.10.0" web3-utils "1.10.0" +web3-eth@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.10.4.tgz#3a908c635cb5d935bd30473e452f3bd7f2ee66a5" + integrity sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA== + dependencies: + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-subscriptions "1.10.4" + web3-eth-abi "1.10.4" + web3-eth-accounts "1.10.4" + web3-eth-contract "1.10.4" + web3-eth-ens "1.10.4" + web3-eth-iban "1.10.4" + web3-eth-personal "1.10.4" + web3-net "1.10.4" + web3-utils "1.10.4" + web3-eth@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.7.4.tgz#a7c1d3ccdbba4de4a82df7e3c4db716e4a944bf2" @@ -8898,6 +10159,15 @@ web3-net@1.10.0: web3-core-method "1.10.0" web3-utils "1.10.0" +web3-net@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.10.4.tgz#20e12c60e4477d4298979d8d5d66b9abf8e66a09" + integrity sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow== + dependencies: + web3-core "1.10.4" + web3-core-method "1.10.4" + web3-utils "1.10.4" + web3-net@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.7.4.tgz#3153dfd3423262dd6fbec7aae5467202c4cad431" @@ -8917,6 +10187,16 @@ web3-providers-http@1.10.0: es6-promise "^4.2.8" web3-core-helpers "1.10.0" +web3-providers-http@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.10.4.tgz#ca7aa58aeaf8123500c24ffe0595896319f830e8" + integrity sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ== + dependencies: + abortcontroller-polyfill "^1.7.5" + cross-fetch "^4.0.0" + es6-promise "^4.2.8" + web3-core-helpers "1.10.4" + web3-providers-http@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.7.4.tgz#8209cdcb115db5ccae1f550d1c4e3005e7538d02" @@ -8933,6 +10213,14 @@ web3-providers-ipc@1.10.0: oboe "2.1.5" web3-core-helpers "1.10.0" +web3-providers-ipc@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz#2e03437909e4e7771d646ff05518efae44b783c3" + integrity sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw== + dependencies: + oboe "2.1.5" + web3-core-helpers "1.10.4" + web3-providers-ipc@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz#02e85e99e48f432c9d34cee7d786c3685ec9fcfa" @@ -8950,6 +10238,15 @@ web3-providers-ws@1.10.0: web3-core-helpers "1.10.0" websocket "^1.0.32" +web3-providers-ws@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz#55d0c3ba36c6a79d105f02e20a707eb3978e7f82" + integrity sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.4" + websocket "^1.0.32" + web3-providers-ws@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz#6e60bcefb456f569a3e766e386d7807a96f90595" @@ -8969,6 +10266,16 @@ web3-shh@1.10.0: web3-core-subscriptions "1.10.0" web3-net "1.10.0" +web3-shh@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.10.4.tgz#9852d6f3d05678e31e49235a60fea10ca7a9e21d" + integrity sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw== + dependencies: + web3-core "1.10.4" + web3-core-method "1.10.4" + web3-core-subscriptions "1.10.4" + web3-net "1.10.4" + web3-shh@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.7.4.tgz#bee91cce2737c529fd347274010b548b6ea060f1" @@ -8992,6 +10299,20 @@ web3-utils@1.10.0: randombytes "^2.1.0" utf8 "3.0.0" +web3-utils@1.10.4, web3-utils@^1.0.0-beta.31, web3-utils@^1.2.5: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + dependencies: + "@ethereumjs/util" "^8.1.0" + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereum-cryptography "^2.1.2" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + web3-utils@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.7.4.tgz#eb6fa3706b058602747228234453811bbee017f5" @@ -9045,11 +10366,29 @@ web3@1.7.4: web3-shh "1.7.4" web3-utils "1.7.4" +web3@^1.2.5: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.10.4.tgz#5d5e59b976eaf758b060fe1a296da5fe87bdc79c" + integrity sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA== + dependencies: + web3-bzz "1.10.4" + web3-core "1.10.4" + web3-eth "1.10.4" + web3-eth-personal "1.10.4" + web3-net "1.10.4" + web3-shh "1.10.4" + web3-utils "1.10.4" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + websocket@^1.0.32: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" @@ -9062,6 +10401,14 @@ websocket@^1.0.32: utf-8-validate "^5.0.2" yaeti "^0.0.6" +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -9081,6 +10428,11 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== + which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" @@ -9118,6 +10470,11 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" +window-size@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== + word-wrap@^1.0.3, word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -9141,6 +10498,14 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -9150,6 +10515,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -9178,6 +10552,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -9239,6 +10618,11 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" @@ -9282,6 +10666,22 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" + integrity sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA== + dependencies: + camelcase "^3.0.0" + lodash.assign "^4.0.6" + yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -9340,6 +10740,23 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^17.0.0: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" @@ -9353,6 +10770,26 @@ yargs@^17.0.0: y18n "^5.0.5" yargs-parser "^21.1.1" +yargs@^4.7.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + integrity sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA== + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + lodash.assign "^4.0.3" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.1" + which-module "^1.0.0" + window-size "^0.2.0" + y18n "^3.2.1" + yargs-parser "^2.4.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"