diff --git a/README.md b/README.md index 6ff5330..c769ab9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ +# Optimistic SVG NFTs +## A Project of the Encode Expert Solidity Bootcamp + +![208145278-3d7783d3-de17-4b55-95b6-99e40fbe8fcd](https://github.com/damianmarti/loogies/assets/466652/ee2b1f9c-e30c-485b-862b-faaaaf95296b) + +This repo shows how to set up an SVG NFT contract so that other NFTs can use it in their SVG code. This leads to an easy composition of SVG NFTs. More information at [ERC-4883: Composable SVG NFT](https://eips.ethereum.org/EIPS/eip-4883) + +Take a look at `SVGNFT.sol`. It describes an SVG NFT that is defined by three parameters: color, chubbiness, and mouthLength randomly generated at mint. + +It exposes a function: + +```function renderTokenById(uint256 id) public view returns (string memory)``` + +It returns the relevant SVG that can be embedded in other SVG code for rendering. + +

+ Website +

+ + +## Bootcamp topics utilized: + +- gas optimization +- assembly +- proxy pattern +- advanced tooling (Foundry) +- implementing Scaffold-ETH 2 (partner workshop) + +## Team Members (group 3, Discord handles): + +- `lostDecade` +- `Muhammed Shahinsha Pottayil` +- `yassin7254` +- `Rainmaker` + +Built using Scaffold-ETH 2 + # ๐Ÿ— Scaffold-ETH 2

@@ -7,7 +44,7 @@ ๐Ÿงช An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts. -โš™๏ธ Built using NextJS, RainbowKit, Foundry, Wagmi, Viem, and Typescript. +โš™๏ธ Built using NextJS, RainbowKit, foundry, Wagmi, Viem, and Typescript. - โœ… **Contract Hot Reload**: Your frontend auto-adapts to your smart contract as you edit it. - ๐Ÿช **[Custom hooks](https://docs.scaffoldeth.io/hooks/)**: Collection of React hooks wrapper around [wagmi](https://wagmi.sh/) to simplify interactions with smart contracts with typescript autocompletion. @@ -29,11 +66,12 @@ Before you begin, you need to install the following tools: To get started with Scaffold-ETH 2, follow the steps below: -1. Install dependencies if it was skipped in CLI: +1. Clone this repo & install dependencies ``` -cd my-dapp-example -yarn install +git clone https://github.com/scaffold-eth/scaffold-eth-2.git +cd scaffold-eth-2 +yarn ``` 2. Run a local network in the first terminal: @@ -42,15 +80,15 @@ yarn install yarn chain ``` -This command starts a local Ethereum network using Foundry. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in `packages/foundry/foundry.toml`. +This command starts a local Ethereum network using foundry. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in `foundry.toml`. -3. On a second terminal, deploy the test contract: +3. In a second terminal, deploy the test contract: ``` yarn deploy ``` -This command deploys a test smart contract to the local network. The contract is located in `packages/foundry/contracts` and can be modified to suit your needs. The `yarn deploy` command uses the deploy script located in `packages/foundry/script` to deploy the contract to the network. You can also customize the deploy script. +This command deploys a test smart contract to the local network. The contract is located in `packages/foundry/contracts` and can be modified to suit your needs. The `yarn deploy` command uses the deploy script located in `packages/foundry/deploy` to deploy the contract to the network. You can also customize the deploy script. 4. On a third terminal, start your NextJS app: @@ -60,12 +98,12 @@ yarn start Visit your app on: `http://localhost:3000`. You can interact with your smart contract using the `Debug Contracts` page. You can tweak the app config in `packages/nextjs/scaffold.config.ts`. -Run smart contract test with `yarn foundry:test` +**What's next**: - Edit your smart contract `YourContract.sol` in `packages/foundry/contracts` - Edit your frontend homepage at `packages/nextjs/app/page.tsx`. For guidance on [routing](https://nextjs.org/docs/app/building-your-application/routing/defining-routes) and configuring [pages/layouts](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts) checkout the Next.js documentation. -- Edit your deployment scripts in `packages/foundry/script` - +- Edit your deployment scripts in `packages/foundry/deploy` +- Edit your smart contract test in: `packages/foundry/test`. To run test use `yarn foundry:test` ## Documentation @@ -77,4 +115,4 @@ To know more about its features, check out our [website](https://scaffoldeth.io) We welcome contributions to Scaffold-ETH 2! -Please see [CONTRIBUTING.MD](https://github.com/scaffold-eth/scaffold-eth-2/blob/main/CONTRIBUTING.md) for more information and guidelines for contributing to Scaffold-ETH 2. \ No newline at end of file +Please see [CONTRIBUTING.MD](https://github.com/scaffold-eth/scaffold-eth-2/blob/main/CONTRIBUTING.md) for more information and guidelines for contributing to Scaffold-ETH 2. diff --git a/package.json b/package.json index c5ac1a9..ab807f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "se-2", + "name": "optimistic_svg_nfts", "version": "0.0.1", - "private": true, "workspaces": { "packages": [ "packages/*" @@ -34,8 +33,8 @@ "verify": "yarn workspace @se-2/foundry verify" }, "devDependencies": { - "husky": "~8.0.3", - "lint-staged": "~13.2.2" + "husky": "9.1.4", + "lint-staged": "15.2.7" }, "packageManager": "yarn@3.2.3", "engines": { diff --git a/packages/foundry/contracts/Calculator.sol b/packages/foundry/contracts/Calculator.sol index 67d2070..a11d92a 100644 --- a/packages/foundry/contracts/Calculator.sol +++ b/packages/foundry/contracts/Calculator.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.20; +pragma solidity 0.8.26; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; diff --git a/packages/foundry/contracts/CalculatorV2.sol b/packages/foundry/contracts/CalculatorV2.sol index 7d9370b..3ef0a88 100644 --- a/packages/foundry/contracts/CalculatorV2.sol +++ b/packages/foundry/contracts/CalculatorV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.20; +pragma solidity 0.8.26; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; diff --git a/packages/foundry/contracts/HexStrings.sol b/packages/foundry/contracts/HexStrings.sol index ac103a3..fa83f78 100644 --- a/packages/foundry/contracts/HexStrings.sol +++ b/packages/foundry/contracts/HexStrings.sol @@ -1,33 +1,30 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; -library HexStrings { - bytes16 private constant ALPHABET = "0123456789abcdef"; - - function toHexString( - uint256 value, - uint256 length - ) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - - assembly { - let pos := add(buffer, 34) - let end := add(pos, mul(length, 2)) - let remaining := value +pragma solidity 0.8.26; - for { +library HexStrings { + bytes16 private constant ALPHABET = "0123456789abcdef"; - } lt(pos, end) { + function toHexString( + uint256 value, + uint256 length + ) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; - } { - mstore8(sub(end, 1), byte(mod(remaining, 16), ALPHABET)) - remaining := div(remaining, 16) - end := sub(end, 1) - } - } + assembly { + let pos := add(buffer, 34) + let end := add(pos, mul(length, 2)) + let remaining := value - return string(buffer); + for { } lt(pos, end) { } { + mstore8(sub(end, 1), byte(mod(remaining, 16), ALPHABET)) + remaining := div(remaining, 16) + end := sub(end, 1) + } } + + return string(buffer); + } } diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index e215214..122ade8 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -1,269 +1,271 @@ -pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; +pragma solidity 0.8.26; -import {HexStrings} from "./HexStrings.sol"; -import {ToColor} from "./ToColor.sol"; +import { ToColor } from "./ToColor.sol"; +import { HexStrings } from "./HexStrings.sol"; +import { Base64 } from "@openzeppelin/contracts/utils/base64.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { OwnableUpgradeable } from + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { ERC721Upgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +/** + * @title Implementation of a simple SVG Generator based on ERC-4883 + */ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { - error SVGNFT__INVALIDTOKENID(); - error SVGNFT__DONEMINTING(); - - using Strings for uint256; - using HexStrings for uint160; - using ToColor for bytes3; - uint256 private _tokenIds; - - // all funds go to buidlguidl.eth - address payable public constant recipient = - payable(0xa81a6a910FeD20374361B35C451a4a44F86CeD46); - - uint256 public constant limit = 10; - uint256 public constant curve = 1002; // price increase 0,4% with each purchase - uint256 public price; - // the 1154th optimistic loogies cost 0.01 ETH, the 2306th cost 0.1ETH, the 3459th cost 1 ETH and the last ones cost 1.7 ETH - - mapping(uint256 => bytes3) public color; - mapping(uint256 => uint256) public chubbiness; - mapping(uint256 => uint256) public mouthLength; - - function initialize() public initializer { - __ERC721_init("OptimisticLoogies", "OPLOOG"); - __Ownable_init(msg.sender); - _tokenIds = 1; - price = 0.001 ether; - emit Initialized(11111111); + error INVALIDTOKENID(); + error DONEMINTING(); + + using Strings for uint256; + using HexStrings for uint160; + using ToColor for bytes3; + + //============================================================================ + // Public State + //============================================================================ + + uint256 public tokenIds; + uint256 public constant limit = 10; + uint256 public constant curve = 1002; // price increase 0.4% with each purchase + uint256 public price; // the 1154th optimistic loogies cost 0.01 ETH, the 2306th cost 0.1ETH, the 3459th cost 1 ETH and the last ones cost 1.7 ETH + + address payable public constant recipient = + payable(0xa81a6a910FeD20374361B35C451a4a44F86CeD46); + + mapping(uint256 => bytes3) public color; + mapping(uint256 => uint256) public chubbiness; + mapping(uint256 => uint256) public mouthLength; + + //============================================================================ + // Mutative Functions + //============================================================================ + + function initialize() public initializer { + __ERC721_init("OptimisticLoogies", "OPLOOG"); + __Ownable_init(msg.sender); + tokenIds = 1; + price = 0.001 ether; + emit Initialized(11111111); + } + + function mintItem() public payable returns (uint256) { + uint256 id; + bytes32 predictableRandom; + + assembly { + // Check if _tokenIds < limit + if gt(sload(tokenIds.slot), limit) { + // Store the error message + mstore(0x00, 0xa0d36543) // SVGNFT__DONEMINTING() selector + revert(0x00, 0x04) + } + + // Check if msg.value >= price + if lt(callvalue(), sload(price.slot)) { + // Store the error message + mstore(0x00, 0x20) // Store offset to error string + mstore(0x20, 0x0a) // Store length of error string + mstore(0x40, "NOT ENOUGH") // Store error string + revert(0x00, 0x60) // Revert with error message + } + + // Calculate new price: price = (price * curve) / 1000 + let newPrice := div(mul(sload(price.slot), curve), 1000) + sstore(price.slot, newPrice) + + // Get current _tokenIds and increment + id := sload(tokenIds.slot) + sstore(tokenIds.slot, add(id, 1)) + + // Generate predictableRandom + mstore(0x00, id) + mstore(0x20, blockhash(sub(number(), 1))) + mstore(0x40, caller()) + mstore(0x60, address()) + predictableRandom := keccak256(0x00, 0x80) + + // Set color + let colorValue := + or( + and(0xFF00, shl(8, byte(0, predictableRandom))), + or( + and(0x00FF, byte(1, predictableRandom)), + and(0xFF0000, shl(16, byte(2, predictableRandom))) + ) + ) + sstore(add(color.slot, id), colorValue) + + // Set chubbiness + let chubbinessValue := + add(35, div(mul(55, byte(3, predictableRandom)), 255)) + sstore(add(chubbiness.slot, id), chubbinessValue) + + // Set mouthLength + let mouthLengthValue := + add( + 180, div(mul(div(chubbinessValue, 4), byte(4, predictableRandom)), 255) + ) + sstore(add(mouthLength.slot, id), mouthLengthValue) + + // Transfer value to recipient + let success := + call( + gas(), + 0xa81a6a910FeD20374361B35C451a4a44F86CeD46, + callvalue(), + 0, + 0, + 0, + 0 + ) + if iszero(success) { + // Store the error message + mstore(0x00, 0x20) // Store offset to error string + mstore(0x20, 0x0e) // Store length of error string + mstore(0x40, "could not send") // Store error string + revert(0x00, 0x60) // Revert with error message + } } - function mintItem() public payable returns (uint256) { - uint256 id; - bytes32 predictableRandom; - - assembly { - // Check if _tokenIds < limit - if gt(sload(_tokenIds.slot), limit) { - // Store the error message - mstore(0x00, 0x2260e398) // SVGNFT__DONEMINTING() selector - revert(0x00, 0x04) - } - - // Check if msg.value >= price - if lt(callvalue(), sload(price.slot)) { - // Store the error message - mstore(0x00, 0x20) // Store offset to error string - mstore(0x20, 0x0a) // Store length of error string - mstore(0x40, "NOT ENOUGH") // Store error string - revert(0x00, 0x60) // Revert with error message - } - - // Calculate new price: price = (price * curve) / 1000 - let newPrice := div(mul(sload(price.slot), curve), 1000) - sstore(price.slot, newPrice) - - // Get current _tokenIds and increment - id := sload(_tokenIds.slot) - sstore(_tokenIds.slot, add(id, 1)) - - // Generate predictableRandom - mstore(0x00, id) - mstore(0x20, blockhash(sub(number(), 1))) - mstore(0x40, caller()) - mstore(0x60, address()) - predictableRandom := keccak256(0x00, 0x80) - - // Set color - let colorValue := or( - and(0xFF00, shl(8, byte(0, predictableRandom))), - or( - and(0x00FF, byte(1, predictableRandom)), - and(0xFF0000, shl(16, byte(2, predictableRandom))) - ) - ) - sstore(add(color.slot, id), colorValue) + // The _mint function call is kept outside of assembly as it's likely an internal function + _mint(msg.sender, id); - // Set chubbiness - let chubbinessValue := add( - 35, - div(mul(55, byte(3, predictableRandom)), 255) - ) - sstore(add(chubbiness.slot, id), chubbinessValue) - - // Set mouthLength - let mouthLengthValue := add( - 180, - div( - mul(div(chubbinessValue, 4), byte(4, predictableRandom)), - 255 - ) - ) - sstore(add(mouthLength.slot, id), mouthLengthValue) - - // Transfer value to recipient - let success := call( - gas(), - 0xa81a6a910FeD20374361B35C451a4a44F86CeD46, - callvalue(), - 0, - 0, - 0, - 0 - ) - if iszero(success) { - // Store the error message - mstore(0x00, 0x20) // Store offset to error string - mstore(0x20, 0x0e) // Store length of error string - mstore(0x40, "could not send") // Store error string - revert(0x00, 0x60) // Revert with error message - } - } - - // The _mint function call is kept outside of assembly as it's likely an internal function - _mint(msg.sender, id); - - return id; - } + return id; + } - function tokenURI(uint256 id) public view override returns (string memory) { - if (ownerOf(id) == address(0)) { - revert SVGNFT__INVALIDTOKENID(); - } - string memory name = string( - abi.encodePacked("Loogie #", id.toString()) - ); - string memory description = string( - abi.encodePacked( - "This Loogie is the color #", - color[id].toColor(), - " with a chubbiness of ", - uint2str(chubbiness[id]), - " and mouth length of ", - uint2str(mouthLength[id]), - "!!!" - ) - ); - string memory image = Base64.encode(bytes(generateSVGofTokenById(id))); - - return - string( - abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - abi.encodePacked( - '{"name":"', - name, - '", "description":"', - description, - '", "external_url":"https://burnyboys.com/token/', - id.toString(), - '", "attributes": [{"trait_type": "color", "value": "#', - color[id].toColor(), - '"},{"trait_type": "chubbiness", "value": ', - uint2str(chubbiness[id]), - '},{"trait_type": "mouthLength", "value": ', - uint2str(mouthLength[id]), - '}], "owner":"', - (uint160(ownerOf(id))).toHexString(20), - '", "image": "', - "data:image/svg+xml;base64,", - image, - '"}' - ) - ) - ) - ) - ); - } - - function generateSVGofTokenById( - uint256 id - ) internal view returns (string memory) { - string memory svg = string( - abi.encodePacked( - '', - renderTokenById(id), - "" - ) - ); + //============================================================================ + // View Functions + //============================================================================ - return svg; + function tokenURI(uint256 id) public view override returns (string memory) { + if (ownerOf(id) == address(0)) { + revert INVALIDTOKENID(); } - - function renderTokenById(uint256 id) public view returns (string memory) { - // the translate function for the mouth is based on the curve y = 810/11 - 9x/11 - string memory render = string( + string memory name = string(abi.encodePacked("Loogie #", id.toString())); + string memory description = string( + abi.encodePacked( + "This Loogie is the color #", + color[id].toColor(), + " with a chubbiness of ", + uint2str(chubbiness[id]), + " and mouth length of ", + uint2str(mouthLength[id]), + "!!!" + ) + ); + string memory image = Base64.encode(bytes(generateSVGofTokenById(id))); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( abi.encodePacked( - '', - '', - '', - "", - '', - '', - "", - '', - '', - '', - "" - '', - '', - "" + '{"name":"', + name, + '", "description":"', + description, + '", "external_url":"https://burnyboys.com/token/', + id.toString(), + '", "attributes": [{"trait_type": "color", "value": "#', + color[id].toColor(), + '"},{"trait_type": "chubbiness", "value": ', + uint2str(chubbiness[id]), + '},{"trait_type": "mouthLength", "value": ', + uint2str(mouthLength[id]), + '}], "owner":"', + (uint160(ownerOf(id))).toHexString(20), + '", "image": "', + "data:image/svg+xml;base64,", + image, + '"}' ) - ); - - return render; + ) + ) + ) + ); + } + + function renderTokenById(uint256 id) public view returns (string memory) { + // the translate function for the mouth is based on the curve y = 810/11 - 9x/11 + string memory render = string( + abi.encodePacked( + '', + '', + '', + "", + '', + '', + "", + '', + '', + '', + "" '', + '', + "" + ) + ); + + return render; + } + + //============================================================================ + // Internal Functions + //============================================================================ + + function generateSVGofTokenById(uint256 id) + internal + view + returns (string memory) + { + string memory svg = string( + abi.encodePacked( + '', + renderTokenById(id), + "" + ) + ); + + return svg; + } + + function uint2str(uint256 _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; } - function uint2str(uint256 _i) internal pure returns (string memory) { - if (_i == 0) { - return "0"; - } - - uint256 j = _i; - uint256 length; - while (j != 0) { - length++; - j /= 10; - } - - bytes memory bstr = new bytes(length); - - assembly { - let position := add(bstr, 32) - let end := add(position, length) - - for { + uint256 j = _i; + uint256 length; + while (j != 0) { + length++; + j /= 10; + } - } gt(length, 0) { + bytes memory bstr = new bytes(length); - } { - length := sub(length, 1) - let remainder := mod(_i, 10) - mstore8(sub(end, 1), add(remainder, 48)) - _i := div(_i, 10) - end := sub(end, 1) - } - } + assembly { + let position := add(bstr, 32) + let end := add(position, length) - return string(bstr); + for { } gt(length, 0) { } { + length := sub(length, 1) + let remainder := mod(_i, 10) + mstore8(sub(end, 1), add(remainder, 48)) + _i := div(_i, 10) + end := sub(end, 1) + } } - function testGetTokenID() external view returns (uint256) { - return _tokenIds; - } - - function getTokenPrice() external view returns (uint256) { - return price; - } + return string(bstr); + } } diff --git a/packages/foundry/contracts/SVGNFTV2.sol b/packages/foundry/contracts/SVGNFTV2.sol index 8a897bf..dbdf5ed 100644 --- a/packages/foundry/contracts/SVGNFTV2.sol +++ b/packages/foundry/contracts/SVGNFTV2.sol @@ -1,214 +1,213 @@ -pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; +pragma solidity 0.8.26; -import {HexStrings} from "./HexStrings.sol"; -import {ToColor} from "./ToColor.sol"; - -error SVGNFTV2__INVALIDTOKENID(); +import { ToColor } from "./ToColor.sol"; +import { HexStrings } from "./HexStrings.sol"; +import { Base64 } from "@openzeppelin/contracts/utils/base64.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { OwnableUpgradeable } from + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { ERC721Upgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +/** + * @title Upgrade Implementation of a simple SVG Generator based on ERC-4883 + */ contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable { - using Strings for uint256; - using HexStrings for uint160; - using ToColor for bytes3; - uint256 private _tokenIds; - - // all funds go to buidlguidl.eth - address payable public constant recipient = - payable(0xa81a6a910FeD20374361B35C451a4a44F86CeD46); - - uint256 public constant limit = 3728; - uint256 public constant curve = 1002; // price increase 0,4% with each purchase - uint256 public price = 0.001 ether; - // the 1154th optimistic loogies cost 0.01 ETH, the 2306th cost 0.1ETH, the 3459th cost 1 ETH and the last ones cost 1.7 ETH - - mapping(uint256 => bytes3) public color; - mapping(uint256 => uint256) public chubbiness; - mapping(uint256 => uint256) public mouthLength; - mapping(uint256 => uint256) public eyeSize; - mapping(uint256 => bytes3) public eyeColor; - - // @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - function initialize() public initializer { - emit Initialized(11111111); + error INVALIDTOKENID(); + error DONEMINTING(); + + using Strings for uint256; + using HexStrings for uint160; + using ToColor for bytes3; + + //============================================================================ + // Public State + //============================================================================ + + uint256 public tokenIds; + uint256 public constant limit = 37; + uint256 public constant curve = 1002; // price increase 0.4% with each purchase + uint256 public price; // the 1154th optimistic loogies cost 0.01 ETH, the 2306th cost 0.1ETH, the 3459th cost 1 ETH and the last ones cost 1.7 ETH + + address payable public constant recipient = + payable(0x2D31A4315e3558109c25C78b3639d316F84A203b); + + mapping(uint256 => bytes3) public color; + mapping(uint256 => uint256) public chubbiness; + mapping(uint256 => uint256) public mouthLength; + mapping(uint256 => uint256) public eyeSize; + mapping(uint256 => bytes3) public eyeColor; + + // @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize() public initializer { + emit Initialized(11111111); + } + + function mintItem() public payable returns (uint256) { + require(tokenIds < limit, "DONE MINTING"); + require(msg.value >= price, "NOT ENOUGH"); + + price = (price * curve) / 1000; + + uint256 id = tokenIds; + + tokenIds += 1; + + _mint(msg.sender, id); + + bytes32 predictableRandom = keccak256( + abi.encodePacked( + id, blockhash(block.number - 1), msg.sender, address(this) + ) + ); + color[id] = bytes2(predictableRandom[0]) + | (bytes2(predictableRandom[1]) >> 8) | (bytes3(predictableRandom[2]) >> 16); + chubbiness[id] = 35 + ((55 * uint256(uint8(predictableRandom[3]))) / 255); + // small chubiness loogies have small mouth + mouthLength[id] = 180 + + ( + (uint256(chubbiness[id] / 4) * uint256(uint8(predictableRandom[4]))) / 255 + ); + eyeSize[id] = 20 + ((20 * uint256(uint8(predictableRandom[5]))) / 255); + eyeColor[id] = bytes2(predictableRandom[6]) + | (bytes2(predictableRandom[7]) >> 8) | (bytes3(predictableRandom[8]) >> 16); + + (bool success,) = recipient.call{ value: msg.value }(""); + require(success, "could not send"); + + return id; + } + + function tokenURI(uint256 id) public view override returns (string memory) { + if (ownerOf(id) == address(0)) { + revert INVALIDTOKENID(); } - - function mintItem() public payable returns (uint256) { - require(_tokenIds < limit, "DONE MINTING"); - require(msg.value >= price, "NOT ENOUGH"); - - price = (price * curve) / 1000; - - uint256 id = _tokenIds; - - _tokenIds += 1; - - _mint(msg.sender, id); - - bytes32 predictableRandom = keccak256( + string memory name = string(abi.encodePacked("Loogie #", id.toString())); + string memory description = string( + abi.encodePacked( + "This Loogie is the color #", + color[id].toColor(), + " with a chubbiness of ", + uint2str(chubbiness[id]), + ",mouth length of ", + uint2str(mouthLength[id]), + ",eye size of ", + uint2str(eyeSize[id]), + "and eye color of ", + eyeColor[id].toColor(), + "!!!" + ) + ); + string memory image = Base64.encode(bytes(generateSVGofTokenById(id))); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( abi.encodePacked( - id, - blockhash(block.number - 1), - msg.sender, - address(this) + '{"name":"', + name, + '", "description":"', + description, + '", "external_url":"https://burnyboys.com/token/', + id.toString(), + '", "attributes": [{"trait_type": "color", "value": "#', + color[id].toColor(), + '"},{"trait_type": "chubbiness", "value": ', + uint2str(chubbiness[id]), + '},{"trait_type": "mouthLength", "value": ', + uint2str(mouthLength[id]), + '}], "owner":"', + (uint160(ownerOf(id))).toHexString(20), + '", "image": "', + "data:image/svg+xml;base64,", + image, + '"}' ) - ); - color[id] = - bytes2(predictableRandom[0]) | - (bytes2(predictableRandom[1]) >> 8) | - (bytes3(predictableRandom[2]) >> 16); - chubbiness[id] = - 35 + - ((55 * uint256(uint8(predictableRandom[3]))) / 255); - // small chubiness loogies have small mouth - mouthLength[id] = - 180 + - ((uint256(chubbiness[id] / 4) * - uint256(uint8(predictableRandom[4]))) / 255); - eyeSize[id] = 20 + ((20 * uint256(uint8(predictableRandom[5]))) / 255); - eyeColor[id] = - bytes2(predictableRandom[6]) | - (bytes2(predictableRandom[7]) >> 8) | - (bytes3(predictableRandom[8]) >> 16); - - (bool success, ) = recipient.call{value: msg.value}(""); - require(success, "could not send"); - - return id; + ) + ) + ) + ); + } + + function generateSVGofTokenById(uint256 id) + internal + view + returns (string memory) + { + string memory svg = string( + abi.encodePacked( + '', + renderTokenById(id), + "" + ) + ); + + return svg; + } + + // Visibility is `public` to enable it being called by other contracts for composition. + function renderTokenById(uint256 id) public view returns (string memory) { + // the translate function for the mouth is based on the curve y = 810/11 - 9x/11 + string memory render = string( + abi.encodePacked( + '', + '', + '', + "", + '', + '', + "", + '', + '', + '', + "" '', + '', + "" + ) + ); + + return render; + } + + function uint2str(uint256 _i) + internal + pure + returns (string memory _uintAsString) + { + if (_i == 0) { + return "0"; } - - function tokenURI(uint256 id) public view override returns (string memory) { - if (ownerOf(id) == address(0)) { - revert SVGNFTV2__INVALIDTOKENID(); - } - string memory name = string( - abi.encodePacked("Loogie #", id.toString()) - ); - string memory description = string( - abi.encodePacked( - "This Loogie is the color #", - color[id].toColor(), - " with a chubbiness of ", - uint2str(chubbiness[id]), - ",mouth length of ", - uint2str(mouthLength[id]), - ",eye size of ", - uint2str(eyeSize[id]), - "and eye color of ", - eyeColor[id].toColor(), - "!!!" - ) - ); - string memory image = Base64.encode(bytes(generateSVGofTokenById(id))); - - return - string( - abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - abi.encodePacked( - '{"name":"', - name, - '", "description":"', - description, - '", "external_url":"https://burnyboys.com/token/', - id.toString(), - '", "attributes": [{"trait_type": "color", "value": "#', - color[id].toColor(), - '"},{"trait_type": "chubbiness", "value": ', - uint2str(chubbiness[id]), - '},{"trait_type": "mouthLength", "value": ', - uint2str(mouthLength[id]), - '}], "owner":"', - (uint160(ownerOf(id))).toHexString(20), - '", "image": "', - "data:image/svg+xml;base64,", - image, - '"}' - ) - ) - ) - ) - ); + uint256 j = _i; + uint256 len; + while (j != 0) { + len++; + j /= 10; } - - function generateSVGofTokenById( - uint256 id - ) internal view returns (string memory) { - string memory svg = string( - abi.encodePacked( - '', - renderTokenById(id), - "" - ) - ); - - return svg; - } - - // Visibility is `public` to enable it being called by other contracts for composition. - function renderTokenById(uint256 id) public view returns (string memory) { - // the translate function for the mouth is based on the curve y = 810/11 - 9x/11 - string memory render = string( - abi.encodePacked( - '', - '', - '', - "", - '', - '', - "", - '', - '', - '', - "" - '', - '', - "" - ) - ); - - return render; - } - - function uint2str( - uint _i - ) internal pure returns (string memory _uintAsString) { - if (_i == 0) { - return "0"; - } - uint j = _i; - uint len; - while (j != 0) { - len++; - j /= 10; - } - bytes memory bstr = new bytes(len); - uint k = len; - while (_i != 0) { - k = k - 1; - uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); - bytes1 b1 = bytes1(temp); - bstr[k] = b1; - _i /= 10; - } - return string(bstr); + bytes memory bstr = new bytes(len); + uint256 k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; } + return string(bstr); + } } diff --git a/packages/foundry/contracts/ToColor.sol b/packages/foundry/contracts/ToColor.sol index 824270f..ad29b03 100644 --- a/packages/foundry/contracts/ToColor.sol +++ b/packages/foundry/contracts/ToColor.sol @@ -1,26 +1,30 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; -library ToColor { - function toColor(bytes3 value) internal pure returns (string memory) { - bytes memory buffer = new bytes(6); +pragma solidity 0.8.26; - assembly { - let pos := add(buffer, 32) +/** + * @title Color Conversion Library for SVG Generator based on ERC-4883 + */ +library ToColor { + function toColor(bytes3 value) internal pure returns (string memory) { + bytes memory buffer = new bytes(6); - // Convert first byte - mstore8(pos, byte(28, mload(add(value, 32)))) - mstore8(add(pos, 1), byte(29, mload(add(value, 32)))) + assembly { + let pos := add(buffer, 32) - // Convert second byte - mstore8(add(pos, 2), byte(30, mload(add(value, 32)))) - mstore8(add(pos, 3), byte(31, mload(add(value, 32)))) + // Convert first byte + mstore8(pos, byte(28, mload(add(value, 32)))) + mstore8(add(pos, 1), byte(29, mload(add(value, 32)))) - // Convert third byte - mstore8(add(pos, 4), byte(0, mload(add(value, 33)))) - mstore8(add(pos, 5), byte(1, mload(add(value, 33)))) - } + // Convert second byte + mstore8(add(pos, 2), byte(30, mload(add(value, 32)))) + mstore8(add(pos, 3), byte(31, mload(add(value, 32)))) - return string(buffer); + // Convert third byte + mstore8(add(pos, 4), byte(0, mload(add(value, 33)))) + mstore8(add(pos, 5), byte(1, mload(add(value, 33)))) } + + return string(buffer); + } } diff --git a/packages/foundry/contracts/YourContract.sol b/packages/foundry/contracts/YourContract.sol deleted file mode 100644 index 3142774..0000000 --- a/packages/foundry/contracts/YourContract.sol +++ /dev/null @@ -1,84 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -// Useful for debugging. Remove when deploying to a live network. -import "forge-std/console.sol"; - -// Use openzeppelin to inherit battle-tested implementations (ERC20, ERC721, etc) -// import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * A smart contract that allows changing a state variable of the contract and tracking the changes - * It also allows the owner to withdraw the Ether in the contract - * @author BuidlGuidl - */ -contract YourContract { - // State Variables - address public immutable owner; - string public greeting = "Building Unstoppable Apps!!!"; - bool public premium = false; - uint256 public totalCounter = 0; - mapping(address => uint256) public userGreetingCounter; - - // Events: a way to emit log statements from smart contract that can be listened to by external parties - event GreetingChange( - address indexed greetingSetter, - string newGreeting, - bool premium, - uint256 value - ); - - // Constructor: Called once on contract deployment - // Check packages/foundry/deploy/Deploy.s.sol - constructor(address _owner) { - owner = _owner; - } - - // Modifier: used to define a set of rules that must be met before or after a function is executed - // Check the withdraw() function - modifier isOwner() { - // msg.sender: predefined variable that represents address of the account that called the current function - require(msg.sender == owner, "Not the Owner"); - _; - } - - /** - * Function that allows anyone to change the state variable "greeting" of the contract and increase the counters - * - * @param _newGreeting (string memory) - new greeting to save on the contract - */ - function setGreeting(string memory _newGreeting) public payable { - // Print data to the anvil chain console. Remove when deploying to a live network. - - console.logString("Setting new greeting"); - console.logString(_newGreeting); - - greeting = _newGreeting; - totalCounter += 1; - userGreetingCounter[msg.sender] += 1; - - // msg.value: built-in global variable that represents the amount of ether sent with the transaction - if (msg.value > 0) { - premium = true; - } else { - premium = false; - } - - // emit: keyword used to trigger an event - emit GreetingChange(msg.sender, _newGreeting, msg.value > 0, msg.value); - } - - /** - * Function that allows the owner to withdraw all the Ether in the contract - * The function can only be called by the owner of the contract as defined by the isOwner modifier - */ - function withdraw() public isOwner { - (bool success,) = owner.call{ value: address(this).balance }(""); - require(success, "Failed to send Ether"); - } - - /** - * Function that allows the contract to receive ETH - */ - receive() external payable { } -} diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index 6921a5d..bb431d9 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -1,62 +1,57 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; -import "../contracts/SVGNFT.sol"; -import "./DeployHelpers.s.sol"; +pragma solidity 0.8.26; + import "forge-std/Script.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "./DeployHelpers.s.sol"; +import "../contracts/SVGNFT.sol"; +import + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract DeployScript is ScaffoldETHDeploy { - error InvalidPrivateKey(string); - - function run() external { - uint256 deployerPrivateKey = setupLocalhostEnv(); - if (deployerPrivateKey == 0) { - revert InvalidPrivateKey( - "You don't have a deployer account. Make sure you have set DEPLOYER_PRIVATE_KEY in .env or use `yarn generate` to generate a new random account" - ); - } - - address admin = vm.envAddress("ADMIN_ADDRESS"); - - vm.startBroadcast(deployerPrivateKey); - - //Deploy SVG Contract - SVGNFT nftContract = new SVGNFT(); - - console.logString( - string.concat( - "The new SVG Contract deployed at: ", - vm.toString(address(nftContract)) - ) - ); - - // Initialize data - bytes memory data = abi.encodeWithSelector( - nftContract.initialize.selector - ); - - // Deploy TransparentUpgradeableProxy - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - address(nftContract), - admin, - data - ); - - console.logString( - string.concat( - "The new proxy Contract deployed at: ", - vm.toString(address(proxy)) - ) - ); - - vm.stopBroadcast(); - - /** - * This function generates the file containing the contracts Abi definitions. - * These definitions are used to derive the types needed in the custom scaffold-eth hooks, for example. - * This function should be called last. - */ - exportDeployments(); + error InvalidPrivateKey(string); + + function run() external { + uint256 deployerPrivateKey = setupLocalhostEnv(); + if (deployerPrivateKey == 0) { + revert InvalidPrivateKey( + "You don't have a deployer account. Make sure you have set DEPLOYER_PRIVATE_KEY in .env or use `yarn generate` to generate a new random account" + ); } + + address admin = vm.envAddress("ADMIN_ADDRESS"); + + vm.startBroadcast(deployerPrivateKey); + + //Deploy SVG Contract + SVGNFT nftContract = new SVGNFT(); + + console.logString( + string.concat( + "The new SVG Contract deployed at: ", vm.toString(address(nftContract)) + ) + ); + + // Initialize data + bytes memory data = abi.encodeWithSelector(nftContract.initialize.selector); + + // Deploy TransparentUpgradeableProxy + TransparentUpgradeableProxy proxy = + new TransparentUpgradeableProxy(address(nftContract), admin, data); + + console.logString( + string.concat( + "The new proxy Contract deployed at: ", vm.toString(address(proxy)) + ) + ); + + vm.stopBroadcast(); + + /** + * This function generates the file containing the contracts Abi definitions. + * These definitions are used to derive the types needed in the custom scaffold-eth hooks, for example. + * This function should be called last. + */ + exportDeployments(); + } } diff --git a/packages/foundry/script/DeployCalculator.s.sol b/packages/foundry/script/DeployCalculator.s.sol index 64e97f6..aaef8e9 100644 --- a/packages/foundry/script/DeployCalculator.s.sol +++ b/packages/foundry/script/DeployCalculator.s.sol @@ -2,38 +2,31 @@ pragma solidity ^0.8.19; import "forge-std/Script.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "../contracts/Calculator.sol"; contract DeployCalculatorScript is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - address admin = vm.envAddress("ADMIN_ADDRESS"); - - // Deploy Calculator logic contract - Calculator calculator = new Calculator(); - - // Initialize data - bytes memory data = abi.encodeWithSelector( - Calculator.initialize.selector - ); - - // Deploy TransparentUpgradeableProxy - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - address(calculator), - admin, - data - ); - - vm.stopBroadcast(); - - // Log addresses - console.log( - "Calculator logic contract deployed to:", - address(calculator) - ); - console.log("TransparentUpgradeableProxy deployed to:", address(proxy)); - } + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + address admin = vm.envAddress("ADMIN_ADDRESS"); + + // Deploy Calculator logic contract + Calculator calculator = new Calculator(); + + // Initialize data + bytes memory data = abi.encodeWithSelector(Calculator.initialize.selector); + + // Deploy TransparentUpgradeableProxy + TransparentUpgradeableProxy proxy = + new TransparentUpgradeableProxy(address(calculator), admin, data); + + vm.stopBroadcast(); + + // Log addresses + console.log("Calculator logic contract deployed to:", address(calculator)); + console.log("TransparentUpgradeableProxy deployed to:", address(proxy)); + } } diff --git a/packages/foundry/script/DeployHelpers.s.sol b/packages/foundry/script/DeployHelpers.s.sol index 7338ac1..b1d50d8 100644 --- a/packages/foundry/script/DeployHelpers.s.sol +++ b/packages/foundry/script/DeployHelpers.s.sol @@ -1,8 +1,9 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; -import "forge-std/Script.sol"; +pragma solidity 0.8.26; + import "forge-std/Vm.sol"; +import "forge-std/Script.sol"; contract ScaffoldETHDeploy is Script { error InvalidChain(); diff --git a/packages/foundry/script/VerifyAll.s.sol b/packages/foundry/script/VerifyAll.s.sol index 829d676..a98f7ad 100644 --- a/packages/foundry/script/VerifyAll.s.sol +++ b/packages/foundry/script/VerifyAll.s.sol @@ -1,8 +1,9 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; -import "forge-std/Script.sol"; +pragma solidity 0.8.26; + import "forge-std/Vm.sol"; +import "forge-std/Script.sol"; import "solidity-bytes-utils/BytesLib.sol"; /** diff --git a/packages/foundry/script/generateTsAbis.js b/packages/foundry/script/generateTsAbis.js index fab8f25..6d3414a 100644 --- a/packages/foundry/script/generateTsAbis.js +++ b/packages/foundry/script/generateTsAbis.js @@ -22,7 +22,7 @@ function getFiles(path) { }); } function getArtifactOfContract(contractName) { - console.log("contractName",contractName) + console.log("contractName", contractName); const current_path_to_artifacts = path.join( __dirname, "..", diff --git a/packages/foundry/test/Implementation.sol b/packages/foundry/test/Implementation.sol new file mode 100644 index 0000000..edb9df5 --- /dev/null +++ b/packages/foundry/test/Implementation.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "../contracts/SVGNFT.sol"; + +contract ImplementationTest is Test { + SVGNFT public implementation; + address ADMIN = address(1); + address USER = address(2); + + function setUp() public { + vm.startPrank(ADMIN); + + // Deploy and initialize the implementation contract + implementation = new SVGNFT(); + implementation.initialize(); + + vm.stopPrank(); + } + + function testMintItem() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 initialBalance = address(implementation.recipient()).balance; + uint256 tokenId = implementation.mintItem{ value: 0.001 ether }(); + + assertEq(tokenId, 1, "First minted token should have ID 1"); + assertEq( + implementation.ownerOf(tokenId), + USER, + "Minted token should belong to USER" + ); + assertEq( + address(implementation.recipient()).balance, + initialBalance + 0.001 ether, + "Recipient should receive the payment" + ); + + vm.stopPrank(); + } + + function testTokenURI() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 tokenId = implementation.mintItem{ value: 0.001 ether }(); + string memory uri = implementation.tokenURI(tokenId); + string memory expectedUri = + "data:application/json;base64,eyJuYW1lIjoiTG9vZ2llICMxIiwgImRlc2NyaXB0aW9uIjoiVGhpcyBMb29naWUgaXMgdGhlIGNvbG9yICMAAAACAAAgd2l0aCBhIGNodWJiaW5lc3Mgb2YgMCBhbmQgbW91dGggbGVuZ3RoIG9mIDAhISEiLCAiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9idXJueWJveXMuY29tL3Rva2VuLzEiLCAiYXR0cmlidXRlcyI6IFt7InRyYWl0X3R5cGUiOiAiY29sb3IiLCAidmFsdWUiOiAiIwAAAAIAACJ9LHsidHJhaXRfdHlwZSI6ICJjaHViYmluZXNzIiwgInZhbHVlIjogMH0seyJ0cmFpdF90eXBlIjogIm1vdXRoTGVuZ3RoIiwgInZhbHVlIjogMH1dLCAib3duZXIiOiIweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIiLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCM2FXUjBhRDBpTkRBd0lpQm9aV2xuYUhROUlqUXdNQ0lnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JajQ4WnlCcFpEMGlaWGxsTVNJK1BHVnNiR2x3YzJVZ2MzUnliMnRsTFhkcFpIUm9QU0l6SWlCeWVUMGlNamt1TlNJZ2NuZzlJakk1TGpVaUlHbGtQU0p6ZG1kZk1TSWdZM2s5SWpFMU5DNDFJaUJqZUQwaU1UZ3hMalVpSUhOMGNtOXJaVDBpSXpBd01DSWdabWxzYkQwaUkyWm1aaUl2UGp4bGJHeHBjSE5sSUhKNVBTSXpMalVpSUhKNFBTSXlMalVpSUdsa1BTSnpkbWRmTXlJZ1kzazlJakUxTkM0MUlpQmplRDBpTVRjekxqVWlJSE4wY205clpTMTNhV1IwYUQwaU15SWdjM1J5YjJ0bFBTSWpNREF3SWlCbWFXeHNQU0lqTURBd01EQXdJaTgrUEM5blBqeG5JR2xrUFNKb1pXRmtJajQ4Wld4c2FYQnpaU0JtYVd4c1BTSWpBQUFBQWdBQUlpQnpkSEp2YTJVdGQybGtkR2c5SWpNaUlHTjRQU0l5TURRdU5TSWdZM2s5SWpJeE1TNDRNREEyTlNJZ2FXUTlJbk4yWjE4MUlpQnllRDBpTUNJZ2NuazlJalV4TGpnd01EWTFJaUJ6ZEhKdmEyVTlJaU13TURBaUx6NDhMMmMrUEdjZ2FXUTlJbVY1WlRJaVBqeGxiR3hwY0hObElITjBjbTlyWlMxM2FXUjBhRDBpTXlJZ2NuazlJakk1TGpVaUlISjRQU0l5T1M0MUlpQnBaRDBpYzNablh6SWlJR041UFNJeE5qZ3VOU0lnWTNnOUlqSXdPUzQxSWlCemRISnZhMlU5SWlNd01EQWlJR1pwYkd3OUlpTm1abVlpTHo0OFpXeHNhWEJ6WlNCeWVUMGlNeTQxSWlCeWVEMGlNeUlnYVdROUluTjJaMTgwSWlCamVUMGlNVFk1TGpVaUlHTjRQU0l5TURnaUlITjBjbTlyWlMxM2FXUjBhRDBpTXlJZ1ptbHNiRDBpSXpBd01EQXdNQ0lnYzNSeWIydGxQU0lqTURBd0lpOCtQQzluUGp4bklHTnNZWE56UFNKdGIzVjBhQ0lnZEhKaGJuTm1iM0p0UFNKMGNtRnVjMnhoZEdVb056TXNNQ2tpUGp4d1lYUm9JR1E5SWswZ01UTXdJREkwTUNCUklERTJOU0F5TlRBZ01DQXlNelVpSUhOMGNtOXJaVDBpWW14aFkyc2lJSE4wY205clpTMTNhV1IwYUQwaU15SWdabWxzYkQwaWRISmhibk53WVhKbGJuUWlMejQ4TDJjK1BDOXpkbWMrIn0="; + + assertTrue( + keccak256(abi.encodePacked(uri)) + == keccak256(abi.encodePacked(expectedUri)), + "Token URI incorrect" + ); + + vm.stopPrank(); + } + + function testRenderTokenById() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 tokenId = implementation.mintItem{ value: 0.001 ether }(); + string memory render = implementation.renderTokenById(tokenId); + + assertTrue(bytes(render).length > 0, "The SVG was not properly rendered"); + + vm.stopPrank(); + } + + function testInitialTokenId() public view { + assertEq(implementation.tokenIds(), 1, "Initial tokenId should equal 1"); + } + + function testPriceIncrease() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + implementation.mintItem{ value: 0.001 ether }(); + uint256 newPrice = implementation.price(); + vm.stopPrank(); + + assertGt(newPrice, 0.001 ether, "Price should increase after minting"); + assertEq( + newPrice, (0.001 ether * 1002) / 1000, "Price should increase by 0.2%" + ); + } + + function testMintLimit() public { + uint256 currentPrice = implementation.price(); + vm.startPrank(USER); + vm.deal(USER, 10 ether); + + for (uint256 i = 1; i < 11; i++) { + implementation.mintItem{ value: currentPrice }(); + currentPrice = implementation.price(); + } + + vm.expectRevert(); + implementation.mintItem{ value: currentPrice }(); + + vm.stopPrank(); + } + + function testInvalidTokenURI() public { + vm.expectRevert(); + implementation.tokenURI(2); + } +} diff --git a/packages/foundry/test/Proxy.t.sol b/packages/foundry/test/Proxy.t.sol new file mode 100644 index 0000000..bb94033 --- /dev/null +++ b/packages/foundry/test/Proxy.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "../contracts/SVGNFT.sol"; +import "../contracts/SVGNFTV2.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract ProxyTest is Test { + SVGNFT public implementation; + TransparentUpgradeableProxy public proxy; + ProxyAdmin public proxyAdmin; + SVGNFT public proxiedSVGNFT; + + address ADMIN = address(1); + address USER = address(2); + + function setUp() public { + vm.startPrank(ADMIN); + + // Deploy the implementation contract + implementation = new SVGNFT(); + + // Deploy the ProxyAdmin + proxyAdmin = new ProxyAdmin(address(ADMIN)); + + // Prepare the initialization data + bytes memory data = abi.encodeWithSelector(SVGNFT.initialize.selector); + + // Deploy the TransparentUpgradeableProxy + proxy = new TransparentUpgradeableProxy( + address(implementation), address(proxyAdmin), data + ); + + // Create a proxied SVGNFT for easier interaction + proxiedSVGNFT = SVGNFT(address(proxy)); + + vm.stopPrank(); + } + + function testMintItem() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 initialBalance = address(proxiedSVGNFT.recipient()).balance; + uint256 tokenId = proxiedSVGNFT.mintItem{ value: 0.001 ether }(); + + assertEq(tokenId, 1, "First minted token should have ID 1"); + assertEq( + proxiedSVGNFT.ownerOf(tokenId), USER, "Minted token should belong to USER" + ); + assertEq( + address(proxiedSVGNFT.recipient()).balance, + initialBalance + 0.001 ether, + "Recipient should receive the payment" + ); + + vm.stopPrank(); + } + + function testTokenURI() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 tokenId = proxiedSVGNFT.mintItem{ value: 0.001 ether }(); + string memory uri = proxiedSVGNFT.tokenURI(tokenId); + string memory expectedUri = + "data:application/json;base64,eyJuYW1lIjoiTG9vZ2llICMxIiwgImRlc2NyaXB0aW9uIjoiVGhpcyBMb29naWUgaXMgdGhlIGNvbG9yICMAAAACAAAgd2l0aCBhIGNodWJiaW5lc3Mgb2YgMCBhbmQgbW91dGggbGVuZ3RoIG9mIDAhISEiLCAiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9idXJueWJveXMuY29tL3Rva2VuLzEiLCAiYXR0cmlidXRlcyI6IFt7InRyYWl0X3R5cGUiOiAiY29sb3IiLCAidmFsdWUiOiAiIwAAAAIAACJ9LHsidHJhaXRfdHlwZSI6ICJjaHViYmluZXNzIiwgInZhbHVlIjogMH0seyJ0cmFpdF90eXBlIjogIm1vdXRoTGVuZ3RoIiwgInZhbHVlIjogMH1dLCAib3duZXIiOiIweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIiLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCM2FXUjBhRDBpTkRBd0lpQm9aV2xuYUhROUlqUXdNQ0lnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JajQ4WnlCcFpEMGlaWGxsTVNJK1BHVnNiR2x3YzJVZ2MzUnliMnRsTFhkcFpIUm9QU0l6SWlCeWVUMGlNamt1TlNJZ2NuZzlJakk1TGpVaUlHbGtQU0p6ZG1kZk1TSWdZM2s5SWpFMU5DNDFJaUJqZUQwaU1UZ3hMalVpSUhOMGNtOXJaVDBpSXpBd01DSWdabWxzYkQwaUkyWm1aaUl2UGp4bGJHeHBjSE5sSUhKNVBTSXpMalVpSUhKNFBTSXlMalVpSUdsa1BTSnpkbWRmTXlJZ1kzazlJakUxTkM0MUlpQmplRDBpTVRjekxqVWlJSE4wY205clpTMTNhV1IwYUQwaU15SWdjM1J5YjJ0bFBTSWpNREF3SWlCbWFXeHNQU0lqTURBd01EQXdJaTgrUEM5blBqeG5JR2xrUFNKb1pXRmtJajQ4Wld4c2FYQnpaU0JtYVd4c1BTSWpBQUFBQWdBQUlpQnpkSEp2YTJVdGQybGtkR2c5SWpNaUlHTjRQU0l5TURRdU5TSWdZM2s5SWpJeE1TNDRNREEyTlNJZ2FXUTlJbk4yWjE4MUlpQnllRDBpTUNJZ2NuazlJalV4TGpnd01EWTFJaUJ6ZEhKdmEyVTlJaU13TURBaUx6NDhMMmMrUEdjZ2FXUTlJbVY1WlRJaVBqeGxiR3hwY0hObElITjBjbTlyWlMxM2FXUjBhRDBpTXlJZ2NuazlJakk1TGpVaUlISjRQU0l5T1M0MUlpQnBaRDBpYzNablh6SWlJR041UFNJeE5qZ3VOU0lnWTNnOUlqSXdPUzQxSWlCemRISnZhMlU5SWlNd01EQWlJR1pwYkd3OUlpTm1abVlpTHo0OFpXeHNhWEJ6WlNCeWVUMGlNeTQxSWlCeWVEMGlNeUlnYVdROUluTjJaMTgwSWlCamVUMGlNVFk1TGpVaUlHTjRQU0l5TURnaUlITjBjbTlyWlMxM2FXUjBhRDBpTXlJZ1ptbHNiRDBpSXpBd01EQXdNQ0lnYzNSeWIydGxQU0lqTURBd0lpOCtQQzluUGp4bklHTnNZWE56UFNKdGIzVjBhQ0lnZEhKaGJuTm1iM0p0UFNKMGNtRnVjMnhoZEdVb056TXNNQ2tpUGp4d1lYUm9JR1E5SWswZ01UTXdJREkwTUNCUklERTJOU0F5TlRBZ01DQXlNelVpSUhOMGNtOXJaVDBpWW14aFkyc2lJSE4wY205clpTMTNhV1IwYUQwaU15SWdabWxzYkQwaWRISmhibk53WVhKbGJuUWlMejQ4TDJjK1BDOXpkbWMrIn0="; + + assertTrue( + keccak256(abi.encodePacked(uri)) + == keccak256(abi.encodePacked(expectedUri)), + "Token URI incorrect" + ); + + vm.stopPrank(); + } + + function testRenderTokenById() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + uint256 tokenId = proxiedSVGNFT.mintItem{ value: 0.001 ether }(); + string memory render = proxiedSVGNFT.renderTokenById(tokenId); + + assertTrue(bytes(render).length > 0, "The SVG was not properly rendered"); + + vm.stopPrank(); + } + + function testInitialTokenId() public view { + assertEq(proxiedSVGNFT.tokenIds(), 1, "Initial tokenId should equal 1"); + } + + function testPriceIncrease() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + proxiedSVGNFT.mintItem{ value: 0.001 ether }(); + uint256 newPrice = proxiedSVGNFT.price(); + vm.stopPrank(); + + assertGt(newPrice, 0.001 ether, "Price should increase after minting"); + assertEq( + newPrice, (0.001 ether * 1002) / 1000, "Price should increase by 0.2%" + ); + } + + function testMintLimit() public { + uint256 currentPrice = proxiedSVGNFT.price(); + vm.startPrank(USER); + vm.deal(USER, 10 ether); + + for (uint256 i = 1; i < 11; i++) { + proxiedSVGNFT.mintItem{ value: currentPrice }(); + currentPrice = proxiedSVGNFT.price(); + } + + vm.expectRevert(); + proxiedSVGNFT.mintItem{ value: currentPrice }(); + + vm.stopPrank(); + } + + function testInvalidTokenURI() public { + vm.expectRevert(); + proxiedSVGNFT.tokenURI(2); + } + + function testUpgrade() public { + vm.prank(address(proxyAdmin)); + SVGNFTV2 svgNFTV2 = new SVGNFTV2(); + + vm.prank(address(proxyAdmin)); + + vm.expectRevert(); + ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall( + address(svgNFTV2), abi.encodeWithSelector(svgNFTV2.initialize.selector) + ); + + // After upgrade, proxiedSVGNFT should still work as expected + uint256 tokenID = proxiedSVGNFT.tokenIds(); + assertEq(tokenID, 1, "TokenId should equal 1"); + } +} diff --git a/yarn.lock b/yarn.lock index cee07a1..37044b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1345,6 +1345,22 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts-upgradeable@npm:^5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts-upgradeable@npm:5.0.2" + peerDependencies: + "@openzeppelin/contracts": 5.0.2 + checksum: 7d1dbd6bcb45fb6fccf2dc51253afd044b9f77b01d34e11f7cf4c3e17c860df31ddc2c831be7ac463e2955eaea9620cec1c38cdffed7fcf235eb1fdc7ceb3716 + languageName: node + linkType: hard + +"@openzeppelin/contracts@npm:^5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts@npm:5.0.2" + checksum: 0cce6fc284bd1d89e2a447027832a62f1356b44ee31088899453e10349a63a62df2f07da63d76e4c41aad9c86b96b650b2b6fc85439ef276850dda1170a047fd + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.4.1": version: 2.4.1 resolution: "@parcel/watcher-android-arm64@npm:2.4.1" @@ -1643,13 +1659,17 @@ __metadata: version: 0.0.0-use.local resolution: "@se-2/foundry@workspace:packages/foundry" dependencies: + "@openzeppelin/contracts": ^5.0.2 + "@openzeppelin/contracts-upgradeable": ^5.0.2 "@types/prettier": 2 "@types/qrcode": 1 + base64-sol: ^1.1.0 dotenv: ~16.3.1 envfile: ~6.18.0 ethers: ~5.7.1 prettier: ~2.8.8 qrcode: ~1.5.3 + solady: ^0.0.228 toml: ~3.0.0 languageName: unknown linkType: soft @@ -3104,12 +3124,12 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.0": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" +"ansi-escapes@npm:^7.0.0": + version: 7.0.0 + resolution: "ansi-escapes@npm:7.0.0" dependencies: - type-fest: ^0.21.3 - checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + environment: ^1.0.0 + checksum: 19baa61e68d1998c03b3b8bd023653a6c2667f0ed6caa9a00780ffd6f0a14f4a6563c57a38b3c0aba71bd704cd49c4c8df41be60bd81c957409f91e9dd49051f languageName: node linkType: hard @@ -3145,7 +3165,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0": +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 @@ -3342,13 +3362,6 @@ __metadata: languageName: node linkType: hard -"astral-regex@npm:^2.0.0": - version: 2.0.0 - resolution: "astral-regex@npm:2.0.0" - checksum: 876231688c66400473ba505731df37ea436e574dd524520294cc3bbc54ea40334865e01fa0d074d74d036ee874ee7e62f486ea38bc421ee8e6a871c06f011766 - languageName: node - linkType: hard - "async-listen@npm:1.2.0": version: 1.2.0 resolution: "async-listen@npm:1.2.0" @@ -3450,6 +3463,13 @@ __metadata: languageName: node linkType: hard +"base64-sol@npm:^1.1.0": + version: 1.1.0 + resolution: "base64-sol@npm:1.1.0" + checksum: 9bea828af71e6adcf7c841cf0001e03bd1eae8cc1aef86daedb36f04117b2a626ac23631f700d56a24515f88c2021d7ebdbee4608369f66c53e51c0b4b39ee47 + languageName: node + linkType: hard + "bech32@npm:1.1.4": version: 1.1.4 resolution: "bech32@npm:1.1.4" @@ -3689,13 +3709,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.2.0": - version: 5.2.0 - resolution: "chalk@npm:5.2.0" - checksum: 03d8060277de6cf2fd567dc25fcf770593eb5bb85f460ce443e49255a30ff1242edd0c90a06a03803b0466ff0687a939b41db1757bec987113e83de89a003caa - languageName: node - linkType: hard - "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -3717,6 +3730,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:~5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "chokidar@npm:3.3.1": version: 3.3.1 resolution: "chokidar@npm:3.3.1" @@ -3785,32 +3805,22 @@ __metadata: languageName: node linkType: hard -"cli-cursor@npm:^3.1.0": - version: 3.1.0 - resolution: "cli-cursor@npm:3.1.0" +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" dependencies: - restore-cursor: ^3.1.0 - checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + restore-cursor: ^5.0.0 + checksum: 1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 languageName: node linkType: hard -"cli-truncate@npm:^2.1.0": - version: 2.1.0 - resolution: "cli-truncate@npm:2.1.0" - dependencies: - slice-ansi: ^3.0.0 - string-width: ^4.2.0 - checksum: bf1e4e6195392dc718bf9cd71f317b6300dc4a9191d052f31046b8773230ece4fa09458813bf0e3455a5e68c0690d2ea2c197d14a8b85a7b5e01c97f4b5feb5d - languageName: node - linkType: hard - -"cli-truncate@npm:^3.1.0": - version: 3.1.0 - resolution: "cli-truncate@npm:3.1.0" +"cli-truncate@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-truncate@npm:4.0.0" dependencies: slice-ansi: ^5.0.0 - string-width: ^5.0.0 - checksum: c3243e41974445691c63f8b405df1d5a24049dc33d324fe448dc572e561a7b772ae982692900b1a5960901cc4fc7def25a629b9c69a4208ee89d12ab3332617a + string-width: ^7.0.0 + checksum: d5149175fd25ca985731bdeec46a55ec237475cf74c1a5e103baea696aceb45e372ac4acbaabf1316f06bd62e348123060f8191ffadfeedebd2a70a2a7fb199d languageName: node linkType: hard @@ -3916,20 +3926,13 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.19": +"colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d languageName: node linkType: hard -"commander@npm:^10.0.0": - version: 10.0.1 - resolution: "commander@npm:10.0.1" - checksum: 436901d64a818295803c1996cd856621a74f30b9f9e28a588e726b2b1670665bccd7c1a77007ebf328729f0139838a88a19265858a0fa7a8728c4656796db948 - languageName: node - linkType: hard - "commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" @@ -3937,6 +3940,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:~12.1.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -4190,6 +4200,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:~4.3.4": + version: 4.3.6 + resolution: "debug@npm:4.3.6" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 1630b748dea3c581295e02137a9f5cbe2c1d85fea35c1e6597a65ca2b16a6fce68cec61b299d480787ef310ba927dc8c92d3061faba0ad06c6a724672f66be7f + languageName: node + linkType: hard + "decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -4493,6 +4515,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.3.0 + resolution: "emoji-regex@npm:10.3.0" + checksum: 5da48edfeb9462fb1ae5495cff2d79129974c696853fb0ce952cbf560f29a2756825433bf51cfd5157ec7b9f93f46f31d712e896d63e3d8ac9c3832bdb45ab73 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -4587,6 +4616,13 @@ __metadata: languageName: node linkType: hard +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: dd3c1b9825e7f71f1e72b03c2344799ac73f2e9ef81b78ea8b373e55db021786c6b9f3858ea43a436a2c4611052670ec0afe85bc029c384cc71165feee2f4ba6 + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -5473,24 +5509,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:^7.0.0": - version: 7.2.0 - resolution: "execa@npm:7.2.0" - dependencies: - cross-spawn: ^7.0.3 - get-stream: ^6.0.1 - human-signals: ^4.3.0 - is-stream: ^3.0.0 - merge-stream: ^2.0.0 - npm-run-path: ^5.1.0 - onetime: ^6.0.0 - signal-exit: ^3.0.7 - strip-final-newline: ^3.0.0 - checksum: 14fd17ba0ca8c87b277584d93b1d9fc24f2a65e5152b31d5eb159a3b814854283eaae5f51efa9525e304447e2f757c691877f7adff8fde5746aae67eb1edd1cc - languageName: node - linkType: hard - -"execa@npm:^8.0.1": +"execa@npm:^8.0.1, execa@npm:~8.0.1": version: 8.0.1 resolution: "execa@npm:8.0.1" dependencies: @@ -5865,6 +5884,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.2.0 + resolution: "get-east-asian-width@npm:1.2.0" + checksum: ea55f4d4a42c4b00d3d9be3111bc17eb0161f60ed23fc257c1390323bb780a592d7a8bdd550260fd4627dabee9a118cdfa3475ae54edca35ebcd3bdae04179e3 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" @@ -5901,13 +5927,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.1": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad - languageName: node - linkType: hard - "get-stream@npm:^8.0.1": version: 8.0.1 resolution: "get-stream@npm:8.0.1" @@ -6265,13 +6284,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^4.3.0": - version: 4.3.1 - resolution: "human-signals@npm:4.3.1" - checksum: 6f12958df3f21b6fdaf02d90896c271df00636a31e2bbea05bddf817a35c66b38a6fdac5863e2df85bd52f34958997f1f50350ff97249e1dff8452865d5235d1 - languageName: node - linkType: hard - "human-signals@npm:^5.0.0": version: 5.0.0 resolution: "human-signals@npm:5.0.0" @@ -6279,12 +6291,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:~8.0.3": - version: 8.0.3 - resolution: "husky@npm:8.0.3" +"husky@npm:9.1.4": + version: 9.1.4 + resolution: "husky@npm:9.1.4" bin: - husky: lib/bin.js - checksum: 837bc7e4413e58c1f2946d38fb050f5d7324c6f16b0fd66411ffce5703b294bd21429e8ba58711cd331951ee86ed529c5be4f76805959ff668a337dbfa82a1b0 + husky: bin.js + checksum: 7608a6dfac264876a2ff37f2db8520e0f9f0ea2b810a9ca151548327e9eca0b7ed58a63e0a208d20d3f43b191d8f111edcab46c3c8132c95e10ef7bd7115ee9b languageName: node linkType: hard @@ -6569,6 +6581,15 @@ __metadata: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^5.0.0": + version: 5.0.0 + resolution: "is-fullwidth-code-point@npm:5.0.0" + dependencies: + get-east-asian-width: ^1.0.0 + checksum: 8dfb2d2831b9e87983c136f5c335cd9d14c1402973e357a8ff057904612ed84b8cba196319fabedf9aefe4639e14fe3afe9d9966d1d006ebeb40fe1fed4babe5 + languageName: node + linkType: hard + "is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7": version: 1.0.10 resolution: "is-generator-function@npm:1.0.10" @@ -7072,14 +7093,14 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:2.1.0, lilconfig@npm:^2.1.0": +"lilconfig@npm:^2.1.0": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117 languageName: node linkType: hard -"lilconfig@npm:^3.0.0": +"lilconfig@npm:^3.0.0, lilconfig@npm:~3.1.1": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" checksum: 4e8b83ddd1d0ad722600994e6ba5d858ddca14f0587aa6b9c8185e17548149b5e13d4d583d811e9e9323157fa8c6a527e827739794c7502b59243c58e210b8c3 @@ -7093,26 +7114,23 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:~13.2.2": - version: 13.2.3 - resolution: "lint-staged@npm:13.2.3" +"lint-staged@npm:15.2.7": + version: 15.2.7 + resolution: "lint-staged@npm:15.2.7" dependencies: - chalk: 5.2.0 - cli-truncate: ^3.1.0 - commander: ^10.0.0 - debug: ^4.3.4 - execa: ^7.0.0 - lilconfig: 2.1.0 - listr2: ^5.0.7 - micromatch: ^4.0.5 - normalize-path: ^3.0.0 - object-inspect: ^1.12.3 - pidtree: ^0.6.0 - string-argv: ^0.3.1 - yaml: ^2.2.2 + chalk: ~5.3.0 + commander: ~12.1.0 + debug: ~4.3.4 + execa: ~8.0.1 + lilconfig: ~3.1.1 + listr2: ~8.2.1 + micromatch: ~4.0.7 + pidtree: ~0.6.0 + string-argv: ~0.3.2 + yaml: ~2.4.2 bin: lint-staged: bin/lint-staged.js - checksum: ff51a1e33072f488b28b938ed47323816a1ff278ef6d0e5cbe1704b292773a6c8ce945b504eae3a9b5702917a979523a741f17023e16077bd5fa35be687cc067 + checksum: 0f21d1b44c046fcfc0388dab66d45d244818afdb24bdf57e7593640c7ca82cc55be7d75e086708e453fac0c0d9ab8760b2cde053944f7b2121c2dd65f6367ffe languageName: node linkType: hard @@ -7145,24 +7163,17 @@ __metadata: languageName: node linkType: hard -"listr2@npm:^5.0.7": - version: 5.0.8 - resolution: "listr2@npm:5.0.8" +"listr2@npm:~8.2.1": + version: 8.2.4 + resolution: "listr2@npm:8.2.4" dependencies: - cli-truncate: ^2.1.0 - colorette: ^2.0.19 - log-update: ^4.0.0 - p-map: ^4.0.0 - rfdc: ^1.3.0 - rxjs: ^7.8.0 - through: ^2.3.8 - wrap-ansi: ^7.0.0 - peerDependencies: - enquirer: ">= 2.3.0 < 3" - peerDependenciesMeta: - enquirer: - optional: true - checksum: 8be9f5632627c4df0dc33f452c98d415a49e5f1614650d3cab1b103c33e95f2a7a0e9f3e1e5de00d51bf0b4179acd8ff11b25be77dbe097cf3773c05e728d46c + cli-truncate: ^4.0.0 + colorette: ^2.0.20 + eventemitter3: ^5.0.1 + log-update: ^6.1.0 + rfdc: ^1.4.1 + wrap-ansi: ^9.0.0 + checksum: b1cdcae653ff967a9b28637e346df2d6614165b4ad1e9e36b1403bc972550c51f57ec0e6d307dc3921ceea0601e244e848ab79457c6d570ab1f088b577a63d90 languageName: node linkType: hard @@ -7243,15 +7254,16 @@ __metadata: languageName: node linkType: hard -"log-update@npm:^4.0.0": - version: 4.0.0 - resolution: "log-update@npm:4.0.0" +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" dependencies: - ansi-escapes: ^4.3.0 - cli-cursor: ^3.1.0 - slice-ansi: ^4.0.0 - wrap-ansi: ^6.2.0 - checksum: ae2f85bbabc1906034154fb7d4c4477c79b3e703d22d78adee8b3862fa913942772e7fa11713e3d96fb46de4e3cabefbf5d0a544344f03b58d3c4bff52aa9eb2 + ansi-escapes: ^7.0.0 + cli-cursor: ^5.0.0 + slice-ansi: ^7.1.0 + strip-ansi: ^7.1.0 + wrap-ansi: ^9.0.0 + checksum: 817a9ba6c5cbc19e94d6359418df8cfe8b3244a2903f6d53354e175e243a85b782dc6a98db8b5e457ee2f09542ca8916c39641b9cd3b0e6ef45e9481d50c918a languageName: node linkType: hard @@ -7361,7 +7373,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:~4.0.7": version: 4.0.7 resolution: "micromatch@npm:4.0.7" dependencies: @@ -7394,6 +7406,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + "minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -7974,7 +7993,7 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.12.3, object-inspect@npm:^1.13.1": +"object-inspect@npm:^1.13.1": version: 1.13.2 resolution: "object-inspect@npm:1.13.2" checksum: 9f850b3c045db60e0e97746e809ee4090d6ce62195af17dd1e9438ac761394a7d8ec4f7906559aea5424eaf61e35d3e53feded2ccd5f62fcc7d9670d3c8eb353 @@ -8116,6 +8135,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: ^5.0.0 + checksum: eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + languageName: node + linkType: hard + "open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -8127,6 +8155,15 @@ __metadata: languageName: node linkType: hard +"optimistic_svg_nfts@workspace:.": + version: 0.0.0-use.local + resolution: "optimistic_svg_nfts@workspace:." + dependencies: + husky: 9.1.4 + lint-staged: 15.2.7 + languageName: unknown + linkType: soft + "optionator@npm:^0.9.1": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -8364,7 +8401,7 @@ __metadata: languageName: node linkType: hard -"pidtree@npm:^0.6.0": +"pidtree@npm:~0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" bin: @@ -9118,13 +9155,13 @@ __metadata: languageName: node linkType: hard -"restore-cursor@npm:^3.1.0": - version: 3.1.0 - resolution: "restore-cursor@npm:3.1.0" +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" dependencies: - onetime: ^5.1.0 - signal-exit: ^3.0.2 - checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + onetime: ^7.0.0 + signal-exit: ^4.1.0 + checksum: 838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c languageName: node linkType: hard @@ -9142,7 +9179,7 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0": +"rfdc@npm:^1.4.1": version: 1.4.1 resolution: "rfdc@npm:1.4.1" checksum: 3b05bd55062c1d78aaabfcea43840cdf7e12099968f368e9a4c3936beb744adb41cbdb315eac6d4d8c6623005d6f87fdf16d8a10e1ff3722e84afea7281c8d13 @@ -9188,15 +9225,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.0": - version: 7.8.1 - resolution: "rxjs@npm:7.8.1" - dependencies: - tslib: ^2.1.0 - checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 - languageName: node - linkType: hard - "safe-array-concat@npm:^1.1.2": version: 1.1.2 resolution: "safe-array-concat@npm:1.1.2" @@ -9264,15 +9292,6 @@ __metadata: languageName: node linkType: hard -"se-2@workspace:.": - version: 0.0.0-use.local - resolution: "se-2@workspace:." - dependencies: - husky: ~8.0.3 - lint-staged: ~13.2.2 - languageName: unknown - linkType: soft - "secp256k1@npm:^5.0.0": version: 5.0.0 resolution: "secp256k1@npm:5.0.0" @@ -9401,7 +9420,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -9422,28 +9441,6 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^3.0.0": - version: 3.0.0 - resolution: "slice-ansi@npm:3.0.0" - dependencies: - ansi-styles: ^4.0.0 - astral-regex: ^2.0.0 - is-fullwidth-code-point: ^3.0.0 - checksum: 5ec6d022d12e016347e9e3e98a7eb2a592213a43a65f1b61b74d2c78288da0aded781f665807a9f3876b9daa9ad94f64f77d7633a0458876c3a4fdc4eb223f24 - languageName: node - linkType: hard - -"slice-ansi@npm:^4.0.0": - version: 4.0.0 - resolution: "slice-ansi@npm:4.0.0" - dependencies: - ansi-styles: ^4.0.0 - astral-regex: ^2.0.0 - is-fullwidth-code-point: ^3.0.0 - checksum: 4a82d7f085b0e1b070e004941ada3c40d3818563ac44766cca4ceadd2080427d337554f9f99a13aaeb3b4a94d9964d9466c807b3d7b7541d1ec37ee32d308756 - languageName: node - linkType: hard - "slice-ansi@npm:^5.0.0": version: 5.0.0 resolution: "slice-ansi@npm:5.0.0" @@ -9454,6 +9451,16 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "slice-ansi@npm:7.1.0" + dependencies: + ansi-styles: ^6.2.1 + is-fullwidth-code-point: ^5.0.0 + checksum: 10313dd3cf7a2e4b265f527b1684c7c568210b09743fd1bd74f2194715ed13ffba653dc93a5fa79e3b1711518b8990a732cb7143aa01ddafe626e99dfa6474b2 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -9504,6 +9511,13 @@ __metadata: languageName: node linkType: hard +"solady@npm:^0.0.228": + version: 0.0.228 + resolution: "solady@npm:0.0.228" + checksum: c8ddb50b23794de21aa4497fd1e9f92d439b4a8f9f40c3a6de3e6efead4a643f3f69829ccdc7380506f7930578bb712b5fcc6735a3fa4961e4e4ac08eee657b1 + languageName: node + linkType: hard + "sonic-boom@npm:^2.2.1": version: 2.8.0 resolution: "sonic-boom@npm:2.8.0" @@ -9635,7 +9649,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:^0.3.1": +"string-argv@npm:~0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 8703ad3f3db0b2641ed2adbb15cf24d3945070d9a751f9e74a924966db9f325ac755169007233e8985a39a6a292f14d4fee20482989b89b96e473c4221508a0f @@ -9653,7 +9667,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" dependencies: @@ -9664,6 +9678,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: ^10.3.0 + get-east-asian-width: ^1.0.0 + strip-ansi: ^7.1.0 + checksum: 42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string.prototype.includes@npm:^2.0.0": version: 2.0.0 resolution: "string.prototype.includes@npm:2.0.0" @@ -9765,7 +9790,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: @@ -9988,13 +10013,6 @@ __metadata: languageName: node linkType: hard -"through@npm:^2.3.8": - version: 2.3.8 - resolution: "through@npm:2.3.8" - checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd - languageName: node - linkType: hard - "time-span@npm:4.0.0": version: 4.0.0 resolution: "time-span@npm:4.0.0" @@ -10202,13 +10220,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 - languageName: node - linkType: hard - "type-fest@npm:~4.6.0": version: 4.6.0 resolution: "type-fest@npm:4.6.0" @@ -10974,6 +10985,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.0 + resolution: "wrap-ansi@npm:9.0.0" + dependencies: + ansi-styles: ^6.2.1 + string-width: ^7.0.0 + strip-ansi: ^7.1.0 + checksum: b2d43b76b3d8dcbdd64768165e548aad3e54e1cae4ecd31bac9966faaa7cf0b0345677ad6879db10ba58eb446ba8fa44fb82b4951872fd397f096712467a809f + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -11101,7 +11123,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.2.2, yaml@npm:^2.3.4": +"yaml@npm:^2.3.4": version: 2.5.0 resolution: "yaml@npm:2.5.0" bin: @@ -11110,6 +11132,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:~2.4.2": + version: 2.4.5 + resolution: "yaml@npm:2.4.5" + bin: + yaml: bin.mjs + checksum: f8efd407c07e095f00f3031108c9960b2b12971d10162b1ec19007200f6c987d2e28f73283f4731119aa610f177a3ea03d4a8fcf640600a25de1b74d00c69b3d + languageName: node + linkType: hard + "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3"