From db688da1be81d2937372eaa1526c7b3ab5a4f142 Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:18:30 -0300 Subject: [PATCH] feat: XP NFT contract audit suggestions (#175) --- .../.openzeppelin/unknown-7001.json | 311 +++++++++++++ .../contracts/xp-nft/test/xpNFTV2.sol | 2 +- .../contracts/xp-nft/xpNFT.sol | 89 ++-- .../zevm-app-contracts/data/addresses.json | 2 +- .../test/xp-nft/test.helpers.ts | 46 +- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 418 ++++++++++++++++-- 6 files changed, 756 insertions(+), 112 deletions(-) create mode 100644 packages/zevm-app-contracts/.openzeppelin/unknown-7001.json diff --git a/packages/zevm-app-contracts/.openzeppelin/unknown-7001.json b/packages/zevm-app-contracts/.openzeppelin/unknown-7001.json new file mode 100644 index 00000000..7263a7a7 --- /dev/null +++ b/packages/zevm-app-contracts/.openzeppelin/unknown-7001.json @@ -0,0 +1,311 @@ +{ + "manifestVersion": "3.2", + "admin": { + "address": "0x9cb34a07CE042E3CFA0194E5eF230Bc8F39072C4", + "txHash": "0x9034e811691ff8bd1f34f17b6827d5d84c8d9c88f352fd947d6a6cd0e0d12fdc" + }, + "proxies": [ + { + "address": "0x5c25b6f4D2b7a550a80561d3Bf274C953aC8be7d", + "txHash": "0x4e9308b8661aba7d1de8a41be4534ec418eb1c5fad60d1ba4fdd75c79f19a168", + "kind": "transparent" + } + ], + "impls": { + "33876915c0f68ba29fef566a0ae95e50929726653f8e7778086f57c4b331ca56": { + "address": "0x1838B9563A984d8222122892BfDE3cFEeB21b29B", + "txHash": "0x5c925f3bb960fc76ae2c28e1f45d1d76431e09c89f5bded7267a518e892f3e1a", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "101", + "type": "t_string_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:25" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "102", + "type": "t_string_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:28" + }, + { + "label": "_owners", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_uint256,t_address)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:31" + }, + { + "label": "_balances", + "offset": 0, + "slot": "104", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:34" + }, + { + "label": "_tokenApprovals", + "offset": 0, + "slot": "105", + "type": "t_mapping(t_uint256,t_address)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:37" + }, + { + "label": "_operatorApprovals", + "offset": 0, + "slot": "106", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "107", + "type": "t_array(t_uint256)44_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:477" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + }, + { + "label": "_hashedName", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol:40", + "renamedFrom": "_HASHED_NAME" + }, + { + "label": "_hashedVersion", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol:42", + "renamedFrom": "_HASHED_VERSION" + }, + { + "label": "_name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol:44" + }, + { + "label": "_version", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol:45" + }, + { + "label": "__gap", + "offset": 0, + "slot": "255", + "type": "t_array(t_uint256)48_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol:204" + }, + { + "label": "lastUpdateTimestampByTokenId", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:22" + }, + { + "label": "tagByTokenId", + "offset": 0, + "slot": "304", + "type": "t_mapping(t_uint256,t_bytes32)", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:23" + }, + { + "label": "tokenByUserTag", + "offset": 0, + "slot": "305", + "type": "t_mapping(t_address,t_mapping(t_bytes32,t_uint256))", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:24" + }, + { + "label": "baseTokenURI", + "offset": 0, + "slot": "306", + "type": "t_string_storage", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:27" + }, + { + "label": "signerAddress", + "offset": 0, + "slot": "307", + "type": "t_address", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:28" + }, + { + "label": "_currentTokenId", + "offset": 0, + "slot": "308", + "type": "t_uint256", + "contract": "ZetaXP", + "src": "contracts/xp-nft/xpNFT.sol:31" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_bool))": { + "label": "mapping(address => mapping(address => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_bytes32,t_uint256))": { + "label": "mapping(address => mapping(bytes32 => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bytes32)": { + "label": "mapping(uint256 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + } + } +} diff --git a/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol index 5995f6fa..37f76e93 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.7; +pragma solidity 0.8.9; import "../xpNFT.sol"; diff --git a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol index 4023f2be..69bcf3c9 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -1,21 +1,19 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.7; +pragma solidity 0.8.9; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { - /* An ECDSA signature. */ - struct Signature { - uint8 v; - bytes32 r; - bytes32 s; - } +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +contract ZetaXP is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { + bytes32 private constant MINTORUPDATE_TYPEHASH = + keccak256("MintOrUpdateNFT(address to,uint256 signatureExpiration,uint256 sigTimestamp,bytes32 tag)"); struct UpdateData { address to; - Signature signature; + bytes signature; uint256 signatureExpiration; uint256 sigTimestamp; bytes32 tag; @@ -36,6 +34,10 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { event NFTMinted(address indexed sender, uint256 indexed tokenId, bytes32 tag); // Event for NFT Update event NFTUpdated(address indexed sender, uint256 indexed tokenId, bytes32 tag); + // Event for Signer Update + event SignerUpdated(address indexed signerAddress); + // Event for Base URI Update + event BaseURIUpdated(string baseURI); error InvalidSigner(); error SignatureExpired(); @@ -45,6 +47,11 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { error OutdatedSignature(); error TagAlreadyHoldByUser(); + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + function initialize( string memory name, string memory symbol, @@ -53,6 +60,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { address owner ) public initializer { if (signerAddress_ == address(0)) revert InvalidAddress(); + __EIP712_init("ZetaXP", "1"); __ERC721_init(name, symbol); __Ownable_init(); transferOwnership(owner); @@ -69,59 +77,42 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { function setSignerAddress(address signerAddress_) external onlyOwner { if (signerAddress_ == address(0)) revert InvalidAddress(); signerAddress = signerAddress_; + emit SignerUpdated(signerAddress_); } // Set the base URI for tokens function setBaseURI(string calldata _uri) external onlyOwner { baseTokenURI = _uri; + emit BaseURIUpdated(_uri); } // The following functions are overrides required by Solidity. function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable) returns (string memory) { _requireMinted(tokenId); - return string(abi.encodePacked(baseTokenURI, _uint2str(tokenId))); + return string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId))); } function supportsInterface(bytes4 interfaceId) public view override(ERC721Upgradeable) 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(uint256 tokenId, UpdateData memory updateData) private view { - bytes32 payloadHash = _calculateHash(updateData); - - bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); - - address messageSigner = ECDSA.recover( - messageHash, - updateData.signature.v, - updateData.signature.r, - updateData.signature.s + bytes32 structHash = keccak256( + abi.encode( + MINTORUPDATE_TYPEHASH, + updateData.to, + updateData.signatureExpiration, + updateData.sigTimestamp, + updateData.tag + ) ); + bytes32 constructedHash = _hashTypedDataV4(structHash); + + if (!SignatureChecker.isValidSignatureNow(signerAddress, constructedHash, updateData.signature)) { + revert InvalidSigner(); + } - if (signerAddress != messageSigner) revert InvalidSigner(); if (block.timestamp > updateData.signatureExpiration) revert SignatureExpired(); if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[tokenId]) revert OutdatedSignature(); } @@ -162,7 +153,13 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { function updateNFT(uint256 tokenId, UpdateData memory updateData) external { address owner = ownerOf(tokenId); updateData.to = owner; - if (tokenByUserTag[owner][updateData.tag] != tokenId) revert TagAlreadyHoldByUser(); + bool willUpdateTag = tagByTokenId[tokenId] != updateData.tag; + + if (willUpdateTag) { + if (tokenByUserTag[owner][updateData.tag] != 0) revert TagAlreadyHoldByUser(); + tokenByUserTag[owner][tagByTokenId[tokenId]] = 0; + } + _updateNFT(tokenId, updateData); emit NFTUpdated(owner, tokenId, updateData.tag); diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json index fd47be79..f7e25ec3 100644 --- a/packages/zevm-app-contracts/data/addresses.json +++ b/packages/zevm-app-contracts/data/addresses.json @@ -7,7 +7,7 @@ "zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559", "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d", "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E", - "ZetaXP": "0xfafd6Aaaa836E7744523Ea934D1da28187faE9F8" + "ZetaXP": "0x5c25b6f4D2b7a550a80561d3Bf274C953aC8be7d" }, "zeta_mainnet": { "disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d", 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 bd704266..093f45bb 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -1,39 +1,49 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ethers } from "hardhat"; - -export interface Signature { - r: string; - s: string; - v: number; -} export interface NFT { tag: string; to: string; + tokenId: number | undefined; } -export interface UpdateParam extends NFT { +export interface NFTSigned extends NFT { sigTimestamp: number; - signature: Signature; + signature: string; signatureExpiration: number; - tokenId: number; } export const getSignature = async ( + chainId: number, + verifyingContract: string, signer: SignerWithAddress, signatureExpiration: number, timestamp: number, to: string, nft: NFT ) => { - let payload = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "bytes32"], - [to, signatureExpiration, timestamp, nft.tag] - ); + const domain = { + chainId: chainId, + name: "ZetaXP", + verifyingContract: verifyingContract, + version: "1", + }; - const payloadHash = ethers.utils.keccak256(payload); + const types = { + MintOrUpdateNFT: [ + { name: "to", type: "address" }, + { name: "signatureExpiration", type: "uint256" }, + { name: "sigTimestamp", type: "uint256" }, + { name: "tag", type: "bytes32" }, + ], + }; - // This adds the message prefix - const signature = await signer.signMessage(ethers.utils.arrayify(payloadHash)); - return ethers.utils.splitSignature(signature); + const value = { + sigTimestamp: timestamp, + signatureExpiration, + tag: nft.tag, + to, + }; + // Signing the data + const signature = await signer._signTypedData(domain, types, value); + return signature; }; diff --git a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts index 49ce6804..de8f7e46 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -3,9 +3,12 @@ import { expect } from "chai"; import { ethers, upgrades } from "hardhat"; import { ZetaXP } from "../../typechain-types"; -import { getSignature, NFT, UpdateParam } from "./test.helpers"; +import { getSignature, NFT, NFTSigned } from "./test.helpers"; const ZETA_BASE_URL = "https://api.zetachain.io/nft/"; +const HARDHAT_CHAIN_ID = 1337; + +const encodeTag = (tag: string) => ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], [tag])); describe("XP NFT Contract test", () => { let zetaXP: ZetaXP, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[]; @@ -24,11 +27,12 @@ describe("XP NFT Contract test", () => { ]); await zetaXP.deployed(); - const tag = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], ["XP_NFT"])); + const tag = encodeTag("XP_NFT"); sampleNFT = { tag, to: user.address, + tokenId: undefined, }; }); @@ -50,14 +54,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = await zetaXP.mintNFT(nftParams); const receipt = await tx.wait(); @@ -71,14 +83,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1, sampleNFT.tag); }); @@ -88,14 +108,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(addrs[0], signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + addrs[0], + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.be.revertedWith("InvalidSigner"); @@ -108,35 +136,51 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = await zetaXP.mintNFT(nftParams); const receipt = await tx.wait(); tokenId = getTokenIdFromRecipient(receipt); } - const updatedSampleNFT = { ...sampleNFT, signedUp: 4321 }; + const updatedSampleNFT = { ...sampleNFT }; { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, updatedSampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...updatedSampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.updateNFT(tokenId, nftParams); } @@ -154,14 +198,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.mintNFT(nftParams); } @@ -175,14 +227,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.mintNFT(nftParams); } @@ -194,21 +254,29 @@ describe("XP NFT Contract test", () => { { const sampleNFT2 = { ...sampleNFT, - tag: ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], ["XP_NFT2"])), + tag: encodeTag("XP_NFT2"), tokenId: 2, }; const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, user.address, sampleNFT2); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + user.address, + sampleNFT2 + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT2, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.mintNFT(nftParams); } @@ -230,14 +298,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.mintNFT(nftParams); } @@ -250,14 +326,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = await zetaXP.mintNFT(nftParams); const receipt = await tx.wait(); @@ -284,14 +368,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; await zetaXP.mintNFT(nftParams); } @@ -305,14 +397,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, user.address, sampleNFT2); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + user.address, + sampleNFT2 + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT2, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.be.revertedWith("TagAlreadyHoldByUser"); @@ -324,14 +424,22 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = await zetaXP.mintNFT(nftParams); const receipt = await tx.wait(); @@ -350,6 +458,7 @@ describe("XP NFT Contract test", () => { expect(ownerAddr).to.be.eq(signer.address); } await zetaXP.transferOwnership(user.address); + await zetaXP.connect(user).acceptOwnership(); { const ownerAddr = await zetaXP.owner(); expect(ownerAddr).to.be.eq(user.address); @@ -361,16 +470,233 @@ describe("XP NFT Contract test", () => { const sigTimestamp = currentBlock.timestamp; const signatureExpiration = sigTimestamp - 1000; - const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); - const nftParams: UpdateParam = { + const nftParams: NFTSigned = { ...sampleNFT, sigTimestamp, signature, signatureExpiration, - } as UpdateParam; + } as NFTSigned; const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.be.revertedWith("SignatureExpired"); }); + + it("Should update NFT tag", async () => { + let tokenId = -1; + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + + const tag = encodeTag("XP_NFT2"); + const updatedSampleNFT = { + ...sampleNFT, + tag, + }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.updateNFT(tokenId, nftParams); + } + + validateNFT(tokenId, updatedSampleNFT); + }); + + it("Should accept to update NFT tag for the same", async () => { + let tokenId = -1; + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + + const updatedSampleNFT = { ...sampleNFT }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.updateNFT(tokenId, nftParams); + } + + validateNFT(tokenId, updatedSampleNFT); + }); + + it("Should revert if try to update NFT used tag", async () => { + let tokenId = -1; + const tag = encodeTag("XP_NFT2"); + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + sampleNFT + ); + + const nftParams: NFTSigned = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = await zetaXP.mintNFT(nftParams); + const receipt = await tx.wait(); + tokenId = getTokenIdFromRecipient(receipt); + } + { + const secondNFT = { + ...sampleNFT, + tag, + }; + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + secondNFT.to, + secondNFT + ); + + const nftParams: NFTSigned = { + ...secondNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + await zetaXP.mintNFT(nftParams); + } + + const updatedSampleNFT = { ...sampleNFT, tag }; + + { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; + + const signature = await getSignature( + HARDHAT_CHAIN_ID, + zetaXP.address, + signer, + signatureExpiration, + sigTimestamp, + sampleNFT.to, + updatedSampleNFT + ); + + const nftParams: NFTSigned = { + ...updatedSampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as NFTSigned; + + const tx = zetaXP.updateNFT(tokenId, nftParams); + await expect(tx).to.be.revertedWith("TagAlreadyHoldByUser"); + } + }); });