From e50d2ae2c6fc3f1f5e0025b24169b52bdb911081 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Tue, 7 May 2024 12:59:13 -0300 Subject: [PATCH 01/12] ZetaXP --- .../contracts/xp-nft/xpNFT.sol | 153 ++++++++++++++++++ .../test/xp-nft/test.helpers.ts | 61 +++++++ .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 71 ++++++++ 3 files changed, 285 insertions(+) create mode 100644 packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol create mode 100644 packages/zevm-app-contracts/test/xp-nft/test.helpers.ts create mode 100644 packages/zevm-app-contracts/test/xp-nft/xp-nft.ts diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol new file mode 100644 index 00000000..295babe2 --- /dev/null +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.7; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract ZetaXP is ERC721URIStorage, Ownable { + /* An ECDSA signature. */ + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } + + struct Task { + bool completed; + uint256 count; + } + + struct Data { + uint256 xpTotal; + uint256 level; + uint256 testnetCampaignParticipant; + uint256 enrollDate; + uint256 mintDate; + uint256 generation; + } + + mapping(uint256 => Data) public data; + mapping(uint256 => mapping(uint256 => Task)) public tasks; + + // Base URL for NFT images + string public baseTokenURI; + address public signerAddress; + + // Event for New Mint + event NewNFTMinted(address indexed sender, uint256 indexed tokenId); + + error InvalidSigner(); + error LengthMismatch(); + + constructor( + string memory name, + string memory symbol, + string memory baseTokenURI_, + address signerAddress_ + ) ERC721(name, symbol) { + baseTokenURI = baseTokenURI_; + signerAddress = signerAddress_; + } + + // The following functions are overrides required by Solidity. + + function tokenURI(uint256 tokenId) public view override(ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + function supportsInterface(bytes4 interfaceId) public view override(ERC721URIStorage) returns (bool) { + return super.supportsInterface(interfaceId); + } + + // Helper function to convert uint to string + 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 = (uint8(48 + (_i % 10))); + bstr[k] = bytes1(temp); + _i /= 10; + } + return string(bstr); + } + + function _verify( + address to, + uint256 tokenId, + Data memory data_, + uint256[] calldata taskIds, + Task[] calldata taskValues, + Signature calldata signature + ) private view { + bytes32 payloadHash = _calculateHash(to, tokenId, data_, taskIds, taskValues); + bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash)); + + address messageSigner = ecrecover(messageHash, signature.v, signature.r, signature.s); + + if (signerAddress != messageSigner) revert InvalidSigner(); + } + + // Function to compute the hash of the data and tasks for a token + function _calculateHash( + address to, + uint256 tokenId, + Data memory data_, + uint256[] memory taskIds, + Task[] memory taskValues + ) private pure returns (bytes32) { + bytes memory encodedData = abi.encodePacked( + to, + tokenId, + data_.xpTotal, + data_.level, + data_.testnetCampaignParticipant, + data_.enrollDate, + data_.mintDate, + data_.generation + ); + + for (uint256 i = 0; i < taskIds.length; i++) { + encodedData = abi.encodePacked(encodedData, taskIds[i], taskValues[i].completed, taskValues[i].count); + } + + return keccak256(encodedData); + } + + // External mint function + function mintNFT( + address to, + uint256 tokenId, + Data memory data_, + uint256[] calldata taskIds, + Task[] calldata taskValues, + Signature calldata signature + ) external { + _verify(to, tokenId, data_, taskIds, taskValues, signature); + if (taskIds.length != taskValues.length) revert LengthMismatch(); + + _mint(to, tokenId); + _setTokenURI(tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(tokenId)))); + + data[tokenId] = data_; + for (uint256 i = 0; i < taskIds.length; i++) { + tasks[tokenId][taskIds[i]] = taskValues[i]; + } + + emit NewNFTMinted(to, tokenId); + } + + // Set the base URI for tokens + function setBaseURI(string calldata _uri) external onlyOwner { + baseTokenURI = _uri; + } +} diff --git a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts new file mode 100644 index 00000000..4a2cdfa8 --- /dev/null +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -0,0 +1,61 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { ethers } from "hardhat"; + +export interface Task { + completed: boolean; + count: number; +} + +export interface Data { + enrollDate: number; + generation: number; + level: number; + mintDate: number; + testnetCampaignParticipant: number; + xpTotal: number; +} + +export interface NFT { + data: Data; + tasks: Task[]; + tasksId: number[]; + to: string; + tokenId: number; +} + +export const getSignature = async ( + signer: SignerWithAddress, + to: string, + tokenId: number, + data: Data, + tasksId: number[], + tasks: Task[] +) => { + let payload = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256"], + [ + to, + tokenId, + data.xpTotal, + data.level, + data.testnetCampaignParticipant, + data.enrollDate, + data.mintDate, + data.generation, + ] + ); + + // let combinedPayload = payload; + for (let i = 0; i < tasksId.length; i++) { + payload = ethers.utils.defaultAbiCoder.encode( + ["bytes", "uint256", "bool", "uint256"], + [payload, tasksId[i], tasks[i].completed, tasks[i].count] + ); + } + + const payloadHash = ethers.utils.keccak256(payload); + + // This adds the message prefix + const signature = await signer.signMessage(ethers.utils.arrayify(payloadHash)); + return ethers.utils.splitSignature(signature); +}; diff --git a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts new file mode 100644 index 00000000..55b93908 --- /dev/null +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -0,0 +1,71 @@ +import { expect, use } from "chai"; +import { solidity } from "ethereum-waffle"; +use(solidity); +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { ethers } from "hardhat"; + +import { ZetaXP } from "../../typechain-types"; +import { Data, getSignature, NFT, Task } from "./test.helpers"; + +describe("XP NFT Contract test", () => { + let zetaXP: ZetaXP, inviter: SignerWithAddress, invitee: SignerWithAddress, addrs: SignerWithAddress[]; + let sampleNFT: NFT; + + beforeEach(async () => { + [inviter, invitee, ...addrs] = await ethers.getSigners(); + const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); + + //@ts-ignore + zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", "https://api.zetachain.io/nft/", inviter.address); + + sampleNFT = { + data: { + enrollDate: 5435, + generation: 2314, + level: 7, + mintDate: 34, + testnetCampaignParticipant: 2, + xpTotal: 67, + }, + tasks: [ + { + completed: true, + count: 10, + }, + { + completed: false, + count: 5, + }, + ], + tasksId: [2, 3], + to: invitee.address, + tokenId: 1, + }; + }); + + describe("True", () => { + it("Should be true", async () => { + expect(true).to.equal(true); + }); + }); + + describe("Invitations test", () => { + it("Should verify an invitation and store it", async () => { + const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + // const expirationDate = await getTomorrowTimestamp(); + // const sig = await getInvitationSig(inviter, expirationDate); + // const hasBeenVerifiedBefore = await zetaXP.hasBeenVerified(invitee.address); + // await expect(hasBeenVerifiedBefore).to.be.eq(false); + // const tx = await zetaXP.connect(invitee).confirmAndAcceptInvitation(inviter.address, expirationDate, sig); + // const rec = await tx.wait(); + // const block = await ethers.provider.getBlock(rec.blockNumber); + // const invitation = await zetaXP.acceptedInvitationsTimestamp(inviter.address, invitee.address); + // await expect(invitation).to.be.eq(block.timestamp); + // const invitationCount = await zetaXP.getInviteeCount(inviter.address); + // await expect(invitationCount).to.be.eq(1); + // const hasBeenVerifiedAfter = await zetaXP.hasBeenVerified(invitee.address); + // await expect(hasBeenVerifiedAfter).to.be.eq(true); + }); + }); +}); From 98b7744a10527ed0d057d6759976bbe8e0541ad0 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Tue, 7 May 2024 13:09:21 -0300 Subject: [PATCH 02/12] add test --- .../contracts/xp-nft/xpNFT.sol | 43 +++++++++++++++---- .../test/xp-nft/test.helpers.ts | 2 +- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 40 ++++++++++------- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 295babe2..397062b0 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -35,6 +35,8 @@ contract ZetaXP is ERC721URIStorage, Ownable { // Event for New Mint event NewNFTMinted(address indexed sender, uint256 indexed tokenId); + // Event for NFT Update + event NFTUpdated(address indexed sender, uint256 indexed tokenId); error InvalidSigner(); error LengthMismatch(); @@ -105,7 +107,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256[] memory taskIds, Task[] memory taskValues ) private pure returns (bytes32) { - bytes memory encodedData = abi.encodePacked( + bytes memory encodedData = abi.encode( to, tokenId, data_.xpTotal, @@ -117,35 +119,60 @@ contract ZetaXP is ERC721URIStorage, Ownable { ); for (uint256 i = 0; i < taskIds.length; i++) { - encodedData = abi.encodePacked(encodedData, taskIds[i], taskValues[i].completed, taskValues[i].count); + encodedData = abi.encode(encodedData, taskIds[i], taskValues[i].completed, taskValues[i].count); } return keccak256(encodedData); } - // External mint function - function mintNFT( + function _updateNFT( address to, uint256 tokenId, Data memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature - ) external { + ) internal { _verify(to, tokenId, data_, taskIds, taskValues, signature); if (taskIds.length != taskValues.length) revert LengthMismatch(); - _mint(to, tokenId); - _setTokenURI(tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(tokenId)))); - data[tokenId] = data_; for (uint256 i = 0; i < taskIds.length; i++) { tasks[tokenId][taskIds[i]] = taskValues[i]; } + } + + // External mint function + function mintNFT( + address to, + uint256 tokenId, + Data memory data_, + uint256[] calldata taskIds, + Task[] calldata taskValues, + Signature calldata signature + ) external { + _mint(to, tokenId); + _setTokenURI(tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(tokenId)))); + + _updateNFT(to, tokenId, data_, taskIds, taskValues, signature); emit NewNFTMinted(to, tokenId); } + // External mint function + function updateNFT( + address to, + uint256 tokenId, + Data memory data_, + uint256[] calldata taskIds, + Task[] calldata taskValues, + Signature calldata signature + ) external { + _updateNFT(to, tokenId, data_, taskIds, taskValues, signature); + + emit NFTUpdated(to, tokenId); + } + // Set the base URI for tokens function setBaseURI(string calldata _uri) external onlyOwner { baseTokenURI = _uri; 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 4a2cdfa8..c3e98794 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -45,7 +45,7 @@ export const getSignature = async ( ] ); - // let combinedPayload = payload; + let combinedPayload = payload; for (let i = 0; i < tasksId.length; i++) { payload = ethers.utils.defaultAbiCoder.encode( ["bytes", "uint256", "bool", "uint256"], 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 55b93908..476a872b 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -5,7 +5,7 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { ethers } from "hardhat"; import { ZetaXP } from "../../typechain-types"; -import { Data, getSignature, NFT, Task } from "./test.helpers"; +import { getSignature, NFT } from "./test.helpers"; describe("XP NFT Contract test", () => { let zetaXP: ZetaXP, inviter: SignerWithAddress, invitee: SignerWithAddress, addrs: SignerWithAddress[]; @@ -49,23 +49,31 @@ describe("XP NFT Contract test", () => { }); }); - describe("Invitations test", () => { - it("Should verify an invitation and store it", async () => { + describe("NFT test", () => { + it("Should mint and NFT", async () => { const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); - // const expirationDate = await getTomorrowTimestamp(); - // const sig = await getInvitationSig(inviter, expirationDate); - // const hasBeenVerifiedBefore = await zetaXP.hasBeenVerified(invitee.address); - // await expect(hasBeenVerifiedBefore).to.be.eq(false); - // const tx = await zetaXP.connect(invitee).confirmAndAcceptInvitation(inviter.address, expirationDate, sig); - // const rec = await tx.wait(); - // const block = await ethers.provider.getBlock(rec.blockNumber); - // const invitation = await zetaXP.acceptedInvitationsTimestamp(inviter.address, invitee.address); - // await expect(invitation).to.be.eq(block.timestamp); - // const invitationCount = await zetaXP.getInviteeCount(inviter.address); - // await expect(invitationCount).to.be.eq(1); - // const hasBeenVerifiedAfter = await zetaXP.hasBeenVerified(invitee.address); - // await expect(hasBeenVerifiedAfter).to.be.eq(true); + + const owner = await zetaXP.ownerOf(1); + await expect(owner).to.be.eq(invitee.address); + + const nftData = await zetaXP.data(1); + await expect(nftData.xpTotal).to.be.eq(sampleNFT.data.xpTotal); + await expect(nftData.level).to.be.eq(sampleNFT.data.level); + await expect(nftData.testnetCampaignParticipant).to.be.eq(sampleNFT.data.testnetCampaignParticipant); + await expect(nftData.enrollDate).to.be.eq(sampleNFT.data.enrollDate); + await expect(nftData.mintDate).to.be.eq(sampleNFT.data.mintDate); + await expect(nftData.generation).to.be.eq(sampleNFT.data.generation); + + for (let i = 0; i < sampleNFT.tasksId.length; i++) { + const sampleTask = sampleNFT.tasks[i]; + const task = await zetaXP.tasks(sampleNFT.tokenId, sampleNFT.tasksId[i]); + await expect(task.completed).to.be.eq(sampleTask.completed); + await expect(task.count).to.be.eq(sampleTask.count); + } + + const url = await zetaXP.tokenURI(1); + await expect(url).to.be.eq("https://api.zetachain.io/nft/1"); }); }); }); From d65cb8ddd440afb80e12152057557901f8e88f26 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Tue, 7 May 2024 13:21:56 -0300 Subject: [PATCH 03/12] add test --- .../contracts/xp-nft/xpNFT.sol | 6 +- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 115 ++++++++++++++---- 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 397062b0..50db4b33 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -161,16 +161,16 @@ contract ZetaXP is ERC721URIStorage, Ownable { // External mint function function updateNFT( - address to, uint256 tokenId, Data memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature ) external { - _updateNFT(to, tokenId, data_, taskIds, taskValues, signature); + address owner = ownerOf(tokenId); + _updateNFT(owner, tokenId, data_, taskIds, taskValues, signature); - emit NFTUpdated(to, tokenId); + emit NFTUpdated(owner, tokenId); } // Set the base URI for tokens 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 476a872b..d6347451 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -49,31 +49,104 @@ describe("XP NFT Contract test", () => { }); }); + const validateNFT = async (nft: NFT) => { + const owner = await zetaXP.ownerOf(nft.tokenId); + await expect(owner).to.be.eq(nft.to); + + const nftData = await zetaXP.data(nft.tokenId); + await expect(nftData.xpTotal).to.be.eq(nft.data.xpTotal); + await expect(nftData.level).to.be.eq(nft.data.level); + await expect(nftData.testnetCampaignParticipant).to.be.eq(nft.data.testnetCampaignParticipant); + await expect(nftData.enrollDate).to.be.eq(nft.data.enrollDate); + await expect(nftData.mintDate).to.be.eq(nft.data.mintDate); + await expect(nftData.generation).to.be.eq(nft.data.generation); + + for (let i = 0; i < nft.tasksId.length; i++) { + const sampleTask = nft.tasks[i]; + const task = await zetaXP.tasks(nft.tokenId, nft.tasksId[i]); + await expect(task.completed).to.be.eq(sampleTask.completed); + await expect(task.count).to.be.eq(sampleTask.count); + } + + const url = await zetaXP.tokenURI(nft.tokenId); + await expect(url).to.be.eq(`https://api.zetachain.io/nft/${nft.tokenId}`); + }; + describe("NFT test", () => { - it("Should mint and NFT", async () => { + it("Should mint an NFT", async () => { + const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + + await validateNFT(sampleNFT); + }); + + it("Should emit event on minting", async () => { + const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + const tx = zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(invitee.address, 1); + }); + + it("Should revert if signature it's not correct", async () => { + const sig = await getSignature(addrs[0], invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + const tx = zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + await expect(tx).to.be.revertedWith("InvalidSigner"); + }); + + it("Should update NFT", async () => { const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); - const owner = await zetaXP.ownerOf(1); - await expect(owner).to.be.eq(invitee.address); - - const nftData = await zetaXP.data(1); - await expect(nftData.xpTotal).to.be.eq(sampleNFT.data.xpTotal); - await expect(nftData.level).to.be.eq(sampleNFT.data.level); - await expect(nftData.testnetCampaignParticipant).to.be.eq(sampleNFT.data.testnetCampaignParticipant); - await expect(nftData.enrollDate).to.be.eq(sampleNFT.data.enrollDate); - await expect(nftData.mintDate).to.be.eq(sampleNFT.data.mintDate); - await expect(nftData.generation).to.be.eq(sampleNFT.data.generation); - - for (let i = 0; i < sampleNFT.tasksId.length; i++) { - const sampleTask = sampleNFT.tasks[i]; - const task = await zetaXP.tasks(sampleNFT.tokenId, sampleNFT.tasksId[i]); - await expect(task.completed).to.be.eq(sampleTask.completed); - await expect(task.count).to.be.eq(sampleTask.count); - } - - const url = await zetaXP.tokenURI(1); - await expect(url).to.be.eq("https://api.zetachain.io/nft/1"); + const updatedSampleNFT = { + data: { + enrollDate: 5435, + generation: 2314, + level: 7, + mintDate: 34, + testnetCampaignParticipant: 2, + xpTotal: 100, + }, + tasks: [ + { + completed: true, + count: 10, + }, + { + completed: true, + count: 5, + }, + ], + tasksId: [2, 3], + to: invitee.address, + tokenId: 1, + }; + + const updatedSig = await getSignature( + inviter, + invitee.address, + 1, + updatedSampleNFT.data, + updatedSampleNFT.tasksId, + updatedSampleNFT.tasks + ); + await zetaXP.updateNFT(1, updatedSampleNFT.data, updatedSampleNFT.tasksId, updatedSampleNFT.tasks, updatedSig); + + validateNFT(updatedSampleNFT); }); }); + + it("Should update base url", async () => { + await zetaXP.setBaseURI("https://api.zetachain.io/nft/v2/"); + const url = await zetaXP.baseTokenURI(); + await expect(url).to.be.eq("https://api.zetachain.io/nft/v2/"); + + const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const tokenURI = await zetaXP.tokenURI(1); + await expect(tokenURI).to.be.eq("https://api.zetachain.io/nft/v2/1"); + }); + + it("Should revert if not owner want to update base url", async () => { + const tx = zetaXP.connect(addrs[0]).setBaseURI("https://api.zetachain.io/nft/v2/"); + expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); + }); }); From 382578199ab5f50fd1b7679b6647d3351c23c343 Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Thu, 9 May 2024 12:49:12 -0300 Subject: [PATCH 04/12] Update packages/zevm-app-contracts/test/xp-nft/xp-nft.ts Co-authored-by: Lucas --- packages/zevm-app-contracts/test/xp-nft/xp-nft.ts | 5 ----- 1 file changed, 5 deletions(-) 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 d6347451..ba825ae4 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -43,11 +43,6 @@ describe("XP NFT Contract test", () => { }; }); - describe("True", () => { - it("Should be true", async () => { - expect(true).to.equal(true); - }); - }); const validateNFT = async (nft: NFT) => { const owner = await zetaXP.ownerOf(nft.tokenId); From 63cd6195a546c92247787566538c4ac1b5664a19 Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Thu, 9 May 2024 12:49:29 -0300 Subject: [PATCH 05/12] Update packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol Co-authored-by: Lucas --- packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 50db4b33..99555c36 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -27,7 +27,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { } mapping(uint256 => Data) public data; - mapping(uint256 => mapping(uint256 => Task)) public tasks; + mapping(uint256 => mapping(uint256 => Task)) public tasksByTokenId; // Base URL for NFT images string public baseTokenURI; From 3888ee97ad19d99204c95cdebabf9dbaa13658e8 Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Thu, 9 May 2024 12:49:37 -0300 Subject: [PATCH 06/12] Update packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol Co-authored-by: Lucas --- packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 99555c36..7763faf8 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -26,7 +26,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256 generation; } - mapping(uint256 => Data) public data; + mapping(uint256 => Data) public tokenData; mapping(uint256 => mapping(uint256 => Task)) public tasksByTokenId; // Base URL for NFT images From b6cf3918bced9838fd215915f972a8db80ae8923 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Thu, 9 May 2024 14:36:27 -0300 Subject: [PATCH 07/12] renames --- .../contracts/xp-nft/xpNFT.sol | 18 ++++---- .../test/xp-nft/test.helpers.ts | 7 ++-- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 41 +++++++++---------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 7763faf8..678be0b8 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -17,7 +17,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256 count; } - struct Data { + struct TokenData { uint256 xpTotal; uint256 level; uint256 testnetCampaignParticipant; @@ -26,7 +26,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256 generation; } - mapping(uint256 => Data) public tokenData; + mapping(uint256 => TokenData) public tokenData; mapping(uint256 => mapping(uint256 => Task)) public tasksByTokenId; // Base URL for NFT images @@ -86,7 +86,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { function _verify( address to, uint256 tokenId, - Data memory data_, + TokenData memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature @@ -103,7 +103,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { function _calculateHash( address to, uint256 tokenId, - Data memory data_, + TokenData memory data_, uint256[] memory taskIds, Task[] memory taskValues ) private pure returns (bytes32) { @@ -128,7 +128,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { function _updateNFT( address to, uint256 tokenId, - Data memory data_, + TokenData memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature @@ -136,9 +136,9 @@ contract ZetaXP is ERC721URIStorage, Ownable { _verify(to, tokenId, data_, taskIds, taskValues, signature); if (taskIds.length != taskValues.length) revert LengthMismatch(); - data[tokenId] = data_; + tokenData[tokenId] = data_; for (uint256 i = 0; i < taskIds.length; i++) { - tasks[tokenId][taskIds[i]] = taskValues[i]; + tasksByTokenId[tokenId][taskIds[i]] = taskValues[i]; } } @@ -146,7 +146,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { function mintNFT( address to, uint256 tokenId, - Data memory data_, + TokenData memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature @@ -162,7 +162,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { // External mint function function updateNFT( uint256 tokenId, - Data memory data_, + TokenData memory data_, uint256[] calldata taskIds, Task[] calldata taskValues, Signature calldata signature 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 c3e98794..03a5ae1b 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -6,7 +6,7 @@ export interface Task { count: number; } -export interface Data { +export interface TokenData { enrollDate: number; generation: number; level: number; @@ -16,7 +16,7 @@ export interface Data { } export interface NFT { - data: Data; + data: TokenData; tasks: Task[]; tasksId: number[]; to: string; @@ -27,7 +27,7 @@ export const getSignature = async ( signer: SignerWithAddress, to: string, tokenId: number, - data: Data, + data: TokenData, tasksId: number[], tasks: Task[] ) => { @@ -45,7 +45,6 @@ export const getSignature = async ( ] ); - let combinedPayload = payload; for (let i = 0; i < tasksId.length; i++) { payload = ethers.utils.defaultAbiCoder.encode( ["bytes", "uint256", "bool", "uint256"], 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 ba825ae4..6db3b3a8 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -8,15 +8,15 @@ import { ZetaXP } from "../../typechain-types"; import { getSignature, NFT } from "./test.helpers"; describe("XP NFT Contract test", () => { - let zetaXP: ZetaXP, inviter: SignerWithAddress, invitee: SignerWithAddress, addrs: SignerWithAddress[]; + let zetaXP: ZetaXP, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; let sampleNFT: NFT; beforeEach(async () => { - [inviter, invitee, ...addrs] = await ethers.getSigners(); + [signer, user, ...addrs] = await ethers.getSigners(); const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); //@ts-ignore - zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", "https://api.zetachain.io/nft/", inviter.address); + zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", "https://api.zetachain.io/nft/", signer.address); sampleNFT = { data: { @@ -38,17 +38,16 @@ describe("XP NFT Contract test", () => { }, ], tasksId: [2, 3], - to: invitee.address, + to: user.address, tokenId: 1, }; }); - const validateNFT = async (nft: NFT) => { const owner = await zetaXP.ownerOf(nft.tokenId); await expect(owner).to.be.eq(nft.to); - const nftData = await zetaXP.data(nft.tokenId); + const nftData = await zetaXP.tokenData(nft.tokenId); await expect(nftData.xpTotal).to.be.eq(nft.data.xpTotal); await expect(nftData.level).to.be.eq(nft.data.level); await expect(nftData.testnetCampaignParticipant).to.be.eq(nft.data.testnetCampaignParticipant); @@ -58,7 +57,7 @@ describe("XP NFT Contract test", () => { for (let i = 0; i < nft.tasksId.length; i++) { const sampleTask = nft.tasks[i]; - const task = await zetaXP.tasks(nft.tokenId, nft.tasksId[i]); + const task = await zetaXP.tasksByTokenId(nft.tokenId, nft.tasksId[i]); await expect(task.completed).to.be.eq(sampleTask.completed); await expect(task.count).to.be.eq(sampleTask.count); } @@ -69,27 +68,27 @@ describe("XP NFT Contract test", () => { describe("NFT test", () => { it("Should mint an NFT", async () => { - const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); await validateNFT(sampleNFT); }); it("Should emit event on minting", async () => { - const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - const tx = zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); - await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(invitee.address, 1); + const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + const tx = zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); }); it("Should revert if signature it's not correct", async () => { - const sig = await getSignature(addrs[0], invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - const tx = zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const sig = await getSignature(addrs[0], user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + const tx = zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); await expect(tx).to.be.revertedWith("InvalidSigner"); }); it("Should update NFT", async () => { - const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); const updatedSampleNFT = { data: { @@ -111,13 +110,13 @@ describe("XP NFT Contract test", () => { }, ], tasksId: [2, 3], - to: invitee.address, + to: user.address, tokenId: 1, }; const updatedSig = await getSignature( - inviter, - invitee.address, + signer, + user.address, 1, updatedSampleNFT.data, updatedSampleNFT.tasksId, @@ -134,8 +133,8 @@ describe("XP NFT Contract test", () => { const url = await zetaXP.baseTokenURI(); await expect(url).to.be.eq("https://api.zetachain.io/nft/v2/"); - const sig = await getSignature(inviter, invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(invitee.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); + await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); const tokenURI = await zetaXP.tokenURI(1); await expect(tokenURI).to.be.eq("https://api.zetachain.io/nft/v2/1"); }); From 5108de2672b7299cc8496fa9f88e116c2490207f Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 24 May 2024 17:07:57 -0300 Subject: [PATCH 08/12] rename and add logic --- .../contracts/xp-nft/xpNFT.sol | 138 +++++------ .../test/xp-nft/test.helpers.ts | 44 ++-- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 215 +++++++++++++----- 3 files changed, 262 insertions(+), 135 deletions(-) diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 678be0b8..6e128630 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -17,7 +17,7 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256 count; } - struct TokenData { + struct ZetaXPData { uint256 xpTotal; uint256 level; uint256 testnetCampaignParticipant; @@ -26,8 +26,19 @@ contract ZetaXP is ERC721URIStorage, Ownable { uint256 generation; } - mapping(uint256 => TokenData) public tokenData; + struct UpdateData { + address to; + uint256 tokenId; + ZetaXPData xpData; + uint256[] taskIds; + Task[] taskValues; + Signature signature; + uint256 sigTimestamp; + } + + mapping(uint256 => ZetaXPData) public zetaXPData; mapping(uint256 => mapping(uint256 => Task)) public tasksByTokenId; + mapping(uint256 => uint256) lastUpdateTimestampByTokenId; // Base URL for NFT images string public baseTokenURI; @@ -40,6 +51,8 @@ contract ZetaXP is ERC721URIStorage, Ownable { error InvalidSigner(); error LengthMismatch(); + error TransferNotAllowed(); + error OutdatedSignature(); constructor( string memory name, @@ -83,98 +96,87 @@ contract ZetaXP is ERC721URIStorage, Ownable { return string(bstr); } - function _verify( - address to, - uint256 tokenId, - TokenData memory data_, - uint256[] calldata taskIds, - Task[] calldata taskValues, - Signature calldata signature - ) private view { - bytes32 payloadHash = _calculateHash(to, tokenId, data_, taskIds, taskValues); + function _verify(UpdateData memory updateData) private view { + bytes32 payloadHash = _calculateHash(updateData); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash)); - address messageSigner = ecrecover(messageHash, signature.v, signature.r, signature.s); + address messageSigner = ecrecover( + messageHash, + updateData.signature.v, + updateData.signature.r, + updateData.signature.s + ); if (signerAddress != messageSigner) revert InvalidSigner(); + if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[updateData.tokenId]) revert OutdatedSignature(); } // Function to compute the hash of the data and tasks for a token - function _calculateHash( - address to, - uint256 tokenId, - TokenData memory data_, - uint256[] memory taskIds, - Task[] memory taskValues - ) private pure returns (bytes32) { + function _calculateHash(UpdateData memory updateData) private pure returns (bytes32) { + ZetaXPData memory xpData = updateData.xpData; bytes memory encodedData = abi.encode( - to, - tokenId, - data_.xpTotal, - data_.level, - data_.testnetCampaignParticipant, - data_.enrollDate, - data_.mintDate, - data_.generation + updateData.to, + updateData.tokenId, + updateData.sigTimestamp, + xpData.xpTotal, + xpData.level, + xpData.testnetCampaignParticipant, + xpData.enrollDate, + xpData.mintDate, + xpData.generation ); - for (uint256 i = 0; i < taskIds.length; i++) { - encodedData = abi.encode(encodedData, taskIds[i], taskValues[i].completed, taskValues[i].count); + for (uint256 i = 0; i < updateData.taskIds.length; i++) { + encodedData = abi.encode( + encodedData, + updateData.taskIds[i], + updateData.taskValues[i].completed, + updateData.taskValues[i].count + ); } return keccak256(encodedData); } - function _updateNFT( - address to, - uint256 tokenId, - TokenData memory data_, - uint256[] calldata taskIds, - Task[] calldata taskValues, - Signature calldata signature - ) internal { - _verify(to, tokenId, data_, taskIds, taskValues, signature); - if (taskIds.length != taskValues.length) revert LengthMismatch(); - - tokenData[tokenId] = data_; - for (uint256 i = 0; i < taskIds.length; i++) { - tasksByTokenId[tokenId][taskIds[i]] = taskValues[i]; + function _updateNFT(UpdateData memory updateData) internal { + _verify(updateData); + lastUpdateTimestampByTokenId[updateData.tokenId] = updateData.sigTimestamp; + ZetaXPData memory xpData = updateData.xpData; + zetaXPData[updateData.tokenId] = xpData; + + if (updateData.taskIds.length != updateData.taskValues.length) revert LengthMismatch(); + + zetaXPData[updateData.tokenId] = updateData.xpData; + for (uint256 i = 0; i < updateData.taskIds.length; i++) { + tasksByTokenId[updateData.tokenId][updateData.taskIds[i]] = updateData.taskValues[i]; } } // External mint function - function mintNFT( - address to, - uint256 tokenId, - TokenData memory data_, - uint256[] calldata taskIds, - Task[] calldata taskValues, - Signature calldata signature - ) external { - _mint(to, tokenId); - _setTokenURI(tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(tokenId)))); - - _updateNFT(to, tokenId, data_, taskIds, taskValues, signature); - - emit NewNFTMinted(to, tokenId); + function mintNFT(UpdateData calldata mintData) external { + _mint(mintData.to, mintData.tokenId); + _setTokenURI(mintData.tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(mintData.tokenId)))); + + _updateNFT(mintData); + + emit NewNFTMinted(mintData.to, mintData.tokenId); } // External mint function - function updateNFT( - uint256 tokenId, - TokenData memory data_, - uint256[] calldata taskIds, - Task[] calldata taskValues, - Signature calldata signature - ) external { - address owner = ownerOf(tokenId); - _updateNFT(owner, tokenId, data_, taskIds, taskValues, signature); - - emit NFTUpdated(owner, tokenId); + function updateNFT(UpdateData memory updateData) external { + address owner = ownerOf(updateData.tokenId); + updateData.to = owner; + _updateNFT(updateData); + + emit NFTUpdated(owner, updateData.tokenId); } // Set the base URI for tokens function setBaseURI(string calldata _uri) external onlyOwner { baseTokenURI = _uri; } + + function _transfer(address from, address to, uint256 tokenId) internal override { + revert TransferNotAllowed(); + } } 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 03a5ae1b..e98d7445 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -6,7 +6,7 @@ export interface Task { count: number; } -export interface TokenData { +export interface ZetaXPData { enrollDate: number; generation: number; level: number; @@ -15,40 +15,52 @@ export interface TokenData { xpTotal: number; } +export interface Signature { + r: string; + s: string; + v: number; +} + export interface NFT { - data: TokenData; - tasks: Task[]; - tasksId: number[]; + taskIds: number[]; + taskValues: Task[]; to: string; tokenId: number; + xpData: ZetaXPData; +} + +export interface UpdateParam extends NFT { + sigTimestamp: number; + signature: Signature; } export const getSignature = async ( signer: SignerWithAddress, + timestamp: number, to: string, tokenId: number, - data: TokenData, - tasksId: number[], - tasks: Task[] + nft: NFT ) => { + const { xpData } = nft; let payload = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256"], + ["address", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256"], [ to, tokenId, - data.xpTotal, - data.level, - data.testnetCampaignParticipant, - data.enrollDate, - data.mintDate, - data.generation, + timestamp, + xpData.xpTotal, + xpData.level, + xpData.testnetCampaignParticipant, + xpData.enrollDate, + xpData.mintDate, + xpData.generation, ] ); - for (let i = 0; i < tasksId.length; i++) { + for (let i = 0; i < nft.taskIds.length; i++) { payload = ethers.utils.defaultAbiCoder.encode( ["bytes", "uint256", "bool", "uint256"], - [payload, tasksId[i], tasks[i].completed, tasks[i].count] + [payload, nft.taskIds[i], nft.taskValues[i].completed, nft.taskValues[i].count] ); } 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 6db3b3a8..56e042cb 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -5,7 +5,7 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { ethers } from "hardhat"; import { ZetaXP } from "../../typechain-types"; -import { getSignature, NFT } from "./test.helpers"; +import { getSignature, NFT, UpdateParam } from "./test.helpers"; describe("XP NFT Contract test", () => { let zetaXP: ZetaXP, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; @@ -19,15 +19,8 @@ describe("XP NFT Contract test", () => { zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", "https://api.zetachain.io/nft/", signer.address); sampleNFT = { - data: { - enrollDate: 5435, - generation: 2314, - level: 7, - mintDate: 34, - testnetCampaignParticipant: 2, - xpTotal: 67, - }, - tasks: [ + taskIds: [2, 3], + taskValues: [ { completed: true, count: 10, @@ -37,9 +30,17 @@ describe("XP NFT Contract test", () => { count: 5, }, ], - tasksId: [2, 3], to: user.address, tokenId: 1, + xpData: { + enrollDate: 5435, + generation: 2314, + level: 7, + mintDate: 34, + + testnetCampaignParticipant: 2, + xpTotal: 67, + }, }; }); @@ -47,17 +48,18 @@ describe("XP NFT Contract test", () => { const owner = await zetaXP.ownerOf(nft.tokenId); await expect(owner).to.be.eq(nft.to); - const nftData = await zetaXP.tokenData(nft.tokenId); - await expect(nftData.xpTotal).to.be.eq(nft.data.xpTotal); - await expect(nftData.level).to.be.eq(nft.data.level); - await expect(nftData.testnetCampaignParticipant).to.be.eq(nft.data.testnetCampaignParticipant); - await expect(nftData.enrollDate).to.be.eq(nft.data.enrollDate); - await expect(nftData.mintDate).to.be.eq(nft.data.mintDate); - await expect(nftData.generation).to.be.eq(nft.data.generation); - - for (let i = 0; i < nft.tasksId.length; i++) { - const sampleTask = nft.tasks[i]; - const task = await zetaXP.tasksByTokenId(nft.tokenId, nft.tasksId[i]); + const nftData = await zetaXP.zetaXPData(nft.tokenId); + const { xpData } = nft; + await expect(nftData.xpTotal).to.be.eq(xpData.xpTotal); + await expect(nftData.level).to.be.eq(xpData.level); + await expect(nftData.testnetCampaignParticipant).to.be.eq(xpData.testnetCampaignParticipant); + await expect(nftData.enrollDate).to.be.eq(xpData.enrollDate); + await expect(nftData.mintDate).to.be.eq(xpData.mintDate); + await expect(nftData.generation).to.be.eq(xpData.generation); + + for (let i = 0; i < nft.taskIds.length; i++) { + const sampleTask = nft.taskValues[i]; + const task = await zetaXP.tasksByTokenId(nft.tokenId, nft.taskIds[i]); await expect(task.completed).to.be.eq(sampleTask.completed); await expect(task.count).to.be.eq(sampleTask.count); } @@ -68,38 +70,80 @@ describe("XP NFT Contract test", () => { describe("NFT test", () => { it("Should mint an NFT", async () => { - const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + await zetaXP.mintNFT(nftParams); await validateNFT(sampleNFT); }); it("Should emit event on minting", async () => { - const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - const tx = zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); }); it("Should revert if signature it's not correct", async () => { - const sig = await getSignature(addrs[0], user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - const tx = zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(addrs[0], sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.be.revertedWith("InvalidSigner"); }); it("Should update NFT", async () => { - const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + await zetaXP.mintNFT(nftParams); + } const updatedSampleNFT = { - data: { - enrollDate: 5435, - generation: 2314, - level: 7, - mintDate: 34, - testnetCampaignParticipant: 2, - xpTotal: 100, - }, - tasks: [ + taskIds: [2, 3], + taskValues: [ { completed: true, count: 10, @@ -109,20 +153,34 @@ describe("XP NFT Contract test", () => { count: 5, }, ], - tasksId: [2, 3], to: user.address, tokenId: 1, + xpData: { + enrollDate: 5435, + generation: 2314, + level: 7, + mintDate: 34, + testnetCampaignParticipant: 2, + xpTotal: 100, + }, }; - const updatedSig = await getSignature( - signer, - user.address, - 1, - updatedSampleNFT.data, - updatedSampleNFT.tasksId, - updatedSampleNFT.tasks - ); - await zetaXP.updateNFT(1, updatedSampleNFT.data, updatedSampleNFT.tasksId, updatedSampleNFT.tasks, updatedSig); + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, user.address, 1, updatedSampleNFT); + + const nftParams: UpdateParam = { + ...updatedSampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + await zetaXP.updateNFT(nftParams); + } validateNFT(updatedSampleNFT); }); @@ -133,8 +191,22 @@ describe("XP NFT Contract test", () => { const url = await zetaXP.baseTokenURI(); await expect(url).to.be.eq("https://api.zetachain.io/nft/v2/"); - const sig = await getSignature(signer, user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks); - await zetaXP.mintNFT(user.address, 1, sampleNFT.data, sampleNFT.tasksId, sampleNFT.tasks, sig); + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + await zetaXP.mintNFT(nftParams); + } const tokenURI = await zetaXP.tokenURI(1); await expect(tokenURI).to.be.eq("https://api.zetachain.io/nft/v2/1"); }); @@ -143,4 +215,45 @@ describe("XP NFT Contract test", () => { const tx = zetaXP.connect(addrs[0]).setBaseURI("https://api.zetachain.io/nft/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 signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + 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 signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + to: user.address, + tokenId: 1, + } as UpdateParam; + + await zetaXP.mintNFT(nftParams); + + const tx = zetaXP.updateNFT(nftParams); + await expect(tx).to.be.revertedWith("OutdatedSignature"); + }); }); From d288a0414b84c1fdaea37d0ca76fdc7dcfa4b4f8 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 24 May 2024 17:13:14 -0300 Subject: [PATCH 09/12] refactor --- packages/zevm-app-contracts/test/xp-nft/xp-nft.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 56e042cb..c66d446f 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -7,6 +7,8 @@ import { ethers } from "hardhat"; import { ZetaXP } from "../../typechain-types"; import { getSignature, NFT, UpdateParam } from "./test.helpers"; +const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; + describe("XP NFT Contract test", () => { let zetaXP: ZetaXP, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; let sampleNFT: NFT; @@ -16,7 +18,7 @@ describe("XP NFT Contract test", () => { const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); //@ts-ignore - zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", "https://api.zetachain.io/nft/", signer.address); + zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", ZETA_BASE_URL, signer.address); sampleNFT = { taskIds: [2, 3], @@ -65,7 +67,7 @@ describe("XP NFT Contract test", () => { } const url = await zetaXP.tokenURI(nft.tokenId); - await expect(url).to.be.eq(`https://api.zetachain.io/nft/${nft.tokenId}`); + await expect(url).to.be.eq(`${ZETA_BASE_URL}${nft.tokenId}`); }; describe("NFT test", () => { @@ -187,9 +189,9 @@ describe("XP NFT Contract test", () => { }); it("Should update base url", async () => { - await zetaXP.setBaseURI("https://api.zetachain.io/nft/v2/"); + await zetaXP.setBaseURI(`${ZETA_BASE_URL}v2/`); const url = await zetaXP.baseTokenURI(); - await expect(url).to.be.eq("https://api.zetachain.io/nft/v2/"); + await expect(url).to.be.eq(`${ZETA_BASE_URL}v2/`); { const currentBlock = await ethers.provider.getBlock("latest"); @@ -208,11 +210,11 @@ describe("XP NFT Contract test", () => { await zetaXP.mintNFT(nftParams); } const tokenURI = await zetaXP.tokenURI(1); - await expect(tokenURI).to.be.eq("https://api.zetachain.io/nft/v2/1"); + await expect(tokenURI).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("https://api.zetachain.io/nft/v2/"); + const tx = zetaXP.connect(addrs[0]).setBaseURI(`${ZETA_BASE_URL}v2/`); expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); From ec6702b812986a3744c2b379eec140779292571f Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Mon, 1 Jul 2024 16:29:10 -0300 Subject: [PATCH 10/12] add upgradable --- .../contracts/xp-nft/test/xpNFTV2.sol | 10 + .../contracts/xp-nft/xpNFT.sol | 72 +- packages/zevm-app-contracts/hardhat.config.ts | 1 + packages/zevm-app-contracts/package.json | 2 + .../test/xp-nft/test.helpers.ts | 40 +- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 251 +++--- yarn.lock | 770 +++++++++++++++++- 7 files changed, 903 insertions(+), 243 deletions(-) create mode 100644 packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol diff --git a/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol new file mode 100644 index 00000000..5995f6fa --- /dev/null +++ b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.7; + +import "../xpNFT.sol"; + +contract ZetaXPV2 is ZetaXP { + function version() public pure override returns (string memory) { + return "2.0.0"; + } +} diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 6e128630..c9c2c949 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.7; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract ZetaXP is ERC721URIStorage, Ownable { +contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { /* An ECDSA signature. */ struct Signature { uint8 v; @@ -12,33 +12,16 @@ contract ZetaXP is ERC721URIStorage, Ownable { bytes32 s; } - struct Task { - bool completed; - uint256 count; - } - - struct ZetaXPData { - uint256 xpTotal; - uint256 level; - uint256 testnetCampaignParticipant; - uint256 enrollDate; - uint256 mintDate; - uint256 generation; - } - struct UpdateData { address to; uint256 tokenId; - ZetaXPData xpData; - uint256[] taskIds; - Task[] taskValues; Signature signature; uint256 sigTimestamp; + uint256 signedUp; } - mapping(uint256 => ZetaXPData) public zetaXPData; - mapping(uint256 => mapping(uint256 => Task)) public tasksByTokenId; mapping(uint256 => uint256) lastUpdateTimestampByTokenId; + mapping(uint256 => uint256) signedUpByTokenId; // Base URL for NFT images string public baseTokenURI; @@ -54,23 +37,30 @@ contract ZetaXP is ERC721URIStorage, Ownable { error TransferNotAllowed(); error OutdatedSignature(); - constructor( + function initialize( string memory name, string memory symbol, string memory baseTokenURI_, address signerAddress_ - ) ERC721(name, symbol) { + ) public initializer { + __ERC721_init(name, symbol); + __Ownable_init(); baseTokenURI = baseTokenURI_; signerAddress = signerAddress_; } + function version() public pure virtual returns (string memory) { + return "1.0.0"; + } + // The following functions are overrides required by Solidity. + function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable) returns (string memory) { + _requireMinted(tokenId); - function tokenURI(uint256 tokenId) public view override(ERC721URIStorage) returns (string memory) { - return super.tokenURI(tokenId); + return string(abi.encodePacked(baseTokenURI, _uint2str(tokenId))); } - function supportsInterface(bytes4 interfaceId) public view override(ERC721URIStorage) returns (bool) { + function supportsInterface(bytes4 interfaceId) public view override(ERC721Upgradeable) returns (bool) { return super.supportsInterface(interfaceId); } @@ -113,49 +103,25 @@ contract ZetaXP is ERC721URIStorage, Ownable { // Function to compute the hash of the data and tasks for a token function _calculateHash(UpdateData memory updateData) private pure returns (bytes32) { - ZetaXPData memory xpData = updateData.xpData; bytes memory encodedData = abi.encode( updateData.to, updateData.tokenId, updateData.sigTimestamp, - xpData.xpTotal, - xpData.level, - xpData.testnetCampaignParticipant, - xpData.enrollDate, - xpData.mintDate, - xpData.generation + updateData.signedUp ); - for (uint256 i = 0; i < updateData.taskIds.length; i++) { - encodedData = abi.encode( - encodedData, - updateData.taskIds[i], - updateData.taskValues[i].completed, - updateData.taskValues[i].count - ); - } - return keccak256(encodedData); } function _updateNFT(UpdateData memory updateData) internal { _verify(updateData); lastUpdateTimestampByTokenId[updateData.tokenId] = updateData.sigTimestamp; - ZetaXPData memory xpData = updateData.xpData; - zetaXPData[updateData.tokenId] = xpData; - - if (updateData.taskIds.length != updateData.taskValues.length) revert LengthMismatch(); - - zetaXPData[updateData.tokenId] = updateData.xpData; - for (uint256 i = 0; i < updateData.taskIds.length; i++) { - tasksByTokenId[updateData.tokenId][updateData.taskIds[i]] = updateData.taskValues[i]; - } + signedUpByTokenId[updateData.tokenId] = updateData.signedUp; } // External mint function function mintNFT(UpdateData calldata mintData) external { _mint(mintData.to, mintData.tokenId); - _setTokenURI(mintData.tokenId, string(abi.encodePacked(baseTokenURI, _uint2str(mintData.tokenId)))); _updateNFT(mintData); diff --git a/packages/zevm-app-contracts/hardhat.config.ts b/packages/zevm-app-contracts/hardhat.config.ts index f7a284e4..5e42ce0f 100644 --- a/packages/zevm-app-contracts/hardhat.config.ts +++ b/packages/zevm-app-contracts/hardhat.config.ts @@ -1,6 +1,7 @@ import "@nomicfoundation/hardhat-verify"; import "@nomiclabs/hardhat-waffle"; import "@typechain/hardhat"; +import "@openzeppelin/hardhat-upgrades"; import "hardhat-gas-reporter"; import "solidity-coverage"; import "tsconfig-paths/register"; diff --git a/packages/zevm-app-contracts/package.json b/packages/zevm-app-contracts/package.json index c836f53e..4069bf6e 100644 --- a/packages/zevm-app-contracts/package.json +++ b/packages/zevm-app-contracts/package.json @@ -35,6 +35,8 @@ }, "dependencies": { "@openzeppelin/contracts": "4.9.3", + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@openzeppelin/hardhat-upgrades": "^1.7.0-rc.0", "@uniswap/v2-periphery": "1.1.0-beta.0", "@zetachain/networks": "^4.0.0", "@zetachain/protocol-contracts": "^4.0.1", 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 e98d7445..8d15d292 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -1,20 +1,6 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { ethers } from "hardhat"; -export interface Task { - completed: boolean; - count: number; -} - -export interface ZetaXPData { - enrollDate: number; - generation: number; - level: number; - mintDate: number; - testnetCampaignParticipant: number; - xpTotal: number; -} - export interface Signature { r: string; s: string; @@ -22,11 +8,9 @@ export interface Signature { } export interface NFT { - taskIds: number[]; - taskValues: Task[]; + signedUp: number; to: string; tokenId: number; - xpData: ZetaXPData; } export interface UpdateParam extends NFT { @@ -41,29 +25,11 @@ export const getSignature = async ( tokenId: number, nft: NFT ) => { - const { xpData } = nft; let payload = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256"], - [ - to, - tokenId, - timestamp, - xpData.xpTotal, - xpData.level, - xpData.testnetCampaignParticipant, - xpData.enrollDate, - xpData.mintDate, - xpData.generation, - ] + ["address", "uint256", "uint256", "uint256"], + [to, tokenId, timestamp, nft.signedUp] ); - for (let i = 0; i < nft.taskIds.length; i++) { - payload = ethers.utils.defaultAbiCoder.encode( - ["bytes", "uint256", "bool", "uint256"], - [payload, nft.taskIds[i], nft.taskValues[i].completed, nft.taskValues[i].count] - ); - } - const payloadHash = ethers.utils.keccak256(payload); // This adds the message prefix 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 c66d446f..a346b11f 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -2,7 +2,7 @@ import { expect, use } from "chai"; import { solidity } from "ethereum-waffle"; use(solidity); import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { ZetaXP } from "../../typechain-types"; import { getSignature, NFT, UpdateParam } from "./test.helpers"; @@ -17,32 +17,14 @@ describe("XP NFT Contract test", () => { [signer, user, ...addrs] = await ethers.getSigners(); const zetaXPFactory = await ethers.getContractFactory("ZetaXP"); - //@ts-ignore - zetaXP = await zetaXPFactory.deploy("ZETA NFT", "ZNFT", ZETA_BASE_URL, signer.address); + zetaXP = await upgrades.deployProxy(zetaXPFactory, ["ZETA NFT", "ZNFT", ZETA_BASE_URL, signer.address]); + + await zetaXP.deployed(); sampleNFT = { - taskIds: [2, 3], - taskValues: [ - { - completed: true, - count: 10, - }, - { - completed: false, - count: 5, - }, - ], + signedUp: 1234, to: user.address, tokenId: 1, - xpData: { - enrollDate: 5435, - generation: 2314, - level: 7, - mintDate: 34, - - testnetCampaignParticipant: 2, - xpTotal: 67, - }, }; }); @@ -50,167 +32,157 @@ describe("XP NFT Contract test", () => { const owner = await zetaXP.ownerOf(nft.tokenId); await expect(owner).to.be.eq(nft.to); - const nftData = await zetaXP.zetaXPData(nft.tokenId); - const { xpData } = nft; - await expect(nftData.xpTotal).to.be.eq(xpData.xpTotal); - await expect(nftData.level).to.be.eq(xpData.level); - await expect(nftData.testnetCampaignParticipant).to.be.eq(xpData.testnetCampaignParticipant); - await expect(nftData.enrollDate).to.be.eq(xpData.enrollDate); - await expect(nftData.mintDate).to.be.eq(xpData.mintDate); - await expect(nftData.generation).to.be.eq(xpData.generation); - - for (let i = 0; i < nft.taskIds.length; i++) { - const sampleTask = nft.taskValues[i]; - const task = await zetaXP.tasksByTokenId(nft.tokenId, nft.taskIds[i]); - await expect(task.completed).to.be.eq(sampleTask.completed); - await expect(task.count).to.be.eq(sampleTask.count); - } - const url = await zetaXP.tokenURI(nft.tokenId); await expect(url).to.be.eq(`${ZETA_BASE_URL}${nft.tokenId}`); }; - describe("NFT test", () => { - it("Should mint an NFT", async () => { + it("Should mint an NFT", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + } as UpdateParam; + + await zetaXP.mintNFT(nftParams); + + await validateNFT(sampleNFT); + }); + + it("Should emit event on minting", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + } as UpdateParam; + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); + }); + + it("Should revert if signature it's not correct", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(addrs[0], sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + } as UpdateParam; + + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.be.revertedWith("InvalidSigner"); + }); + + it("Should update NFT", async () => { + { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; await zetaXP.mintNFT(nftParams); + } - await validateNFT(sampleNFT); - }); + const updatedSampleNFT = { ...sampleNFT, signedUp: 4321 }; - it("Should emit event on minting", async () => { + { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, updatedSampleNFT); const nftParams: UpdateParam = { - ...sampleNFT, + ...updatedSampleNFT, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; - const tx = zetaXP.mintNFT(nftParams); - await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); - }); - it("Should revert if signature it's not correct", async () => { + await zetaXP.updateNFT(nftParams); + } + + validateNFT(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 signature = await getSignature(addrs[0], sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; - const tx = zetaXP.mintNFT(nftParams); - await expect(tx).to.be.revertedWith("InvalidSigner"); - }); - - it("Should update NFT", async () => { - { - const currentBlock = await ethers.provider.getBlock("latest"); - const sigTimestamp = currentBlock.timestamp; - - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); - - const nftParams: UpdateParam = { - ...sampleNFT, - sigTimestamp, - signature, - to: user.address, - tokenId: 1, - } as UpdateParam; - - await zetaXP.mintNFT(nftParams); - } - - const updatedSampleNFT = { - taskIds: [2, 3], - taskValues: [ - { - completed: true, - count: 10, - }, - { - completed: true, - count: 5, - }, - ], - to: user.address, - tokenId: 1, - xpData: { - enrollDate: 5435, - generation: 2314, - level: 7, - mintDate: 34, - testnetCampaignParticipant: 2, - xpTotal: 100, - }, - }; - - { - const currentBlock = await ethers.provider.getBlock("latest"); - const sigTimestamp = currentBlock.timestamp; - - const signature = await getSignature(signer, sigTimestamp, user.address, 1, updatedSampleNFT); - - const nftParams: UpdateParam = { - ...updatedSampleNFT, - sigTimestamp, - signature, - to: user.address, - tokenId: 1, - } as UpdateParam; - - await zetaXP.updateNFT(nftParams); - } - - validateNFT(updatedSampleNFT); - }); + 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", async () => { + it("Should update base url for minted tokens", async () => { + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + } as UpdateParam; + + 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, tokenId: 2 }; const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, user.address, sampleNFT2.tokenId, sampleNFT2); const nftParams: UpdateParam = { - ...sampleNFT, + ...sampleNFT2, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; await zetaXP.mintNFT(nftParams); } - const tokenURI = await zetaXP.tokenURI(1); - await expect(tokenURI).to.be.eq(`${ZETA_BASE_URL}v2/1`); + 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 () => { @@ -223,14 +195,12 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -243,14 +213,12 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, 1, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, - to: user.address, - tokenId: 1, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -258,4 +226,15 @@ describe("XP NFT Contract test", () => { const tx = zetaXP.updateNFT(nftParams); await expect(tx).to.be.revertedWith("OutdatedSignature"); }); + + it("Should upgrade", async () => { + const version = await zetaXP.version(); + await expect(version).to.be.eq("1.0.0"); + + const ZetaXPFactory = await ethers.getContractFactory("ZetaXPV2"); + const zetaXPV2 = await upgrades.upgradeProxy(zetaXP.address, ZetaXPFactory); + + const version2 = await zetaXPV2.version(); + await expect(version2).to.be.eq("2.0.0"); + }); }); diff --git a/yarn.lock b/yarn.lock index ca5b69d7..03265ee4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,47 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/sha256-js@npm:1.2.2": + version: 1.2.2 + resolution: "@aws-crypto/sha256-js@npm:1.2.2" + dependencies: + "@aws-crypto/util": ^1.2.2 + "@aws-sdk/types": ^3.1.0 + tslib: ^1.11.1 + checksum: b6aeb71f88ecc219c5473803345bb15150ecd056a337582638dd60fb2344e0ff63908c684ef55268b249290fe0776e8e6fc830605f0aad850ff325b9cfe0dc6a + languageName: node + linkType: hard + +"@aws-crypto/util@npm:^1.2.2": + version: 1.2.2 + resolution: "@aws-crypto/util@npm:1.2.2" + dependencies: + "@aws-sdk/types": ^3.1.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: 54d72ce4945b52f3fcbcb62574a55bc038cc3ff165742f340cabca1bdc979faf69c97709cf56daf434e4ad69e33582a04a64da33b4e4e13b25c6ff67f8abe5ae + languageName: node + linkType: hard + +"@aws-sdk/types@npm:^3.1.0": + version: 3.598.0 + resolution: "@aws-sdk/types@npm:3.598.0" + dependencies: + "@smithy/types": ^3.1.0 + tslib: ^2.6.2 + checksum: 9b2bd50d6935422dd1046e6eaa48c4c774d06aa1374bf4600a3af2c7a52432b5e25ec111cf49976b07b03d7cb5f4fa6fd44b7ce8a67dd0b3a4cee4abaf9e6fa5 + languageName: node + linkType: hard + +"@aws-sdk/util-utf8-browser@npm:^3.0.0": + version: 3.259.0 + resolution: "@aws-sdk/util-utf8-browser@npm:3.259.0" + dependencies: + tslib: ^2.3.1 + checksum: b6a1e580da1c9b62c749814182a7649a748ca4253edb4063aa521df97d25b76eae3359eb1680b86f71aac668e05cc05c514379bca39ebf4ba998ae4348412da8 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" @@ -2696,6 +2737,13 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts-upgradeable@npm:4.9.3": + version: 4.9.3 + resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.3" + checksum: bda0240b1d44c913ec5a4e109c622f216c2bbd7b468d210822f75782a5f7fe0609d08bf03b78b253333625e99e507cf2f75212f1de3b274bd9fc64ae967aeec3 + languageName: node + linkType: hard + "@openzeppelin/contracts@npm:4.9.3, @openzeppelin/contracts@npm:^4.9.3": version: 4.9.3 resolution: "@openzeppelin/contracts@npm:4.9.3" @@ -2710,6 +2758,74 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/defender-base-client@npm:^1.46.0": + version: 1.54.6 + resolution: "@openzeppelin/defender-base-client@npm:1.54.6" + dependencies: + amazon-cognito-identity-js: ^6.0.1 + async-retry: ^1.3.3 + axios: ^1.4.0 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 75b260a545fd734b7678d5591b29847f5211466bad25caca95fc24490c36d49b419b1ef06e6abc9dc6c9b4be97ae2957e033f3050a8675ddd246da817eaedd83 + languageName: node + linkType: hard + +"@openzeppelin/hardhat-upgrades@npm:^1.7.0-rc.0": + version: 1.28.0 + resolution: "@openzeppelin/hardhat-upgrades@npm:1.28.0" + dependencies: + "@openzeppelin/defender-base-client": ^1.46.0 + "@openzeppelin/platform-deploy-client": ^0.8.0 + "@openzeppelin/upgrades-core": ^1.27.0 + chalk: ^4.1.0 + debug: ^4.1.1 + proper-lockfile: ^4.1.1 + peerDependencies: + "@nomiclabs/hardhat-ethers": ^2.0.0 + "@nomiclabs/hardhat-etherscan": ^3.1.0 + ethers: ^5.0.5 + hardhat: ^2.0.2 + peerDependenciesMeta: + "@nomiclabs/harhdat-etherscan": + optional: true + bin: + migrate-oz-cli-project: dist/scripts/migrate-oz-cli-project.js + checksum: b37a5eb7c3a5c1fb4ae6754f5fe1d6e93eb6bc143861f57babf5c7d66706ee3e44ca7d57db17ce2ec6c7014f09c269d506f62b3b116897407fdb0d1ff68f4925 + languageName: node + linkType: hard + +"@openzeppelin/platform-deploy-client@npm:^0.8.0": + version: 0.8.0 + resolution: "@openzeppelin/platform-deploy-client@npm:0.8.0" + dependencies: + "@ethersproject/abi": ^5.6.3 + "@openzeppelin/defender-base-client": ^1.46.0 + axios: ^0.21.2 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 0ce050e185a812c366ceef7dcfce526815babab9396275d9724f324a548ddfdca92ea9913ce61356dcd8c014fc495890c8e21afab4a197e0e14e761c698cce68 + languageName: node + linkType: hard + +"@openzeppelin/upgrades-core@npm:^1.27.0": + version: 1.34.1 + resolution: "@openzeppelin/upgrades-core@npm:1.34.1" + dependencies: + cbor: ^9.0.0 + chalk: ^4.1.0 + compare-versions: ^6.0.0 + debug: ^4.1.1 + ethereumjs-util: ^7.0.3 + minimist: ^1.2.7 + proper-lockfile: ^4.1.1 + solidity-ast: ^0.4.51 + bin: + openzeppelin-upgrades-core: dist/cli/cli.js + checksum: a35024d1861c83531cc434f8d2bda346dc2133e97b6d40128c229658cf680935cf36b4eaee742ec2d7c662d3371d0ee14d6dd6dafb5e0278f661adf0066f2f0b + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2907,6 +3023,15 @@ __metadata: languageName: node linkType: hard +"@smithy/types@npm:^3.1.0": + version: 3.3.0 + resolution: "@smithy/types@npm:3.3.0" + dependencies: + tslib: ^2.6.2 + checksum: 29bb5f83c41e32f8d4094a2aba2d3dfbd763ab5943784a700f3fa22df0dcf0ccac1b1907f7a87fbb9f6f2269fcd4750524bcb48f892249e200ffe397c0981309 + languageName: node + linkType: hard + "@solidity-parser/parser@npm:^0.14.0": version: 0.14.5 resolution: "@solidity-parser/parser@npm:0.14.5" @@ -3662,6 +3787,8 @@ __metadata: resolution: "@zetachain/zevm-app-contracts@workspace:packages/zevm-app-contracts" dependencies: "@openzeppelin/contracts": 4.9.3 + "@openzeppelin/contracts-upgradeable": 4.9.3 + "@openzeppelin/hardhat-upgrades": ^1.7.0-rc.0 "@uniswap/v2-periphery": 1.1.0-beta.0 "@zetachain/networks": ^4.0.0 "@zetachain/protocol-contracts": ^4.0.1 @@ -3881,6 +4008,19 @@ __metadata: languageName: node linkType: hard +"amazon-cognito-identity-js@npm:^6.0.1": + version: 6.3.13 + resolution: "amazon-cognito-identity-js@npm:6.3.13" + dependencies: + "@aws-crypto/sha256-js": 1.2.2 + buffer: 4.9.2 + fast-base64-decode: ^1.0.0 + isomorphic-unfetch: ^3.0.0 + js-cookie: ^2.2.1 + checksum: 8a608ef1b99fca57d87103ceb2706d38e499a548d7724f194d50a709fa564ec63dbc703ded6a106e112d5443d06859f40e242815c25c465fdee4806bbce79459 + languageName: node + linkType: hard + "amdefine@npm:>=0.0.4": version: 1.0.1 resolution: "amdefine@npm:1.0.1" @@ -4039,6 +4179,16 @@ __metadata: languageName: node linkType: hard +"array-buffer-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "array-buffer-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.5 + is-array-buffer: ^3.0.4 + checksum: 53524e08f40867f6a9f35318fafe467c32e45e9c682ba67b11943e167344d2febc0f6977a17e699b05699e805c3e8f073d876f8bbf1b559ed494ad2cd0fae09e + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -4073,6 +4223,20 @@ __metadata: languageName: node linkType: hard +"array.prototype.findlast@npm:^1.2.2": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.2 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + es-shim-unscopables: ^1.0.2 + checksum: 83ce4ad95bae07f136d316f5a7c3a5b911ac3296c3476abe60225bc4a17938bf37541972fcc37dd5adbc99cbb9c928c70bbbfc1c1ce549d41a415144030bb446 + languageName: node + linkType: hard + "array.prototype.findlastindex@npm:^1.2.3": version: 1.2.3 resolution: "array.prototype.findlastindex@npm:1.2.3" @@ -4125,6 +4289,22 @@ __metadata: languageName: node linkType: hard +"arraybuffer.prototype.slice@npm:^1.0.3": + version: 1.0.3 + resolution: "arraybuffer.prototype.slice@npm:1.0.3" + dependencies: + array-buffer-byte-length: ^1.0.1 + call-bind: ^1.0.5 + define-properties: ^1.2.1 + es-abstract: ^1.22.3 + es-errors: ^1.2.1 + get-intrinsic: ^1.2.3 + is-array-buffer: ^3.0.4 + is-shared-array-buffer: ^1.0.2 + checksum: 352259cba534dcdd969c92ab002efd2ba5025b2e3b9bead3973150edbdf0696c629d7f4b3f061c5931511e8207bdc2306da614703c820b45dabce39e3daf7e3e + languageName: node + linkType: hard + "arrify@npm:^1.0.0, arrify@npm:^1.0.1": version: 1.0.1 resolution: "arrify@npm:1.0.1" @@ -4197,6 +4377,15 @@ __metadata: languageName: node linkType: hard +"async-retry@npm:^1.3.3": + version: 1.3.3 + resolution: "async-retry@npm:1.3.3" + dependencies: + retry: 0.13.1 + checksum: 38a7152ff7265a9321ea214b9c69e8224ab1febbdec98efbbde6e562f17ff68405569b796b1c5271f354aef8783665d29953f051f68c1fc45306e61aec82fdc4 + languageName: node + linkType: hard + "async@npm:1.x": version: 1.5.2 resolution: "async@npm:1.5.2" @@ -4234,6 +4423,15 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: ^1.0.0 + checksum: 1aa3ffbfe6578276996de660848b6e95669d9a95ad149e3dd0c0cda77db6ee1dbd9d1dd723b65b6d277b882dd0c4b91a654ae9d3cf9e1254b7e93e4908d78fd3 + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -4259,6 +4457,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.21.2": + version: 0.21.4 + resolution: "axios@npm:0.21.4" + dependencies: + follow-redirects: ^1.14.0 + checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c + languageName: node + linkType: hard + "axios@npm:^1.4.0": version: 1.6.7 resolution: "axios@npm:1.6.7" @@ -4293,7 +4500,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": +"base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -4711,6 +4918,17 @@ __metadata: languageName: node linkType: hard +"buffer@npm:4.9.2": + version: 4.9.2 + resolution: "buffer@npm:4.9.2" + dependencies: + base64-js: ^1.0.2 + ieee754: ^1.1.4 + isarray: ^1.0.0 + checksum: 8801bc1ba08539f3be70eee307a8b9db3d40f6afbfd3cf623ab7ef41dffff1d0a31de0addbe1e66e0ca5f7193eeb667bfb1ecad3647f8f1b0750de07c13295c3 + languageName: node + linkType: hard + "buffer@npm:^5.0.5, buffer@npm:^5.5.0, buffer@npm:^5.6.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" @@ -4833,6 +5051,19 @@ __metadata: languageName: node linkType: hard +"call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + set-function-length: ^1.2.1 + checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029 + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -4888,6 +5119,15 @@ __metadata: languageName: node linkType: hard +"cbor@npm:^9.0.0": + version: 9.0.2 + resolution: "cbor@npm:9.0.2" + dependencies: + nofilter: ^3.1.0 + checksum: 925edae7bf964be5a26dba1b7ba6311ac12b6a66234dc958958997a0576cdc740632dc19852a5b84d8a75101936bea1fe122dc22539d6e11f4539c731853ba2e + languageName: node + linkType: hard + "chai@npm:^4.3.6": version: 4.3.10 resolution: "chai@npm:4.3.10" @@ -5245,6 +5485,13 @@ __metadata: languageName: node linkType: hard +"compare-versions@npm:^6.0.0": + version: 6.1.0 + resolution: "compare-versions@npm:6.1.0" + checksum: d4e2a45706a023d8d0b6680338b66b79e20bd02d1947f0ac6531dab634cbed89fa373b3f03d503c5e489761194258d6e1bae67a07f88b1efc61648454f2d47e7 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -5512,6 +5759,39 @@ __metadata: languageName: node linkType: hard +"data-view-buffer@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-buffer@npm:1.0.1" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: ce24348f3c6231223b216da92e7e6a57a12b4af81a23f27eff8feabdf06acfb16c00639c8b705ca4d167f761cfc756e27e5f065d0a1f840c10b907fdaf8b988c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: dbb3200edcb7c1ef0d68979834f81d64fd8cab2f7691b3a4c6b97e67f22182f3ec2c8602efd7b76997b55af6ff8bce485829c1feda4fa2165a6b71fb7baa4269 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.0": + version: 1.0.0 + resolution: "data-view-byte-offset@npm:1.0.0" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: 7f0bf8720b7414ca719eedf1846aeec392f2054d7af707c5dc9a753cc77eb8625f067fa901e0b5127e831f9da9056138d894b9c2be79c27a21f6db5824f009c2 + languageName: node + linkType: hard + "dayjs@npm:1.11.10": version: 1.11.10 resolution: "dayjs@npm:1.11.10" @@ -5672,6 +5952,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + gopd: ^1.0.1 + checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -6047,6 +6338,85 @@ __metadata: languageName: node linkType: hard +"es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2": + version: 1.23.3 + resolution: "es-abstract@npm:1.23.3" + dependencies: + array-buffer-byte-length: ^1.0.1 + arraybuffer.prototype.slice: ^1.0.3 + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + data-view-buffer: ^1.0.1 + data-view-byte-length: ^1.0.1 + data-view-byte-offset: ^1.0.0 + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + es-set-tostringtag: ^2.0.3 + es-to-primitive: ^1.2.1 + function.prototype.name: ^1.1.6 + get-intrinsic: ^1.2.4 + get-symbol-description: ^1.0.2 + globalthis: ^1.0.3 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + has-proto: ^1.0.3 + has-symbols: ^1.0.3 + hasown: ^2.0.2 + internal-slot: ^1.0.7 + is-array-buffer: ^3.0.4 + is-callable: ^1.2.7 + is-data-view: ^1.0.1 + is-negative-zero: ^2.0.3 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.3 + is-string: ^1.0.7 + is-typed-array: ^1.1.13 + is-weakref: ^1.0.2 + object-inspect: ^1.13.1 + object-keys: ^1.1.1 + object.assign: ^4.1.5 + regexp.prototype.flags: ^1.5.2 + safe-array-concat: ^1.1.2 + safe-regex-test: ^1.0.3 + string.prototype.trim: ^1.2.9 + string.prototype.trimend: ^1.0.8 + string.prototype.trimstart: ^1.0.8 + typed-array-buffer: ^1.0.2 + typed-array-byte-length: ^1.0.1 + typed-array-byte-offset: ^1.0.2 + typed-array-length: ^1.0.6 + unbox-primitive: ^1.0.2 + which-typed-array: ^1.1.15 + checksum: f840cf161224252512f9527306b57117192696571e07920f777cb893454e32999206198b4f075516112af6459daca282826d1735c450528470356d09eff3a9ae + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: ^1.2.4 + checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0": + version: 1.0.0 + resolution: "es-object-atoms@npm:1.0.0" + dependencies: + es-errors: ^1.3.0 + checksum: 26f0ff78ab93b63394e8403c353842b2272836968de4eafe97656adfb8a7c84b9099bf0fe96ed58f4a4cddc860f6e34c77f91649a58a5daa4a9c40b902744e3c + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.2 resolution: "es-set-tostringtag@npm:2.0.2" @@ -6058,7 +6428,18 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0": +"es-set-tostringtag@npm:^2.0.3": + version: 2.0.3 + resolution: "es-set-tostringtag@npm:2.0.3" + dependencies: + get-intrinsic: ^1.2.4 + has-tostringtag: ^1.0.2 + hasown: ^2.0.1 + checksum: 7227fa48a41c0ce83e0377b11130d324ac797390688135b8da5c28994c0165be8b252e15cd1de41e1325e5a5412511586960213e88f9ab4a5e7d028895db5129 + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": version: 1.0.2 resolution: "es-shim-unscopables@npm:1.0.2" dependencies: @@ -6700,7 +7081,7 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:^7.0.10, ethereumjs-util@npm:^7.1.0, ethereumjs-util@npm:^7.1.1, ethereumjs-util@npm:^7.1.2, ethereumjs-util@npm:^7.1.3, ethereumjs-util@npm:^7.1.4, ethereumjs-util@npm:^7.1.5": +"ethereumjs-util@npm:^7.0.10, ethereumjs-util@npm:^7.0.3, ethereumjs-util@npm:^7.1.0, ethereumjs-util@npm:^7.1.1, ethereumjs-util@npm:^7.1.2, ethereumjs-util@npm:^7.1.3, ethereumjs-util@npm:^7.1.4, ethereumjs-util@npm:^7.1.5": version: 7.1.5 resolution: "ethereumjs-util@npm:7.1.5" dependencies: @@ -7005,6 +7386,13 @@ __metadata: languageName: node linkType: hard +"fast-base64-decode@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-base64-decode@npm:1.0.0" + checksum: 4c59eb1775a7f132333f296c5082476fdcc8f58d023c42ed6d378d2e2da4c328c7a71562f271181a725dd17cdaa8f2805346cc330cdbad3b8e4b9751508bd0a3 + languageName: node + linkType: hard + "fast-deep-equal@npm:^2.0.1": version: 2.0.1 resolution: "fast-deep-equal@npm:2.0.1" @@ -7205,6 +7593,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.14.0": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: a62c378dfc8c00f60b9c80cab158ba54e99ba0239a5dd7c81245e5a5b39d10f0c35e249c3379eae719ff0285fff88c365dd446fab19dee771f1d76252df1bbf5 + languageName: node + linkType: hard + "follow-redirects@npm:^1.15.4": version: 1.15.5 resolution: "follow-redirects@npm:1.15.5" @@ -7512,6 +7910,19 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: ^1.3.0 + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951 + languageName: node + linkType: hard + "get-port@npm:^3.1.0": version: 3.2.0 resolution: "get-port@npm:3.2.0" @@ -7554,6 +7965,17 @@ __metadata: languageName: node linkType: hard +"get-symbol-description@npm:^1.0.2": + version: 1.0.2 + resolution: "get-symbol-description@npm:1.0.2" + dependencies: + call-bind: ^1.0.5 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.4 + checksum: e1cb53bc211f9dbe9691a4f97a46837a553c4e7caadd0488dc24ac694db8a390b93edd412b48dcdd0b4bbb4c595de1709effc75fc87c0839deedc6968f5bd973 + languageName: node + linkType: hard + "getpass@npm:^0.1.1": version: 0.1.7 resolution: "getpass@npm:0.1.7" @@ -7809,7 +8231,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -8069,6 +8491,15 @@ __metadata: languageName: node linkType: hard +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: ^1.0.0 + checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3 + languageName: node + linkType: hard + "has-proto@npm:^1.0.1": version: 1.0.1 resolution: "has-proto@npm:1.0.1" @@ -8076,6 +8507,13 @@ __metadata: languageName: node linkType: hard +"has-proto@npm:^1.0.3": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: fe7c3d50b33f50f3933a04413ed1f69441d21d2d2944f81036276d30635cad9279f6b43bc8f32036c31ebdfcf6e731150f46c1907ad90c669ffe9b066c3ba5c4 + languageName: node + linkType: hard + "has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" @@ -8092,6 +8530,15 @@ __metadata: languageName: node linkType: hard +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: ^1.0.3 + checksum: 999d60bb753ad714356b2c6c87b7fb74f32463b8426e159397da4bde5bca7e598ab1073f4d8d4deafac297f2eb311484cd177af242776bf05f0d11565680468d + languageName: node + linkType: hard + "hash-base@npm:^3.0.0": version: 3.1.0 resolution: "hash-base@npm:3.1.0" @@ -8132,6 +8579,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.1, hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: ^1.1.2 + checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db + languageName: node + linkType: hard + "he@npm:1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -8302,7 +8758,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": +"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e @@ -8419,6 +8875,17 @@ __metadata: languageName: node linkType: hard +"internal-slot@npm:^1.0.7": + version: 1.0.7 + resolution: "internal-slot@npm:1.0.7" + dependencies: + es-errors: ^1.3.0 + hasown: ^2.0.0 + side-channel: ^1.0.4 + checksum: cadc5eea5d7d9bc2342e93aae9f31f04c196afebb11bde97448327049f492cd7081e18623ae71388aac9cd237b692ca3a105be9c68ac39c1dec679d7409e33eb + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -8470,6 +8937,16 @@ __metadata: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.4": + version: 3.0.4 + resolution: "is-array-buffer@npm:3.0.4" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.1 + checksum: e4e3e6ef0ff2239e75371d221f74bc3c26a03564a22efb39f6bb02609b598917ddeecef4e8c877df2a25888f247a98198959842a5e73236bc7f22cabdf6351a7 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -8528,6 +9005,15 @@ __metadata: languageName: node linkType: hard +"is-data-view@npm:^1.0.1": + version: 1.0.1 + resolution: "is-data-view@npm:1.0.1" + dependencies: + is-typed-array: ^1.1.13 + checksum: 4ba4562ac2b2ec005fefe48269d6bd0152785458cd253c746154ffb8a8ab506a29d0cfb3b74af87513843776a88e4981ae25c89457bf640a33748eab1a7216b5 + languageName: node + linkType: hard + "is-date-object@npm:^1.0.1": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" @@ -8611,6 +9097,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: c1e6b23d2070c0539d7b36022d5a94407132411d01aba39ec549af824231f3804b1aea90b5e4e58e807a65d23ceb538ed6e355ce76b267bdd86edb757ffcbdcd + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -8667,6 +9160,15 @@ __metadata: languageName: node linkType: hard +"is-shared-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "is-shared-array-buffer@npm:1.0.3" + dependencies: + call-bind: ^1.0.7 + checksum: a4fff602c309e64ccaa83b859255a43bb011145a42d3f56f67d9268b55bc7e6d98a5981a1d834186ad3105d6739d21547083fe7259c76c0468483fc538e716d8 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -8703,6 +9205,15 @@ __metadata: languageName: node linkType: hard +"is-typed-array@npm:^1.1.13": + version: 1.1.13 + resolution: "is-typed-array@npm:1.1.13" + dependencies: + which-typed-array: ^1.1.14 + checksum: 150f9ada183a61554c91e1c4290086d2c100b0dff45f60b028519be72a8db964da403c48760723bf5253979b8dffe7b544246e0e5351dcd05c5fdb1dcc1dc0f0 + languageName: node + linkType: hard + "is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" @@ -8740,6 +9251,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:^1.0.0, isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -8747,13 +9265,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -8778,6 +9289,16 @@ __metadata: languageName: node linkType: hard +"isomorphic-unfetch@npm:^3.0.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: ^2.6.1 + unfetch: ^4.2.0 + checksum: 82b92fe4ec2823a81ab0fc0d11bd94d710e6f9a940d56b3cba31896d4345ec9ffc7949f4ff31ebcae84f6b95f7ebf3474c4c7452b834eb4078ea3f2c37e459c5 + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -8798,6 +9319,13 @@ __metadata: languageName: node linkType: hard +"js-cookie@npm:^2.2.1": + version: 2.2.1 + resolution: "js-cookie@npm:2.2.1" + checksum: 9b1fb980a1c5e624fd4b28ea4867bb30c71e04c4484bb3a42766344c533faa684de9498e443425479ec68609e96e27b60614bfe354877c449c631529b6d932f2 + languageName: node + linkType: hard + "js-sha3@npm:0.5.7, js-sha3@npm:^0.5.7": version: 0.5.7 resolution: "js-sha3@npm:0.5.7" @@ -9364,7 +9892,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21": +"lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -9761,7 +10289,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 @@ -10155,7 +10683,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -10331,7 +10859,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.4": +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5": version: 4.1.5 resolution: "object.assign@npm:4.1.5" dependencies: @@ -10773,6 +11301,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: b32d403ece71e042385cc7856385cecf1cd8e144fa74d2f1de40d1e16035dba097bc189715925e79b67bdd1472796ff168d3a90d296356c9c94d272d5b95f3ae + languageName: node + linkType: hard + "preferred-pm@npm:^3.0.0": version: 3.1.2 resolution: "preferred-pm@npm:3.1.2" @@ -10864,6 +11399,17 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^4.1.1": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: ^4.2.4 + retry: ^0.12.0 + signal-exit: ^3.0.2 + checksum: 00078ee6a61c216a56a6140c7d2a98c6c733b3678503002dc073ab8beca5d50ca271de4c85fca13b9b8ee2ff546c36674d1850509b84a04a5d0363bcb8638939 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -11184,6 +11730,18 @@ __metadata: languageName: node linkType: hard +"regexp.prototype.flags@npm:^1.5.2": + version: 1.5.2 + resolution: "regexp.prototype.flags@npm:1.5.2" + dependencies: + call-bind: ^1.0.6 + define-properties: ^1.2.1 + es-errors: ^1.3.0 + set-function-name: ^2.0.1 + checksum: d7f333667d5c564e2d7a97c56c3075d64c722c9bb51b2b4df6822b2e8096d623a5e63088fb4c83df919b6951ef8113841de8b47de7224872fa6838bc5d8a7d64 + languageName: node + linkType: hard + "regexpp@npm:^3.0.0": version: 3.2.0 resolution: "regexpp@npm:3.2.0" @@ -11386,6 +11944,13 @@ __metadata: languageName: node linkType: hard +"retry@npm:0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 47c4d5be674f7c13eee4cfe927345023972197dbbdfba5d3af7e461d13b44de1bfd663bfc80d2f601f8ef3fc8164c16dd99655a221921954a65d044a2fc1233b + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -11514,6 +12079,18 @@ __metadata: languageName: node linkType: hard +"safe-array-concat@npm:^1.1.2": + version: 1.1.2 + resolution: "safe-array-concat@npm:1.1.2" + dependencies: + call-bind: ^1.0.7 + get-intrinsic: ^1.2.4 + has-symbols: ^1.0.3 + isarray: ^2.0.5 + checksum: a3b259694754ddfb73ae0663829e396977b99ff21cbe8607f35a469655656da8e271753497e59da8a7575baa94d2e684bea3e10ddd74ba046c0c9b4418ffa0c4 + languageName: node + linkType: hard + "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -11539,6 +12116,17 @@ __metadata: languageName: node linkType: hard +"safe-regex-test@npm:^1.0.3": + version: 1.0.3 + resolution: "safe-regex-test@npm:1.0.3" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-regex: ^1.1.4 + checksum: 6c7d392ff1ae7a3ae85273450ed02d1d131f1d2c76e177d6b03eb88e6df8fa062639070e7d311802c1615f351f18dc58f9454501c58e28d5ffd9b8f502ba6489 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -11730,6 +12318,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72 + languageName: node + linkType: hard + "set-function-name@npm:^2.0.0": version: 2.0.1 resolution: "set-function-name@npm:2.0.1" @@ -11741,6 +12343,18 @@ __metadata: languageName: node linkType: hard +"set-function-name@npm:^2.0.1": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + functions-have-names: ^1.2.3 + has-property-descriptors: ^1.0.2 + checksum: d6229a71527fd0404399fc6227e0ff0652800362510822a291925c9d7b48a1ca1a468b11b281471c34cd5a2da0db4f5d7ff315a61d26655e77f6e971e6d0c80f + languageName: node + linkType: hard + "setimmediate@npm:1.0.4": version: 1.0.4 resolution: "setimmediate@npm:1.0.4" @@ -11996,6 +12610,15 @@ __metadata: languageName: node linkType: hard +"solidity-ast@npm:^0.4.51": + version: 0.4.56 + resolution: "solidity-ast@npm:0.4.56" + dependencies: + array.prototype.findlast: ^1.2.2 + checksum: 124cd54dc187860c83f4e8a3cbc41f890fbd0aaad4695356763034bdc782046eac414b161b7f354e423e075dba303d6bef213682df8932fee5d143d52135cd4e + languageName: node + linkType: hard + "solidity-coverage@npm:^0.7.20": version: 0.7.22 resolution: "solidity-coverage@npm:0.7.22" @@ -12224,6 +12847,18 @@ __metadata: languageName: node linkType: hard +"string.prototype.trim@npm:^1.2.9": + version: 1.2.9 + resolution: "string.prototype.trim@npm:1.2.9" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.0 + es-object-atoms: ^1.0.0 + checksum: ea2df6ec1e914c9d4e2dc856fa08228e8b1be59b59e50b17578c94a66a176888f417264bb763d4aac638ad3b3dad56e7a03d9317086a178078d131aa293ba193 + languageName: node + linkType: hard + "string.prototype.trimend@npm:^1.0.7": version: 1.0.7 resolution: "string.prototype.trimend@npm:1.0.7" @@ -12235,6 +12870,17 @@ __metadata: languageName: node linkType: hard +"string.prototype.trimend@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimend@npm:1.0.8" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + checksum: cc3bd2de08d8968a28787deba9a3cb3f17ca5f9f770c91e7e8fa3e7d47f079bad70fadce16f05dda9f261788be2c6e84a942f618c3bed31e42abc5c1084f8dfd + languageName: node + linkType: hard + "string.prototype.trimstart@npm:^1.0.7": version: 1.0.7 resolution: "string.prototype.trimstart@npm:1.0.7" @@ -12246,6 +12892,17 @@ __metadata: languageName: node linkType: hard +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + checksum: df1007a7f580a49d692375d996521dc14fd103acda7f3034b3c558a60b82beeed3a64fa91e494e164581793a8ab0ae2f59578a49896a7af6583c1f20472bce96 + languageName: node + linkType: hard + "string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -12688,7 +13345,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.8.1, tslib@npm:^1.9.3": +"tslib@npm:^1.11.1, tslib@npm:^1.8.1, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd @@ -12702,6 +13359,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.3.1, tslib@npm:^2.6.2": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 74fce0e100f1ebd95b8995fbbd0e6c91bdd8f4c35c00d4da62e285a3363aaa534de40a80db30ecfd388ed7c313c42d930ee0eaf108e8114214b180eec3dbe6f5 + languageName: node + linkType: hard + "tsort@npm:0.0.1": version: 0.0.1 resolution: "tsort@npm:0.0.1" @@ -12891,6 +13555,17 @@ __metadata: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-buffer@npm:1.0.2" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + is-typed-array: ^1.1.13 + checksum: 02ffc185d29c6df07968272b15d5319a1610817916ec8d4cd670ded5d1efe72901541ff2202fcc622730d8a549c76e198a2f74e312eabbfb712ed907d45cbb0b + languageName: node + linkType: hard + "typed-array-byte-length@npm:^1.0.0": version: 1.0.0 resolution: "typed-array-byte-length@npm:1.0.0" @@ -12903,6 +13578,19 @@ __metadata: languageName: node linkType: hard +"typed-array-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "typed-array-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + checksum: f65e5ecd1cf76b1a2d0d6f631f3ea3cdb5e08da106c6703ffe687d583e49954d570cc80434816d3746e18be889ffe53c58bf3e538081ea4077c26a41055b216d + languageName: node + linkType: hard + "typed-array-byte-offset@npm:^1.0.0": version: 1.0.0 resolution: "typed-array-byte-offset@npm:1.0.0" @@ -12916,6 +13604,20 @@ __metadata: languageName: node linkType: hard +"typed-array-byte-offset@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-byte-offset@npm:1.0.2" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + checksum: c8645c8794a621a0adcc142e0e2c57b1823bbfa4d590ad2c76b266aa3823895cf7afb9a893bf6685e18454ab1b0241e1a8d885a2d1340948efa4b56add4b5f67 + languageName: node + linkType: hard + "typed-array-length@npm:^1.0.4": version: 1.0.4 resolution: "typed-array-length@npm:1.0.4" @@ -12927,6 +13629,20 @@ __metadata: languageName: node linkType: hard +"typed-array-length@npm:^1.0.6": + version: 1.0.6 + resolution: "typed-array-length@npm:1.0.6" + dependencies: + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + possible-typed-array-names: ^1.0.0 + checksum: f0315e5b8f0168c29d390ff410ad13e4d511c78e6006df4a104576844812ee447fcc32daab1f3a76c9ef4f64eff808e134528b5b2439de335586b392e9750e5c + languageName: node + linkType: hard + "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -13055,6 +13771,13 @@ __metadata: languageName: node linkType: hard +"unfetch@npm:^4.2.0": + version: 4.2.0 + resolution: "unfetch@npm:4.2.0" + checksum: 6a4b2557e1d921eaa80c4425ce27a404945ec26491ed06e62598f333996a91a44c7908cb26dc7c2746d735762b13276cf4aa41829b4c8f438dde63add3045d7a + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -13934,6 +14657,19 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.2 + checksum: 65227dcbfadf5677aacc43ec84356d17b5500cb8b8753059bb4397de5cd0c2de681d24e1a7bd575633f976a95f88233abfd6549c2105ef4ebd58af8aa1807c75 + languageName: node + linkType: hard + "which@npm:^1.1.1, which@npm:^1.2.9, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" From aec9cbe36fb66442b6e6dc5cf0f8d546d483375a Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:40:25 -0300 Subject: [PATCH 11/12] Update packages/zevm-app-contracts/test/xp-nft/xp-nft.ts Co-authored-by: Lucas --- packages/zevm-app-contracts/test/xp-nft/xp-nft.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a346b11f..eeceaa02 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -68,7 +68,7 @@ describe("XP NFT Contract test", () => { await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); }); - it("Should revert if signature it's not correct", async () => { + it("Should revert if signature is not correct", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; From 2c36fc11e801dcacda596773398e65be96353eea Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Tue, 13 Aug 2024 11:01:24 -0300 Subject: [PATCH 12/12] renames and test --- .../contracts/xp-nft/xpNFT.sol | 4 +-- .../zevm-app-contracts/data/addresses.json | 3 +- packages/zevm-app-contracts/hardhat.config.ts | 1 + .../scripts/xp-nft/deploy.ts | 32 +++++++++++++++++++ .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 2 +- 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 packages/zevm-app-contracts/scripts/xp-nft/deploy.ts diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index c9c2c949..b0a57839 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -28,7 +28,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { address public signerAddress; // Event for New Mint - event NewNFTMinted(address indexed sender, uint256 indexed tokenId); + event NFTMinted(address indexed sender, uint256 indexed tokenId); // Event for NFT Update event NFTUpdated(address indexed sender, uint256 indexed tokenId); @@ -125,7 +125,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { _updateNFT(mintData); - emit NewNFTMinted(mintData.to, mintData.tokenId); + emit NFTMinted(mintData.to, mintData.tokenId); } // External mint function diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json index 83d5c063..74a8647d 100644 --- a/packages/zevm-app-contracts/data/addresses.json +++ b/packages/zevm-app-contracts/data/addresses.json @@ -6,7 +6,8 @@ "zetaSwap": "0xA8168Dc495Ed61E70f5c1941e2860050AB902cEF", "zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559", "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d", - "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E" + "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E", + "ZetaXP": "0x80ECE9a08ba893e1B863C0c6B3c098268C146E40" } } } \ No newline at end of file diff --git a/packages/zevm-app-contracts/hardhat.config.ts b/packages/zevm-app-contracts/hardhat.config.ts index 5e42ce0f..3fa96701 100644 --- a/packages/zevm-app-contracts/hardhat.config.ts +++ b/packages/zevm-app-contracts/hardhat.config.ts @@ -58,6 +58,7 @@ const config: HardhatUserConfig = { { version: "0.5.10" /** For create2 factory */ }, { version: "0.6.6" /** For uniswap v2 */ }, { version: "0.8.7" }, + { version: "0.8.9" }, ], settings: { /** diff --git a/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts b/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts new file mode 100644 index 00000000..cd1d8877 --- /dev/null +++ b/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts @@ -0,0 +1,32 @@ +import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; +import { ethers, network, upgrades } from "hardhat"; + +import { ZetaXP__factory } from "../../typechain-types"; +import { saveAddress } from "../address.helpers"; + +const networkName = network.name; + +const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; +const signer = "0x1d24d94520B94B26351f6573de5ef9731c48531A"; + +const deployZetaXP = async () => { + if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); + + const ZetaXPFactory = (await ethers.getContractFactory("ZetaXP")) as ZetaXP__factory; + const zetaXP = await upgrades.deployProxy(ZetaXPFactory, ["ZETA NFT", "ZNFT", ZETA_BASE_URL, signer]); + + await zetaXP.deployed(); + + console.log("ZetaXP deployed to:", zetaXP.address); + saveAddress("ZetaXP", zetaXP.address, networkName); +}; + +const main = async () => { + if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); + await deployZetaXP(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); 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 eeceaa02..f3d5f1c6 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -65,7 +65,7 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; const tx = zetaXP.mintNFT(nftParams); - await expect(tx).to.emit(zetaXP, "NewNFTMinted").withArgs(user.address, 1); + await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1); }); it("Should revert if signature is not correct", async () => {