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: xp nfts contract skeleton #164

Merged
merged 12 commits into from
Aug 13, 2024
10 changes: 10 additions & 0 deletions packages/zevm-app-contracts/contracts/xp-nft/test/xpNFTV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "../xpNFT.sol";

contract ZetaXPV2 is ZetaXP {
function version() public pure override returns (string memory) {
return "2.0.0";
}
}
148 changes: 148 additions & 0 deletions packages/zevm-app-contracts/contracts/xp-nft/xpNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract ZetaXP is ERC721Upgradeable, OwnableUpgradeable {
/* An ECDSA signature. */
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

struct UpdateData {
address to;
uint256 tokenId;
Signature signature;
uint256 sigTimestamp;
uint256 signedUp;
}

mapping(uint256 => uint256) lastUpdateTimestampByTokenId;
mapping(uint256 => uint256) signedUpByTokenId;

// Base URL for NFT images
string public baseTokenURI;
address public signerAddress;

// Event for New Mint
event NFTMinted(address indexed sender, uint256 indexed tokenId);
// Event for NFT Update
event NFTUpdated(address indexed sender, uint256 indexed tokenId);

error InvalidSigner();
error LengthMismatch();
error TransferNotAllowed();
error OutdatedSignature();

function initialize(
string memory name,
string memory symbol,
string memory baseTokenURI_,
address signerAddress_

Check notice

Code scanning / Slither

Missing zero address validation Low

) public initializer {
__ERC721_init(name, symbol);
__Ownable_init();
baseTokenURI = baseTokenURI_;
signerAddress = signerAddress_;
}
andresaiello marked this conversation as resolved.
Show resolved Hide resolved

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

// The following functions are overrides required by Solidity.
function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable) returns (string memory) {
_requireMinted(tokenId);

return string(abi.encodePacked(baseTokenURI, _uint2str(tokenId)));
}
Comment on lines +57 to +61

Check failure

Code scanning / Slither

ABI encodePacked Collision High

ZetaXP.tokenURI(uint256) calls abi.encodePacked() with multiple dynamic arguments:
- string(abi.encodePacked(baseTokenURI,_uint2str(tokenId)))
andresaiello marked this conversation as resolved.
Show resolved Hide resolved

function supportsInterface(bytes4 interfaceId) public view override(ERC721Upgradeable) returns (bool) {
return super.supportsInterface(interfaceId);
}

// Helper function to convert uint to string
function _uint2str(uint _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len;
while (_i != 0) {
k = k - 1;
uint8 temp = (uint8(48 + (_i % 10)));
bstr[k] = bytes1(temp);
_i /= 10;
}
return string(bstr);
}

function _verify(UpdateData memory updateData) private view {
bytes32 payloadHash = _calculateHash(updateData);
bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash));

address messageSigner = ecrecover(
messageHash,
updateData.signature.v,
updateData.signature.r,
updateData.signature.s
);

if (signerAddress != messageSigner) revert InvalidSigner();
if (updateData.sigTimestamp <= lastUpdateTimestampByTokenId[updateData.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
);

return keccak256(encodedData);
}

function _updateNFT(UpdateData memory updateData) internal {
_verify(updateData);
lastUpdateTimestampByTokenId[updateData.tokenId] = updateData.sigTimestamp;
signedUpByTokenId[updateData.tokenId] = updateData.signedUp;
}

// External mint function
function mintNFT(UpdateData calldata mintData) external {
_mint(mintData.to, mintData.tokenId);

_updateNFT(mintData);

emit NFTMinted(mintData.to, mintData.tokenId);
}

// External mint function
function updateNFT(UpdateData memory updateData) external {
address owner = ownerOf(updateData.tokenId);
updateData.to = owner;
_updateNFT(updateData);

emit NFTUpdated(owner, updateData.tokenId);
}

// Set the base URI for tokens
function setBaseURI(string calldata _uri) external onlyOwner {
Dismissed Show dismissed Hide dismissed
andresaiello marked this conversation as resolved.
Show resolved Hide resolved
baseTokenURI = _uri;
}

function _transfer(address from, address to, uint256 tokenId) internal override {
revert TransferNotAllowed();
}
}
3 changes: 2 additions & 1 deletion packages/zevm-app-contracts/data/addresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"zetaSwap": "0xA8168Dc495Ed61E70f5c1941e2860050AB902cEF",
"zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559",
"invitationManager": "0x3649C03C472B698213926543456E9c21081e529d",
"withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E"
"withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E",
"ZetaXP": "0x80ECE9a08ba893e1B863C0c6B3c098268C146E40"
}
}
}
2 changes: 2 additions & 0 deletions packages/zevm-app-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "@nomicfoundation/hardhat-verify";
import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
import "@openzeppelin/hardhat-upgrades";
import "hardhat-gas-reporter";
import "solidity-coverage";
import "tsconfig-paths/register";
Expand Down Expand Up @@ -57,6 +58,7 @@ const config: HardhatUserConfig = {
{ version: "0.5.10" /** For create2 factory */ },
{ version: "0.6.6" /** For uniswap v2 */ },
{ version: "0.8.7" },
{ version: "0.8.9" },
],
settings: {
/**
Expand Down
2 changes: 2 additions & 0 deletions packages/zevm-app-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
},
"dependencies": {
"@openzeppelin/contracts": "4.9.3",
"@openzeppelin/contracts-upgradeable": "4.9.3",
"@openzeppelin/hardhat-upgrades": "^1.7.0-rc.0",
"@uniswap/v2-periphery": "1.1.0-beta.0",
"@zetachain/networks": "^4.0.0",
"@zetachain/protocol-contracts": "^4.0.1",
Expand Down
32 changes: 32 additions & 0 deletions packages/zevm-app-contracts/scripts/xp-nft/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isProtocolNetworkName } from "@zetachain/protocol-contracts";
import { ethers, network, upgrades } from "hardhat";

import { ZetaXP__factory } from "../../typechain-types";
import { saveAddress } from "../address.helpers";

const networkName = network.name;

const ZETA_BASE_URL = "https://api.zetachain.io/nft/";
const signer = "0x1d24d94520B94B26351f6573de5ef9731c48531A";
andresaiello marked this conversation as resolved.
Show resolved Hide resolved

const deployZetaXP = async () => {
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name");

const ZetaXPFactory = (await ethers.getContractFactory("ZetaXP")) as ZetaXP__factory;
const zetaXP = await upgrades.deployProxy(ZetaXPFactory, ["ZETA NFT", "ZNFT", ZETA_BASE_URL, signer]);

await zetaXP.deployed();

console.log("ZetaXP deployed to:", zetaXP.address);
saveAddress("ZetaXP", zetaXP.address, networkName);
};

const main = async () => {
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name");
await deployZetaXP();
};

main().catch((error) => {
console.error(error);
process.exit(1);
});
38 changes: 38 additions & 0 deletions packages/zevm-app-contracts/test/xp-nft/test.helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { ethers } from "hardhat";

export interface Signature {
r: string;
s: string;
v: number;
}

export interface NFT {
signedUp: number;
to: string;
tokenId: number;
}

export interface UpdateParam extends NFT {
sigTimestamp: number;
signature: Signature;
}

export const getSignature = async (
signer: SignerWithAddress,
timestamp: number,
to: string,
tokenId: number,
nft: NFT
) => {
let payload = ethers.utils.defaultAbiCoder.encode(
["address", "uint256", "uint256", "uint256"],
[to, tokenId, timestamp, nft.signedUp]
);

const payloadHash = ethers.utils.keccak256(payload);

// This adds the message prefix
const signature = await signer.signMessage(ethers.utils.arrayify(payloadHash));
return ethers.utils.splitSignature(signature);
};
andresaiello marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading