From a5b6ea25a719defe4eb7757613e9bd6f6cbda87f Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Thu, 22 Aug 2024 11:32:26 -0300 Subject: [PATCH 1/2] feat: Add signature expiration --- .../contracts/xp-nft/xpNFT.sol | 8 +-- .../test/xp-nft/test.helpers.ts | 12 +++- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 58 +++++++++++++------ 3 files changed, 53 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 d5729c0..ce1a2d5 100644 --- a/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol +++ b/packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol @@ -16,13 +16,12 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { struct UpdateData { address to; Signature signature; + uint256 signatureExpiration; uint256 sigTimestamp; - uint256 signedUp; bytes32 tag; } mapping(uint256 => uint256) public lastUpdateTimestampByTokenId; - mapping(uint256 => uint256) public signedUpByTokenId; mapping(uint256 => bytes32) public tagByTokenId; mapping(address => mapping(bytes32 => uint256)) public tokenByUserTag; @@ -39,6 +38,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { event NFTUpdated(address indexed sender, uint256 indexed tokenId, bytes32 tag); error InvalidSigner(); + error SignatureExpired(); error InvalidAddress(); error LengthMismatch(); error TransferNotAllowed(); @@ -111,6 +111,7 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { ); if (signerAddress != messageSigner) revert InvalidSigner(); + if (block.timestamp > updateData.signatureExpiration) revert SignatureExpired(); if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[tokenId]) revert OutdatedSignature(); } @@ -118,8 +119,8 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { function _calculateHash(UpdateData memory updateData) private pure returns (bytes32) { bytes memory encodedData = abi.encode( updateData.to, + updateData.signatureExpiration, updateData.sigTimestamp, - updateData.signedUp, updateData.tag ); @@ -129,7 +130,6 @@ contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable { function _updateNFT(uint256 tokenId, UpdateData memory updateData) internal { _verify(tokenId, updateData); lastUpdateTimestampByTokenId[tokenId] = updateData.sigTimestamp; - signedUpByTokenId[tokenId] = updateData.signedUp; tagByTokenId[tokenId] = updateData.tag; tokenByUserTag[updateData.to][updateData.tag] = tokenId; } diff --git a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts index fbf68a8..bd70426 100644 --- a/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts +++ b/packages/zevm-app-contracts/test/xp-nft/test.helpers.ts @@ -8,7 +8,6 @@ export interface Signature { } export interface NFT { - signedUp: number; tag: string; to: string; } @@ -16,13 +15,20 @@ export interface NFT { export interface UpdateParam extends NFT { sigTimestamp: number; signature: Signature; + signatureExpiration: number; tokenId: number; } -export const getSignature = async (signer: SignerWithAddress, timestamp: number, to: string, nft: NFT) => { +export const getSignature = async ( + signer: SignerWithAddress, + signatureExpiration: number, + timestamp: number, + to: string, + nft: NFT +) => { let payload = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "bytes32"], - [to, timestamp, nft.signedUp, nft.tag] + [to, signatureExpiration, timestamp, 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 86e4f22..2109d98 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -1,8 +1,5 @@ -import { expect, use } from "chai"; -import { solidity } from "ethereum-waffle"; -use(solidity); import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import exp from "constants"; +import { expect } from "chai"; import { ethers, upgrades } from "hardhat"; import { ZetaXP } from "../../typechain-types"; @@ -30,7 +27,6 @@ describe("XP NFT Contract test", () => { const tag = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string"], ["XP_NFT"])); sampleNFT = { - signedUp: 1234, tag, to: user.address, }; @@ -52,13 +48,15 @@ describe("XP NFT Contract test", () => { it("Should mint an NFT", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = await zetaXP.mintNFT(nftParams); @@ -71,13 +69,15 @@ describe("XP NFT Contract test", () => { it("Should emit event on minting", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = zetaXP.mintNFT(nftParams); await expect(tx).to.emit(zetaXP, "NFTMinted").withArgs(user.address, 1, sampleNFT.tag); @@ -86,13 +86,15 @@ describe("XP NFT Contract test", () => { it("Should revert if signature is not correct", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(addrs[0], sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(addrs[0], signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = zetaXP.mintNFT(nftParams); @@ -104,13 +106,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = await zetaXP.mintNFT(nftParams); @@ -123,13 +127,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, updatedSampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, updatedSampleNFT); const nftParams: UpdateParam = { ...updatedSampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.updateNFT(tokenId, nftParams); @@ -146,13 +152,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -165,13 +173,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -189,13 +199,15 @@ describe("XP NFT Contract test", () => { }; const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, user.address, sampleNFT2); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, user.address, sampleNFT2); const nftParams: UpdateParam = { ...sampleNFT2, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -216,13 +228,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -234,13 +248,15 @@ describe("XP NFT Contract test", () => { it("Should revert if try to use same signature twice", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = await zetaXP.mintNFT(nftParams); @@ -266,13 +282,15 @@ describe("XP NFT Contract test", () => { { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; await zetaXP.mintNFT(nftParams); @@ -285,13 +303,15 @@ describe("XP NFT Contract test", () => { }; const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, user.address, sampleNFT2); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, user.address, sampleNFT2); const nftParams: UpdateParam = { ...sampleNFT2, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = zetaXP.mintNFT(nftParams); @@ -302,13 +322,15 @@ describe("XP NFT Contract test", () => { it("Should query by tag and by user", async () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp + 1000; - const signature = await getSignature(signer, sigTimestamp, sampleNFT.to, sampleNFT); + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); const nftParams: UpdateParam = { ...sampleNFT, sigTimestamp, signature, + signatureExpiration, } as UpdateParam; const tx = await zetaXP.mintNFT(nftParams); From ad10f479180f528b927f029b01667f59f7c2dbdf Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Thu, 22 Aug 2024 11:34:18 -0300 Subject: [PATCH 2/2] add test --- .../zevm-app-contracts/data/addresses.json | 2 +- .../zevm-app-contracts/test/xp-nft/xp-nft.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json index 789e08a..fd47be7 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": "0xE1DfA5dfd1d6c6b47C8c9b8726EdD019D365491D" + "ZetaXP": "0xfafd6Aaaa836E7744523Ea934D1da28187faE9F8" }, "zeta_mainnet": { "disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d", 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 2109d98..49ce680 100644 --- a/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts +++ b/packages/zevm-app-contracts/test/xp-nft/xp-nft.ts @@ -355,4 +355,22 @@ describe("XP NFT Contract test", () => { expect(ownerAddr).to.be.eq(user.address); } }); + + it("Should revert if signatured expired", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigTimestamp = currentBlock.timestamp; + const signatureExpiration = sigTimestamp - 1000; + + const signature = await getSignature(signer, signatureExpiration, sigTimestamp, sampleNFT.to, sampleNFT); + + const nftParams: UpdateParam = { + ...sampleNFT, + sigTimestamp, + signature, + signatureExpiration, + } as UpdateParam; + + const tx = zetaXP.mintNFT(nftParams); + await expect(tx).to.be.revertedWith("SignatureExpired"); + }); });