Skip to content

Commit

Permalink
feat: Enable XP NFT to be transferable (#199)
Browse files Browse the repository at this point in the history
* feat: Enable XP NFT to be transferable

* Update packages/zevm-app-contracts/test/xp-nft/xp-nft-v3.ts

Co-authored-by: Guillermo Maiolo <guille.maiolo@gmail.com>

---------

Co-authored-by: Guillermo Maiolo <guille.maiolo@gmail.com>
  • Loading branch information
andresaiello and GMaiolo authored Jan 3, 2025
1 parent 27d7c2f commit 66ad597
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
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();
}
}
2 changes: 1 addition & 1 deletion packages/zevm-app-contracts/contracts/xp-nft/xpNFT_V2.sol
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);
}
}
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 transfer successfully", async () => {
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);
}
});
});

0 comments on commit 66ad597

Please sign in to comment.