From 9ef6dba329ad2d54c09fb2196c77784e54be5542 Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:25:03 -0300 Subject: [PATCH] feat: Update nft contract to calculate tokenid (#167) * feat: Update nft contract to calculate tokenid * deploy and verify --- package.json | 2 +- .../contracts/withdrawErc20/withdrawErc20.sol | 10 +-- .../contracts/xp-nft/xpNFT.sol | 54 +++++++++------- .../zevm-app-contracts/data/addresses.json | 11 +++- .../scripts/xp-nft/deploy.ts | 24 +++++++- .../test/xp-nft/test.helpers.ts | 15 ++--- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 61 +++++++++++-------- yarn.lock | 35 +++++------ 8 files changed, 126 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index 7e621e50..1090d77b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@types/node": "^17.0.25", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "@zetachain/toolkit": "^5.0.0", + "@zetachain/toolkit": "10.0.0", "chai": "^4.3.6", "dotenv": "^16.0.0", "eslint": "^8.13.0", diff --git a/packages/zevm-app-contracts/contracts/withdrawErc20/withdrawErc20.sol b/packages/zevm-app-contracts/contracts/withdrawErc20/withdrawErc20.sol index ab84e65e..97732191 100644 --- a/packages/zevm-app-contracts/contracts/withdrawErc20/withdrawErc20.sol +++ b/packages/zevm-app-contracts/contracts/withdrawErc20/withdrawErc20.sol @@ -19,15 +19,7 @@ contract WithdrawERC20 { (address gasZRC20, uint256 gasFee) = IZRC20(zrc20).withdrawGasFee(); - uint256 inputForGas = SwapHelperLib.swapTokensForExactTokens( - systemContract.wZetaContractAddress(), - systemContract.uniswapv2FactoryAddress(), - systemContract.uniswapv2Router02Address(), - zrc20, - gasFee, - gasZRC20, - amount - ); + uint256 inputForGas = SwapHelperLib.swapTokensForExactTokens(systemContract, zrc20, gasFee, gasZRC20, amount); if (inputForGas > amount) revert InsufficientInputAmount(); diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index b0a57839..9b1477b4 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -14,23 +14,27 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { struct UpdateData { address to; - uint256 tokenId; Signature signature; uint256 sigTimestamp; uint256 signedUp; + bytes32 tag; } - mapping(uint256 => uint256) lastUpdateTimestampByTokenId; - mapping(uint256 => uint256) signedUpByTokenId; + mapping(uint256 => uint256) public lastUpdateTimestampByTokenId; + mapping(uint256 => uint256) public signedUpByTokenId; + mapping(uint256 => bytes32) public tagByTokenId; // Base URL for NFT images string public baseTokenURI; address public signerAddress; + // Counter for the next token ID + uint256 private _currentTokenId; + // Event for New Mint - event NFTMinted(address indexed sender, uint256 indexed tokenId); + event NFTMinted(address indexed sender, uint256 indexed tokenId, bytes32 tag); // Event for NFT Update - event NFTUpdated(address indexed sender, uint256 indexed tokenId); + event NFTUpdated(address indexed sender, uint256 indexed tokenId, bytes32 tag); error InvalidSigner(); error LengthMismatch(); @@ -47,6 +51,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { __Ownable_init(); baseTokenURI = baseTokenURI_; signerAddress = signerAddress_; + _currentTokenId = 1; // Start token IDs from 1 } function version() public pure virtual returns (string memory) { @@ -86,7 +91,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { return string(bstr); } - function _verify(UpdateData memory updateData) private view { + function _verify(uint256 tokenId, UpdateData memory updateData) private view { bytes32 payloadHash = _calculateHash(updateData); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash)); @@ -98,43 +103,46 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { ); if (signerAddress != messageSigner) revert InvalidSigner(); - if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[updateData.tokenId]) revert OutdatedSignature(); + if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[tokenId]) revert OutdatedSignature(); } // Function to compute the hash of the data and tasks for a token function _calculateHash(UpdateData memory updateData) private pure returns (bytes32) { bytes memory encodedData = abi.encode( updateData.to, - updateData.tokenId, updateData.sigTimestamp, - updateData.signedUp + updateData.signedUp, + updateData.tag ); return keccak256(encodedData); } - function _updateNFT(UpdateData memory updateData) internal { - _verify(updateData); - lastUpdateTimestampByTokenId[updateData.tokenId] = updateData.sigTimestamp; - signedUpByTokenId[updateData.tokenId] = updateData.signedUp; + function _updateNFT(uint256 tokenId, UpdateData memory updateData) internal { + _verify(tokenId, updateData); + lastUpdateTimestampByTokenId[tokenId] = updateData.sigTimestamp; + signedUpByTokenId[tokenId] = updateData.signedUp; } - // External mint function - function mintNFT(UpdateData calldata mintData) external { - _mint(mintData.to, mintData.tokenId); + // External mint function with auto-incremented token ID + function mintNFT(UpdateData memory mintData) external { + uint256 newTokenId = _currentTokenId; + _mint(mintData.to, newTokenId); + + _updateNFT(newTokenId, mintData); - _updateNFT(mintData); + emit NFTMinted(mintData.to, newTokenId, mintData.tag); - emit NFTMinted(mintData.to, mintData.tokenId); + _currentTokenId++; // Increment the token ID for the next mint } - // External mint function - function updateNFT(UpdateData memory updateData) external { - address owner = ownerOf(updateData.tokenId); + // External update function + function updateNFT(uint256 tokenId, UpdateData memory updateData) external { + address owner = ownerOf(tokenId); updateData.to = owner; - _updateNFT(updateData); + _updateNFT(tokenId, updateData); - emit NFTUpdated(owner, updateData.tokenId); + emit NFTUpdated(owner, tokenId, updateData.tag); } // Set the base URI for tokens diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json index 74a8647d..789e08a0 100644 --- a/packages/zevm-app-contracts/data/addresses.json +++ b/packages/zevm-app-contracts/data/addresses.json @@ -7,7 +7,16 @@ "zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559", "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d", "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E", - "ZetaXP": "0x80ECE9a08ba893e1B863C0c6B3c098268C146E40" + "ZetaXP": "0xE1DfA5dfd1d6c6b47C8c9b8726EdD019D365491D" + }, + "zeta_mainnet": { + "disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d", + "rewardDistributorFactory": "0xB9dc665610CF5109cE23aBBdaAc315B41FA094c1", + "zetaSwap": "0xA8168Dc495Ed61E70f5c1941e2860050AB902cEF", + "zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559", + "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d", + "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E", + "ZetaXP": "0x6926b6ea978d59a8Ab6aC05486Fd0bED6D15eC29" } } } \ No newline at end of file diff --git a/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts b/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts index cd1d8877..b06e6c5d 100644 --- a/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts +++ b/packages/zevm-app-contracts/scripts/xp-nft/deploy.ts @@ -1,5 +1,5 @@ import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; -import { ethers, network, upgrades } from "hardhat"; +import { ethers, network, run, upgrades } from "hardhat"; import { ZetaXP__factory } from "../../typechain-types"; import { saveAddress } from "../address.helpers"; @@ -9,6 +9,20 @@ const networkName = network.name; const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; const signer = "0x1d24d94520B94B26351f6573de5ef9731c48531A"; +const verifyContract = async (contractAddress: string, constructorArguments: any[]) => { + // Verification process + console.log(`Verifying contract ${contractAddress}...`); + try { + await run("verify:verify", { + address: contractAddress, + constructorArguments, + }); + console.log("Verification successful"); + } catch (error) { + console.error("Verification failed:", error); + } +}; + const deployZetaXP = async () => { if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); @@ -17,8 +31,16 @@ const deployZetaXP = async () => { await zetaXP.deployed(); + // Get the implementation address + const implementationAddress = await upgrades.erc1967.getImplementationAddress(zetaXP.address); + console.log("ZetaXP deployed to:", zetaXP.address); + console.log("ZetaXP implementation deployed to:", implementationAddress); + saveAddress("ZetaXP", zetaXP.address, networkName); + + await verifyContract(zetaXP.address, ["ZETA NFT", "ZNFT", ZETA_BASE_URL, signer]); + await verifyContract(implementationAddress, []); }; const main = async () => { 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 8d15d292..fbf68a8c 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -9,25 +9,20 @@ export interface Signature { export interface NFT { signedUp: number; + tag: string; to: string; - tokenId: number; } export interface UpdateParam extends NFT { sigTimestamp: number; signature: Signature; + tokenId: number; } -export const getSignature = async ( - signer: SignerWithAddress, - timestamp: number, - to: string, - tokenId: number, - nft: NFT -) => { +export const getSignature = async (signer: SignerWithAddress, timestamp: number, to: string, nft: NFT) => { let payload = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256"], - [to, tokenId, timestamp, nft.signedUp] + ["address", "uint256", "uint256", "bytes32"], + [to, timestamp, nft.signedUp, nft.tag] ); const payloadHash = ethers.utils.keccak256(payload); 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 f3d5f1c6..27fffd7f 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -20,27 +20,33 @@ describe("XP NFT Contract test", () => { zetaXP = await upgrades.deployProxy(zetaXPFactory, ["ZETA NFT", "ZNFT", ZETA_BASE_URL, signer.address]); await zetaXP.deployed(); + const tag = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], ["XP_NFT"])); sampleNFT = { signedUp: 1234, + tag, to: user.address, - tokenId: 1, }; }); - const validateNFT = async (nft: NFT) => { - const owner = await zetaXP.ownerOf(nft.tokenId); + const validateNFT = async (tokenId: number, nft: NFT) => { + const owner = await zetaXP.ownerOf(tokenId); await expect(owner).to.be.eq(nft.to); - const url = await zetaXP.tokenURI(nft.tokenId); - await expect(url).to.be.eq(`${ZETA_BASE_URL}${nft.tokenId}`); + const url = await zetaXP.tokenURI(tokenId); + await expect(url).to.be.eq(`${ZETA_BASE_URL}${tokenId}`); + }; + + const getTokenIdFromRecipient = (receipt: any): number => { + //@ts-ignore + return receipt.events[0].args?.tokenId; }; it("Should mint an NFT", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -48,16 +54,18 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; - await zetaXP.mintNFT(nftParams); + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + const tokenId = getTokenIdFromRecipient(receipt); - await validateNFT(sampleNFT); + await validateNFT(tokenId, 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 signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -65,14 +73,14 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; const tx = zetaXP.mintNFT(nftParams); - await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1); + await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1, sampleNFT.tag); }); it("Should revert if signature is not correct", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(addrs[0], sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(addrs[0], sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -85,11 +93,12 @@ describe("XP NFT Contract test", () => { }); it("Should update NFT", async () => { + let tokenId = -1; { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -97,7 +106,9 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; - await zetaXP.mintNFT(nftParams); + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); } const updatedSampleNFT = { ...sampleNFT, signedUp: 4321 }; @@ -106,7 +117,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, updatedSampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, updatedSampleNFT); const nftParams: UpdateParam = { ...updatedSampleNFT, @@ -114,10 +125,10 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; - await zetaXP.updateNFT(nftParams); + await zetaXP.updateNFT(tokenId, nftParams); } - validateNFT(updatedSampleNFT); + validateNFT(tokenId, updatedSampleNFT); }); it("Should update base url", async () => { @@ -129,7 +140,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -148,7 +159,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -168,7 +179,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, user.address, sampleNFT2.tokenId, sampleNFT2); + const signature = await getSignature(signer, sigTimestamp, user.address, sampleNFT2); const nftParams: UpdateParam = { ...sampleNFT2, @@ -195,7 +206,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -213,7 +224,7 @@ describe("XP NFT Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT.tokenId, sampleNFT); + const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, @@ -221,10 +232,12 @@ describe("XP NFT Contract test", () => { signature, } as UpdateParam; - await zetaXP.mintNFT(nftParams); + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + const tokenId = getTokenIdFromRecipient(receipt); - const tx = zetaXP.updateNFT(nftParams); - await expect(tx).to.be.revertedWith("OutdatedSignature"); + const tx1 = zetaXP.updateNFT(tokenId, nftParams); + await expect(tx1).to.be.revertedWith("OutdatedSignature"); }); it("Should upgrade", async () => { diff --git a/yarn.lock b/yarn.lock index 03265ee4..c86a0c5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2751,7 +2751,7 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:^4.9.2": +"@openzeppelin/contracts@npm:^4.9.6": version: 4.9.6 resolution: "@openzeppelin/contracts@npm:4.9.6" checksum: 274b6e968268294f12d5ca4f0278f6e6357792c8bb4d76664f83dbdc325f780541538a127e6a6e97e4f018088b42f65952014dec9c745c0fa25081f43ef9c4bf @@ -3701,12 +3701,12 @@ __metadata: languageName: node linkType: hard -"@zetachain/networks@npm:^3.0.0": - version: 3.0.1 - resolution: "@zetachain/networks@npm:3.0.1" +"@zetachain/networks@npm:7.0.0": + version: 7.0.0 + resolution: "@zetachain/networks@npm:7.0.0" dependencies: dotenv: ^16.1.4 - checksum: b017735d829935f67efc6b5ee9a6ed23ce41e236e13120eb5c45ca444715395fc200476603570468aa420a85db54052c7690cc2cb0462ea1a8b7222eb53c4557 + checksum: 96eab0095341ca4f14dec73d53da301e05d6af86dc51b452c3fb5d7fb52feed4be56bba6b5ce0ddf0cd5aa3461563e0e5fe1e507a7c60a37944b1f6449afb04a languageName: node linkType: hard @@ -3719,10 +3719,10 @@ __metadata: languageName: node linkType: hard -"@zetachain/protocol-contracts@npm:^3.0.2": - version: 3.0.2 - resolution: "@zetachain/protocol-contracts@npm:3.0.2" - checksum: 7e24b486da26bea9f37a5e81d2462947668ade3e81c0f955fa8509da179fcdf1ca2d4ef5fae1d9006343bf18919402770bedd16554454218a0df44980ed6b5df +"@zetachain/protocol-contracts@npm:7.0.0": + version: 7.0.0 + resolution: "@zetachain/protocol-contracts@npm:7.0.0" + checksum: 550f46c38cc334c8fb62ff810bd5f92a2db778b4db4bc18eace971aaa63635d6ce99efda73df00b54638442afe807de654a81489bf8521ca76d23b0b328bcc11 languageName: node linkType: hard @@ -3733,18 +3733,18 @@ __metadata: languageName: node linkType: hard -"@zetachain/toolkit@npm:^5.0.0": - version: 5.0.0 - resolution: "@zetachain/toolkit@npm:5.0.0" +"@zetachain/toolkit@npm:10.0.0": + version: 10.0.0 + resolution: "@zetachain/toolkit@npm:10.0.0" dependencies: "@inquirer/prompts": ^2.1.1 "@inquirer/select": 1.1.3 "@nomiclabs/hardhat-ethers": ^2.2.3 - "@openzeppelin/contracts": ^4.9.2 + "@openzeppelin/contracts": ^4.9.6 "@uniswap/v2-periphery": ^1.1.0-beta.0 "@zetachain/faucet-cli": ^4.0.1 - "@zetachain/networks": ^3.0.0 - "@zetachain/protocol-contracts": ^3.0.2 + "@zetachain/networks": 7.0.0 + "@zetachain/protocol-contracts": 7.0.0 axios: ^1.4.0 bech32: ^2.0.0 bip39: ^3.1.0 @@ -3758,12 +3758,13 @@ __metadata: handlebars: 4.7.7 hardhat: ^2.15.0 isomorphic-fetch: ^3.0.0 + lodash: ^4.17.21 moment: ^2.29.4 ora: 5.4.1 spinnies: ^0.5.1 tiny-secp256k1: ^2.2.3 ws: ^8.13.0 - checksum: 16f73a2d4ec12d46a4809094d7c1414e54a223bab587ddbb3a8743d1473eeb62637ae370df119f7e5d77c53e1434a7cc3d78a9a6f341675f3f130bf82d4ba95d + checksum: 21d240cb5c4ab570831ae2991526a30742033b42d3c91163a8282943885ebfaf0aa24e83483582804bbd561fbeb5bc713f728bfea39660b1b334601f9fcad152 languageName: node linkType: hard @@ -15089,7 +15090,7 @@ __metadata: "@types/node": ^17.0.25 "@typescript-eslint/eslint-plugin": ^5.20.0 "@typescript-eslint/parser": ^5.20.0 - "@zetachain/toolkit": ^5.0.0 + "@zetachain/toolkit": 10.0.0 chai: ^4.3.6 dotenv: ^16.0.0 eslint: ^8.13.0