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

feat(nfts): upgrade kbw contract to v2 #17967

Merged
merged 45 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
70c03b3
contract and basic tests
bearni95 Jul 17, 2024
06b0607
fixed comment typo
bearni95 Jul 17, 2024
287c4f1
linted and formatted
bearni95 Jul 30, 2024
6d6a6b7
deployment scripts
bearni95 Jul 30, 2024
e1aa36d
cleanup
bearni95 Jul 30, 2024
68fedc1
reverted changes to taikoon data
bearni95 Jul 30, 2024
fed511c
change revert cleanup
bearni95 Jul 30, 2024
cbfe8ee
Merge branch 'main' into kbw-contracts-setup
bearni95 Jul 30, 2024
1e101bb
Update packages/nfts/contracts/party-ticket/TaikoPartyTicket.sol
bearni95 Jul 30, 2024
5d92631
removal of tokenId from tokenURI logic, beyond determining the winner
bearni95 Jul 30, 2024
ba7845a
Merge branch 'main' into kbw-contracts-setup
bearni95 Jul 30, 2024
61f729e
added mintWinner method
bearni95 Jul 30, 2024
96d7309
Merge branch 'main' into kbw-contracts-setup
bearni95 Jul 31, 2024
3e4f009
Deployment updates for taiko-party-ticket
bearni95 Aug 7, 2024
5dc4952
Merge branch 'kbw-contracts-setup' of ssh://github.com/taikoxyz/taiko…
bearni95 Aug 7, 2024
bfaa57f
modifications for single-dir ipfs data root
bearni95 Aug 7, 2024
00d48a2
proper values for ipfs hekla testing
bearni95 Aug 7, 2024
4e78bc4
dev push
bearni95 Aug 7, 2024
7f47c85
uncommented dev lines
bearni95 Aug 7, 2024
a3aef0e
proper hekla deployment
bearni95 Aug 7, 2024
7d057a9
reverted error changes
bearni95 Aug 7, 2024
6f6f51d
patched up tests
bearni95 Aug 7, 2024
d7049cf
gap allocation fix; blacklist modifier
bearni95 Aug 8, 2024
3dcf0e2
added revokeWinners method
bearni95 Aug 8, 2024
5f7f4c1
modified code and added tests for upgradeability
bearni95 Aug 8, 2024
7d405f2
Merge branch 'main' into kbw-contracts-setup
bearni95 Aug 8, 2024
1ee33de
upgrade test confirmations
bearni95 Aug 8, 2024
997174e
Merge branch 'kbw-contracts-setup' of ssh://github.com/taikoxyz/taiko…
bearni95 Aug 8, 2024
c535e82
a typo...
bearni95 Aug 8, 2024
1ceacc3
uncommented blacklist modifier
bearni95 Aug 8, 2024
c660131
updated ipfs data with winner image
bearni95 Aug 8, 2024
e6b495b
deployment values
bearni95 Aug 14, 2024
b1ab805
removed unused owner role
bearni95 Aug 14, 2024
804f9a6
updated 'withdraw' to 'payout', as it's executed by the admin
bearni95 Aug 14, 2024
452d902
reverted protocol changes
bearni95 Aug 14, 2024
b6efa46
reverted protocol changes
bearni95 Aug 14, 2024
0751962
updated deployment price point
bearni95 Aug 14, 2024
388fd54
final adjustments for prod values
bearni95 Aug 14, 2024
d49ed2f
production deployment
bearni95 Aug 14, 2024
e992411
contract upgrade v2 for golden tickets
bearni95 Aug 22, 2024
11b3af7
Merge branch 'main' into taiko-party-ticket-v2
bearni95 Aug 22, 2024
2013932
uncommented modifier
bearni95 Aug 22, 2024
774922a
removed unwanted changes
bearni95 Aug 22, 2024
01602e6
removed unwanted changes
bearni95 Aug 22, 2024
5514990
v2 deployment script + mainnet upgraded data
bearni95 Aug 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions packages/nfts/contracts/party-ticket/TaikoPartyTicketV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.24;

import { TaikoPartyTicket } from "./TaikoPartyTicket.sol";

/// @title TaikoPartyTicketV2
/// @dev Upgrade to support Golden Ticket (winner of winners, singular) ticket
/// @custom:security-contact security@taiko.xyz
contract TaikoPartyTicketV2 is TaikoPartyTicket {
/// @notice Get the version of the contract
/// @return The version of the contract
function version() public pure returns (string memory) {
return "v2";
}

/// @notice Get individual token's URI
/// @param tokenId The token ID
/// @return The token URI
/// @dev re-implemented to support golden winner
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (winnerIds.length == 0) {
return string(abi.encodePacked(baseURI, "/raffle.json"));
} else if (winners[tokenId] && winnerIds[0] == tokenId) {
return string(abi.encodePacked(baseURI, "/golden-winner.json"));
} else if (winners[tokenId]) {
return string(abi.encodePacked(baseURI, "/winner.json"));
} else {
return string(abi.encodePacked(baseURI, "/loser.json"));
}
}

/// @notice Checks if a tokenId is the golden winner
/// @param tokenId The token ID
/// @return True if the token is the golden winner
function isGoldenWinner(uint256 tokenId) public view returns (bool) {
return winners[tokenId] && winnerIds[0] == tokenId;
}

/// @notice Checks if an account has a golden winner token
/// @param account The account address
/// @return True if the account has a golden winner
function isGoldenWinner(address account) public view returns (bool) {
for (uint256 i = 0; i < balanceOf(account); i++) {
if (isGoldenWinner(tokenOfOwnerByIndex(account, i))) {
return true;
}
}
return false;
}
}
5 changes: 5 additions & 0 deletions packages/nfts/data/party-token/metadata/golden-winner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "[GW] KBW Party Raffle Ticket",
"description": "A unique raffle ticket for the KBW Party. This ticket won a special prize at the raffle.",
"image": "https://taikonfts.4everland.link/ipfs/bafybeif2piyppimpd4rn6wkq4mxtwdgajil7vt6shkb2gt72a2zyqufg2a/golden-winner.png"
}
2 changes: 1 addition & 1 deletion packages/nfts/data/party-token/metadata/loser.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "[L] KBW Party Raffle Ticket",
"description": "A raffle ticket for the KBW Party. This ticket won nothing at the raffle.",
"image": "https://taikonfts.4everland.link/ipfs/bafybeialwrhlnfb46o3mdd2gcrrc3ksf5exuji5lmwwcljynae4kdq4pae/loser.png"
"image": "https://taikonfts.4everland.link/ipfs/bafybeif2piyppimpd4rn6wkq4mxtwdgajil7vt6shkb2gt72a2zyqufg2a/loser.png"
}
2 changes: 1 addition & 1 deletion packages/nfts/data/party-token/metadata/raffle.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "KBW Party Raffle Ticket",
"description": "A raffle ticket for the KBW Party. This ticket gives you a chance to win a special prize.",
"image": "https://taikonfts.4everland.link/ipfs/bafybeialwrhlnfb46o3mdd2gcrrc3ksf5exuji5lmwwcljynae4kdq4pae/raffle.png"
"image": "https://taikonfts.4everland.link/ipfs/bafybeif2piyppimpd4rn6wkq4mxtwdgajil7vt6shkb2gt72a2zyqufg2a/raffle.png"
}
4 changes: 2 additions & 2 deletions packages/nfts/data/party-token/metadata/winner.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "[W] KBW Party Raffle Ticket",
"description": "A raffle ticket for the KBW Party. This ticket won a special prize at the raffle.",
"image": "https://taikonfts.4everland.link/ipfs/bafybeialwrhlnfb46o3mdd2gcrrc3ksf5exuji5lmwwcljynae4kdq4pae/winner.gif",
"animation_url": "https://taikonfts.4everland.link/ipfs/bafybeialwrhlnfb46o3mdd2gcrrc3ksf5exuji5lmwwcljynae4kdq4pae/winner.gif"
"image": "https://taikonfts.4everland.link/ipfs/bafybeif2piyppimpd4rn6wkq4mxtwdgajil7vt6shkb2gt72a2zyqufg2a/winner.gif",
"animation_url": "https://taikonfts.4everland.link/ipfs/bafybeif2piyppimpd4rn6wkq4mxtwdgajil7vt6shkb2gt72a2zyqufg2a/winner.gif"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/nfts/data/party-token/static/loser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/nfts/deployments/party-ticket/hekla.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"TaikoPartyTicket": "0x1fE073fb9C749Ba99aab01aEc4E9d08875ea55a9"
"TaikoPartyTicket": "0x1d504615c42130F4fdbEb87775585B250BA78422"
}
3 changes: 3 additions & 0 deletions packages/nfts/deployments/party-ticket/mainnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"TaikoPartyTicket": "0x00E6dc8B0a58d505de61309df3568Ba3f9734a6C"
}
5 changes: 4 additions & 1 deletion packages/nfts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
"galxe:deploy:mainnet": "forge clean && pnpm compile && forge script script/galxe/Deploy.s.sol --rpc-url https://rpc.mainnet.taiko.xyz --legacy --with-gas-price 1",
"tbzb:deploy:mainnet": "forge clean && pnpm compile && forge script script/trailblazers-badges/sol/Deploy.s.sol --rpc-url https://rpc.mainnet.taiko.xyz --broadcast --legacy --with-gas-price 13 ",
"taikoon:deploy:v2": "forge clean && pnpm compile && forge script script/taikoon/sol/UpgradeV2.sol --rpc-url https://rpc.mainnet.taiko.xyz --broadcast",
"kbw:deploy:hekla": "forge clean && pnpm compile && forge script script/party-ticket/sol/Deploy.s.sol --rpc-url https://rpc.hekla.taiko.xyz --broadcast --gas-estimate-multiplier 200"
"kbw:deploy:hekla": "forge clean && pnpm compile && forge script script/party-ticket/sol/Deploy.s.sol --rpc-url https://rpc.hekla.taiko.xyz --broadcast --gas-estimate-multiplier 200",
"kbw:deploy:mainnet": "forge clean && pnpm compile && forge script script/party-ticket/sol/Deploy.s.sol --rpc-url https://rpc.mainnet.taiko.xyz --broadcast --legacy --with-gas-price 30 ",
"kbw:upgradeV2:hekla": "forge clean && pnpm compile && forge script script/party-ticket/sol/UpgradeV2.s.sol --rpc-url https://rpc.hekla.taiko.xyz --broadcast --gas-estimate-multiplier 200",
"kbw:upgradeV2:mainnet": "forge clean && pnpm compile && forge script script/party-ticket/sol/UpgradeV2.s.sol --rpc-url https://rpc.mainnet.taiko.xyz --broadcast"
},
"devDependencies": {
"@types/node": "^20.11.30",
Expand Down
10 changes: 9 additions & 1 deletion packages/nfts/script/party-ticket/sol/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Merkle } from "murky/Merkle.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { TaikoPartyTicket } from "../../../contracts/party-ticket/TaikoPartyTicket.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";
import { TaikoPartyTicketV2 } from "../../../contracts/party-ticket/TaikoPartyTicketV2.sol";

contract DeployScript is Script {
UtilsScript public utils;
Expand All @@ -15,7 +16,7 @@ contract DeployScript is Script {
address public deployerAddress;

string baseURI =
"https://taikonfts.4everland.link/ipfs/bafybeia5mmkauevbhs4fm6wfib5wfifefrwfremlc67whrrgsgzj46kfsm";
"https://taikonfts.4everland.link/ipfs/bafybeiep3ju3glnzsrqdzaibv7v5ifa7dy4bkyprwkjz6wytl37oqwcmya";
IMinimalBlacklist blacklist = IMinimalBlacklist(0xfA5EA6f9A13532cd64e805996a941F101CCaAc9a);

uint256 mintFee = 0.002 ether;
Expand Down Expand Up @@ -50,7 +51,14 @@ contract DeployScript is Script {

console.log("Token Base URI:", baseURI);
console.log("Deployed TaikoPartyTicket to:", address(token));
/*
token.upgradeToAndCall(
address(new TaikoPartyTicketV2()), abi.encodeCall(TaikoPartyTicketV2.version, ())
);

TaikoPartyTicketV2 tokenV2 = TaikoPartyTicketV2(address(token));

*/
string memory finalJson = vm.serializeAddress(jsonRoot, "TaikoPartyTicket", address(token));
vm.writeJson(finalJson, jsonLocation);

Expand Down
54 changes: 54 additions & 0 deletions packages/nfts/script/party-ticket/sol/UpgradeV2.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { UtilsScript } from "./Utils.s.sol";
import { Script, console } from "forge-std/src/Script.sol";
import { Merkle } from "murky/Merkle.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { TaikoPartyTicket } from "../../../contracts/party-ticket/TaikoPartyTicket.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";
import { TaikoPartyTicketV2 } from "../../../contracts/party-ticket/TaikoPartyTicketV2.sol";

contract DeployScript is Script {
UtilsScript public utils;
string public jsonLocation;
uint256 public deployerPrivateKey;
address public deployerAddress;

// hekla
//address tokenV1 = 0x1d504615c42130F4fdbEb87775585B250BA78422;
// mainnet
address tokenV1 = 0x00E6dc8B0a58d505de61309df3568Ba3f9734a6C;

function setUp() public {
utils = new UtilsScript();
utils.setUp();

jsonLocation = utils.getContractJsonLocation();
deployerPrivateKey = utils.getPrivateKey();
deployerAddress = utils.getAddress();
}

function run() public {
string memory jsonRoot = "root";

vm.startBroadcast(deployerPrivateKey);

TaikoPartyTicket token = TaikoPartyTicket(tokenV1);

console.log("Deployed TaikoPartyTicket to:", address(token));

token.upgradeToAndCall(
address(new TaikoPartyTicketV2()), abi.encodeCall(TaikoPartyTicketV2.version, ())
);

TaikoPartyTicketV2 tokenV2 = TaikoPartyTicketV2(address(token));
console.log("Upgraded token to:", address(tokenV2));
console.log("Version:", tokenV2.version());

string memory finalJson = vm.serializeAddress(jsonRoot, "TaikoPartyTicket", address(token));
vm.writeJson(finalJson, jsonLocation);

vm.stopBroadcast();
}
}
197 changes: 197 additions & 0 deletions packages/nfts/test/party-ticket/TaikoPartyTicketV2.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { Test } from "forge-std/src/Test.sol";

import { TaikoPartyTicket } from "../../contracts/party-ticket/TaikoPartyTicket.sol";
import { Merkle } from "murky/Merkle.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { MockBlacklist } from "../util/Blacklist.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";
import { TaikoPartyTicketV2 } from "../../contracts/party-ticket/TaikoPartyTicketV2.sol";

contract TaikoPartyTicketTest is Test {
TaikoPartyTicket public tokenV1;
TaikoPartyTicketV2 public tokenV2;

address public payoutWallet = vm.addr(0x5);
address public admin = vm.addr(0x6);

address[3] public minters = [vm.addr(0x1), vm.addr(0x2), vm.addr(0x3)];

uint256 constant MINT_FEE = 1.1 ether;
uint256 constant INITIAL_BALANCE = 2 ether;

MockBlacklist public blacklist;

function setUp() public {
blacklist = new MockBlacklist();
// create whitelist merkle tree
vm.startPrank(admin);

address impl = address(new TaikoPartyTicket());
address proxy = address(
new ERC1967Proxy(
impl,
abi.encodeCall(
TaikoPartyTicket.initialize,
(payoutWallet, MINT_FEE, "ipfs://baseURI", blacklist)
)
)
);

tokenV1 = TaikoPartyTicket(proxy);

// deploy v2 upgrade

tokenV1.upgradeToAndCall(
address(new TaikoPartyTicketV2()), abi.encodeCall(TaikoPartyTicketV2.version, ())
);

tokenV2 = TaikoPartyTicketV2(address(tokenV1));

// assign initial balance to all minters
for (uint256 i = 0; i < minters.length; i++) {
vm.deal(minters[i], INITIAL_BALANCE);
}

vm.stopPrank();
}

function test_metadata() public view {
assertEq(tokenV2.name(), "TaikoPartyTicket");
assertEq(tokenV2.symbol(), "TPT");
assertEq(tokenV2.totalSupply(), 0);
}

function test_mint() public {
vm.prank(minters[0]);
tokenV2.mint{ value: MINT_FEE }();
assertEq(tokenV2.totalSupply(), 1);
assertEq(tokenV2.ownerOf(0), minters[0]);
assertEq(minters[0].balance, INITIAL_BALANCE - MINT_FEE);
}

function test_mint_admin() public {
vm.prank(admin);
tokenV2.mint(minters[1]);
assertEq(tokenV2.totalSupply(), 1);
assertEq(tokenV2.ownerOf(0), minters[1]);
}

function test_winnerFlow() public {
// have all minters mint
vm.prank(minters[0]);
tokenV2.mint{ value: MINT_FEE }();
vm.prank(minters[1]);
tokenV2.mint{ value: MINT_FEE }();
vm.prank(minters[2]);
tokenV2.mint{ value: MINT_FEE }();

// set minters[0] as winner
vm.startPrank(admin);
uint256[] memory winners = new uint256[](2);
// assign the winners
winners[0] = 0;
winners[1] = 1;
// ability to pause the minting and set the winners later
tokenV2.pause();
tokenV2.setWinners(winners);
vm.stopPrank();
// check winner with both tokenId and address
assertTrue(tokenV2.isWinner(0));
assertTrue(tokenV2.isWinner(minters[0]));
assertTrue(tokenV2.isWinner(minters[1]));
assertFalse(tokenV2.isWinner(minters[2]));
// check golden winner
assertTrue(tokenV2.isGoldenWinner(0));
assertTrue(tokenV2.isGoldenWinner(minters[0]));
assertFalse(tokenV2.isGoldenWinner(1));
assertFalse(tokenV2.isGoldenWinner(minters[1]));
assertFalse(tokenV2.isGoldenWinner(2));
assertFalse(tokenV2.isGoldenWinner(minters[2]));

// and the contract is paused
assertTrue(tokenV2.paused());
// ensure the contract's balance
assertEq(address(tokenV2).balance, MINT_FEE * minters.length);
}

function test_payout() public {
test_winnerFlow();
uint256 collectedEth = address(tokenV2).balance;
vm.prank(admin);
tokenV2.payout();
assertEq(payoutWallet.balance, collectedEth);
assertEq(address(tokenV2).balance, 0);
}

function test_ipfs_metadata_goldenWinner() public {
// ensure URIs are "ticket" before setting winners
assertEq(tokenV2.baseURI(), "ipfs://baseURI");
assertEq(tokenV2.tokenURI(0), "ipfs://baseURI/raffle.json");
assertEq(tokenV2.tokenURI(1), "ipfs://baseURI/raffle.json");
// run winner flow
test_winnerFlow();
// ensure URIs are "winner" and "loser" after setting winners
assertEq(tokenV2.tokenURI(0), "ipfs://baseURI/golden-winner.json");
assertEq(tokenV2.tokenURI(1), "ipfs://baseURI/winner.json");
assertEq(tokenV2.tokenURI(2), "ipfs://baseURI/loser.json");
}

function test_revokeWinner() public {
test_winnerFlow();
// ensure the contract is paused
assertTrue(tokenV2.paused());
// ensure wallet0 is winner
assertTrue(tokenV2.isWinner(minters[0]));

uint256[] memory winnerIds = tokenV2.getWinnerTokenIds();
assertEq(winnerIds.length, 2);
address[] memory winners = tokenV2.getWinners();
assertEq(winners.length, 2);

// revoke the winner
vm.prank(admin);
tokenV2.revokeWinner(winnerIds[0]);

winnerIds = tokenV2.getWinnerTokenIds();
assertEq(winnerIds.length, 1);
winners = tokenV2.getWinners();
assertEq(winners.length, 1);
}

function test_revokeAndReplaceWinner() public {
test_winnerFlow();
// ensure the contract is paused
assertTrue(tokenV2.paused());

// ensure wallet0 is winner
assertTrue(tokenV2.isWinner(minters[0]));
assertTrue(tokenV2.isWinner(minters[1]));
assertFalse(tokenV2.isWinner(minters[2]));

uint256[] memory winnerIds = tokenV2.getWinnerTokenIds();
assertEq(winnerIds.length, 2);
address[] memory winners = tokenV2.getWinners();
assertEq(winners.length, 2);

// revoke and replace with token id 2
vm.prank(admin);
tokenV2.revokeAndReplaceWinner(winnerIds[0], 2);

assertFalse(tokenV2.isWinner(minters[0]));
assertTrue(tokenV2.isWinner(minters[1]));
assertTrue(tokenV2.isWinner(minters[2]));

winnerIds = tokenV2.getWinnerTokenIds();
assertEq(winnerIds.length, 2);

winners = tokenV2.getWinners();
assertEq(winners.length, 2);

assertFalse(tokenV2.isWinner(minters[0]));
assertTrue(tokenV2.isWinner(minters[1]));
assertTrue(tokenV2.isWinner(minters[2]));
}
}
Loading