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: NFT and FT upgradeable using OpenZeppelin's UUPS #6

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
560abc1
upgradeable wip
fadeev Dec 2, 2024
9cbb94d
wip
fadeev Dec 3, 2024
6b8e43c
wip
fadeev Dec 3, 2024
76b927b
wip
fadeev Dec 3, 2024
76ecec6
wip
fadeev Dec 3, 2024
7b75695
NFT works
fadeev Dec 11, 2024
e56a27b
yarn lock
fadeev Dec 11, 2024
3299510
Remove initialize task
fadeev Dec 12, 2024
c10a0c4
token contract now upgradeable
fadeev Dec 12, 2024
2703523
solidity version
fadeev Dec 12, 2024
2d20568
remove comment
fadeev Dec 13, 2024
510ad53
gitignore
fadeev Dec 13, 2024
41115ea
feat: use ZETA for gas when sending from ZetaChain (#10)
fadeev Dec 17, 2024
9ca161f
rename ft contracts
fadeev Dec 17, 2024
a3f58ba
FT: pay with ZETA when transferring tokens from ZetaChain
fadeev Dec 17, 2024
fd3d640
Merge branch 'main' into upgradeable
fadeev Dec 18, 2024
fcab2fa
fix: remove fallback functions (#26)
fadeev Dec 19, 2024
76b5c25
fix: added setGasLimit (#25)
fadeev Dec 19, 2024
326c16b
burnable init
fadeev Dec 19, 2024
b5767bb
ZeroMsgValue
fadeev Dec 19, 2024
dcae400
nft: TokenTransfer event
fadeev Dec 19, 2024
a2fd412
mint: throw errors if token not found
fadeev Dec 19, 2024
1d52473
UniversalTokenEvents
fadeev Dec 19, 2024
9d9834e
Universal Events
fadeev Dec 19, 2024
f62a780
fix mint task
fadeev Dec 19, 2024
f61fb4f
token: fix mint
fadeev Dec 19, 2024
3b9a093
fix: validate token URI (#29)
fadeev Dec 19, 2024
0b208f1
fix: address validation (#28)
fadeev Dec 20, 2024
565a6fe
rename tasks
fadeev Dec 20, 2024
99d2b08
token: rename tasks
fadeev Dec 20, 2024
ae1b1d8
rename
fadeev Dec 20, 2024
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
75 changes: 54 additions & 21 deletions contracts/nft/contracts/evm/UniversalNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,69 @@
pragma solidity 0.8.26;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol";
import {RevertContext} from "@zetachain/protocol-contracts/contracts/Revert.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "../shared/Events.sol";

abstract contract UniversalNFT is
ERC721,
ERC721Enumerable,
ERC721URIStorage,
Ownable2Step,
Initializable,
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
ERC721URIStorageUpgradeable,
ERC721BurnableUpgradeable,
OwnableUpgradeable,
Events
{
GatewayEVM public immutable gateway;
GatewayEVM public gateway;
uint256 private _nextTokenId;
address public universal;
uint256 public immutable gasLimitAmount;
uint256 public gasLimitAmount;

error InvalidAddress();
error Unauthorized();
error InvalidGasLimit();
error GasTokenTransferFailed();

function setUniversal(address contractAddress) external onlyOwner {
if (contractAddress == address(0)) revert InvalidAddress();
universal = contractAddress;
emit SetUniversal(contractAddress);
}

modifier onlyGateway() {
if (msg.sender != address(gateway)) revert Unauthorized();
_;
}

constructor(address payable gatewayAddress, uint256 gas) {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(
address initialOwner,
string memory name,
string memory symbol,
address payable gatewayAddress,
uint256 gas
) public initializer {
__ERC721_init(name, symbol);
__ERC721Enumerable_init();
__ERC721URIStorage_init();
__Ownable_init(initialOwner);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also call init for ERC721BurnableUpgradeable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added burnable to universal token contract.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general ownable is ok but AccessControl.sol gives more granular roles and overall better, worth checking for furhter improvements imo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#33

if (gatewayAddress == address(0)) revert InvalidAddress();
if (gas == 0) revert InvalidGasLimit();
gasLimitAmount = gas;
gateway = GatewayEVM(gatewayAddress);
}

function setUniversal(address contractAddress) external onlyOwner {
if (contractAddress == address(0)) revert InvalidAddress();
universal = contractAddress;
emit SetUniversal(contractAddress);
}

function safeMint(address to, string memory uri) public onlyOwner {
uint256 hash = uint256(
keccak256(
Expand Down Expand Up @@ -144,20 +164,29 @@ abstract contract UniversalNFT is
address to,
uint256 tokenId,
address auth
) internal override(ERC721, ERC721Enumerable) returns (address) {
)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (address)
{
return super._update(to, tokenId, auth);
}

function _increaseBalance(
address account,
uint128 value
) internal override(ERC721, ERC721Enumerable) {
) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) {
super._increaseBalance(account, value);
}

function tokenURI(
uint256 tokenId
) public view override(ERC721, ERC721URIStorage) returns (string memory) {
)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory)
{
return super.tokenURI(tokenId);
}

Expand All @@ -166,7 +195,11 @@ abstract contract UniversalNFT is
)
public
view
override(ERC721, ERC721Enumerable, ERC721URIStorage)
override(
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
ERC721URIStorageUpgradeable
)
returns (bool)
{
return super.supportsInterface(interfaceId);
Expand Down
22 changes: 11 additions & 11 deletions contracts/nft/contracts/example/Connected.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity 0.8.26;
import "../evm/UniversalNFT.sol";

contract Connected is UniversalNFT {
constructor(
address payable gatewayAddress,
address owner,
string memory name,
string memory symbol,
uint256 gasLimit
)
UniversalNFT(gatewayAddress, gasLimit)
Ownable(owner)
ERC721(name, symbol)
{}
// constructor(
// address payable gatewayAddress,
// address owner,
// string memory name,
// string memory symbol,
// uint256 gasLimit
// )
// UniversalNFT(gatewayAddress, gasLimit)
// Ownable(owner)
// ERC721(name, symbol)
// {}
}
18 changes: 3 additions & 15 deletions contracts/nft/contracts/example/Universal.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
Fixed Show fixed Hide fixed

import "../zetachain/UniversalNFT.sol";

contract Universal is UniversalNFT {
constructor(
address payable gatewayAddress,
address owner,
string memory name,
string memory symbol,
uint256 gasLimit,
address uniswapRouter
)
UniversalNFT(gatewayAddress, gasLimit, uniswapRouter)
Ownable(owner)
ERC721(name, symbol)
{}
}
contract Universal is UniversalNFT {}
68 changes: 49 additions & 19 deletions contracts/nft/contracts/zetachain/UniversalNFT.sol
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol";
import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol";
import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol";
import {SystemContract} from "@zetachain/toolkit/contracts/SystemContract.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "../shared/Events.sol";

abstract contract UniversalNFT is
ERC721,
ERC721Enumerable,
ERC721URIStorage,
Ownable2Step,
contract UniversalNFT is
Initializable,
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
ERC721URIStorageUpgradeable,
ERC721BurnableUpgradeable,
OwnableUpgradeable,
UniversalContract,
Events
{
GatewayZEVM public immutable gateway;
address public immutable uniswapRouter;
GatewayZEVM public gateway;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would say gateway and uniswapRouter should not change address so they can remain immutable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract is upgradeable, so I believe state variables have to be mutable, so they can be initialized in the initialize function.

address public uniswapRouter;
uint256 private _nextTokenId;
bool public constant isUniversal = true;
uint256 public immutable gasLimitAmount;
uint256 public gasLimitAmount;

error TransferFailed();
error Unauthorized();
Expand All @@ -41,11 +45,24 @@ abstract contract UniversalNFT is
_;
}

constructor(
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(
address initialOwner,
string memory name,
string memory symbol,
address payable gatewayAddress,
uint256 gas,
address uniswapRouterAddress
) {
) public initializer {
__ERC721_init(name, symbol);
__ERC721Enumerable_init();
__ERC721URIStorage_init();
__ERC721Burnable_init();
__Ownable_init(initialOwner);
if (gatewayAddress == address(0) || uniswapRouterAddress == address(0))
revert InvalidAddress();
if (gas == 0) revert InvalidGasLimit();
Expand Down Expand Up @@ -193,20 +210,29 @@ abstract contract UniversalNFT is
address to,
uint256 tokenId,
address auth
) internal override(ERC721, ERC721Enumerable) returns (address) {
)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (address)
{
return super._update(to, tokenId, auth);
}

function _increaseBalance(
address account,
uint128 value
) internal override(ERC721, ERC721Enumerable) {
) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) {
super._increaseBalance(account, value);
}

function tokenURI(
uint256 tokenId
) public view override(ERC721, ERC721URIStorage) returns (string memory) {
)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory)
{
return super.tokenURI(tokenId);
}

Expand All @@ -215,7 +241,11 @@ abstract contract UniversalNFT is
)
public
view
override(ERC721, ERC721Enumerable, ERC721URIStorage)
override(
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
ERC721URIStorageUpgradeable
)
returns (bool)
{
return super.supportsInterface(interfaceId);
Expand Down
9 changes: 7 additions & 2 deletions contracts/nft/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "./tasks/deploy";
import "./tasks/deploy";
import "./tasks/mint";
import "./tasks/transfer";
import "./tasks/initialize";
import "./tasks/universalSetConnected";
import "./tasks/connectedSetUniversal";
import "@zetachain/localnet/tasks";
Expand All @@ -11,11 +11,16 @@ import "@zetachain/toolkit/tasks";
import { getHardhatConfigNetworks } from "@zetachain/networks";
import { HardhatUserConfig } from "hardhat/config";

import "@nomiclabs/hardhat-ethers";
import "@openzeppelin/hardhat-upgrades";

const config: HardhatUserConfig = {
networks: {
...getHardhatConfigNetworks(),
},
solidity: "0.8.26",
solidity: {
version: "0.8.26",
},
};

export default config;
9 changes: 7 additions & 2 deletions contracts/nft/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"@nomicfoundation/hardhat-foundry": "^1.1.2",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.12",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@openzeppelin/hardhat-upgrades": "^3.6.0",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
Expand All @@ -41,7 +44,7 @@
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-typescript-sort-keys": "^2.3.0",
"ethers": "^5.4.7",
"ethers": "^6.13.4",
"hardhat": "^2.17.2",
"hardhat-gas-reporter": "^1.0.8",
"prettier": "^2.8.8",
Expand All @@ -53,6 +56,8 @@
"packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72",
"dependencies": {
"@coral-xyz/anchor": "0.30.0",
"@openzeppelin/contracts": "^5.1.0",
"@openzeppelin/contracts-upgradeable": "^5.1.0",
"@solana-developers/helpers": "^2.4.0",
"@solana/spl-memo": "^0.2.5",
"@solana/web3.js": "^1.95.2",
Expand Down
Loading
Loading