From a312b5ffc5cf5f3eec1504663715de22b9ca7ef7 Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Sat, 27 Jul 2024 19:57:58 +0100 Subject: [PATCH 1/8] add svg contracts --- packages/foundry/.env.example | 7 - packages/foundry/contracts/CalculatorV2.sol | 7 +- packages/foundry/contracts/HexStrings.sol | 18 ++ packages/foundry/contracts/SVGNFT.sol | 202 +++++++++++++++++ packages/foundry/contracts/SVGNFTV2.sol | 210 ++++++++++++++++++ packages/foundry/contracts/ToColor.sol | 12 + packages/foundry/foundry.toml | 3 +- packages/foundry/package.json | 4 +- packages/foundry/script/Deploy.s.sol | 29 ++- packages/foundry/script/generateTsAbis.js | 1 + .../foundry/test/CalculatorUpgradeTest.t.sol | 5 +- 11 files changed, 480 insertions(+), 18 deletions(-) delete mode 100644 packages/foundry/.env.example create mode 100644 packages/foundry/contracts/HexStrings.sol create mode 100644 packages/foundry/contracts/SVGNFT.sol create mode 100644 packages/foundry/contracts/SVGNFTV2.sol create mode 100644 packages/foundry/contracts/ToColor.sol diff --git a/packages/foundry/.env.example b/packages/foundry/.env.example deleted file mode 100644 index c5ca0de..0000000 --- a/packages/foundry/.env.example +++ /dev/null @@ -1,7 +0,0 @@ - -PK='YOUR_PRIVATE_KEY' -ADMIN_ADDRESS='ADMIN_ADDRESS' - -# Deployment script -# forge script script/DeployCalculator.s.sol:DeployCalculatorScript --broadcast --verify - diff --git a/packages/foundry/contracts/CalculatorV2.sol b/packages/foundry/contracts/CalculatorV2.sol index b83ea6e..7d9370b 100644 --- a/packages/foundry/contracts/CalculatorV2.sol +++ b/packages/foundry/contracts/CalculatorV2.sol @@ -4,7 +4,12 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; contract CalculatorV2 is Initializable { - function initialize() external {} + + string public testSring; + + function initialize(string calldata _string) external { + testSring = _string; + } function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b; diff --git a/packages/foundry/contracts/HexStrings.sol b/packages/foundry/contracts/HexStrings.sol new file mode 100644 index 0000000..7c7eb3b --- /dev/null +++ b/packages/foundry/contracts/HexStrings.sol @@ -0,0 +1,18 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library HexStrings { + bytes16 internal 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'; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = ALPHABET[value & 0xf]; + value >>= 4; + } + return string(buffer); + } +} diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol new file mode 100644 index 0000000..4806e07 --- /dev/null +++ b/packages/foundry/contracts/SVGNFT.sol @@ -0,0 +1,202 @@ +pragma solidity ^0.8.0; +//SPDX-License-Identifier: MIT + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/base64.sol"; + +import "./HexStrings.sol"; +import "./ToColor.sol"; +//learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 + +// GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two + +error SVGNFT__INVALIDTOKENID(); + +contract SVGNFT is ERC721Enumerable, Ownable { + 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; + + constructor() public ERC721("OptimisticLoogies", "OPLOOG") Ownable(msg.sender) { + // RELEASE THE OPTIMISTIC LOOGIES! + _tokenIds = 1; + } + + 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); + + (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 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), + "" + ) + ); + + 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); + } +} diff --git a/packages/foundry/contracts/SVGNFTV2.sol b/packages/foundry/contracts/SVGNFTV2.sol new file mode 100644 index 0000000..80dac06 --- /dev/null +++ b/packages/foundry/contracts/SVGNFTV2.sol @@ -0,0 +1,210 @@ +pragma solidity ^0.8.0; +//SPDX-License-Identifier: MIT + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/base64.sol"; + +import "./HexStrings.sol"; +import "./ToColor.sol"; +//learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 + +// GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two + +error SVGNFT__INVALIDTOKENID(); + +contract SVGNFT is ERC721Enumerable, Ownable { + 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; + + constructor() public ERC721("OptimisticLoogies", "OPLOOG") Ownable(msg.sender) { + // RELEASE THE OPTIMISTIC LOOGIES! + _tokenIds = 1; + } + + 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 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]), + ",mouth length of ", + uint2str(mouthLength[id]), + ",eye size of ", + uint2str(eyeSize[id]), + "and eye color of ", + uint2str(eyeColor[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), + "" + ) + ); + + 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); + } +} diff --git a/packages/foundry/contracts/ToColor.sol b/packages/foundry/contracts/ToColor.sol new file mode 100644 index 0000000..f26f3fa --- /dev/null +++ b/packages/foundry/contracts/ToColor.sol @@ -0,0 +1,12 @@ +library ToColor { + bytes16 internal constant ALPHABET = '0123456789abcdef'; + + function toColor(bytes3 value) internal pure returns (string memory) { + bytes memory buffer = new bytes(6); + for (uint256 i = 0; i < 3; i++) { + buffer[i*2+1] = ALPHABET[uint8(value[i]) & 0xf]; + buffer[i*2] = ALPHABET[uint8(value[i]>>4) & 0xf]; + } + return string(buffer); + } +} \ No newline at end of file diff --git a/packages/foundry/foundry.toml b/packages/foundry/foundry.toml index feee8aa..f1270b0 100644 --- a/packages/foundry/foundry.toml +++ b/packages/foundry/foundry.toml @@ -3,6 +3,7 @@ src = 'contracts' out = 'out' libs = ['lib'] fs_permissions = [{ access = "read-write", path = "./"}] +via_ir = true [rpc_endpoints] default_network = "http://127.0.0.1:8545" @@ -20,7 +21,7 @@ gnosis = "https://rpc.gnosischain.com" chiado = "https://rpc.chiadochain.net" base = "https://mainnet.base.org" baseGoerli = "https://goerli.base.org" -baseSepolia = "https://sepolia.base.org" +baseSepolia = "https://base-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}" polygonZkEvm = "https://polygonzkevm-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" polygonZkEvmTestnet = "https://polygonzkevm-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" zkSyncTestnet = "https://testnet.era.zksync.dev" diff --git a/packages/foundry/package.json b/packages/foundry/package.json index 9bf2836..cd75958 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -24,7 +24,9 @@ "toml": "~3.0.0" }, "devDependencies": { + "@openzeppelin/contracts": "^5.0.2", "@types/prettier": "2", - "@types/qrcode": "1" + "@types/qrcode": "1", + "base64-sol": "^1.1.0" } } diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index d680aa0..95cc618 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -1,11 +1,12 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../contracts/YourContract.sol"; +import "../contracts/svgNFT.sol"; import "./DeployHelpers.s.sol"; contract DeployScript is ScaffoldETHDeploy { error InvalidPrivateKey(string); + SVGNFT nftContract; function run() external { uint256 deployerPrivateKey = setupLocalhostEnv(); @@ -16,16 +17,27 @@ contract DeployScript is ScaffoldETHDeploy { } vm.startBroadcast(deployerPrivateKey); - YourContract yourContract = new YourContract( - vm.addr(deployerPrivateKey) - ); + nftContract = new SVGNFT(); console.logString( string.concat( - "YourContract deployed at: ", - vm.toString(address(yourContract)) + "The new SVG Contract deployed at: ", + vm.toString(address(nftContract)) ) ); + uint256 nftPrice = nftContract.price(); + + mintNFT(nftPrice); + + nftContract = new SVGNFT(); + + Deployment memory newContractDeployed = Deployment( + "SVGNFT", + address(nftContract) + ); + + deployments.push(newContractDeployed); + vm.stopBroadcast(); /** @@ -36,5 +48,8 @@ contract DeployScript is ScaffoldETHDeploy { exportDeployments(); } - function test() public {} + function mintNFT(uint256 _nftPrice) public { + uint256 tokenID = nftContract.mintItem{value: _nftPrice}(); + console.log("@@@tokenID", tokenID); + } } diff --git a/packages/foundry/script/generateTsAbis.js b/packages/foundry/script/generateTsAbis.js index 699c914..fab8f25 100644 --- a/packages/foundry/script/generateTsAbis.js +++ b/packages/foundry/script/generateTsAbis.js @@ -22,6 +22,7 @@ function getFiles(path) { }); } function getArtifactOfContract(contractName) { + console.log("contractName",contractName) const current_path_to_artifacts = path.join( __dirname, "..", diff --git a/packages/foundry/test/CalculatorUpgradeTest.t.sol b/packages/foundry/test/CalculatorUpgradeTest.t.sol index 2eb3cc3..c4feee7 100644 --- a/packages/foundry/test/CalculatorUpgradeTest.t.sol +++ b/packages/foundry/test/CalculatorUpgradeTest.t.sol @@ -43,12 +43,15 @@ contract CalculatorUpgradeTest is Test { ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall( address(calculatorV2), - abi.encodeWithSelector(calculatorV2.initialize.selector) + abi.encodeWithSelector(calculatorV2.initialize.selector , "Test") ); // Interact with proxy using CalculatorV2 functions CalculatorV2 proxyCalculatorV2 = CalculatorV2(address(proxy)); uint256 result = proxyCalculatorV2.mod(10, 3); assertEq(result, 1); + + string memory text = proxyCalculatorV2.testSring(); + assertEq(text, "Test"); } } From 1d2f011b4153f9fcc7f75dc50c992d701ba5be68 Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Sun, 28 Jul 2024 07:39:37 +0100 Subject: [PATCH 2/8] unable to write abis --- packages/foundry/contracts/SVGNFT.sol | 14 ++++----- packages/foundry/contracts/SVGNFTV2.sol | 31 ++++++++++++------- packages/foundry/package.json | 4 +-- packages/foundry/script/Deploy.s.sol | 23 +++----------- .../foundry/script/DeployCalculator.s.sol | 4 +-- 5 files changed, 35 insertions(+), 41 deletions(-) diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index 4806e07..17401ed 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -1,20 +1,20 @@ pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/base64.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; -import "./HexStrings.sol"; -import "./ToColor.sol"; +import {HexStrings} from "./HexStrings.sol"; +import {ToColor} from "./ToColor.sol"; //learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 // GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two error SVGNFT__INVALIDTOKENID(); -contract SVGNFT is ERC721Enumerable, Ownable { +contract SVGNFT is ERC721, Ownable { using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; diff --git a/packages/foundry/contracts/SVGNFTV2.sol b/packages/foundry/contracts/SVGNFTV2.sol index 80dac06..08540b2 100644 --- a/packages/foundry/contracts/SVGNFTV2.sol +++ b/packages/foundry/contracts/SVGNFTV2.sol @@ -1,20 +1,20 @@ pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/base64.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; -import "./HexStrings.sol"; -import "./ToColor.sol"; +import {HexStrings} from "./HexStrings.sol"; +import {ToColor} from "./ToColor.sol"; //learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 // GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two error SVGNFT__INVALIDTOKENID(); -contract SVGNFT is ERC721Enumerable, Ownable { +contract SVGNFT is ERC721, Ownable { using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; @@ -32,10 +32,14 @@ contract SVGNFT is ERC721Enumerable, Ownable { mapping(uint256 => bytes3) public color; mapping(uint256 => uint256) public chubbiness; mapping(uint256 => uint256) public mouthLength; - mapping(uint256 => uint256) public eyeSize; + mapping(uint256 => uint256) public eyeSize; mapping(uint256 => bytes3) public eyeColor; - constructor() public ERC721("OptimisticLoogies", "OPLOOG") Ownable(msg.sender) { + constructor() + public + ERC721("OptimisticLoogies", "OPLOOG") + Ownable(msg.sender) + { // RELEASE THE OPTIMISTIC LOOGIES! _tokenIds = 1; } @@ -72,8 +76,11 @@ contract SVGNFT is ERC721Enumerable, Ownable { 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); + 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"); @@ -99,7 +106,7 @@ contract SVGNFT is ERC721Enumerable, Ownable { ",eye size of ", uint2str(eyeSize[id]), "and eye color of ", - uint2str(eyeColor[id]), + eyeColor[id].toColor(), "!!!" ) ); diff --git a/packages/foundry/package.json b/packages/foundry/package.json index cd75958..13c4022 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -5,8 +5,8 @@ "account": "node script/ListAccount.js", "chain": "anvil --config-out localhost.json", "compile": "forge compile", - "deploy": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol --rpc-url ${1:-default_network} --broadcast --legacy && node script/generateTsAbis.js", - "deploy:verify": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol --rpc-url ${1:-default_network} --broadcast --legacy --verify ; node script/generateTsAbis.js", + "deploy": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol -vvvv --rpc-url ${1:-default_network} --broadcast --legacy && node script/generateTsAbis.js", + "deploy:verify": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol -vvvv --rpc-url ${1:-default_network} --broadcast --legacy --verify ; node script/generateTsAbis.js", "flatten": "forge flatten", "fork": "anvil --fork-url ${0:-mainnet} --chain-id 31337 --config-out localhost.json", "format": "forge fmt && prettier --write ./script/**/*.js", diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index 95cc618..272e2d1 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -1,12 +1,11 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../contracts/svgNFT.sol"; +import "../contracts/SVGNFT.sol"; import "./DeployHelpers.s.sol"; contract DeployScript is ScaffoldETHDeploy { error InvalidPrivateKey(string); - SVGNFT nftContract; function run() external { uint256 deployerPrivateKey = setupLocalhostEnv(); @@ -15,9 +14,10 @@ contract DeployScript is ScaffoldETHDeploy { "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" ); } + vm.startBroadcast(deployerPrivateKey); - nftContract = new SVGNFT(); + SVGNFT nftContract = new SVGNFT(); console.logString( string.concat( "The new SVG Contract deployed at: ", @@ -27,16 +27,8 @@ contract DeployScript is ScaffoldETHDeploy { uint256 nftPrice = nftContract.price(); - mintNFT(nftPrice); - - nftContract = new SVGNFT(); - - Deployment memory newContractDeployed = Deployment( - "SVGNFT", - address(nftContract) - ); - - deployments.push(newContractDeployed); + uint256 tokenID = nftContract.mintItem{value: nftPrice}(); + console.log("@@@tokenID", tokenID); vm.stopBroadcast(); @@ -47,9 +39,4 @@ contract DeployScript is ScaffoldETHDeploy { */ exportDeployments(); } - - function mintNFT(uint256 _nftPrice) public { - uint256 tokenID = nftContract.mintItem{value: _nftPrice}(); - console.log("@@@tokenID", tokenID); - } } diff --git a/packages/foundry/script/DeployCalculator.s.sol b/packages/foundry/script/DeployCalculator.s.sol index f2187d8..64e97f6 100644 --- a/packages/foundry/script/DeployCalculator.s.sol +++ b/packages/foundry/script/DeployCalculator.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.20; +pragma solidity ^0.8.19; import "forge-std/Script.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; @@ -7,7 +7,7 @@ import "../contracts/Calculator.sol"; contract DeployCalculatorScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PK"); + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); address admin = vm.envAddress("ADMIN_ADDRESS"); From 3c306569fb9b993bc971a8343fda90cb5ceeff15 Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Mon, 29 Jul 2024 12:28:55 +0100 Subject: [PATCH 3/8] clear staging area --- packages/foundry/contracts/SVGNFT.sol | 16 ++++++++++++---- packages/foundry/package.json | 1 + packages/foundry/script/Deploy.s.sol | 17 ++++++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index 17401ed..ae99b7a 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -1,10 +1,11 @@ pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; @@ -14,7 +15,7 @@ import {ToColor} from "./ToColor.sol"; error SVGNFT__INVALIDTOKENID(); -contract SVGNFT is ERC721, Ownable { +contract SVGNFT is ERC721, Ownable, Initializable { using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; @@ -33,11 +34,18 @@ contract SVGNFT is ERC721, Ownable { mapping(uint256 => uint256) public chubbiness; mapping(uint256 => uint256) public mouthLength; - constructor() public ERC721("OptimisticLoogies", "OPLOOG") Ownable(msg.sender) { + function initialize() public initializer { // RELEASE THE OPTIMISTIC LOOGIES! _tokenIds = 1; + emit Initialized(11111111); } + constructor() + public + ERC721("OptimisticLoogies", "OPLOOG") + Ownable(msg.sender) + {} + function mintItem() public payable returns (uint256) { require(_tokenIds < limit, "DONE MINTING"); require(msg.value >= price, "NOT ENOUGH"); diff --git a/packages/foundry/package.json b/packages/foundry/package.json index 13c4022..2971df4 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", "@types/prettier": "2", "@types/qrcode": "1", "base64-sol": "^1.1.0" diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index 272e2d1..1e5f82e 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.19; import "../contracts/SVGNFT.sol"; import "./DeployHelpers.s.sol"; +import "forge-std/Script.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract DeployScript is ScaffoldETHDeploy { error InvalidPrivateKey(string); @@ -17,7 +19,9 @@ contract DeployScript is ScaffoldETHDeploy { vm.startBroadcast(deployerPrivateKey); + //Deploy SVG Contract SVGNFT nftContract = new SVGNFT(); + console.logString( string.concat( "The new SVG Contract deployed at: ", @@ -25,10 +29,17 @@ contract DeployScript is ScaffoldETHDeploy { ) ); - uint256 nftPrice = nftContract.price(); + // Initialize data + bytes memory data = abi.encodeWithSelector( + Calculator.initialize.selector + ); - uint256 tokenID = nftContract.mintItem{value: nftPrice}(); - console.log("@@@tokenID", tokenID); + // Deploy TransparentUpgradeableProxy + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(calculator), + admin, + data + ); vm.stopBroadcast(); From 6811d0c3ee89670dd469ace5202c3b71b288866d Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Mon, 29 Jul 2024 13:19:18 +0100 Subject: [PATCH 4/8] deploy SVFNFT and proxy contract --- .gitmodules | 3 +++ packages/foundry/contracts/SVGNFT.sol | 21 ++++++++++--------- .../lib/openzeppelin-contracts-upgradeable | 1 + packages/foundry/remappings.txt | 1 + packages/foundry/script/Deploy.s.sol | 13 ++++++++++-- 5 files changed, 27 insertions(+), 12 deletions(-) create mode 160000 packages/foundry/lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index baaf2c0..4441870 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "packages/foundry/lib/solidity-bytes-utils"] path = packages/foundry/lib/solidity-bytes-utils url = https://github.com/gnsps/solidity-bytes-utils +[submodule "packages/foundry/lib/openzeppelin-contracts-upgradeable"] + path = packages/foundry/lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index ae99b7a..241e4c8 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -1,11 +1,10 @@ pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +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"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; @@ -15,7 +14,7 @@ import {ToColor} from "./ToColor.sol"; error SVGNFT__INVALIDTOKENID(); -contract SVGNFT is ERC721, Ownable, Initializable { +contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; @@ -34,18 +33,20 @@ contract SVGNFT is ERC721, Ownable, Initializable { mapping(uint256 => uint256) public chubbiness; mapping(uint256 => uint256) public mouthLength; + // @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + function initialize() public initializer { + __ERC721_init("OptimisticLoogies", "OPLOOG"); + __Ownable_init(msg.sender); + // RELEASE THE OPTIMISTIC LOOGIES! _tokenIds = 1; emit Initialized(11111111); } - constructor() - public - ERC721("OptimisticLoogies", "OPLOOG") - Ownable(msg.sender) - {} - function mintItem() public payable returns (uint256) { require(_tokenIds < limit, "DONE MINTING"); require(msg.value >= price, "NOT ENOUGH"); diff --git a/packages/foundry/lib/openzeppelin-contracts-upgradeable b/packages/foundry/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..723f8ca --- /dev/null +++ b/packages/foundry/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 diff --git a/packages/foundry/remappings.txt b/packages/foundry/remappings.txt index df3cb81..079eea8 100644 --- a/packages/foundry/remappings.txt +++ b/packages/foundry/remappings.txt @@ -1 +1,2 @@ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts \ No newline at end of file diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index 1e5f82e..6921a5d 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -17,6 +17,8 @@ contract DeployScript is ScaffoldETHDeploy { ); } + address admin = vm.envAddress("ADMIN_ADDRESS"); + vm.startBroadcast(deployerPrivateKey); //Deploy SVG Contract @@ -31,16 +33,23 @@ contract DeployScript is ScaffoldETHDeploy { // Initialize data bytes memory data = abi.encodeWithSelector( - Calculator.initialize.selector + nftContract.initialize.selector ); // Deploy TransparentUpgradeableProxy TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - address(calculator), + address(nftContract), admin, data ); + console.logString( + string.concat( + "The new proxy Contract deployed at: ", + vm.toString(address(proxy)) + ) + ); + vm.stopBroadcast(); /** From 299c560c628671c90e56efbca1d67c2272a04a1d Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Tue, 30 Jul 2024 06:19:48 +0100 Subject: [PATCH 5/8] upgradeble contract tests --- packages/foundry/contracts/SVGNFT.sol | 20 +-- packages/foundry/contracts/SVGNFTV2.sol | 23 ++- packages/foundry/test/Calculator.t.sol | 63 -------- .../foundry/test/CalculatorUpgradeTest.t.sol | 57 ------- packages/foundry/test/SVGNFT.t.sol | 141 ++++++++++++++++++ packages/foundry/test/YourContract.t.sol | 28 ---- 6 files changed, 161 insertions(+), 171 deletions(-) delete mode 100644 packages/foundry/test/Calculator.t.sol delete mode 100644 packages/foundry/test/CalculatorUpgradeTest.t.sol create mode 100644 packages/foundry/test/SVGNFT.t.sol delete mode 100644 packages/foundry/test/YourContract.t.sol diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index 241e4c8..9cd31d7 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -8,11 +8,8 @@ import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; -//learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 -// GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two - -error SVGNFT__INVALIDTOKENID(); +error SVGNFTV2__INVALIDTOKENID(); contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { using Strings for uint256; @@ -33,11 +30,6 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { mapping(uint256 => uint256) public chubbiness; mapping(uint256 => uint256) public mouthLength; - // @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - function initialize() public initializer { __ERC721_init("OptimisticLoogies", "OPLOOG"); __Ownable_init(msg.sender); @@ -88,7 +80,7 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { function tokenURI(uint256 id) public view override returns (string memory) { if (ownerOf(id) == address(0)) { - revert SVGNFT__INVALIDTOKENID(); + revert SVGNFTV2__INVALIDTOKENID(); } string memory name = string( abi.encodePacked("Loogie #", id.toString()) @@ -208,4 +200,12 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { } return string(bstr); } + + function testGetTokenID() external view returns (uint256) { + return _tokenIds; + } + + function getTokenPrice() external view returns (uint256) { + return price; + } } diff --git a/packages/foundry/contracts/SVGNFTV2.sol b/packages/foundry/contracts/SVGNFTV2.sol index 08540b2..26a50d5 100644 --- a/packages/foundry/contracts/SVGNFTV2.sol +++ b/packages/foundry/contracts/SVGNFTV2.sol @@ -1,20 +1,17 @@ pragma solidity ^0.8.0; //SPDX-License-Identifier: MIT -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +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"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; -//learn more: https://docs.openzeppelin.com/contracts/3.x/erc721 - -// GET LISTED ON OPENSEA: https://testnets.opensea.io/get-listed/step-two error SVGNFT__INVALIDTOKENID(); -contract SVGNFT is ERC721, Ownable { +contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable { using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; @@ -35,13 +32,13 @@ contract SVGNFT is ERC721, Ownable { mapping(uint256 => uint256) public eyeSize; mapping(uint256 => bytes3) public eyeColor; - constructor() - public - ERC721("OptimisticLoogies", "OPLOOG") - Ownable(msg.sender) - { - // RELEASE THE OPTIMISTIC LOOGIES! - _tokenIds = 1; + // @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize() public initializer { + emit Initialized(11111111); } function mintItem() public payable returns (uint256) { diff --git a/packages/foundry/test/Calculator.t.sol b/packages/foundry/test/Calculator.t.sol deleted file mode 100644 index cbf240e..0000000 --- a/packages/foundry/test/Calculator.t.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -import "forge-std/Test.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "../contracts/Calculator.sol"; - -contract CalculatorTest is Test { - ProxyAdmin proxyAdmin; - TransparentUpgradeableProxy proxy; - Calculator calculator; - Calculator proxyCalculator; - - function setUp() public { - // Deploy ProxyAdmin - address admin = makeAddr("admin"); - proxyAdmin = new ProxyAdmin(admin); - - // Deploy Calculator logic contract - calculator = new Calculator(); - - // Initialize data - bytes memory data = abi.encodeWithSelector( - Calculator.initialize.selector - ); - - // Deploy TransparentUpgradeableProxy - proxy = new TransparentUpgradeableProxy( - address(calculator), - address(proxyAdmin), - data - ); - - // Initialize proxyCalculator to interact with the proxy as a Calculator - proxyCalculator = Calculator(address(proxy)); - } - - function testAdd() public view { - uint256 result = proxyCalculator.add(1, 2); - assertEq(result, 3); - } - - function testSubtract() public view { - uint256 result = proxyCalculator.subtract(5, 3); - assertEq(result, 2); - } - - function testMultiply() public view { - uint256 result = proxyCalculator.multiply(4, 5); - assertEq(result, 20); - } - - function testDivide() public view { - uint256 result = proxyCalculator.divide(10, 2); - assertEq(result, 5); - } - - function testDivideByZero() public { - vm.expectRevert("Calculator: division by zero"); - proxyCalculator.divide(10, 0); - } -} diff --git a/packages/foundry/test/CalculatorUpgradeTest.t.sol b/packages/foundry/test/CalculatorUpgradeTest.t.sol deleted file mode 100644 index c4feee7..0000000 --- a/packages/foundry/test/CalculatorUpgradeTest.t.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -import "forge-std/Test.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "../contracts/Calculator.sol"; -import "../contracts/CalculatorV2.sol"; - -contract CalculatorUpgradeTest is Test { - TransparentUpgradeableProxy proxy; - Calculator calculator; - CalculatorV2 calculatorV2; - - function setUp() public { - address admin = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; - - // Deploy Calculator logic contract - calculator = new Calculator(); - - // Initialize data - bytes memory data = abi.encodeWithSelector( - Calculator.initialize.selector - ); - - // Deploy TransparentUpgradeableProxy - proxy = new TransparentUpgradeableProxy( - address(calculator), - admin, - data - ); - } - - function testUpgradeToCalculatorV2() public { - // Deploy CalculatorV2 logic contract - calculatorV2 = new CalculatorV2(); - - // Set the caller to the ProxyAdmin - // this is the address of the deployed ProxyAdmin from the TransparentUpgradeableProxy contract - address adminProxy = 0xffD4505B3452Dc22f8473616d50503bA9E1710Ac; - - vm.prank(adminProxy); - - ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall( - address(calculatorV2), - abi.encodeWithSelector(calculatorV2.initialize.selector , "Test") - ); - - // Interact with proxy using CalculatorV2 functions - CalculatorV2 proxyCalculatorV2 = CalculatorV2(address(proxy)); - uint256 result = proxyCalculatorV2.mod(10, 3); - assertEq(result, 1); - - string memory text = proxyCalculatorV2.testSring(); - assertEq(text, "Test"); - } -} diff --git a/packages/foundry/test/SVGNFT.t.sol b/packages/foundry/test/SVGNFT.t.sol new file mode 100644 index 0000000..3339978 --- /dev/null +++ b/packages/foundry/test/SVGNFT.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../contracts/SVGNFT.sol"; +import "../contracts/SVGNFTV2.sol"; + +contract SVGNFTProxyTest 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 testInitianTokenId() public { + assertEq( + proxiedSVGNFT.testGetTokenID(), + 1, + "Initial tokenId should equal 1" + ); + } + + 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 testPriceIncrease() public { + vm.startPrank(USER); + vm.deal(USER, 1 ether); + + proxiedSVGNFT.mintItem{value: 0.001 ether}(); + uint256 newPrice = proxiedSVGNFT.getTokenPrice(); + 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 { + vm.startPrank(USER); + vm.deal(USER, 1000 ether); + + for (uint i = 0; i < 3728; i++) { + proxiedSVGNFT.mintItem{value: 1 ether}(); + } + + vm.expectRevert("DONE MINTING"); + proxiedSVGNFT.mintItem{value: 1 ether}(); + + 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); + console.log(tokenId); + console.log(uri); + + assertTrue(bytes(uri).length > 0, "Token URI should not be empty"); + + vm.stopPrank(); + } + + // function testInvalidTokenURI() public { + // vm.expectRevert(SVGNFT.SVGNFT__INVALIDTOKENID.selector); + // proxiedSVGNFT.tokenURI(999); + // } + + function testUpgrade() public { + // This test is a placeholder for upgrade functionality + // You would typically deploy a new implementation and upgrade to it + + SVGNFTV2 svgNFTV2 = new SVGNFTV2(); + + vm.prank(address(proxyAdmin)); + + ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall( + address(svgNFTV2), + abi.encodeWithSelector(svgNFTV2.initialize.selector) + ); + + // After upgrade, proxiedSVGNFT should still work as expected + uint256 tokenID = proxiedSVGNFT.testGetTokenID(); + console.log(tokenID); + assertEq(tokenID, 2, "TokenId should equal 2"); + } +} diff --git a/packages/foundry/test/YourContract.t.sol b/packages/foundry/test/YourContract.t.sol deleted file mode 100644 index a6a2c10..0000000 --- a/packages/foundry/test/YourContract.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../contracts/YourContract.sol"; - -contract YourContractTest is Test { - YourContract public yourContract; - - function setUp() public { - yourContract = new YourContract(vm.addr(1)); - } - - function testMessageOnDeployment() public view { - require( - keccak256(bytes(yourContract.greeting())) - == keccak256("Building Unstoppable Apps!!!") - ); - } - - function testSetNewMessage() public { - yourContract.setGreeting("Learn Scaffold-ETH 2! :)"); - require( - keccak256(bytes(yourContract.greeting())) - == keccak256("Learn Scaffold-ETH 2! :)") - ); - } -} From 7beb532567a08456dc3155db3261021382057ce8 Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Tue, 30 Jul 2024 08:06:01 +0100 Subject: [PATCH 6/8] improving gas effeciency for mintToken and TokenURI funcitons --- packages/foundry/contracts/HexStrings.sol | 31 +++-- packages/foundry/contracts/SVGNFT.sol | 151 +++++++++++++++------- packages/foundry/contracts/SVGNFTV2.sol | 4 +- packages/foundry/contracts/ToColor.sol | 32 +++-- packages/foundry/foundry.toml | 4 +- packages/foundry/package.json | 2 +- packages/foundry/test/SVGNFT.t.sol | 6 +- 7 files changed, 160 insertions(+), 70 deletions(-) diff --git a/packages/foundry/contracts/HexStrings.sol b/packages/foundry/contracts/HexStrings.sol index 7c7eb3b..ac103a3 100644 --- a/packages/foundry/contracts/HexStrings.sol +++ b/packages/foundry/contracts/HexStrings.sol @@ -1,18 +1,33 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; library HexStrings { - bytes16 internal constant ALPHABET = '0123456789abcdef'; + bytes16 private constant ALPHABET = "0123456789abcdef"; - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + 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'; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = ALPHABET[value & 0xf]; - value >>= 4; + buffer[0] = "0"; + buffer[1] = "x"; + + assembly { + let pos := add(buffer, 34) + let end := add(pos, mul(length, 2)) + let remaining := value + + 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 9cd31d7..d4a849c 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -9,7 +9,7 @@ import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; -error SVGNFTV2__INVALIDTOKENID(); +error SVGNFT__INVALIDTOKENID(); contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { using Strings for uint256; @@ -33,54 +33,103 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { function initialize() public initializer { __ERC721_init("OptimisticLoogies", "OPLOOG"); __Ownable_init(msg.sender); - - // RELEASE THE OPTIMISTIC LOOGIES! _tokenIds = 1; emit Initialized(11111111); } function mintItem() public payable returns (uint256) { - require(_tokenIds < limit, "DONE MINTING"); - require(msg.value >= price, "NOT ENOUGH"); + uint256 id; + bytes32 predictableRandom; - price = (price * curve) / 1000; + assembly { + // Check if _tokenIds < limit + if gt(sload(_tokenIds.slot), limit) { + // Store the error message + mstore(0x00, 0x20) // Store offset to error string + mstore(0x20, 0x0c) // Store length of error string + mstore(0x40, "DONE MINTING") // Store error string + revert(0x00, 0x60) // Revert with error message + } - uint256 id = _tokenIds; + // 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 + } - _tokenIds += 1; + // Calculate new price: price = (price * curve) / 1000 + let newPrice := div(mul(sload(price.slot), curve), 1000) + sstore(price.slot, newPrice) - _mint(msg.sender, id); + // Get current _tokenIds and increment + id := sload(_tokenIds.slot) + sstore(_tokenIds.slot, add(id, 1)) - bytes32 predictableRandom = keccak256( - abi.encodePacked( - id, - blockhash(block.number - 1), - msg.sender, - address(this) + // 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))) + ) ) - ); - 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); - - (bool success, ) = recipient.call{value: msg.value}(""); - require(success, "could not send"); + 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 + } + } + + // The _mint function call is kept outside of assembly as it's likely an internal function + _mint(msg.sender, id); return id; } function tokenURI(uint256 id) public view override returns (string memory) { if (ownerOf(id) == address(0)) { - revert SVGNFTV2__INVALIDTOKENID(); + revert SVGNFT__INVALIDTOKENID(); } string memory name = string( abi.encodePacked("Loogie #", id.toString()) @@ -177,27 +226,37 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { return render; } - function uint2str( - uint _i - ) internal pure returns (string memory _uintAsString) { + function uint2str(uint256 _i) internal pure returns (string memory) { if (_i == 0) { return "0"; } - uint j = _i; - uint len; + + uint256 j = _i; + uint256 length; while (j != 0) { - len++; + length++; 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; + + bytes memory bstr = new bytes(length); + + assembly { + let position := add(bstr, 32) + let end := add(position, length) + + 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) + } } + return string(bstr); } diff --git a/packages/foundry/contracts/SVGNFTV2.sol b/packages/foundry/contracts/SVGNFTV2.sol index 26a50d5..8a897bf 100644 --- a/packages/foundry/contracts/SVGNFTV2.sol +++ b/packages/foundry/contracts/SVGNFTV2.sol @@ -9,7 +9,7 @@ import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; -error SVGNFT__INVALIDTOKENID(); +error SVGNFTV2__INVALIDTOKENID(); contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable { using Strings for uint256; @@ -87,7 +87,7 @@ contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable { function tokenURI(uint256 id) public view override returns (string memory) { if (ownerOf(id) == address(0)) { - revert SVGNFT__INVALIDTOKENID(); + revert SVGNFTV2__INVALIDTOKENID(); } string memory name = string( abi.encodePacked("Loogie #", id.toString()) diff --git a/packages/foundry/contracts/ToColor.sol b/packages/foundry/contracts/ToColor.sol index f26f3fa..824270f 100644 --- a/packages/foundry/contracts/ToColor.sol +++ b/packages/foundry/contracts/ToColor.sol @@ -1,12 +1,26 @@ -library ToColor { - bytes16 internal constant ALPHABET = '0123456789abcdef'; +// 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); - for (uint256 i = 0; i < 3; i++) { - buffer[i*2+1] = ALPHABET[uint8(value[i]) & 0xf]; - buffer[i*2] = ALPHABET[uint8(value[i]>>4) & 0xf]; - } - return string(buffer); + bytes memory buffer = new bytes(6); + + assembly { + let pos := add(buffer, 32) + + // Convert first byte + mstore8(pos, byte(28, mload(add(value, 32)))) + mstore8(add(pos, 1), byte(29, mload(add(value, 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 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); } -} \ No newline at end of file +} diff --git a/packages/foundry/foundry.toml b/packages/foundry/foundry.toml index f1270b0..d9b7222 100644 --- a/packages/foundry/foundry.toml +++ b/packages/foundry/foundry.toml @@ -2,8 +2,10 @@ src = 'contracts' out = 'out' libs = ['lib'] -fs_permissions = [{ access = "read-write", path = "./"}] +fs_permissions = [{ access = "read-write", path = "./" }] via_ir = true +optimizer = true +optimizer_runs = 20_000 [rpc_endpoints] default_network = "http://127.0.0.1:8545" diff --git a/packages/foundry/package.json b/packages/foundry/package.json index 2971df4..1a25a92 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -12,7 +12,7 @@ "format": "forge fmt && prettier --write ./script/**/*.js", "generate": "node script/generateAccount.js", "lint": "forge fmt --check && prettier --check ./script/**/*.js", - "test": "forge test", + "test": "forge test --gas-report -vv", "verify": "forge build --build-info --build-info-path out/build-info/ && forge script script/VerifyAll.s.sol --ffi --rpc-url ${1:-default_network}" }, "dependencies": { diff --git a/packages/foundry/test/SVGNFT.t.sol b/packages/foundry/test/SVGNFT.t.sol index 3339978..e7e347b 100644 --- a/packages/foundry/test/SVGNFT.t.sol +++ b/packages/foundry/test/SVGNFT.t.sol @@ -91,12 +91,12 @@ contract SVGNFTProxyTest is Test { vm.startPrank(USER); vm.deal(USER, 1000 ether); - for (uint i = 0; i < 3728; i++) { - proxiedSVGNFT.mintItem{value: 1 ether}(); + for (uint i = 0; i < 11; i++) { + proxiedSVGNFT.mintItem{value: 0.01 ether}(); } vm.expectRevert("DONE MINTING"); - proxiedSVGNFT.mintItem{value: 1 ether}(); + proxiedSVGNFT.mintItem{value: 0.01 ether}(); vm.stopPrank(); } From a702c2b92de00947a2d89c848a3e566d51394f2a Mon Sep 17 00:00:00 2001 From: Ahmed Borwin Date: Tue, 30 Jul 2024 22:25:28 +0100 Subject: [PATCH 7/8] fix to some of the tests --- packages/foundry/contracts/SVGNFT.sol | 17 ++++++++--------- packages/foundry/test/SVGNFT.t.sol | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/foundry/contracts/SVGNFT.sol b/packages/foundry/contracts/SVGNFT.sol index d4a849c..e215214 100644 --- a/packages/foundry/contracts/SVGNFT.sol +++ b/packages/foundry/contracts/SVGNFT.sol @@ -9,9 +9,10 @@ import {Base64} from "@openzeppelin/contracts/utils/base64.sol"; import {HexStrings} from "./HexStrings.sol"; import {ToColor} from "./ToColor.sol"; -error SVGNFT__INVALIDTOKENID(); - contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { + error SVGNFT__INVALIDTOKENID(); + error SVGNFT__DONEMINTING(); + using Strings for uint256; using HexStrings for uint160; using ToColor for bytes3; @@ -21,9 +22,9 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { address payable public constant recipient = payable(0xa81a6a910FeD20374361B35C451a4a44F86CeD46); - uint256 public constant limit = 3728; + uint256 public constant limit = 10; uint256 public constant curve = 1002; // price increase 0,4% with each purchase - uint256 public price = 0.001 ether; + 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; @@ -34,6 +35,7 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { __ERC721_init("OptimisticLoogies", "OPLOOG"); __Ownable_init(msg.sender); _tokenIds = 1; + price = 0.001 ether; emit Initialized(11111111); } @@ -45,10 +47,8 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { // Check if _tokenIds < limit if gt(sload(_tokenIds.slot), limit) { // Store the error message - mstore(0x00, 0x20) // Store offset to error string - mstore(0x20, 0x0c) // Store length of error string - mstore(0x40, "DONE MINTING") // Store error string - revert(0x00, 0x60) // Revert with error message + mstore(0x00, 0x2260e398) // SVGNFT__DONEMINTING() selector + revert(0x00, 0x04) } // Check if msg.value >= price @@ -193,7 +193,6 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable { 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( diff --git a/packages/foundry/test/SVGNFT.t.sol b/packages/foundry/test/SVGNFT.t.sol index e7e347b..73f851e 100644 --- a/packages/foundry/test/SVGNFT.t.sol +++ b/packages/foundry/test/SVGNFT.t.sol @@ -89,14 +89,16 @@ contract SVGNFTProxyTest is Test { function testMintLimit() public { vm.startPrank(USER); - vm.deal(USER, 1000 ether); + vm.deal(USER, 10 ether); - for (uint i = 0; i < 11; i++) { - proxiedSVGNFT.mintItem{value: 0.01 ether}(); + for (uint i = 1; i < 12; i++) { + uint256 currentPrice = proxiedSVGNFT.getTokenPrice(); + proxiedSVGNFT.mintItem{value: currentPrice}(); } - vm.expectRevert("DONE MINTING"); - proxiedSVGNFT.mintItem{value: 0.01 ether}(); + vm.expectRevert(SVGNFT.SVGNFT__DONEMINTING.selector); + uint256 currentPrice = proxiedSVGNFT.getTokenPrice(); + proxiedSVGNFT.mintItem{value: currentPrice}(); vm.stopPrank(); } @@ -107,18 +109,16 @@ contract SVGNFTProxyTest is Test { uint256 tokenId = proxiedSVGNFT.mintItem{value: 0.001 ether}(); string memory uri = proxiedSVGNFT.tokenURI(tokenId); - console.log(tokenId); - console.log(uri); assertTrue(bytes(uri).length > 0, "Token URI should not be empty"); vm.stopPrank(); } - // function testInvalidTokenURI() public { - // vm.expectRevert(SVGNFT.SVGNFT__INVALIDTOKENID.selector); - // proxiedSVGNFT.tokenURI(999); - // } + function testInvalidTokenURI() public { + vm.expectRevert(SVGNFT.SVGNFT__INVALIDTOKENID.selector); + proxiedSVGNFT.tokenURI(999); + } function testUpgrade() public { // This test is a placeholder for upgrade functionality From 034b909e116f151d983550e442ceff559e5fb9cb Mon Sep 17 00:00:00 2001 From: The Rainmaker Date: Wed, 31 Jul 2024 14:51:07 -0400 Subject: [PATCH 8/8] Fix --- .gitmodules | 3 + README.md | 60 ++- package.json | 7 +- packages/foundry/contracts/Calculator.sol | 2 +- packages/foundry/contracts/CalculatorV2.sol | 2 +- packages/foundry/contracts/HexStrings.sol | 47 +- packages/foundry/contracts/SVGNFT.sol | 498 +++++++++--------- packages/foundry/contracts/SVGNFTV2.sol | 403 +++++++------- packages/foundry/contracts/ToColor.sol | 38 +- packages/foundry/contracts/YourContract.sol | 84 --- packages/foundry/lib/solady | 1 + packages/foundry/package.json | 5 +- packages/foundry/script/Deploy.s.sol | 105 ++-- .../foundry/script/DeployCalculator.s.sol | 56 +- packages/foundry/script/DeployHelpers.s.sol | 5 +- packages/foundry/script/VerifyAll.s.sol | 5 +- packages/foundry/script/generateTsAbis.js | 2 +- packages/foundry/test/Implementation.sol | 113 ++++ packages/foundry/test/Proxy.t.sol | 148 ++++++ packages/foundry/test/SVGNFT.t.sol | 141 ----- yarn.lock | 427 ++++++++------- 21 files changed, 1127 insertions(+), 1025 deletions(-) delete mode 100644 packages/foundry/contracts/YourContract.sol create mode 160000 packages/foundry/lib/solady create mode 100644 packages/foundry/test/Implementation.sol create mode 100644 packages/foundry/test/Proxy.t.sol delete mode 100644 packages/foundry/test/SVGNFT.t.sol diff --git a/.gitmodules b/.gitmodules index 4441870..7da2eb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "packages/foundry/lib/openzeppelin-contracts-upgradeable"] path = packages/foundry/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "packages/foundry/lib/solady"] + path = packages/foundry/lib/solady + url = https://github.com/Vectorized/solady 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/lib/solady b/packages/foundry/lib/solady new file mode 160000 index 0000000..a1f9be9 --- /dev/null +++ b/packages/foundry/lib/solady @@ -0,0 +1 @@ +Subproject commit a1f9be988d3c12655692cb8cdfc6864cc393cff6 diff --git a/packages/foundry/package.json b/packages/foundry/package.json index 1a25a92..dd4c92e 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -12,7 +12,7 @@ "format": "forge fmt && prettier --write ./script/**/*.js", "generate": "node script/generateAccount.js", "lint": "forge fmt --check && prettier --check ./script/**/*.js", - "test": "forge test --gas-report -vv", + "test": "forge test --gas-report -vvv", "verify": "forge build --build-info --build-info-path out/build-info/ && forge script script/VerifyAll.s.sol --ffi --rpc-url ${1:-default_network}" }, "dependencies": { @@ -28,6 +28,7 @@ "@openzeppelin/contracts-upgradeable": "^5.0.2", "@types/prettier": "2", "@types/qrcode": "1", - "base64-sol": "^1.1.0" + "base64-sol": "^1.1.0", + "solady": "^0.0.228" } } 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..3eab7e9 100644 --- a/packages/foundry/script/DeployCalculator.s.sol +++ b/packages/foundry/script/DeployCalculator.s.sol @@ -1,39 +1,33 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +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/packages/foundry/test/SVGNFT.t.sol b/packages/foundry/test/SVGNFT.t.sol deleted file mode 100644 index 73f851e..0000000 --- a/packages/foundry/test/SVGNFT.t.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "../contracts/SVGNFT.sol"; -import "../contracts/SVGNFTV2.sol"; - -contract SVGNFTProxyTest 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 testInitianTokenId() public { - assertEq( - proxiedSVGNFT.testGetTokenID(), - 1, - "Initial tokenId should equal 1" - ); - } - - 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 testPriceIncrease() public { - vm.startPrank(USER); - vm.deal(USER, 1 ether); - - proxiedSVGNFT.mintItem{value: 0.001 ether}(); - uint256 newPrice = proxiedSVGNFT.getTokenPrice(); - 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 { - vm.startPrank(USER); - vm.deal(USER, 10 ether); - - for (uint i = 1; i < 12; i++) { - uint256 currentPrice = proxiedSVGNFT.getTokenPrice(); - proxiedSVGNFT.mintItem{value: currentPrice}(); - } - - vm.expectRevert(SVGNFT.SVGNFT__DONEMINTING.selector); - uint256 currentPrice = proxiedSVGNFT.getTokenPrice(); - proxiedSVGNFT.mintItem{value: currentPrice}(); - - 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); - - assertTrue(bytes(uri).length > 0, "Token URI should not be empty"); - - vm.stopPrank(); - } - - function testInvalidTokenURI() public { - vm.expectRevert(SVGNFT.SVGNFT__INVALIDTOKENID.selector); - proxiedSVGNFT.tokenURI(999); - } - - function testUpgrade() public { - // This test is a placeholder for upgrade functionality - // You would typically deploy a new implementation and upgrade to it - - SVGNFTV2 svgNFTV2 = new SVGNFTV2(); - - vm.prank(address(proxyAdmin)); - - ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall( - address(svgNFTV2), - abi.encodeWithSelector(svgNFTV2.initialize.selector) - ); - - // After upgrade, proxiedSVGNFT should still work as expected - uint256 tokenID = proxiedSVGNFT.testGetTokenID(); - console.log(tokenID); - assertEq(tokenID, 2, "TokenId should equal 2"); - } -} 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"