From 13d64bb93922a9cb08ef96fdbb43827b09b37459 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 4 Oct 2024 13:26:41 -0300 Subject: [PATCH 1/3] feat: Change the NFT metadata --- .../contracts/xp-nft/test/xpNFTV2.sol | 2 +- .../contracts/xp-nft/xpNFT_V2.sol | 50 ++ .../test/xp-nft/test.helpers.ts | 36 + .../test/xp-nft/xp-nft-v2-back.ts | 695 ++++++++++++++++++ .../test/xp-nft/xp-nft-v2.ts | 119 +++ .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 2 +- 6 files changed, 902 insertions(+), 2 deletions(-) create mode 100644 packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol create mode 100644 packages/zevm-app-contracts/test/xp-nft/xp-nft-v2-back.ts create mode 100644 packages/zevm-app-contracts/test/xp-nft/xp-nft-v2.ts diff --git a/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol index 5114c94..ee56e0c 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol @@ -5,6 +5,6 @@ import "../xpNFT.sol"; contract ZetaXPV2 is ZetaXP { function version() public pure override returns (string memory) { - return "2.0.0"; + return "1.0.1"; } } diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol new file mode 100644 index 0000000..979eb31 --- /dev/null +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./xpNFT.sol"; + +contract ZetaXP_V2 is ZetaXP { + bytes32 private constant SETLEVEL_TYPEHASH = + keccak256("SetLevel(uint256 tokenId,uint256 signatureExpiration,uint256 sigTimestamp,uint256 level)"); + + struct SetLevelData { + uint256 tokenId; + bytes signature; + uint256 signatureExpiration; + uint256 sigTimestamp; + uint256 level; + } + + mapping(uint256 => uint256) public levelByTokenId; + event LevelSet(address indexed sender, uint256 indexed tokenId, uint256 level); + + function version() public pure override returns (string memory) { + return "2.0.0"; + } + + function _verifySetLevelSignature(SetLevelData memory data) private view { + bytes32 structHash = keccak256( + abi.encode(SETLEVEL_TYPEHASH, data.tokenId, data.signatureExpiration, data.sigTimestamp, data.level) + ); + bytes32 constructedHash = _hashTypedDataV4(structHash); + + if (!SignatureChecker.isValidSignatureNow(signerAddress, constructedHash, data.signature)) { + revert InvalidSigner(); + } + + if (block.timestamp > data.signatureExpiration) revert SignatureExpired(); + if (data.sigTimestamp <= lastUpdateTimestampByTokenId[data.tokenId]) revert OutdatedSignature(); + } + + function setLevel(SetLevelData memory data) external { + _verifySetLevelSignature(data); + + levelByTokenId[data.tokenId] = data.level; + lastUpdateTimestampByTokenId[data.tokenId] = data.sigTimestamp; + emit LevelSet(msg.sender, data.tokenId, data.level); + } + + function getLevel(uint256 tokenId) external view returns (uint256) { + return levelByTokenId[tokenId]; + } +} diff --git a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts index 093f45b..bc9a967 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -47,3 +47,39 @@ export const getSignature = async ( const signature = await signer._signTypedData(domain, types, value); return signature; }; + +export const getSelLevelSignature = async ( + chainId: number, + verifyingContract: string, + signer: SignerWithAddress, + signatureExpiration: number, + timestamp: number, + tokenId: number, + level: number +) => { + const domain = { + chainId: chainId, + name: "ZetaXP", + verifyingContract: verifyingContract, + version: "1", + }; + + const types = { + SetLevel: [ + { name: "tokenId", type: "uint256" }, + { name: "signatureExpiration", type: "uint256" }, + { name: "sigTimestamp", type: "uint256" }, + { name: "level", type: "uint256" }, + ], + }; + + const value = { + level, + sigTimestamp: timestamp, + signatureExpiration, + tokenId, + }; + // Signing the data + const signature = await signer._signTypedData(domain, types, value); + return signature; +}; diff --git a/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2-back.ts b/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2-back.ts new file mode 100644 index 0000000..e2a0185 --- /dev/null +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2-back.ts @@ -0,0 +1,695 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { ZetaXP } from "../../typechain-types"; +import { getSignature, NFT, NFTSigned } from "./test.helpers"; + +const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; +const HARDHAT_CHAIN_ID = 1337; + +const encodeTag = (tag: string) => ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], [tag])); + +describe("XP NFT V2 Contract Back compatibility test", () => { + let zetaXP: ZetaXP, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; + let sampleNFT: NFT; + + beforeEach(async () => { + [signer, user, ...addrs] = await ethers.getSigners(); + const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); + + zetaXP = await upgrades.deployProxy(zetaXPFactory, [ + "ZETA NFT", + "ZNFT", + ZETA_BASE_URL, + signer.address, + signer.address, + ]); + + await zetaXP.deployed(); + + const ZetaXPFactory = await ethers.getContractFactory("ZetaXP_V2"); + await upgrades.upgradeProxy(zetaXP.address, ZetaXPFactory); + + const tag = encodeTag("XP_NFT"); + + sampleNFT = { + tag, + to: user.address, + tokenId: undefined, + }; + }); + + const validateNFT = async (tokenId: number, nft: NFT) => { + const owner = await zetaXP.ownerOf(tokenId); + await expect(owner).to.be.eq(nft.to); + + const url = await zetaXP.tokenURI(tokenId); + await expect(url).to.be.eq(`${ZETA_BASE_URL}${tokenId}`); + }; + + const getTokenIdFromRecipient = (receipt: any): number => { + //@ts-ignore + return receipt.events[0].args?.tokenId; + }; + + it("Should mint an NFT", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + const tokenId = getTokenIdFromRecipient(receipt); + + await validateNFT(tokenId, sampleNFT); + }); + + it("Should emit event on minting", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1, sampleNFT.tag); + }); + + it("Should revert if signature is not correct", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + addrs[0], + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.be.revertedWith("InvalidSigner"); + }); + + it("Should update NFT", async () => { + let tokenId = -1; + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + + const updatedSampleNFT = { ...sampleNFT }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.updateNFT(tokenId, nftParams); + } + + validateNFT(tokenId, updatedSampleNFT); + }); + + it("Should update base url", async () => { + await zetaXP.setBaseURI(`${ZETA_BASE_URL}v2/`); + const url = await zetaXP.baseTokenURI(); + await expect(url).to.be.eq(`${ZETA_BASE_URL}v2/`); + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + const tokenURI = await zetaXP.tokenURI(1); + await expect(tokenURI).to.be.eq(`${ZETA_BASE_URL}v2/1`); + }); + + it("Should update base url for minted tokens", async () => { + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + + await zetaXP.setBaseURI(`${ZETA_BASE_URL}v2/`); + const url = await zetaXP.baseTokenURI(); + await expect(url).to.be.eq(`${ZETA_BASE_URL}v2/`); + + { + const sampleNFT2 = { + ...sampleNFT, + tag: encodeTag("XP_NFT2"), + tokenId: 2, + }; + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + user.address, + sampleNFT2 + ); + + const nftParams: NFTSigned = { + ...sampleNFT2, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + const tokenURI1 = await zetaXP.tokenURI(1); + await expect(tokenURI1).to.be.eq(`${ZETA_BASE_URL}v2/1`); + + const tokenURI2 = await zetaXP.tokenURI(1); + await expect(tokenURI2).to.be.eq(`${ZETA_BASE_URL}v2/1`); + }); + + it("Should revert if not owner want to update base url", async () => { + const tx = zetaXP.connect(addrs[0]).setBaseURI(`${ZETA_BASE_URL}v2/`); + expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Should revert if try to transfer", async () => { + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + const tx = zetaXP.connect(user).transferFrom(user.address, addrs[0].address, 1); + await expect(tx).to.be.revertedWith("TransferNotAllowed"); + }); + + it("Should revert if try to use same signature twice", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + const tokenId = getTokenIdFromRecipient(receipt); + + const tx1 = zetaXP.updateNFT(tokenId, nftParams); + await expect(tx1).to.be.revertedWith("OutdatedSignature"); + }); + + it("Should revert if user already have the tag", async () => { + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + + { + const sampleNFT2 = { + ...sampleNFT, + tokenId: 2, + }; + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + user.address, + sampleNFT2 + ); + + const nftParams: NFTSigned = { + ...sampleNFT2, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.be.revertedWith("TagAlreadyHoldByUser"); + } + }); + + it("Should query by tag and by user", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + const tokenId = getTokenIdFromRecipient(receipt); + + const queriedTokenId = await zetaXP.tokenByUserTag(sampleNFT.to, sampleNFT.tag); + await expect(queriedTokenId).to.be.eq(tokenId); + + const queriedTag = await zetaXP.tagByTokenId(tokenId); + await expect(queriedTag).to.be.eq(sampleNFT.tag); + }); + + it("Should transfer ownership", async () => { + { + const ownerAddr = await zetaXP.owner(); + expect(ownerAddr).to.be.eq(signer.address); + } + await zetaXP.transferOwnership(user.address); + await zetaXP.connect(user).acceptOwnership(); + { + const ownerAddr = await zetaXP.owner(); + expect(ownerAddr).to.be.eq(user.address); + } + }); + + it("Should revert if signatured expired", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp - 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.be.revertedWith("SignatureExpired"); + }); + + it("Should update NFT tag", async () => { + let tokenId = -1; + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + + const tag = encodeTag("XP_NFT2"); + const updatedSampleNFT = { + ...sampleNFT, + tag, + }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.updateNFT(tokenId, nftParams); + } + + validateNFT(tokenId, updatedSampleNFT); + }); + + it("Should accept to update NFT tag for the same", async () => { + let tokenId = -1; + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + + const updatedSampleNFT = { ...sampleNFT }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.updateNFT(tokenId, nftParams); + } + + validateNFT(tokenId, updatedSampleNFT); + }); + + it("Should revert if try to update NFT used tag", async () => { + let tokenId = -1; + const tag = encodeTag("XP_NFT2"); + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + { + const secondNFT = { + ...sampleNFT, + tag, + }; + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + secondNFT.to, + secondNFT + ); + + const nftParams: NFTSigned = { + ...secondNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + + const updatedSampleNFT = { ...sampleNFT, tag }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = zetaXP.updateNFT(tokenId, nftParams); + await expect(tx).to.be.revertedWith("TagAlreadyHoldByUser"); + } + }); +}); diff --git a/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2.ts b/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2.ts new file mode 100644 index 0000000..86c64cc --- /dev/null +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft-v2.ts @@ -0,0 +1,119 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; + +import { ZetaXP_V2 } from "../../typechain-types"; +import { getSelLevelSignature, getSignature, NFT, NFTSigned } from "./test.helpers"; + +const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; +const HARDHAT_CHAIN_ID = 1337; + +const encodeTag = (tag: string) => ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], [tag])); + +const getTokenIdFromRecipient = (receipt: any): number => { + //@ts-ignore + return receipt.events[0].args?.tokenId; +}; + +describe("XP NFT V2 Contract test", () => { + let zetaXP: ZetaXP_V2, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; + let sampleNFT: NFT; + + beforeEach(async () => { + [signer, user, ...addrs] = await ethers.getSigners(); + const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); + + zetaXP = await upgrades.deployProxy(zetaXPFactory, [ + "ZETA NFT", + "ZNFT", + ZETA_BASE_URL, + signer.address, + signer.address, + ]); + + await zetaXP.deployed(); + + const ZetaXPFactory = await ethers.getContractFactory("ZetaXP_V2"); + zetaXP = await upgrades.upgradeProxy(zetaXP.address, ZetaXPFactory); + + const tag = encodeTag("XP_NFT"); + + sampleNFT = { + tag, + to: user.address, + tokenId: undefined, + }; + }); + + const mintNFT = async (nft: NFT) => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + nft.to, + nft + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + return getTokenIdFromRecipient(receipt); + }; + + it("Should update NFT level", async () => { + const tokenId = await mintNFT(sampleNFT); + + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + const level = 3; + + const signature = await getSelLevelSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + tokenId, + level + ); + + await zetaXP.setLevel({ level, sigTimestamp, signature, signatureExpiration, tokenId }); + const onchainLevel = await zetaXP.getLevel(tokenId); + expect(onchainLevel).to.be.eq(level); + }); + + it("Should emit event on set level", async () => { + const tokenId = await mintNFT(sampleNFT); + + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + const level = 3; + + const signature = await getSelLevelSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + tokenId, + level + ); + + const tx = zetaXP.setLevel({ level, sigTimestamp, signature, signatureExpiration, tokenId }); + await expect(tx).to.emit(zetaXP, "LevelSet").withArgs(signer.address, tokenId, level); + }); +}); diff --git a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts index de8f7e4..f082fd2 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -359,7 +359,7 @@ describe("XP NFT Contract test", () => { const zetaXPV2 = await upgrades.upgradeProxy(zetaXP.address, ZetaXPFactory); const version2 = await zetaXPV2.version(); - await expect(version2).to.be.eq("2.0.0"); + await expect(version2).to.be.eq("1.0.1"); }); it("Should revert if user already have the tag", async () => { From 41c3bab77759ed9237f81ce1f9fec16c21dbc5b3 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 4 Oct 2024 13:36:49 -0300 Subject: [PATCH 2/3] add upgrade script --- .../scripts/xp-nft/upgrade-v2.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts diff --git a/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts b/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts new file mode 100644 index 0000000..e16ba1c --- /dev/null +++ b/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts @@ -0,0 +1,36 @@ +import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; +import { ethers, network, upgrades } from "hardhat"; + +import addresses from "../../data/addresses.json"; +import { ZetaXP__factory } from "../../typechain-types"; +import { saveAddress } from "../address.helpers"; +import { verifyContract } from "../explorer.helpers"; + +const networkName = network.name; + +const upgradeZetaXP = async () => { + if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); + + //@ts-ignore + const nftAddress = addresses[networkName].ZetaXP; + + const ZetaXPFactory = await ethers.getContractFactory("ZetaXP_V2"); + const zetaXP = await upgrades.upgradeProxy(nftAddress, ZetaXPFactory); + const implementationAddress = await upgrades.erc1967.getImplementationAddress(zetaXP.address); + + console.log("ZetaXP upgraded in:", zetaXP.address); + console.log("ZetaXP implementation deployed to:", implementationAddress); + + saveAddress("ZetaXP", zetaXP.address, networkName); + + await verifyContract(implementationAddress, []); +}; + +const main = async () => { + await upgradeZetaXP(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); From 61adba0aed278ff95863b474ac80acd73b68d4dc Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 4 Oct 2024 14:04:25 -0300 Subject: [PATCH 3/3] fix deploy script --- packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts b/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts index e16ba1c..120c553 100644 --- a/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts +++ b/packages/zevm-app-contracts/scripts/xp-nft/upgrade-v2.ts @@ -2,7 +2,6 @@ import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; import { ethers, network, upgrades } from "hardhat"; import addresses from "../../data/addresses.json"; -import { ZetaXP__factory } from "../../typechain-types"; import { saveAddress } from "../address.helpers"; import { verifyContract } from "../explorer.helpers"; @@ -12,7 +11,7 @@ const upgradeZetaXP = async () => { if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); //@ts-ignore - const nftAddress = addresses[networkName].ZetaXP; + const nftAddress = addresses["zevm"][networkName].ZetaXP; const ZetaXPFactory = await ethers.getContractFactory("ZetaXP_V2"); const zetaXP = await upgrades.upgradeProxy(nftAddress, ZetaXPFactory);