Skip to content

Commit

Permalink
feat: Update nft contract to calculate tokenid
Browse files Browse the repository at this point in the history
  • Loading branch information
andresaiello committed Aug 15, 2024
1 parent b0132b4 commit 3d1b6ec
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 84 deletions.
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

0 comments on commit 3d1b6ec

Please sign in to comment.