Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade test updated #5

Closed
wants to merge 11 commits into from
143 changes: 77 additions & 66 deletions packages/foundry/contracts/SVGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ pragma solidity ^0.8.0;
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 {Base64} from "@openzeppelin/contracts/utils/Base64.sol";

import {HexStrings} from "./HexStrings.sol";
import {ToColor} from "./ToColor.sol";

contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable {
error SVGNFT__INVALIDTOKENID();
error SVGNFT__DONEMINTING();
error SVGNFT__NFTPRICEHIGHER();

using Strings for uint256;
using HexStrings for uint160;
Expand Down Expand Up @@ -43,23 +44,15 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable {
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)
}
if (_tokenIds > limit) {
revert SVGNFT__DONEMINTING();
}

// 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
}
if (msg.value < price) {
revert SVGNFT__NFTPRICEHIGHER();
}

assembly {
// Calculate new price: price = (price * curve) / 1000
let newPrice := div(mul(sload(price.slot), curve), 1000)
sstore(price.slot, newPrice)
Expand Down Expand Up @@ -152,33 +145,62 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable {
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,
'"}'
)
abi.encodePacked(
'{"name":"',
name,
'","description":"',
description,
'","external_url":"https://burnyboys.com/token/',
id.toString(),
'","attributes":[',
generateAttributes(id),
'],"owner":"',
uint256(uint160(ownerOf(id))).toHexString(20),
'","image":"data:image/svg+xml;base64,',
image,
'"}'
)
)
)
);
}

function generateDescription(
uint256 id
) internal view returns (string memory) {
return
string(
abi.encodePacked(
"This Loogie is the color #",
color[id].toColor(),
" with a chubbiness of ",
chubbiness[id].toString(),
" and mouth length of ",
mouthLength[id].toString(),
"!!!"
)
);
}

function generateAttributes(
uint256 id
) internal view returns (string memory) {
return
string(
abi.encodePacked(
'{"trait_type":"color","value":"#',
color[id].toColor(),
'"},',
'{"trait_type":"chubbiness","value":',
chubbiness[id].toString(),
"},",
'{"trait_type":"mouthLength","value":',
mouthLength[id].toString(),
"}"
)
);
}

function generateSVGofTokenById(
uint256 id
) internal view returns (string memory) {
Expand All @@ -194,37 +216,26 @@ contract SVGNFT is ERC721Upgradeable, OwnableUpgradeable {
}

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(
'<g id="eye1">',
'<ellipse stroke-width="3" ry="29.5" rx="29.5" id="svg_1" cy="154.5" cx="181.5" stroke="#000" fill="#fff"/>',
'<ellipse ry="3.5" rx="2.5" id="svg_3" cy="154.5" cx="173.5" stroke-width="3" stroke="#000" fill="#000000"/>',
"</g>",
'<g id="head">',
'<ellipse fill="#',
color[id].toColor(),
'" stroke-width="3" cx="204.5" cy="211.80065" id="svg_5" rx="',
chubbiness[id].toString(),
'" ry="51.80065" stroke="#000"/>',
"</g>",
'<g id="eye2">',
'<ellipse stroke-width="3" ry="29.5" rx="29.5" id="svg_2" cy="168.5" cx="209.5" stroke="#000" fill="#fff"/>',
'<ellipse ry="3.5" rx="3" id="svg_4" cy="169.5" cx="208" stroke-width="3" fill="#000000" stroke="#000"/>',
"</g>"
'<g class="mouth" transform="translate(',
uint256((810 - 9 * chubbiness[id]) / 11).toString(),
',0)">',
'<path d="M 130 240 Q 165 250 ',
mouthLength[id].toString(),
' 235" stroke="black" stroke-width="3" fill="transparent"/>',
"</g>"
)
);
uint256 translateX = (810 - 9 * chubbiness[id]) / 11;

return render;
return
string(
abi.encodePacked(
'<g id="eye1"><ellipse stroke-width="3" ry="29.5" rx="29.5" cy="154.5" cx="181.5" stroke="#000" fill="#fff"/><ellipse ry="3.5" rx="2.5" cy="154.5" cx="173.5" stroke-width="3" stroke="#000" fill="#000"/></g>',
'<g id="head"><ellipse fill="#',
color[id].toColor(),
'" stroke-width="3" cx="204.5" cy="211.80065" rx="',
chubbiness[id].toString(),
'" ry="51.80065" stroke="#000"/></g>',
'<g id="eye2"><ellipse stroke-width="3" ry="29.5" rx="29.5" cy="168.5" cx="209.5" stroke="#000" fill="#fff"/><ellipse ry="3.5" rx="3" cy="169.5" cx="208" stroke-width="3" fill="#000" stroke="#000"/></g>',
'<g class="mouth" transform="translate(',
translateX.toString(),
',0)"><path d="M130 240Q165 250 ',
mouthLength[id].toString(),
' 235" stroke="#000" stroke-width="3" fill="none"/></g>'
)
);
}

function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
Expand Down
13 changes: 11 additions & 2 deletions packages/foundry/contracts/SVGNFTV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
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 {Base64} from "@openzeppelin/contracts/utils/Base64.sol";

import {HexStrings} from "./HexStrings.sol";
import {ToColor} from "./ToColor.sol";
Expand Down Expand Up @@ -37,7 +37,8 @@ contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable {
_disableInitializers();
}

function initialize() public initializer {
// reinitializer is not needed or mandatory
function initialize() public reinitializer(2) {
emit Initialized(11111111);
}

Expand Down Expand Up @@ -211,4 +212,12 @@ contract SVGNFTV2 is ERC721Upgradeable, OwnableUpgradeable {
}
return string(bstr);
}

function testGetTokenID() external view returns (uint256) {
return _tokenIds;
}

function getTokenPrice() external view returns (uint256) {
return price;
}
}
63 changes: 43 additions & 20 deletions packages/foundry/test/SVGNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,36 @@ contract SVGNFTProxyTest is Test {
// 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),
ADMIN,
data
);

// Create a proxied SVGNFT for easier interaction
proxiedSVGNFT = SVGNFT(address(proxy));

// Get the address of the automatically created ProxyAdmin
address proxyAdminAddress = address(
uint160(
uint256(
vm.load(
address(proxy),
bytes32(
uint256(
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
)
)
)
)
)
);
proxyAdmin = ProxyAdmin(proxyAdminAddress);

vm.stopPrank();
}

Expand Down Expand Up @@ -91,18 +105,19 @@ contract SVGNFTProxyTest is Test {
vm.startPrank(USER);
vm.deal(USER, 10 ether);

for (uint i = 1; i < 12; i++) {
for (uint i = 1; i < 11; i++) {
uint256 currentPrice = proxiedSVGNFT.getTokenPrice();
proxiedSVGNFT.mintItem{value: currentPrice}();
}

vm.expectRevert(SVGNFT.SVGNFT__DONEMINTING.selector);
uint256 currentPrice = proxiedSVGNFT.getTokenPrice();
try proxiedSVGNFT.mintItem{value: currentPrice}() {} catch Error(
string memory reason
) {
console.log("Minting failed with reason:", reason);
}

// The selector for ERC721NonexistentToken(uint256)
bytes4 selector = bytes4(keccak256("SVGNFT__DONEMINTING()"));

vm.expectRevert(abi.encodePacked(selector));

proxiedSVGNFT.mintItem{value: currentPrice}();

vm.stopPrank();
}
Expand All @@ -111,7 +126,14 @@ contract SVGNFTProxyTest is Test {
vm.startPrank(USER);
vm.deal(USER, 1 ether);

uint256 tokenId = proxiedSVGNFT.mintItem{value: 0.001 ether}();
for (uint i = 1; i < 9; i++) {
uint256 currentPrice = proxiedSVGNFT.getTokenPrice();
uint256 tokenId = proxiedSVGNFT.mintItem{value: currentPrice}();
proxiedSVGNFT.tokenURI(tokenId);
}

uint256 currentPrice = proxiedSVGNFT.getTokenPrice();
uint256 tokenId = proxiedSVGNFT.mintItem{value: currentPrice}();
string memory uri = proxiedSVGNFT.tokenURI(tokenId);

assertTrue(bytes(uri).length > 0, "Token URI should not be empty");
Expand All @@ -120,28 +142,29 @@ contract SVGNFTProxyTest is Test {
}

function testInvalidTokenURI() public {
vm.expectRevert(SVGNFT.SVGNFT__INVALIDTOKENID.selector);
// The selector for ERC721NonexistentToken(uint256)
bytes4 selector = bytes4(keccak256("ERC721NonexistentToken(uint256)"));

vm.expectRevert(abi.encodeWithSelector(selector, 999));
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

proxiedSVGNFT.testGetTokenID();
SVGNFTV2 svgNFTV2 = new SVGNFTV2();

vm.prank(address(proxyAdmin));
vm.prank(ADMIN);

try
ITransparentUpgradeableProxy(address(proxy)).upgradeToAndCall(
proxyAdmin.upgradeAndCall(
ITransparentUpgradeableProxy(address(proxy)),
address(svgNFTV2),
abi.encodeWithSelector(svgNFTV2.initialize.selector)
)
{
// After upgrade, proxiedSVGNFT should still work as expected
uint256 tokenID = proxiedSVGNFT.testGetTokenID();
console.log("@@tokenID", tokenID);
assertEq(tokenID, 2, "TokenId should equal 2");
assertEq(tokenID, 1, "TokenId should equal 1");
} catch Error(string memory reason) {
console.log("Upgrade failed with reason:", reason);
}
Expand Down
Loading