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: Enable XP NFT to be transferable #199

Merged
merged 2 commits into from
Jan 3, 2025
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 packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ contract ZetaXP is ERC721Upgradeable, Ownable2StepUpgradeable, EIP712Upgradeable
emit NFTUpdated(owner, tokenId, updateData.tag);
}

function _transfer(address from, address to, uint256 tokenId) internal override {
function _transfer(address from, address to, uint256 tokenId) internal virtual override {
revert TransferNotAllowed();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract ZetaXP_V2 is ZetaXP {
// Event for Level Set
event LevelSet(address indexed sender, uint256 indexed tokenId, uint256 level);

function version() public pure override returns (string memory) {
function version() public pure virtual override returns (string memory) {
return "2.0.0";
}

Expand Down
41 changes: 41 additions & 0 deletions packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./xpNFT_V2.sol";

contract ZetaXP_V3 is ZetaXP_V2 {
event TagUpdated(address indexed sender, uint256 indexed tokenId, bytes32 tag);

function version() public pure override returns (string memory) {
return "3.0.0";
}

function _transfer(address from, address to, uint256 tokenId) internal override {
bytes32 tag = tagByTokenId[tokenId];
if (tag != 0) {
tokenByUserTag[from][tag] = 0;
}
if (tokenByUserTag[to][tag] == 0) {
tokenByUserTag[to][tag] = tokenId;
}
ERC721Upgradeable._transfer(from, to, tokenId);
}

function moveTagToToken(uint256 tokenId, bytes32 tag) external {
uint256 currentTokenId = tokenByUserTag[msg.sender][tag];
address owner = ownerOf(tokenId);
if (owner != msg.sender) {
revert TransferNotAllowed();
}
if (currentTokenId == tokenId) {
return;
}
if (currentTokenId != 0) {
tagByTokenId[currentTokenId] = 0;
}

tagByTokenId[tokenId] = tag;
tokenByUserTag[msg.sender][tag] = tokenId;
emit TagUpdated(msg.sender, tokenId, tag);
}
}
Dismissed Show dismissed Hide dismissed
114 changes: 114 additions & 0 deletions packages/zevm-app-contracts/test/xp-nft/xp-nft-v3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers, upgrades } from "hardhat";

import { ZetaXP_V3 } from "../../typechain-types";
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]));

const getTokenIdFromRecipient = (receipt: any): number => {
//@ts-ignore
return receipt.events[0].args?.tokenId;
};

describe("XP NFT V3 Contract test", () => {
let zetaXP: ZetaXP_V3, signer: SignerWithAddress, user: SignerWithAddress, addrs: SignerWithAddress[];
let sampleNFT: NFT;

beforeEach(async () => {
[signer, user, ...addrs] = await ethers.getSigners();
const zetaXPFactory = await ethers.getContractFactory("ZetaXP");

zetaXP = await upgrades.deployProxy(zetaXPFactory, [
"ZETA NFT",
"ZNFT",
ZETA_BASE_URL,
signer.address,
signer.address,
]);

await zetaXP.deployed();

const ZetaXPFactory = await ethers.getContractFactory("ZetaXP_V3");
zetaXP = await upgrades.upgradeProxy(zetaXP.address, ZetaXPFactory);

const tag = encodeTag("XP_NFT");

sampleNFT = {
tag,
to: user.address,
tokenId: undefined,
};
});

const mintNFT = async (nft: NFT) => {
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,
nft.to,
nft
);

const nftParams: NFTSigned = {
...nft,
sigTimestamp,
signature,
signatureExpiration,
} as NFTSigned;

const tx = await zetaXP.mintNFT(nftParams);
const receipt = await tx.wait();
return getTokenIdFromRecipient(receipt);
};

it("Should update NFT level", async () => {
andresaiello marked this conversation as resolved.
Show resolved Hide resolved
const user2 = addrs[0];
const sampleNFT2 = { ...sampleNFT, to: user2.address };

const tokenId = await mintNFT(sampleNFT);
const tokenId2 = await mintNFT(sampleNFT2);
{
const owner1 = await zetaXP.ownerOf(tokenId);
const owner2 = await zetaXP.ownerOf(tokenId2);
expect(owner1).to.equal(user.address);
expect(owner2).to.equal(user2.address);
}
{
const token1 = await zetaXP.tokenByUserTag(user.address, sampleNFT.tag);
const token2 = await zetaXP.tokenByUserTag(user2.address, sampleNFT.tag);
expect(token1).to.equal(tokenId);
expect(token2).to.equal(tokenId2);
}
await zetaXP.connect(user).transferFrom(user.address, user2.address, tokenId);
{
const owner1 = await zetaXP.ownerOf(tokenId);
const owner2 = await zetaXP.ownerOf(tokenId2);
expect(owner1).to.equal(user2.address);
expect(owner2).to.equal(user2.address);
}
{
const token1 = await zetaXP.tokenByUserTag(user.address, sampleNFT.tag);
const token2 = await zetaXP.tokenByUserTag(user2.address, sampleNFT.tag);
expect(token1).to.equal(0);
expect(token2).to.equal(tokenId2);
}
await zetaXP.connect(user2).moveTagToToken(tokenId, sampleNFT.tag);
{
const token1 = await zetaXP.tokenByUserTag(user.address, sampleNFT.tag);
const token2 = await zetaXP.tokenByUserTag(user2.address, sampleNFT.tag);
expect(token1).to.equal(0);
expect(token2).to.equal(tokenId);
}
});
});
Loading