Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update nft contract to calculate tokenid #167

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
54 changes: 31 additions & 23 deletions packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -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));

Expand All @@ -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
Expand Down
15 changes: 5 additions & 10 deletions packages/zevm-app-contracts/test/xp-nft/test.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
61 changes: 37 additions & 24 deletions packages/zevm-app-contracts/test/xp-nft/xp-nft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,59 +20,67 @@ 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,
sigTimestamp,
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,
sigTimestamp,
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,
Expand All @@ -85,19 +93,22 @@ 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,
sigTimestamp,
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 };
Expand All @@ -106,18 +117,18 @@ 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,
sigTimestamp,
signature,
} as UpdateParam;

await zetaXP.updateNFT(nftParams);
await zetaXP.updateNFT(tokenId, nftParams);
}

validateNFT(updatedSampleNFT);
validateNFT(tokenId, updatedSampleNFT);
});

it("Should update base url", async () => {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -213,18 +224,20 @@ 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,
sigTimestamp,
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 () => {
Expand Down
Loading
Loading