From 3420cb7eafe0233ddf744e0223ddf6f3b4410e31 Mon Sep 17 00:00:00 2001 From: hoanm Date: Mon, 14 Oct 2024 11:14:13 +0700 Subject: [PATCH] init project --- .editorconfig | 21 + .gitignore | 7 + .prettierignore | 9 + .prettierrc | 24 + .solhint.json | 25 + .vscode/settings.json | 3 + Makefile | 15 + contracts/AccessControl.sol | 59 ++ contracts/ERC721Mock.sol | 32 ++ contracts/ILaunchpadNFT.sol | 22 + contracts/ISPGNFT.sol | 69 +++ contracts/IStoryProtocolGateway.sol | 199 +++++++ contracts/LaunchpadNFT.sol | 87 +++ contracts/MockERC20.sol | 14 + contracts/PunkgaContestNFT.sol | 87 +++ contracts/StoryCampaign.sol | 272 +++++++++ contracts/StoryLaunchpad.sol | 341 +++++++++++ contracts/multicall.sol | 84 +++ foundry.toml | 14 + package.json | 26 + remappings.txt | 5 + test/story-launchpad.test.js | 441 ++++++++++++++ yarn.lock | 857 ++++++++++++++++++++++++++++ 23 files changed, 2713 insertions(+) create mode 100644 .editorconfig create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .solhint.json create mode 100644 .vscode/settings.json create mode 100644 Makefile create mode 100644 contracts/AccessControl.sol create mode 100644 contracts/ERC721Mock.sol create mode 100644 contracts/ILaunchpadNFT.sol create mode 100644 contracts/ISPGNFT.sol create mode 100644 contracts/IStoryProtocolGateway.sol create mode 100644 contracts/LaunchpadNFT.sol create mode 100644 contracts/MockERC20.sol create mode 100644 contracts/PunkgaContestNFT.sol create mode 100644 contracts/StoryCampaign.sol create mode 100644 contracts/StoryLaunchpad.sol create mode 100644 contracts/multicall.sol create mode 100644 foundry.toml create mode 100644 package.json create mode 100644 remappings.txt create mode 100644 test/story-launchpad.test.js create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0b4e85a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = false +max_line_length = 120 + +[*.sol] +indent_size = 4 + +[*.{js,ts}] +indent_size = 2 + +[*.{adoc,md}] +max_line_length = 0 diff --git a/.gitignore b/.gitignore index c6bba59..959f707 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,10 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + + +# foundry args +args + +# foundry cache +forge-cache \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..90ca9a1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +build +coverage +out +/lib +assets +node_modules +.next +.idea +.github \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..900f1cc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,24 @@ +{ + "plugins": ["prettier-plugin-solidity"], + "useTabs": false, + "printWidth": 120, + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": false, + "bracketSpacing": true, + "overrides": [ + { + "files": ["*.ts", "*.js"], + "options": { + "tabWidth": 2 + } + }, + { + "files": "*.sol", + "options": { + "singleQuote": false + } + } + ] +} \ No newline at end of file diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..cf88b26 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,25 @@ +{ + "extends": "solhint:recommended", + "plugins": ["prettier"], + "rules": { + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.23"], + "const-name-snakecase": "off", + "no-empty-blocks": "off", + "constructor-syntax": "error", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "not-rely-on-time": "off", + "reason-string": ["warn", { "maxLength": 64 }], + "no-unused-import": "error", + "no-unused-vars": "off", + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "no-global-import": "error", + "prettier/prettier": "error", + "private-vars-leading-underscore": "off", + "func-name-mixedcase": "off", + "var-name-mixedcase": "off", + "modifier-name-mixedcase": "off" + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bf47ba9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "solidity.compileUsingRemoteVersion": "v0.8.26+commit.8a97fa7a" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..97b513e --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +-include .env + +.PHONY: all test clean + +all: clean install build + +clean :; yarn cache clean + +install :; yarn install + +update :; forge update + +build :; forge build + +test :; forge test diff --git a/contracts/AccessControl.sol b/contracts/AccessControl.sol new file mode 100644 index 0000000..d8b799c --- /dev/null +++ b/contracts/AccessControl.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.23; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract AccessControl { + using SafeERC20 for IERC20; + + address payable public owner; + mapping(address => bool) public operators; + + event SetOperator(address indexed add, bool value); + + error OnlyOwnerAllowed(); + error OnlyOperatorAllowed(); + error ZeroAddressNotAllowed(); + + constructor(address _ownerAddress) { + owner = payable(_ownerAddress); + } + + modifier onlyOwner() { + if (msg.sender != owner) { + revert OnlyOwnerAllowed(); + } + _; + } + + modifier onlyOperator() { + if (!operators[msg.sender]) { + revert OnlyOperatorAllowed(); + } + _; + } + + function setOwner(address payable _newOwner) external onlyOwner { + if (_newOwner == address(0)) { + revert ZeroAddressNotAllowed(); + } + owner = _newOwner; + } + + function setOperator(address _operator, bool _v) external onlyOwner { + operators[_operator] = _v; + emit SetOperator(_operator, _v); + } + + function emergencyWithdraw(address _token, address payable _to, uint256 amount) external onlyOwner { + if (_token == address(0x0)) { + amount = amount != 0 ? amount : address(this).balance; + payable(_to).transfer(amount); + } + else { + amount = amount != 0 ? amount : IERC20(_token).balanceOf(address(this)); + IERC20(_token).safeTransfer(_to, amount); + } + } +} diff --git a/contracts/ERC721Mock.sol b/contracts/ERC721Mock.sol new file mode 100644 index 0000000..7981c80 --- /dev/null +++ b/contracts/ERC721Mock.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.23; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MockERC721 is ERC721 { + uint256 private _counter; + + constructor(string memory name) ERC721(name, name) { + _counter = 0; + } + + function mint(address to) public returns (uint256 tokenId) { + tokenId = ++_counter; + _safeMint(to, tokenId); + return tokenId; + } + + function mintId(address to, uint256 tokenId) public returns (uint256) { + _safeMint(to, tokenId); + return tokenId; + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } + + function transferFrom(address from, address to, uint256 tokenId) public override { + _transfer(from, to, tokenId); + } +} diff --git a/contracts/ILaunchpadNFT.sol b/contracts/ILaunchpadNFT.sol new file mode 100644 index 0000000..8c8c315 --- /dev/null +++ b/contracts/ILaunchpadNFT.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; + + +interface ILaunchpadNFT is IERC721Metadata, IERC721Enumerable { + + struct LaunchpadTokenMetadata { + address licensorIpId; + address licenseTemplate; + uint256 licenseTermsId; + bool transferable; + } + + event LaunchpadNFTMinted(address indexed minter, address indexed receiver, uint256 indexed tokenId); + + function mintTokens(address _receiver, + uint256 _amount // mint amount + ) external; +} diff --git a/contracts/ISPGNFT.sol b/contracts/ISPGNFT.sol new file mode 100644 index 0000000..c950e2b --- /dev/null +++ b/contracts/ISPGNFT.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; + +interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata { + /// @dev Initializes the NFT collection. + /// @dev If mint cost is non-zero, mint token must be set. + /// @param name The name of the collection. + /// @param symbol The symbol of the collection. + /// @param maxSupply The maximum supply of the collection. + /// @param mintFee The cost to mint an NFT from the collection. + /// @param mintFeeToken The token to pay for minting. + /// @param owner The owner of the collection. + function initialize( + string memory name, + string memory symbol, + uint32 maxSupply, + uint256 mintFee, + address mintFeeToken, + address owner + ) external; + + /// @notice Returns the total minted supply of the collection. + function totalSupply() external view returns (uint256); + + /// @notice Returns the current mint token of the collection. + function mintFeeToken() external view returns (address); + + /// @notice Returns the current mint fee of the collection. + function mintFee() external view returns (uint256); + + /// @notice Sets the mint token for the collection. + /// @dev Only callable by the admin role. + /// @param token The new mint token for mint payment. + function setMintFeeToken(address token) external; + + /// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency. + /// @dev Only callable by the admin role. + /// @param fee The new mint fee paid in the mint token. + function setMintFee(uint256 fee) external; + + /// @notice Mints an NFT from the collection. Only callable by the minter role. + /// @param to The address of the recipient of the minted NFT. + /// @param nftMetadataURI OPTIONAL. The desired metadata for the newly minted NFT. + /// @return tokenId The ID of the minted NFT. + function mint( + address to, + string calldata nftMetadataURI + ) external returns (uint256 tokenId); + + /// @notice Mints an NFT from the collection. Only callable by the SPG. + /// @param to The address of the recipient of the minted NFT. + /// @param payer The address of the payer for the mint fee. + /// @param nftMetadataURI OPTIONAL. The desired metadata for the newly minted NFT. + /// @return tokenId The ID of the minted NFT. + function mintBySPG( + address to, + address payer, + string calldata nftMetadataURI + ) external returns (uint256 tokenId); + + /// @dev Withdraws the contract's token balance to the recipient. + /// @param recipient The token to withdraw. + /// @param recipient The address to receive the withdrawn balance. + function withdrawToken(address token, address recipient) external; +} diff --git a/contracts/IStoryProtocolGateway.sol b/contracts/IStoryProtocolGateway.sol new file mode 100644 index 0000000..4ffb2f6 --- /dev/null +++ b/contracts/IStoryProtocolGateway.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import { PILTerms } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol"; + +interface IStoryProtocolGateway { + /// @notice Event emitted when a new NFT collection is created. + /// @param nftContract The address of the newly created NFT collection. + event CollectionCreated(address indexed nftContract); + + /// @notice Struct for metadata for NFT minting and IP registration. + /// @dev Leave the nftMetadataURI empty if not minting an NFT. + /// @param ipMetadataURI The URI of the metadata for the IP. + /// @param ipMetadataHash The hash of the metadata for the IP. + /// @param nftMetadataURI The URI of the metadata for the NFT. + /// @param nftMetadataHash The hash of the metadata for the IP NFT. + struct IPMetadata { + string ipMetadataURI; + bytes32 ipMetadataHash; + string nftMetadataURI; + bytes32 nftMetadataHash; + } + + /// @notice Struct for signature data for execution via IP Account. + /// @param signer The address of the signer for execution with signature. + /// @param deadline The deadline for the signature. + /// @param signature The signature for the execution via IP Account. + struct SignatureData { + address signer; + uint256 deadline; + bytes signature; + } + + /// @notice Struct for creating a derivative IP without license tokens. + /// @param parentIpIds The IDs of the parent IPs to link the registered derivative IP. + /// @param licenseTemplate The address of the license template to be used for the linking. + /// @param licenseTermsIds The IDs of the license terms to be used for the linking. + /// @param royaltyContext The context for royalty module, should be empty for Royalty Policy LAP. + struct MakeDerivative { + address[] parentIpIds; + address licenseTemplate; + uint256[] licenseTermsIds; + bytes royaltyContext; + } + + /// @notice Creates a new NFT collection to be used by SPG. + /// @param name The name of the collection. + /// @param symbol The symbol of the collection. + /// @param maxSupply The maximum supply of the collection. + /// @param mintFee The cost to mint an NFT from the collection. + /// @param mintFeeToken The token to be used for mint payment. + /// @param owner The owner of the collection. + /// @return nftContract The address of the newly created NFT collection. + function createCollection( + string calldata name, + string calldata symbol, + uint32 maxSupply, + uint256 mintFee, + address mintFeeToken, + address owner + ) external returns (address nftContract); + + /// @notice Mint an NFT from a collection and register it with metadata as an IP. + /// @dev Caller must have the minter role for the provided SPG NFT. + /// @param nftContract The address of the NFT collection. + /// @param recipient The address of the recipient of the minted NFT. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP. + /// @return ipId The ID of the registered IP. + /// @return tokenId The ID of the minted NFT. + function mintAndRegisterIp( + address nftContract, + address recipient, + IPMetadata calldata ipMetadata + ) external returns (address ipId, uint256 tokenId); + + /// @notice Registers an NFT as IP with metadata. + /// @param nftContract The address of the NFT collection. + /// @param tokenId The ID of the NFT. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP. + /// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module. + /// @return ipId The ID of the registered IP. + function registerIp( + address nftContract, + uint256 tokenId, + IPMetadata calldata ipMetadata, + SignatureData calldata sigMetadata + ) external returns (address ipId); + + /// @notice Register Programmable IP License Terms (if unregistered) and attach it to IP. + /// @param ipId The ID of the IP. + /// @param terms The PIL terms to be registered. + /// @return licenseTermsId The ID of the registered PIL terms. + function registerPILTermsAndAttach(address ipId, PILTerms calldata terms) external returns (uint256 licenseTermsId); + + /// @notice Mint an NFT from a collection, register it with metadata as an IP, register Programmable IP License + /// Terms (if unregistered), and attach it to the registered IP. + /// @dev Caller must have the minter role for the provided SPG NFT. + /// @param nftContract The address of the NFT collection. + /// @param recipient The address of the recipient of the minted NFT. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP. + /// @param terms The PIL terms to be registered. + /// @return ipId The ID of the registered IP. + /// @return tokenId The ID of the minted NFT. + /// @return licenseTermsId The ID of the registered PIL terms. + function mintAndRegisterIpAndAttachPILTerms( + address nftContract, + address recipient, + IPMetadata calldata ipMetadata, + PILTerms calldata terms + ) external returns (address ipId, uint256 tokenId, uint256 licenseTermsId); + + /// @notice Register a given NFT as an IP and attach Programmable IP License Terms. + /// @dev Because IP Account is created in this function, we need to set the permission via signature to allow this + /// contract to attach PIL Terms to the newly created IP Account in the same function. + /// @param nftContract The address of the NFT collection. + /// @param tokenId The ID of the NFT. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP. + /// @param terms The PIL terms to be registered. + /// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module. + /// @param sigAttach Signature data for attachLicenseTerms to the IP via the Licensing Module. + /// @return ipId The ID of the registered IP. + /// @return licenseTermsId The ID of the registered PIL terms. + function registerIpAndAttachPILTerms( + address nftContract, + uint256 tokenId, + IPMetadata calldata ipMetadata, + PILTerms calldata terms, + SignatureData calldata sigMetadata, + SignatureData calldata sigAttach + ) external returns (address ipId, uint256 licenseTermsId); + + /// @notice Mint an NFT from a collection and register it as a derivative IP without license tokens. + /// @dev Caller must have the minter role for the provided SPG NFT. + /// @param nftContract The address of the NFT collection. + /// @param derivData The derivative data to be used for registerDerivative. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP. + /// @param recipient The address to receive the minted NFT. + /// @return ipId The ID of the registered IP. + /// @return tokenId The ID of the minted NFT. + function mintAndRegisterIpAndMakeDerivative( + address nftContract, + MakeDerivative calldata derivData, + IPMetadata calldata ipMetadata, + address recipient + ) external returns (address ipId, uint256 tokenId); + + /// @notice Register the given NFT as a derivative IP with metadata without using license tokens. + /// @param nftContract The address of the NFT collection. + /// @param tokenId The ID of the NFT. + /// @param derivData The derivative data to be used for registerDerivative. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP. + /// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module. + /// @param sigRegister Signature data for registerDerivative for the IP via the Licensing Module. + /// @return ipId The ID of the registered IP. + function registerIpAndMakeDerivative( + address nftContract, + uint256 tokenId, + MakeDerivative calldata derivData, + IPMetadata calldata ipMetadata, + SignatureData calldata sigMetadata, + SignatureData calldata sigRegister + ) external returns (address ipId); + + /// @notice Mint an NFT from a collection and register it as a derivative IP using license tokens. + /// @dev Caller must have the minter role for the provided SPG NFT. + /// @param nftContract The address of the NFT collection. + /// @param licenseTokenIds The IDs of the license tokens to be burned for linking the IP to parent IPs. + /// @param royaltyContext The context for royalty module, should be empty for Royalty Policy LAP. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly minted NFT and registered IP. + /// @param recipient The address to receive the minted NFT. + /// @return ipId The ID of the registered IP. + /// @return tokenId The ID of the minted NFT. + function mintAndRegisterIpAndMakeDerivativeWithLicenseTokens( + address nftContract, + uint256[] calldata licenseTokenIds, + bytes calldata royaltyContext, + IPMetadata calldata ipMetadata, + address recipient + ) external returns (address ipId, uint256 tokenId); + + /// @notice Register the given NFT as a derivative IP using license tokens. + /// @param nftContract The address of the NFT collection. + /// @param tokenId The ID of the NFT. + /// @param licenseTokenIds The IDs of the license tokens to be burned for linking the IP to parent IPs. + /// @param royaltyContext The context for royalty module, should be empty for Royalty Policy LAP. + /// @param ipMetadata OPTIONAL. The desired metadata for the newly registered IP. + /// @param sigMetadata OPTIONAL. Signature data for setAll (metadata) for the IP via the Core Metadata Module. + /// @param sigRegister Signature data for registerDerivativeWithLicenseTokens for the IP via the Licensing Module. + /// @return ipId The ID of the registered IP. + function registerIpAndMakeDerivativeWithLicenseTokens( + address nftContract, + uint256 tokenId, + uint256[] calldata licenseTokenIds, + bytes calldata royaltyContext, + IPMetadata calldata ipMetadata, + SignatureData calldata sigMetadata, + SignatureData calldata sigRegister + ) external returns (address ipId); +} diff --git a/contracts/LaunchpadNFT.sol b/contracts/LaunchpadNFT.sol new file mode 100644 index 0000000..9a3476e --- /dev/null +++ b/contracts/LaunchpadNFT.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import "./AccessControl.sol"; +import "./ILaunchpadNFT.sol"; + +contract LaunchpadNFT is ERC721Enumerable, AccessControl { + uint64 private _counter; + + string public baseURI = ""; + string public contractURIPrefix = ""; + bool public paused = false; + + mapping(address => bool) public minters; + mapping(uint256 => string) private uris; + + error Paused(); + error OnlyMinter(); + + event Minted(uint256 indexed _tokenId, address indexed _owner); + + constructor( + address _owner, + string memory _name, + string memory _symbol + ) ERC721(_name, _symbol) AccessControl(_owner) { + _counter = 0; + minters[msg.sender] = true; + } + + function setMinter(address _minter, bool _v) external onlyOwner { + minters[_minter] = _v; + emit SetOperator(_minter, _v); + } + + function _baseURI() internal view override returns (string memory) { + return baseURI; + } + + function contractURI() external view returns (string memory) { + return contractURIPrefix; + } + + function togglePause() external onlyOwner { + paused = !paused; + } + + function setBaseURI(string memory _uri) external onlyOwner { + baseURI = _uri; + } + + function setContractURI(string memory _uri) external onlyOwner { + contractURIPrefix = _uri; + } + + function mintTokens(address _receiver, string memory _uri) external onlyMinter returns (uint256) { + uint256 tokenId = _counter + 1; + _counter += 1; + uris[tokenId] = _uri; + + _safeMint(_receiver, tokenId); + + emit Minted(tokenId, _receiver); + return tokenId; + } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + _requireOwned(tokenId); + return string.concat(uris[tokenId], Strings.toString(tokenId)); + } + + modifier whenNotPaused() { + if (paused) { + revert Paused(); + } + _; + } + + modifier onlyMinter() { + if (!minters[msg.sender]) { + revert OnlyMinter(); + } + _; + } +} diff --git a/contracts/MockERC20.sol b/contracts/MockERC20.sol new file mode 100644 index 0000000..464fa10 --- /dev/null +++ b/contracts/MockERC20.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor(string memory name, string memory symbol, uint256 supply) ERC20(name, symbol) { + _mint(msg.sender, supply); + } + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/contracts/PunkgaContestNFT.sol b/contracts/PunkgaContestNFT.sol new file mode 100644 index 0000000..3de478d --- /dev/null +++ b/contracts/PunkgaContestNFT.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import "./AccessControl.sol"; +import "./ILaunchpadNFT.sol"; + +contract LaunchpadNFT is ERC721Enumerable, AccessControl { + + uint private _counter; + + string public baseURI = ""; + string public contractURIPrefix = ""; + bool public paused = false; + + mapping(address => bool) public minters; + mapping(uint256 => string) private uris; + + event minted( + uint256 indexed _tokenId, + address indexed _owner + ); + + constructor(address _owner, string memory _name, string memory _symbol) ERC721(_name, _symbol) AccessControl(_owner) { + _counter = 0; + minters[msg.sender] = true; + } + + modifier whenNotPaused() { + require(!paused, "Paused"); + _; + } + + function setMinter(address _minter, bool _v) external onlyOwner { + minters[_minter] = _v; + emit SetOperator(_minter, _v); + } + + modifier onlyMinter() { + require(minters[msg.sender]); + _; + } + + function _baseURI() internal view override returns (string memory) { + return baseURI; + } + + function contractURI() external view returns (string memory) { + return contractURIPrefix; + } + + function togglePause() external onlyOwner { + paused = !paused; + } + + function setBaseURI(string memory _uri) external onlyOwner { + baseURI = _uri; + } + + function setContractURI(string memory _uri) external onlyOwner { + contractURIPrefix = _uri; + } + + function mintTokens(address _receiver, + string memory _uri) external onlyMinter returns (uint256 tokenId) { + + uint256 tokenId = _counter + 1; + _counter += 1; + uris[tokenId] = _uri; + + _safeMint(_receiver, tokenId); + + emit minted(tokenId, _receiver); + return tokenId; + } + + function tokenURI(uint256 tokenId) + public + view + override + returns (string memory) + { + _requireOwned(tokenId); + return uris[tokenId]; + } +} diff --git a/contracts/StoryCampaign.sol b/contracts/StoryCampaign.sol new file mode 100644 index 0000000..4c90fbc --- /dev/null +++ b/contracts/StoryCampaign.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.23; +pragma experimental ABIEncoderV2; + +import { IAccessController } from "@story-protocol/protocol-core/contracts/interfaces/access/IAccessController.sol"; +import { IIPAssetRegistry } from "@story-protocol/protocol-core/contracts/interfaces/registries/IIPAssetRegistry.sol"; +import { ILicenseRegistry } from "@story-protocol/protocol-core/contracts/interfaces/registries/ILicenseRegistry.sol"; +import { ILicensingModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicensingModule.sol"; +import { ICoreMetadataViewModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/metadata/ICoreMetadataViewModule.sol"; +import { IPILicenseTemplate, PILTerms } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol"; +import { ILicenseTemplate } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicenseTemplate.sol"; +import { IRoyaltyModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/royalty/IRoyaltyModule.sol"; +import { IIPAccount } from "@story-protocol/protocol-core/contracts/interfaces/IIPAccount.sol"; + +import { IStoryProtocolGateway } from "./IStoryProtocolGateway.sol"; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "./AccessControl.sol"; +import "./PunkgaContestNFT.sol"; + +contract StoryCampaign is AccessControl, IERC721Receiver { + using SafeERC20 for IERC20; + + address public ipAssetRegistry = 0x1a9d0d28a0422F26D31Be72Edc6f13ea4371E11B; + address public licensingModule = 0xd81fd78f557b457b4350cB95D20b547bFEb4D857; + address public licenseToken = 0xc7A302E03cd7A304394B401192bfED872af501BE; + address public licenseTemplate = 0x0752f61E59fD2D39193a74610F1bd9a6Ade2E3f9; + address public coreMetadataView = 0x48ecAa9F197135A4614d1c7A5Db5641ffd8ad2b9; + address public licenseRegistry = 0xedf8e338F05f7B1b857C3a8d3a0aBB4bc2c41723; + address public royaltyModule = 0x3C27b2D7d30131D4b58C3584FD7c86e3358744de; + + address public collectionAddress; + + uint256 private maxParents = 5; + uint256 private maxIpasset = 30; + + address public SPG_ADDRESS; + + //creator address -> count + mapping(address => uint256) public userMintCount; + + constructor(address _owner, address _storyProtocolGateway) public AccessControl(_owner) { + SPG_ADDRESS = _storyProtocolGateway; + } + + struct MakeDerivative { + address[] parentIpIds; + uint256[] licenseTermsIds; + bytes royaltyContext; + } + + struct IPMetadata { + string ipMetadataURI; + bytes32 ipMetadataHash; + string nftMetadataURI; + bytes32 nftMetadataHash; + } + + event CollectionCreated(address indexed nftContract); + /** + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } + + // function _owns(address _licensorIpid) internal view returns (bool) { + // return (ICoreMetadataViewModule(coreMetadataView).getOwner(_licensorIpid) == msg.sender); + // } + + function setStoryProtocolGateway(address _addr) public onlyOwner { + SPG_ADDRESS = _addr; + } + + function setIpAssetRegistry(address _addr) public onlyOwner { + ipAssetRegistry = _addr; + } + + function setLicensingModule(address _addr) public onlyOwner { + licensingModule = _addr; + } + + function setMaxParents(uint256 _maxParents) public onlyOwner { + maxParents = _maxParents; + } + + function setMaxIpasset(uint256 _maxIpasset) public onlyOwner { + maxParents = _maxIpasset; + } + + function setLicenseToken(address _addr) public onlyOwner { + licenseToken = _addr; + } + + function setCoreMetadataView(address _addr) public onlyOwner { + coreMetadataView = _addr; + } + + function setLicenseRegistry(address _addr) public onlyOwner { + licenseRegistry = _addr; + } + + function setCollectionAddress(address _addr) public onlyOwner { + collectionAddress = _addr; + } + + function setLicenseTemplate(address _addr) public onlyOwner { + licenseTemplate = _addr; + } + + function setRoyaltyModule(address _addr) public onlyOwner { + royaltyModule = _addr; + } + + function getToken(address ipAccountAddress) public view returns (uint chainId, address tokenContract, uint tokenId) { + (chainId, tokenContract, tokenId) = IIPAccount(payable(ipAccountAddress)).token(); + } + + function transferHelper( + address token, + address payable add, + uint256 amount + ) private { + if (token == address(0)) { + add.transfer(amount); + } else { + IERC20(token).transfer(add, amount); + } + } + + function createCollection(string memory colectionName, string memory colectionSymbol) public onlyOperator { + collectionAddress = address(new LaunchpadNFT(msg.sender, colectionName, colectionSymbol)); + emit CollectionCreated(address(collectionAddress)); + } + + function _mintAndRegisterIp( + address recipient, + string memory uri + ) internal returns (address ipId, uint256 tokenId) { + + tokenId = LaunchpadNFT(collectionAddress).mintTokens(recipient, uri); + require(tokenId > 0); + + ipId = IIPAssetRegistry(ipAssetRegistry).register(block.chainid, collectionAddress, tokenId); + } + + function mintAndRegisterIpAndAttach( + address recipient, + IStoryProtocolGateway.IPMetadata calldata ipMetadata, + PILTerms calldata terms + ) public onlyOperator returns (address ipId, uint256 tokenId, uint256 licenseTermsId) { + require(userMintCount[recipient] + 1 <= maxIpasset, "StoryCampaign: AboveMintLimit"); + + userMintCount[recipient] += 1; + // (ipId, tokenId) = _mintAndRegisterIp(address(this), uri); + // //Register and attack PIL + // licenseTermsId = _registerPILTermsAndAttach(ipId, terms); + + // LaunchpadNFT(collectionAddress).safeTransferFrom(address(this), recipient, tokenId); + + IStoryProtocolGateway(SPG_ADDRESS).mintAndRegisterIpAndAttachPILTerms(collectionAddress, recipient, ipMetadata, terms); + } + + function _registerPILTermsAndAttach( + address ipId, + PILTerms calldata terms + ) internal returns (uint256 licenseTermsId) { + + licenseTermsId = IPILicenseTemplate(licenseTemplate).registerLicenseTerms(terms); + // Returns if license terms are already attached. + if (ILicenseRegistry(licenseRegistry).hasIpAttachedLicenseTerms(ipId, licenseTemplate, licenseTermsId)) return licenseTermsId; + + ILicensingModule(licensingModule).attachLicenseTerms(ipId, licenseTemplate, licenseTermsId); + + } + + function mintAndRegisterIpAndMakeDerivative( + MakeDerivative calldata derivData, + IPMetadata calldata ipMetadata, + address recipient + ) external onlyOperator returns (address ipId, uint256 tokenId) { + + require(derivData.parentIpIds.length <= maxParents, "StoryCampaign: AboveParentLimit"); + + for (uint256 i = 0; i < derivData.parentIpIds.length; i++) { + (, address nftContract,) = getToken(derivData.parentIpIds[i]); + require(nftContract == collectionAddress, "StoryCampaign: Ipasset not come from this contest collection"); + } + + tokenId = LaunchpadNFT(collectionAddress).mintTokens(address(this), ipMetadata.nftMetadataURI); + require(tokenId > 0); + + ipId = IIPAssetRegistry(ipAssetRegistry).register(block.chainid, collectionAddress, tokenId); + + // MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata); + + _collectMintFeesAndSetApproval( + msg.sender, + derivData.parentIpIds, + derivData.licenseTermsIds + ); + + ILicensingModule(licensingModule).registerDerivative({ + childIpId: ipId, + parentIpIds: derivData.parentIpIds, + licenseTermsIds: derivData.licenseTermsIds, + licenseTemplate: licenseTemplate, + royaltyContext: derivData.royaltyContext + }); + + LaunchpadNFT(collectionAddress).safeTransferFrom(address(this), recipient, tokenId); + } + + /// @dev Aggregate license mint fees for all parent IPs. + /// @param payerAddress The address of the payer for the license mint fees. + /// @param parentIpIds The IDs of all the parent IPs. + /// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP. + /// @return totalMintFee The sum of license mint fees across all parent IPs. + function _aggregateMintFees( + address payerAddress, + address[] calldata parentIpIds, + uint256[] calldata licenseTermsIds + ) internal view returns (uint256 totalMintFee) { + uint256 mintFee; + + for (uint256 i = 0; i < parentIpIds.length; i++) { + (, mintFee) = ILicensingModule(licensingModule).predictMintingLicenseFee({ + licensorIpId: parentIpIds[i], + licenseTemplate: licenseTemplate, + licenseTermsId: licenseTermsIds[i], + amount: 1, + receiver: payerAddress, + royaltyContext: "" + }); + totalMintFee += mintFee; + } + } + + /// @dev Collect mint fees for all parent IPs from the payer and set approval for Royalty Module to spend mint fees. + /// @param payerAddress The address of the payer for the license mint fees. + /// @param parentIpIds The IDs of all the parent IPs. + /// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP. + function _collectMintFeesAndSetApproval( + address payerAddress, + address[] calldata parentIpIds, + uint256[] calldata licenseTermsIds + ) internal { + ILicenseTemplate lct = ILicenseTemplate(licenseTemplate); + (address royaltyPolicy, , , address mintFeeCurrencyToken) = lct.getRoyaltyPolicy(licenseTermsIds[0]); + + if (royaltyPolicy != address(0)) { + // Get total mint fee for all parent IPs + uint256 totalMintFee = _aggregateMintFees({ + payerAddress: payerAddress, + parentIpIds: parentIpIds, + licenseTermsIds: licenseTermsIds + }); + + if (totalMintFee != 0) { + // Transfer mint fee from payer to this contract + IERC20(mintFeeCurrencyToken).safeTransferFrom(payerAddress, address(this), totalMintFee); + + // Approve Royalty Policy to spend mint fee + IERC20(mintFeeCurrencyToken).forceApprove(royaltyModule, totalMintFee); + } + } + } +} diff --git a/contracts/StoryLaunchpad.sol b/contracts/StoryLaunchpad.sol new file mode 100644 index 0000000..4bacda7 --- /dev/null +++ b/contracts/StoryLaunchpad.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.23; +pragma experimental ABIEncoderV2; + +import { LicenseToken } from "@story-protocol/protocol-core/contracts/LicenseToken.sol"; +import { LicensingModule } from "@story-protocol/protocol-core/contracts/modules/licensing/LicensingModule.sol"; +import { CoreMetadataViewModule } from "@story-protocol/protocol-core/contracts/modules/metadata/CoreMetadataViewModule.sol"; +import { IPAssetRegistry } from "@story-protocol/protocol-core/contracts/registries/IPAssetRegistry.sol"; +import { LicenseRegistry } from "@story-protocol/protocol-core/contracts/registries/LicenseRegistry.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "./AccessControl.sol"; +import "./LaunchpadNFT.sol"; + +contract StoryLaunchpad is AccessControl, IERC721Receiver { + using SafeERC20 for IERC20; + + mapping(address => bool) public acceptPayTokens; + //sender address -> creatorAddress -> licensorIpid -> launchpad phase -> count + mapping(address => mapping(address => mapping(address => mapping(uint256 => uint256)))) public userBuyCount; + //creatorAddress -> licensorIpid -> launchpad phase -> count + mapping(address => mapping(address => mapping(uint256 => uint256))) public numberOfNftSold; + + address public ipAssetRegistryAddr = 0xd43fE0d865cb5C26b1351d3eAf2E3064BE3276F6; + address public licensingModuleAddr = 0xe89b0EaA8a0949738efA80bB531a165FB3456CBe; + address public licenseTokenAddr = 0x1333c78A821c9a576209B01a16dDCEF881cAb6f2; + address public licenseTemplateAddr = 0x260B6CB6284c89dbE660c0004233f7bB99B5edE7; + address public coreMetadataViewAddr = 0x17aD427cd467A85c256acDF57848Ec6383D70dF5; + + struct StoryLaunchpad { + address payable creatorAddress; + address licensorIpid; + string colectionName; + uint256 startTime; + uint256 endTime; + uint256 totalQuantity; + uint256 maxBuy; + } + + struct LaunchpadInfor { + address nftAddress; + address payToken; + uint256 price; + string uriLaunchpad; + string uriNFT; + uint256 royaltyPercentage; + address royaltyAddress; + } + + // Map from creator address > licensorIpid > NFTSales. + mapping(address => mapping(address => mapping(uint256 => StoryLaunchpad))) public StoryLaunchpads; + mapping(address => mapping(address => mapping(uint256 => LaunchpadInfor))) public LaunchpadInfors; + mapping(address => mapping(address => uint256)) public licensorLaunchpadCount; + + event StoryLaunchpadCreated( + address indexed _creatorAddress, + address indexed _licensorIpid, + StoryLaunchpad storyLaunchpad + ); + + event MintNFTSuccessful(address indexed _minter, address indexed _licensorIpid, uint256 _quantity); + + constructor(address _owner) AccessControl(_owner) {} + + /** + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } + + modifier isOpening(address _creatorAddress, address _licensorIpid) { + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + StoryLaunchpad memory launchpad = StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + require( + launchpad.startTime <= block.timestamp && block.timestamp < launchpad.endTime, + "StoryLaunchpad: Launchpad should be open" + ); + _; + } + + modifier isNotLaunchpadRunning(address _creatorAddress, address _licensorIpid) { + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + StoryLaunchpad memory launchpad = StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + require(launchpad.endTime < block.timestamp, "StoryLaunchpad: last Launchpad should be end"); + _; + } + + modifier notOpen(address _creatorAddress, address _licensorIpid) { + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + StoryLaunchpad memory launchpad = StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + require(block.timestamp < launchpad.startTime, "StoryLaunchpad: Launchpad should not open"); + _; + } + + modifier existLaunchpad(address _creatorAddress, address _licensorIpid) { + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + StoryLaunchpad memory launchpad = StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + require(launchpad.startTime > 0, "StoryLaunchpad: Launchpad should exist"); + _; + } + + modifier notExistLaunchpad(address _creatorAddress, address _licensorIpid) { + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + if (licensorLaunchpadCount > 0) { + require( + StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount].licensorIpid == address(0x0), + "StoryLaunchpad: Launchpad should not exist" + ); + _; + } + } + + function _owns(address _licensorIpid) internal view returns (bool) { + CoreMetadataViewModule _coreMetadataViewContract = CoreMetadataViewModule(coreMetadataViewAddr); + return (_coreMetadataViewContract.getOwner(_licensorIpid) == msg.sender); + } + + function setAcceptPayToken(address _payToken, bool _accept) public onlyOwner { + acceptPayTokens[_payToken] = _accept; + } + + function setIpAssetRegistryAddr(address _addr) public onlyOwner { + ipAssetRegistryAddr = _addr; + } + + function setLicensingModuleAddr(address _addr) public onlyOwner { + licensingModuleAddr = _addr; + } + + function setLicenseTokenAddr(address _addr) public onlyOwner { + licenseTokenAddr = _addr; + } + + function setCoreMetadataViewAddr(address _addr) public onlyOwner { + coreMetadataViewAddr = _addr; + } + + function getCurrentLaunchpadCount(address _creatorAddress, address _licensorIpid) public view returns (uint256) { + return licensorLaunchpadCount[_creatorAddress][_licensorIpid]; + } + + function getLicenseIdByLicensor(address _licensorIpid) public view returns (uint256) { + LicenseToken licenseTokenContract = LicenseToken(licenseTokenAddr); + uint256 balanceOf = licenseTokenContract.balanceOf(address(this)); + + for (uint256 i = 0; i < balanceOf; i++) { + uint256 licenseId = licenseTokenContract.tokenOfOwnerByIndex(address(this), i); + address licensorIpId = licenseTokenContract.getLicensorIpId(licenseId); + + if (licensorIpId == _licensorIpid) { + return licenseId; + } + } + } + + function transferHelper(address token, address payable add, uint256 amount) private { + if (token == address(0)) { + add.transfer(amount); + } else { + IERC20(token).transfer(add, amount); + } + } + + function createLaunchpad( + StoryLaunchpad memory _launchpad, + LaunchpadInfor memory _launchpadInfor + ) public isNotLaunchpadRunning(_launchpad.creatorAddress, _launchpad.licensorIpid) { + require(_launchpad.creatorAddress != address(0), "creatorAddress is wrong"); + require(_owns(_launchpad.licensorIpid), "StoryLaunchpad: You are not owner licensorIpid"); + require(acceptPayTokens[_launchpadInfor.payToken], "StoryLaunchpad: wrong pay token"); + require(_launchpad.maxBuy > 0, "INVALID maxBuy"); + require(_launchpad.licensorIpid != address(0), "licensorIpid is wrong"); + require(_launchpadInfor.price >= 0, "INVALID price"); + require(_launchpad.totalQuantity >= _launchpad.maxBuy, "INVALID totalQuantity"); + require(_launchpad.startTime > block.timestamp, "INVALID startTime"); + require(_launchpad.endTime > _launchpad.startTime, "INVALID endTime"); + require( + _launchpadInfor.royaltyPercentage >= 0 && _launchpadInfor.royaltyPercentage <= 100, + "INVALID royaltyPercentage" + ); + + LaunchpadNFT launchpadNFT = new LaunchpadNFT(msg.sender, _launchpad.colectionName, "STORYAURANFT"); + + LaunchpadInfor memory launchpadInfor; + launchpadInfor = _launchpadInfor; + launchpadInfor.nftAddress = address(launchpadNFT); + + uint256 currentPhase = getCurrentLaunchpadCount(_launchpad.creatorAddress, _launchpad.licensorIpid); + currentPhase += 1; + StoryLaunchpads[_launchpad.creatorAddress][_launchpad.licensorIpid][currentPhase] = _launchpad; + licensorLaunchpadCount[_launchpad.creatorAddress][_launchpad.licensorIpid] += 1; // cumulative + + LaunchpadInfors[_launchpad.creatorAddress][_launchpad.licensorIpid][currentPhase] = _launchpadInfor; + + emit StoryLaunchpadCreated(_launchpad.creatorAddress, _launchpad.licensorIpid, _launchpad); + } + + function updateInfo( + StoryLaunchpad memory _launchpad, + LaunchpadInfor memory _launchpadInfor + ) + public + onlyOwner + existLaunchpad(_launchpad.creatorAddress, _launchpad.licensorIpid) + notOpen(_launchpad.creatorAddress, _launchpad.licensorIpid) + { + require(_launchpad.creatorAddress != address(0), "creatorAddress is wrong"); + require(acceptPayTokens[_launchpadInfor.payToken], "StoryLaunchpad: wrong pay token"); + require(_launchpad.maxBuy > 0, "INVALID maxBuy"); + require(_launchpad.licensorIpid != address(0), "licensorIpid is wrong"); + require(_launchpadInfor.price >= 0, "INVALID price"); + require(_launchpad.totalQuantity >= _launchpad.maxBuy, "INVALID totalQuantity"); + require(_launchpad.startTime > block.timestamp, "INVALID startTime"); + require(_launchpad.endTime > _launchpad.startTime, "INVALID endTime"); + require( + _launchpadInfor.royaltyPercentage >= 0 && _launchpadInfor.royaltyPercentage <= 100, + "INVALID royaltyPercentage" + ); + + uint256 currentPhase = getCurrentLaunchpadCount(_launchpad.creatorAddress, _launchpad.licensorIpid); + StoryLaunchpad storage storyLaunchpad = StoryLaunchpads[_launchpad.creatorAddress][_launchpad.licensorIpid][ + currentPhase + ]; + LaunchpadInfor storage launchpadInfor = LaunchpadInfors[_launchpad.creatorAddress][_launchpad.licensorIpid][ + currentPhase + ]; + + storyLaunchpad.startTime = _launchpad.startTime; + storyLaunchpad.endTime = _launchpad.endTime; + storyLaunchpad.totalQuantity = _launchpad.totalQuantity; + storyLaunchpad.maxBuy = _launchpad.maxBuy; + storyLaunchpad.colectionName = _launchpad.colectionName; + + launchpadInfor.price = _launchpadInfor.price; + launchpadInfor.uriLaunchpad = _launchpadInfor.uriLaunchpad; + launchpadInfor.uriNFT = _launchpadInfor.uriNFT; + launchpadInfor.royaltyPercentage = _launchpadInfor.royaltyPercentage; + launchpadInfor.royaltyAddress = _launchpadInfor.royaltyAddress; + } + + function mintNFT( + address _creatorAddress, + address _licensorIpid, + uint256 _quantity, + address _payToken, + uint256 _payAmount + ) public payable existLaunchpad(_creatorAddress, _licensorIpid) isOpening(_creatorAddress, _licensorIpid) { + require(acceptPayTokens[_payToken], "StoryLaunchpad: wrong pay token"); + uint256 amount = 0; + if (_payToken == address(0)) { + amount = msg.value; + } else { + require(msg.value == 0, "StoryLaunchpad: Invalid msg.value"); + amount = _payAmount; + } + + _mintNFT(_creatorAddress, _licensorIpid, _quantity, _payToken, amount); + + emit MintNFTSuccessful(msg.sender, _licensorIpid, _quantity); + } + + function _mintNFT( + address _creatorAddress, + address _licensorIpid, + uint256 _quantity, + address _payToken, + uint256 _payAmount + ) internal { + require(_creatorAddress != address(0), "StoryLaunchpad: creatorAddress is wrong"); + // Get a reference to the sale struct + uint256 licensorLaunchpadCount = getCurrentLaunchpadCount(_creatorAddress, _licensorIpid); + StoryLaunchpad storage storyLaunchpad = StoryLaunchpads[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + LaunchpadInfor storage launchpadInfor = LaunchpadInfors[_creatorAddress][_licensorIpid][licensorLaunchpadCount]; + + require(_licensorIpid == storyLaunchpad.licensorIpid, "StoryLaunchpad: Invalid _licensorIpid"); + require(_quantity > 0, "StoryLaunchpad: Invalid quantity"); + require(_quantity <= storyLaunchpad.maxBuy, "StoryLaunchpad: Invalid quantity"); + require( + _quantity <= + storyLaunchpad.totalQuantity - + (numberOfNftSold[_creatorAddress][_licensorIpid][licensorLaunchpadCount]), + "StoryLaunchpad: NFT sold out" + ); + require( + userBuyCount[msg.sender][_creatorAddress][_licensorIpid][licensorLaunchpadCount] + _quantity <= + storyLaunchpad.maxBuy, + "StoryLaunchpad: You buy too much" + ); + + require(_payAmount >= launchpadInfor.price * _quantity, "StoryLaunchpad: not enough fund"); + + numberOfNftSold[_creatorAddress][_licensorIpid][licensorLaunchpadCount] += _quantity; + userBuyCount[msg.sender][_creatorAddress][_licensorIpid][licensorLaunchpadCount] += _quantity; + + if (_payToken == address(0)) { + require(msg.value >= launchpadInfor.price * _quantity, "INVALID MSG.VALUE"); + } else { + require(msg.value == 0, "MSG.VALUE SHOULD BE ZERO"); + IERC20(_payToken).safeTransferFrom( + msg.sender, + storyLaunchpad.creatorAddress, + launchpadInfor.price * _quantity + ); + } + + if (msg.value > launchpadInfor.price * _quantity) { + payable(msg.sender).transfer(msg.value - (launchpadInfor.price * _quantity)); + } + + LaunchpadNFT _nftContract = LaunchpadNFT(launchpadInfor.nftAddress); + for (uint256 i = 0; i < _quantity; i++) { + //Mint NFT + uint256 tokenId = _nftContract.mintTokens(address(this), launchpadInfor.uriNFT); + require(tokenId > 0); + + //Regigter a childIpId for NFT + address childIpId = IPAssetRegistry(ipAssetRegistryAddr).register( + block.chainid, + launchpadInfor.nftAddress, + tokenId + ); + + //Register Derivative for NFT with licenseId + uint256[] memory licenseTokenIds = new uint256[](1); + licenseTokenIds[0] = getLicenseIdByLicensor(_licensorIpid); + require(licenseTokenIds[0] > 0, "StoryLaunchpad: License token not avaiable for mint"); + + LicensingModule(licensingModuleAddr).registerDerivativeWithLicenseTokens({ + childIpId: childIpId, + licenseTokenIds: licenseTokenIds, + royaltyContext: "0x0" // empty for PIL + }); + + _nftContract.safeTransferFrom(address(this), msg.sender, tokenId); + } + } +} diff --git a/contracts/multicall.sol b/contracts/multicall.sol new file mode 100644 index 0000000..39aa74f --- /dev/null +++ b/contracts/multicall.sol @@ -0,0 +1,84 @@ +pragma solidity >=0.5.0; +pragma experimental ABIEncoderV2; + +/// @title Multicall2 - Aggregate results from multiple read-only function calls +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson + +contract Multicall2 { + struct Call { + address target; + bytes callData; + } + struct Result { + bool success; + bytes returnData; + } + + error MulticallAggregate(string content); + + function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); + if (!success) { + revert MulticallAggregate("aggregate call failed"); + } + returnData[i] = ret; + } + } + function blockAndAggregate( + Call[] memory calls + ) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { + // from Paris version, difficuty is changed to prevrandao + difficulty = block.prevrandao; + } + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + function getLastBlockHash() public view returns (bytes32 blockHash) { + blockHash = blockhash(block.number - 1); + } + function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) { + returnData = new Result[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); + + if (requireSuccess) { + if (!success) { + revert MulticallAggregate("tryAggregate call failed"); + } + } + + returnData[i] = Result(success, ret); + } + } + function tryBlockAndAggregate( + bool requireSuccess, + Call[] memory calls + ) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } +} diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..67701b6 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,14 @@ +[profile.default] +src = 'contracts' +out = 'out' +libs = ['node_modules', 'lib'] +cache_path = 'forge-cache' +gas_reports = ["*"] +optimizer = true +optimizer_runs = 2000 +test = 'test' +solc = '0.8.26' +fs_permissions = [ + { access = 'read', path = './' }, + { access = 'read-write', path = './deploy-out' }, +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f1308a --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "story-hackathon-contract", + "version": "1.0.0", + "description": "story-hackathon-contract with Story Protocol", + "directories": { + "lib": "lib", + "test": "test" + }, + "author": "Aura Network", + "license": "MIT", + "devDependencies": { + "ds-test": "https://github.com/dapphub/ds-test", + "forge-std": "github:foundry-rs/forge-std#v1.7.6", + "prettier": "^3.2.5", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^5.0.1", + "solhint-community": "^4.0.0", + "solhint-plugin-prettier": "^0.1.0" + }, + "dependencies": { + "@openzeppelin/contracts": "5.0.1", + "@openzeppelin/contracts-upgradeable": "5.0.1", + "@story-protocol/protocol-core": "github:storyprotocol/protocol-core-v1#main", + "@story-protocol/protocol-periphery": "github:storyprotocol/protocol-periphery-v1#main" + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..ab8e562 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,5 @@ +@openzeppelin/=./node_modules/@openzeppelin/ +@story-protocol/=./node_modules/@story-protocol/ +erc6551/=./node_modules/erc6551/ +forge-std/=./node_modules/forge-std/src/ +ds-test/=./node_modules/ds-test/src/ diff --git a/test/story-launchpad.test.js b/test/story-launchpad.test.js new file mode 100644 index 0000000..f5b0cc1 --- /dev/null +++ b/test/story-launchpad.test.js @@ -0,0 +1,441 @@ +const { expectRevert, time } = require('@openzeppelin/test-helpers') +const MockERC20 = artifacts.require('MockERC20') +const MockERC721 = artifacts.require('MockERC721') +const NftToMint = artifacts.require('LaunchpadToken') +const StoryLaunchpadNFT721 = artifacts.require('StoryLaunchpadNFT721') +const truffleAssert = require('truffle-assertions'); +const BN = require('bignumber.js') + +contract('StoryLaunchpadNFT721', ([Owner, Creator, alice, carol, bob, minter, vic]) => { + let launchPadNft; + let payToken; + let license; + let NFT721; + let tokenId = 0; + + before(async () => { + license = await MockERC721.new("SALENFT", { from: Owner }); + NFT721 = await NftToMint.new(Owner, "SALENFT", "SALENFT", { from: Owner }); + payToken = await MockERC20.new('PAYTOKEN', 'PAYTOKEN', '100000000', { from: Owner }); + launchPadNft = await StoryLaunchpadNFT721.new(Owner, { from: Owner }); + await NFT721.setMinter(launchPadNft.address, true, { from: Owner }); + }); + + var blockTime = Date.now() / 1000 | 0 + + it('should set correct state variables', async () => { + blockTime = BN(await time.latest()).toNumber(); + tokenId += 1; + + await payToken.mint(alice, 100000000, { from: Owner }) + await payToken.mint(vic, 100000000, { from: Owner }) + await license.mintId(Creator, tokenId, { from: Owner }) + await license.mintId(carol, tokenId+1, { from: Owner }) + await launchPadNft.setAcceptPayToken(payToken.address, true, { from: Owner }) + + assert.equal((await payToken.balanceOf(alice)).valueOf(), '100000000'); + assert.equal(await license.ownerOf(tokenId), Creator); + const owner = await launchPadNft.owner() + assert.equal(owner.valueOf(), Owner) + assert.equal(await NFT721.minters(launchPadNft.address), true); + }) + + it('should fail to create launchpad', async () => { + //should faile when input invalid parameter + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + vic, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: wrong pay token" + ) + + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId+1, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: You are not owner license Token" + ) + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + "0x0000000000000000000000000000000000000000", + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "nftAddress is wrong" + ) + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 0, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "INVALID maxBuy" + ) + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 0, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "INVALID totalQuantity" + ) + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime - 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "INVALID startTime" + ) + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 2*30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "INVALID endTime" + ) + + }) + + it('should correct create launchpad', async () => { + //should success when input valid parameter + await launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 10, //total quantity + 8, // max mint per user + 100, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}) + + let currentPhase = await launchPadNft.currentLaunchpadPhase(license.address, tokenId).valueOf() + assert.equal(currentPhase, "1") + const phase = await launchPadNft.NFTSales(license.address, tokenId, currentPhase) + + assert.equal( phase.creator, Creator) + assert.equal( phase.nftAddress, NFT721.address) + assert.equal( phase.payToken, payToken.address) + assert.equal( phase.colectionName, "NFTSALE") + assert.equal( phase.licenseTokenId, tokenId) + assert.equal( phase.totalQuantity, 10) + assert.equal( phase.maxBuy, 8) + assert.equal( phase.price, 100) + assert.equal( phase.startTime, blockTime + 30*60) + assert.equal( phase.endTime, blockTime + 1*60*60) + assert.equal( phase.royaltyPercentage, 0) + }) + + it('should fail to create launchpad when exist other', async () => { + await truffleAssert.fails( + launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 100, //total quantity + 5, // max mint per user + 1000, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: last nft sale should be end" + ) + }) + + it('should fail to mint when incorrect parameter', async () => { + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId + 10, + 1, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: nft sale should not exist" + ) + + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 1, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: nft sale should be open" + ) + + await time.increaseTo(blockTime + 31*60); //advance to block time + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 1, //quantity + vic, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: wrong pay token" + ) + + await time.increaseTo(blockTime + 2*31*60); //advance to block time + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 1, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: nft sale should be open" + ) + }) + + it('User mint NFT', async () => { + blockTime = BN(await time.latest()).toNumber(); + await launchPadNft.createNFTSale(Creator, + NFT721.address, + license.address, + tokenId, + "NFTSALE", + blockTime + 30*60, //start time + blockTime + 1*60*60, // end time + payToken.address, //payToken + 10, //total quantity + 8, // max mint per user + 100, // price + "URI", + "URI", + 0, // _royaltyPercentage + bob, // _royaltyAddress + { from: Creator}) + + await time.increaseTo(blockTime + 31*60); //advance to block time + await payToken.approve(launchPadNft.address, '1000000', { from: alice }) + await payToken.approve(launchPadNft.address, '1000000', { from: vic }) + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 0, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: Invalid quantity" + ) + + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 10, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: Invalid quantity" + ) + + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 2, //quantity + payToken.address, //payToken + 100, //pay amount + "uri", + { from: alice} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: not enough fund" + ) + //alice mint 6 nft + // await truffleAssert.passes( + launchPadNft.mintNFT( + license.address, + tokenId, + 6, //quantity + payToken.address, //payToken + 600, //pay amount + "uri", + { from: alice} + ) + // ) + + // await truffleAssert.fails( + // launchPadNft.mintNFT( + // license.address, + // tokenId, + // 3, //quantity + // payToken.address, //payToken + // 300, //pay amount + // "uri", + // { from: alice} + // ), + // truffleAssert.ErrorType.revert, + // "StoryLaunchpad: You buy too much" + // ) + + //vic mint 4 nft + await truffleAssert.passes( + launchPadNft.mintNFT( + license.address, + tokenId, + 3, //quantity + payToken.address, //payToken + 400, //pay amount + "uri", + { from: vic} + ) + ) + + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 3, //quantity + payToken.address, //payToken + 300, //pay amount + "uri", + { from: vic} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: NFT sold out" + ) + + await time.increaseTo(blockTime + 1*61*60); //advance to block time + await truffleAssert.fails( + launchPadNft.mintNFT( + license.address, + tokenId, + 1, //quantity + payToken.address, //payToken + 1, //pay amount + "uri", + { from: vic} + ), + truffleAssert.ErrorType.revert, + "StoryLaunchpad: nft sale should be open" + ) + }) + +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e637faf --- /dev/null +++ b/yarn.lock @@ -0,0 +1,857 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.6": + 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-upgradeable@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.1.tgz#ebc163cbed2de6b8b69bff628261d18deb912a81" + integrity sha512-MvaLoPnVcoZr/qqZP+4cl9piuR4gg0iIGgxVSZ/AL1iId3M6IdEHzz9Naw5Lirl4KKBI6ciTVnX07yL4dOMIJg== + +"@openzeppelin/contracts-upgradeable@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz#3e5321a2ecdd0b206064356798c21225b6ec7105" + integrity sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ== + +"@openzeppelin/contracts@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.1.tgz#93da90fc209a0a4ff09c1deb037fbb35e4020890" + integrity sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w== + +"@openzeppelin/contracts@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" + integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" + integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" + integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.1.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0" + integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw== + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@prettier/sync@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@prettier/sync/-/sync-0.3.0.tgz#91f2cfc23490a21586d1cf89c6f72157c000ca1e" + integrity sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw== + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" + integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== + +"@solidity-parser/parser@^0.16.0": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" + integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== + dependencies: + antlr4ts "^0.5.0-alpha.4" + +"@solidity-parser/parser@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.18.0.tgz#8e77a02a09ecce957255a2f48c9a7178ec191908" + integrity sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA== + +"@story-protocol/create3-deployer@github:storyprotocol/create3-deployer#main": + version "0.0.0" + resolved "https://codeload.github.com/storyprotocol/create3-deployer/tar.gz/094147bda866490dd1c3e8261bb3dfee65e7af54" + +"@story-protocol/protocol-core@github:storyprotocol/protocol-core-v1#main": + version "1.1.0" + resolved "https://codeload.github.com/storyprotocol/protocol-core-v1/tar.gz/69fae657136818e2ada2433525efd2afca99f198" + dependencies: + "@openzeppelin/contracts" "5.0.2" + "@openzeppelin/contracts-upgradeable" "5.0.2" + "@openzeppelin/contracts-upgradeable-v4" "npm:@openzeppelin/contracts-upgradeable@4.9.6" + erc6551 "^0.3.1" + solady "^0.0.192" + +"@story-protocol/protocol-periphery@github:storyprotocol/protocol-periphery-v1#main": + version "1.2.3" + resolved "https://codeload.github.com/storyprotocol/protocol-periphery-v1/tar.gz/6acb0db66ee1acd48e0d33bea5c5dd81c8425994" + dependencies: + "@openzeppelin/contracts" "5.0.1" + "@openzeppelin/contracts-upgradeable" "5.0.1" + "@story-protocol/create3-deployer" "github:storyprotocol/create3-deployer#main" + "@story-protocol/protocol-core" "github:storyprotocol/protocol-core-v1#main" + erc6551 "^0.3.1" + solady "^0.0.192" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +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== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +antlr4@^4.11.0, antlr4@^4.13.1-patch-1: + version "4.13.2" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.13.2.tgz#0d084ad0e32620482a9c3a0e2470c02e72e4006d" + integrity sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg== + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +ast-parents@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" + integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^10.2.8: + version "10.2.14" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" + integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +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== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +cosmiconfig@^8.0.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +"ds-test@https://github.com/dapphub/ds-test": + version "1.0.0" + resolved "https://github.com/dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +erc6551@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/erc6551/-/erc6551-0.3.1.tgz#f9ccc0901c210b0cc29c1c730398878319a1fd30" + integrity sha512-4+O7CJ5gn3/s2Mum1QiJDJUbGE1P1m0t3do2nnFAKWEXLubWfZjS12HqraUJ6Y6su3WJ8EqdWm0270gC0bqpCA== + +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== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +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" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2, fast-diff@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-uri@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" + integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== + +"forge-std@github:foundry-rs/forge-std#v1.7.6": + version "1.7.6" + resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/ae570fec082bfe1c1f45b0acca4a2b4f84d345ce" + +form-data-encoder@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" + integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +got@^12.1.0: + version "12.6.1" + resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" + integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@4.2.10: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +latest-version@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da" + integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg== + dependencies: + package-json "^8.1.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +normalize-url@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a" + integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + +package-json@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" + integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA== + dependencies: + got "^12.1.0" + registry-auth-token "^5.0.1" + registry-url "^6.0.0" + semver "^7.3.7" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier-plugin-solidity@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz#8060baf18853a9e34d2e09e47e87b4f19e15afe9" + integrity sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg== + dependencies: + "@solidity-parser/parser" "^0.18.0" + semver "^7.5.4" + +prettier@^2.8.3: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.2.5: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +rc@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +registry-auth-token@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.0.2.tgz#8b026cc507c8552ebbe06724136267e63302f756" + integrity sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ== + dependencies: + "@pnpm/npm-conf" "^2.1.0" + +registry-url@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" + integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== + dependencies: + rc "1.2.8" + +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== + +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + +semver@^6.3.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.2, semver@^7.5.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +solady@^0.0.192: + version "0.0.192" + resolved "https://registry.yarnpkg.com/solady/-/solady-0.0.192.tgz#f80ea061903ba1914d2c96c3c69f53d66865f88f" + integrity sha512-96A4dhYkSB/xUyZIuc6l8RMbQ+nqk7GFr0hEci7/64D3G63Ial06puqXA14ZVy/xFe8nzBJbz3Mxssn7SH/1Ag== + +solhint-community@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/solhint-community/-/solhint-community-4.0.0.tgz#4dba66932ff54ced426a8c035b7ceaa13a224f24" + integrity sha512-BERw3qYzkJE64EwvYrp2+iiTN8yAZOJ74FCiL4bTBp7v0JFUvRYCEGZKAqfHcfi/koKkzM6qThsJUceKm9vvfg== + dependencies: + "@solidity-parser/parser" "^0.16.0" + ajv "^6.12.6" + antlr4 "^4.11.0" + ast-parents "^0.0.1" + chalk "^4.1.2" + commander "^11.1.0" + cosmiconfig "^8.0.0" + fast-diff "^1.2.0" + glob "^8.0.3" + ignore "^5.2.4" + js-yaml "^4.1.0" + lodash "^4.17.21" + pluralize "^8.0.0" + semver "^6.3.0" + strip-ansi "^6.0.1" + table "^6.8.1" + text-table "^0.2.0" + optionalDependencies: + prettier "^2.8.3" + +solhint-plugin-prettier@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz#2f46999e26d6c6bc80281c22a7a21e381175bef7" + integrity sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw== + dependencies: + "@prettier/sync" "^0.3.0" + prettier-linter-helpers "^1.0.0" + +solhint@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/solhint/-/solhint-5.0.3.tgz#b57f6d2534fe09a60f9db1b92e834363edd1cbde" + integrity sha512-OLCH6qm/mZTCpplTXzXTJGId1zrtNuDYP5c2e6snIv/hdRVxPfBBz/bAlL91bY/Accavkayp2Zp2BaDSrLVXTQ== + dependencies: + "@solidity-parser/parser" "^0.18.0" + ajv "^6.12.6" + antlr4 "^4.13.1-patch-1" + ast-parents "^0.0.1" + chalk "^4.1.2" + commander "^10.0.0" + cosmiconfig "^8.0.0" + fast-diff "^1.2.0" + glob "^8.0.3" + ignore "^5.2.4" + js-yaml "^4.1.0" + latest-version "^7.0.0" + lodash "^4.17.21" + pluralize "^8.0.0" + semver "^7.5.2" + strip-ansi "^6.0.1" + table "^6.8.1" + text-table "^0.2.0" + optionalDependencies: + prettier "^2.8.3" + +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.8.1: + version "6.8.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==