diff --git a/contracts/nft/contracts/evm/UniversalNFT.sol b/contracts/nft/contracts/evm/UniversalNFT.sol index 3a99e89..131d519 100644 --- a/contracts/nft/contracts/evm/UniversalNFT.sol +++ b/contracts/nft/contracts/evm/UniversalNFT.sol @@ -11,18 +11,20 @@ import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/t import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ERC721PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721PausableUpgradeable.sol"; -import "../shared/Events.sol"; +import "../shared/UniversalNFTEvents.sol"; contract UniversalNFT is Initializable, ERC721Upgradeable, - ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable, - ERC721BurnableUpgradeable, + ERC721EnumerableUpgradeable, + ERC721PausableUpgradeable, OwnableUpgradeable, + ERC721BurnableUpgradeable, UUPSUpgradeable, - Events + UniversalNFTEvents { GatewayEVM public gateway; uint256 private _nextTokenId; @@ -54,6 +56,7 @@ contract UniversalNFT is __ERC721Enumerable_init(); __ERC721URIStorage_init(); __Ownable_init(initialOwner); + __ERC721Burnable_init(); __UUPSUpgradeable_init(); if (gatewayAddress == address(0)) revert InvalidAddress(); if (gas == 0) revert InvalidGasLimit(); @@ -61,13 +64,21 @@ contract UniversalNFT is gateway = GatewayEVM(gatewayAddress); } + function setGasLimit(uint256 gas) external onlyOwner { + if (gas == 0) revert InvalidGasLimit(); + gasLimitAmount = gas; + } + 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 { + function safeMint( + address to, + string memory uri + ) public whenNotPaused onlyOwner { uint256 hash = uint256( keccak256( abi.encodePacked(address(this), block.number, _nextTokenId++) @@ -85,7 +96,7 @@ contract UniversalNFT is uint256 tokenId, address receiver, address destination - ) external payable { + ) external payable whenNotPaused { if (receiver == address(0)) revert InvalidAddress(); string memory uri = tokenURI(tokenId); @@ -156,10 +167,6 @@ contract UniversalNFT is emit TokenTransferReverted(sender, tokenId, uri); } - receive() external payable {} - - fallback() external payable {} - // The following functions are overrides required by Solidity. function _update( @@ -168,7 +175,11 @@ contract UniversalNFT is address auth ) internal - override(ERC721Upgradeable, ERC721EnumerableUpgradeable) + override( + ERC721Upgradeable, + ERC721EnumerableUpgradeable, + ERC721PausableUpgradeable + ) returns (address) { return super._update(to, tokenId, auth); @@ -210,4 +221,14 @@ contract UniversalNFT is function _authorizeUpgrade( address newImplementation ) internal override onlyOwner {} + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + receive() external payable {} } diff --git a/contracts/nft/contracts/shared/Events.sol b/contracts/nft/contracts/shared/UniversalNFTEvents.sol similarity index 96% rename from contracts/nft/contracts/shared/Events.sol rename to contracts/nft/contracts/shared/UniversalNFTEvents.sol index 385f8c7..815a095 100644 --- a/contracts/nft/contracts/shared/Events.sol +++ b/contracts/nft/contracts/shared/UniversalNFTEvents.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -contract Events { +contract UniversalNFTEvents { event SetUniversal(address indexed universalAddress); event SetConnected(address indexed zrc20, address contractAddress); event TokenMinted(address indexed to, uint256 indexed tokenId, string uri); diff --git a/contracts/nft/contracts/zetachain/UniversalNFT.sol b/contracts/nft/contracts/zetachain/UniversalNFT.sol index de0ba5e..7b3e142 100644 --- a/contracts/nft/contracts/zetachain/UniversalNFT.sol +++ b/contracts/nft/contracts/zetachain/UniversalNFT.sol @@ -8,26 +8,28 @@ import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.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 {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; +import {ERC721PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721PausableUpgradeable.sol"; import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; -import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "../shared/Events.sol"; +import "../shared/UniversalNFTEvents.sol"; contract UniversalNFT is Initializable, ERC721Upgradeable, - ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable, - ERC721BurnableUpgradeable, + ERC721EnumerableUpgradeable, + ERC721PausableUpgradeable, OwnableUpgradeable, + ERC721BurnableUpgradeable, UniversalContract, UUPSUpgradeable, - Events + UniversalNFTEvents { GatewayZEVM public gateway; address public uniswapRouter; @@ -40,6 +42,7 @@ contract UniversalNFT is error InvalidAddress(); error InvalidGasLimit(); error ApproveFailed(); + error ZeroMsgValue(); mapping(address => address) public connected; @@ -64,8 +67,8 @@ contract UniversalNFT is __ERC721_init(name, symbol); __ERC721Enumerable_init(); __ERC721URIStorage_init(); - __ERC721Burnable_init(); __Ownable_init(initialOwner); + __ERC721Burnable_init(); __UUPSUpgradeable_init(); if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) revert InvalidAddress(); @@ -75,6 +78,11 @@ contract UniversalNFT is gasLimitAmount = gas; } + function setGasLimit(uint256 gas) external onlyOwner { + if (gas == 0) revert InvalidGasLimit(); + gasLimitAmount = gas; + } + function setConnected( address zrc20, address contractAddress @@ -87,7 +95,8 @@ contract UniversalNFT is uint256 tokenId, address receiver, address destination - ) public payable { + ) public payable whenNotPaused { + if (msg.value == 0) revert ZeroMsgValue(); if (receiver == address(0)) revert InvalidAddress(); string memory uri = tokenURI(tokenId); _burn(tokenId); @@ -142,9 +151,14 @@ contract UniversalNFT is callOptions, revertOptions ); + + emit TokenTransfer(receiver, destination, tokenId, uri); } - function safeMint(address to, string memory uri) public onlyOwner { + function safeMint( + address to, + string memory uri + ) public onlyOwner whenNotPaused { uint256 hash = uint256( keccak256( abi.encodePacked(address(this), block.number, _nextTokenId++) @@ -230,7 +244,11 @@ contract UniversalNFT is address auth ) internal - override(ERC721Upgradeable, ERC721EnumerableUpgradeable) + override( + ERC721Upgradeable, + ERC721EnumerableUpgradeable, + ERC721PausableUpgradeable + ) returns (address) { return super._update(to, tokenId, auth); @@ -273,5 +291,13 @@ contract UniversalNFT is address newImplementation ) internal override onlyOwner {} + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + receive() external payable {} } diff --git a/contracts/nft/hardhat.config.ts b/contracts/nft/hardhat.config.ts index 32b68ca..8a5cadd 100644 --- a/contracts/nft/hardhat.config.ts +++ b/contracts/nft/hardhat.config.ts @@ -1,8 +1,4 @@ -import "./tasks/deploy"; -import "./tasks/mint"; -import "./tasks/transfer"; -import "./tasks/universalSetConnected"; -import "./tasks/connectedSetUniversal"; +import "./tasks"; import "@zetachain/localnet/tasks"; import "@nomicfoundation/hardhat-toolbox"; import "@zetachain/toolkit/tasks"; diff --git a/contracts/nft/package.json b/contracts/nft/package.json index 5b17b38..a857c28 100644 --- a/contracts/nft/package.json +++ b/contracts/nft/package.json @@ -28,6 +28,7 @@ "@types/chai": "^4.2.0", "@types/mocha": ">=9.1.0", "@types/node": ">=12.0.0", + "@types/validator": "^13.12.2", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "@zetachain/localnet": "4.0.0-rc6", @@ -61,6 +62,7 @@ "@solana-developers/helpers": "^2.4.0", "@solana/spl-memo": "^0.2.5", "@solana/web3.js": "^1.95.2", - "@zetachain/protocol-contracts": "11.0.0-rc3" + "@zetachain/protocol-contracts": "11.0.0-rc3", + "validator": "^13.12.0" } -} \ No newline at end of file +} diff --git a/contracts/nft/scripts/localnet.sh b/contracts/nft/scripts/localnet.sh index 274ec98..731b2c4 100755 --- a/contracts/nft/scripts/localnet.sh +++ b/contracts/nft/scripts/localnet.sh @@ -29,46 +29,46 @@ GATEWAY_BNB=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="bnb" UNISWAP_ROUTER=$(jq -r '.addresses[] | select(.type=="uniswapRouterInstance" and .chain=="zetachain") | .address' localnet.json) SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -CONTRACT_ZETACHAIN=$(npx hardhat deploy --network localhost --name ZetaChainUniversalNFT --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') +CONTRACT_ZETACHAIN=$(npx hardhat nft:deploy --network localhost --name ZetaChainUniversalNFT --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') echo -e "\nšŸš€ Deployed NFT contract on ZetaChain: $CONTRACT_ZETACHAIN" -CONTRACT_ETHEREUM=$(npx hardhat deploy --name EVMUniversalNFT --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') +CONTRACT_ETHEREUM=$(npx hardhat nft:deploy --name EVMUniversalNFT --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') echo -e "šŸš€ Deployed NFT contract on Ethereum: $CONTRACT_ETHEREUM" -CONTRACT_BNB=$(npx hardhat deploy --name EVMUniversalNFT --json --network localhost --gas-limit 1000000 --gateway "$GATEWAY_BNB" | jq -r '.contractAddress') +CONTRACT_BNB=$(npx hardhat nft:deploy --name EVMUniversalNFT --json --network localhost --gas-limit 1000000 --gateway "$GATEWAY_BNB" | jq -r '.contractAddress') echo -e "šŸš€ Deployed NFT contract on BNB chain: $CONTRACT_BNB" echo -e "\nšŸ“® User Address: $SENDER" echo -e "\nšŸ”— Setting universal and connected contracts..." -npx hardhat connected-set-universal --network localhost --contract "$CONTRACT_ETHEREUM" --universal "$CONTRACT_ZETACHAIN" --json -npx hardhat connected-set-universal --network localhost --contract "$CONTRACT_BNB" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null -npx hardhat universal-set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_ETHEREUM" --zrc20 "$ZRC20_ETHEREUM" --json &>/dev/null -npx hardhat universal-set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_BNB" --zrc20 "$ZRC20_BNB" --json &>/dev/null +npx hardhat nft:set-universal --network localhost --contract "$CONTRACT_ETHEREUM" --universal "$CONTRACT_ZETACHAIN" --json +npx hardhat nft:set-universal --network localhost --contract "$CONTRACT_BNB" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null +npx hardhat nft:set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_ETHEREUM" --zrc20 "$ZRC20_ETHEREUM" --json &>/dev/null +npx hardhat nft:set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_BNB" --zrc20 "$ZRC20_BNB" --json &>/dev/null npx hardhat localnet-check balance -NFT_ID=$(npx hardhat mint --network localhost --json --contract "$CONTRACT_ZETACHAIN" --token-uri https://example.com/nft/metadata/1 | jq -r '.tokenId') +NFT_ID=$(npx hardhat nft:mint --network localhost --json --contract "$CONTRACT_ZETACHAIN" --token-uri https://example.com/nft/metadata/1 | jq -r '.tokenId') echo -e "\nMinted NFT with ID: $NFT_ID on ZetaChain." npx hardhat localnet-check balance echo -e "\nTransferring NFT: ZetaChain ā†’ Ethereum..." -npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" --gas-amount 1 +npx hardhat nft:transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" --gas-amount 1 npx hardhat localnet-check balance echo -e "\nTransferring NFT: Ethereum ā†’ BNB..." -npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ETHEREUM" --to "$ZRC20_BNB" --gas-amount 1 +npx hardhat nft:transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ETHEREUM" --to "$ZRC20_BNB" --gas-amount 1 npx hardhat localnet-check balance echo -e "\nTransferring NFT: BNB ā†’ ZetaChain..." -npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_BNB" +npx hardhat nft:transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_BNB" npx hardhat localnet-check balance diff --git a/contracts/nft/tasks/deploy.ts b/contracts/nft/tasks/deploy.ts index 5a4d6bc..a64109f 100644 --- a/contracts/nft/tasks/deploy.ts +++ b/contracts/nft/tasks/deploy.ts @@ -2,6 +2,7 @@ import { task, types } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; const network = hre.network.name; const [signer] = await hre.ethers.getSigners(); @@ -11,6 +12,13 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if ( + !isAddress(args.gateway) || + (args.uniswapRouter && !isAddress(args.uniswapRouter)) + ) { + throw new Error("Invalid Ethereum address provided."); + } + const factory: any = await hre.ethers.getContractFactory(args.name); const contract = await hre.upgrades.deployProxy(factory, [ @@ -37,7 +45,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("deploy", "Deploy the NFT contract", main) +export const nftDeploy = task("nft:deploy", "Deploy the NFT contract", main) .addFlag("json", "Output the result in JSON format") .addOptionalParam("tokenName", "NFT name", "Universal NFT") .addOptionalParam("tokenSymbol", "NFT symbol", "UNFT") diff --git a/contracts/nft/tasks/index.ts b/contracts/nft/tasks/index.ts new file mode 100644 index 0000000..c6de558 --- /dev/null +++ b/contracts/nft/tasks/index.ts @@ -0,0 +1,5 @@ +export { nftSetUniversal } from "./setUniversal"; +export { nftMint } from "./mint"; +export { nftTransfer } from "./transfer"; +export { nftSetConnected } from "./setConnected"; +export { nftDeploy } from "./deploy"; diff --git a/contracts/nft/tasks/mint.ts b/contracts/nft/tasks/mint.ts index dc45c44..4e55d6d 100644 --- a/contracts/nft/tasks/mint.ts +++ b/contracts/nft/tasks/mint.ts @@ -1,7 +1,10 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import isURL from "validator/lib/isURL"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; + const [signer] = await hre.ethers.getSigners(); if (signer === undefined) { throw new Error( @@ -9,6 +12,25 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if (!isAddress(args.contract)) { + throw new Error("Invalid Ethereum address provided."); + } + + const supportedProtocols = ["https", "ipfs"]; + + const isValidTokenUri = isURL(args.tokenUri, { + require_protocol: true, + protocols: supportedProtocols, + }); + + if (!isValidTokenUri) { + throw new Error( + `Invalid token URI: ${ + args.tokenUri + }. Supported protocols are: ${supportedProtocols.join(", ")}.` + ); + } + const contract = await hre.ethers.getContractAt( args.name as "ZetaChainUniversalNFT" | "EVMUniversalNFT", args.contract @@ -19,7 +41,6 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const tx = await contract.safeMint(recipient, args.tokenUri); const receipt = await tx.wait(); - // Decode logs using contract interface const transferEvent = receipt.logs .map((log: any) => { try { @@ -30,7 +51,15 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { }) .find((parsedLog: any) => parsedLog?.name === "Transfer"); - const tokenId = transferEvent?.args?.tokenId?.toString(); + if (!transferEvent) { + throw new Error("Transfer event not found in transaction logs."); + } + + const tokenId = transferEvent?.args?.tokenId; + + if (!tokenId) { + throw new Error("Transfer event not found in transaction logs."); + } if (args.json) { console.log( @@ -39,19 +68,19 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { mintTransactionHash: tx.hash, recipient: recipient, tokenURI: args.tokenUri, - tokenId: tokenId, + tokenId: tokenId.toString(), }) ); } else { console.log(`šŸš€ Successfully minted NFT. šŸ“œ Contract address: ${args.contract} šŸ‘¤ Recipient: ${recipient} -šŸ†” Token ID: ${tokenId} +šŸ†” Token ID: ${tokenId.toString()} šŸ”— Transaction hash: ${tx.hash}`); } }; -task("mint", "Mint an NFT", main) +export const nftMint = task("nft:mint", "Mint an NFT", main) .addParam("contract", "The address of the deployed NFT contract") .addOptionalParam( "to", diff --git a/contracts/nft/tasks/universalSetConnected.ts b/contracts/nft/tasks/setConnected.ts similarity index 81% rename from contracts/nft/tasks/universalSetConnected.ts rename to contracts/nft/tasks/setConnected.ts index 50ed651..3916695 100644 --- a/contracts/nft/tasks/universalSetConnected.ts +++ b/contracts/nft/tasks/setConnected.ts @@ -3,6 +3,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { ZetaChainUniversalNFT } from "@/typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; const [signer] = await hre.ethers.getSigners(); if (!signer) { throw new Error( @@ -10,6 +11,14 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if ( + !isAddress(args.contract) || + !isAddress(args.zrc20) || + !isAddress(args.connected) + ) { + throw new Error("Invalid Ethereum address provided."); + } + const contract: ZetaChainUniversalNFT = await hre.ethers.getContractAt( "ZetaChainUniversalNFT", args.contract @@ -35,7 +44,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("universal-set-connected", "Sets the connected contract address", main) +export const nftSetConnected = task( + "nft:set-connected", + "Sets the connected contract address", + main +) .addParam("contract", "The address of the deployed contract") .addParam("zrc20", "The ZRC20 address to link to the connected contract") .addParam("connected", "The address of the connected contract to set") diff --git a/contracts/nft/tasks/connectedSetUniversal.ts b/contracts/nft/tasks/setUniversal.ts similarity index 82% rename from contracts/nft/tasks/connectedSetUniversal.ts rename to contracts/nft/tasks/setUniversal.ts index 7f99525..9a54073 100644 --- a/contracts/nft/tasks/connectedSetUniversal.ts +++ b/contracts/nft/tasks/setUniversal.ts @@ -3,6 +3,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { EVMUniversalNFT } from "@/typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; const [signer] = await hre.ethers.getSigners(); if (!signer) { throw new Error( @@ -10,6 +11,10 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if (!isAddress(args.contract) || !isAddress(args.universal)) { + throw new Error("Invalid Ethereum address provided."); + } + const contract: EVMUniversalNFT = await hre.ethers.getContractAt( "EVMUniversalNFT", args.contract @@ -36,7 +41,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("connected-set-universal", "Sets the universal contract address", main) +export const nftSetUniversal = task( + "nft:set-universal", + "Sets the universal contract address", + main +) .addParam("contract", "The address of the deployed contract") .addParam("universal", "The address of the universal contract to set") .addFlag("json", "Output the result in JSON format"); diff --git a/contracts/nft/tasks/transfer.ts b/contracts/nft/tasks/transfer.ts index 70ea8b6..cf062da 100644 --- a/contracts/nft/tasks/transfer.ts +++ b/contracts/nft/tasks/transfer.ts @@ -4,6 +4,13 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre; const [signer] = await ethers.getSigners(); + + const { isAddress } = hre.ethers.utils; + + if (!isAddress(args.to) || !isAddress(args.revertAddress)) { + throw new Error("Invalid Ethereum address provided."); + } + const nftContract = await ethers.getContractAt("IERC721", args.from); const approveTx = await nftContract .connect(signer) @@ -50,7 +57,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("transfer", "Transfer and lock an NFT", main) +export const nftTransfer = task( + "nft:transfer", + "Transfer and lock an NFT", + main +) .addOptionalParam("receiver", "The address to receive the NFT") .addParam("from", "The contract being transferred from") .addParam("tokenId", "The ID of the NFT to transfer") diff --git a/contracts/nft/yarn.lock b/contracts/nft/yarn.lock index df476a4..92a9705 100644 --- a/contracts/nft/yarn.lock +++ b/contracts/nft/yarn.lock @@ -2407,6 +2407,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/validator@^13.12.2": + version "13.12.2" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.2.tgz#760329e756e18a4aab82fc502b51ebdfebbe49f5" + integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== + "@types/wrap-ansi@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" @@ -7404,6 +7409,11 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validator@^13.12.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + varuint-bitcoin@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" diff --git a/contracts/token/contracts/evm/UniversalToken.sol b/contracts/token/contracts/evm/UniversalToken.sol index 206fa38..1b97ff1 100644 --- a/contracts/token/contracts/evm/UniversalToken.sol +++ b/contracts/token/contracts/evm/UniversalToken.sol @@ -4,18 +4,26 @@ pragma solidity 0.8.26; import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol"; import {RevertContext} from "@zetachain/protocol-contracts/contracts/Revert.sol"; import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; -import "../shared/Events.sol"; +import "../shared/UniversalTokenEvents.sol"; contract UniversalToken is Initializable, ERC20Upgradeable, + ERC20BurnableUpgradeable, + ERC20PausableUpgradeable, OwnableUpgradeable, + ERC20PermitUpgradeable, UUPSUpgradeable, - Events + UniversalTokenEvents { GatewayEVM public gateway; address public universal; @@ -43,6 +51,7 @@ contract UniversalToken is uint256 gas ) public initializer { __ERC20_init(name, symbol); + __ERC20Burnable_init(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); if (gatewayAddress == address(0)) revert InvalidAddress(); @@ -50,13 +59,18 @@ contract UniversalToken is gateway = GatewayEVM(gatewayAddress); } + function setGasLimit(uint256 gas) external onlyOwner { + if (gas == 0) revert InvalidGasLimit(); + gasLimitAmount = gas; + } + function setUniversal(address contractAddress) external onlyOwner { if (contractAddress == address(0)) revert InvalidAddress(); universal = contractAddress; emit SetUniversal(contractAddress); } - function mint(address to, uint256 amount) public onlyOwner { + function mint(address to, uint256 amount) public onlyOwner whenNotPaused { _mint(to, amount); } @@ -64,7 +78,7 @@ contract UniversalToken is address destination, address receiver, uint256 amount - ) external payable { + ) external payable whenNotPaused { if (receiver == address(0)) revert InvalidAddress(); _burn(msg.sender, amount); @@ -127,11 +141,25 @@ contract UniversalToken is emit TokenTransferReverted(receiver, amount); } - receive() external payable {} - - fallback() external payable {} - function _authorizeUpgrade( address newImplementation ) internal override onlyOwner {} + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + function _update( + address from, + address to, + uint256 value + ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { + super._update(from, to, value); + } + + receive() external payable {} } diff --git a/contracts/token/contracts/example/Connected.sol b/contracts/token/contracts/example/EVMUniversalToken.sol similarity index 100% rename from contracts/token/contracts/example/Connected.sol rename to contracts/token/contracts/example/EVMUniversalToken.sol diff --git a/contracts/token/contracts/example/Universal.sol b/contracts/token/contracts/example/ZetaChainUniversalToken.sol similarity index 100% rename from contracts/token/contracts/example/Universal.sol rename to contracts/token/contracts/example/ZetaChainUniversalToken.sol diff --git a/contracts/token/contracts/shared/Events.sol b/contracts/token/contracts/shared/UniversalTokenEvents.sol similarity index 95% rename from contracts/token/contracts/shared/Events.sol rename to contracts/token/contracts/shared/UniversalTokenEvents.sol index 74cc2b7..8aa12a4 100644 --- a/contracts/token/contracts/shared/Events.sol +++ b/contracts/token/contracts/shared/UniversalTokenEvents.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -contract Events { +contract UniversalTokenEvents { event SetUniversal(address indexed universalAddress); event SetConnected(address indexed zrc20, address contractAddress); event TokenMinted(address indexed to, uint256 amount); diff --git a/contracts/token/contracts/zetachain/UniversalToken.sol b/contracts/token/contracts/zetachain/UniversalToken.sol index 0868549..b5ed54f 100644 --- a/contracts/token/contracts/zetachain/UniversalToken.sol +++ b/contracts/token/contracts/zetachain/UniversalToken.sol @@ -6,21 +6,28 @@ import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContrac 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 {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; -import "../shared/Events.sol"; +import "../shared/UniversalTokenEvents.sol"; contract UniversalToken is Initializable, ERC20Upgradeable, + ERC20BurnableUpgradeable, + ERC20PausableUpgradeable, OwnableUpgradeable, + ERC20PermitUpgradeable, UUPSUpgradeable, UniversalContract, - Events + UniversalTokenEvents { bool public constant isUniversal = true; @@ -34,6 +41,7 @@ contract UniversalToken is error InvalidAddress(); error InvalidGasLimit(); error ApproveFailed(); + error ZeroMsgValue(); mapping(address => address) public connected; @@ -56,6 +64,7 @@ contract UniversalToken is address uniswapRouterAddress ) public initializer { __ERC20_init(name, symbol); + __ERC20Burnable_init(); __Ownable_init(initialOwner); __UUPSUpgradeable_init(); if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) @@ -66,6 +75,11 @@ contract UniversalToken is gasLimitAmount = gas; } + function setGasLimit(uint256 gas) external onlyOwner { + if (gas == 0) revert InvalidGasLimit(); + gasLimitAmount = gas; + } + function setConnected( address zrc20, address contractAddress @@ -78,7 +92,8 @@ contract UniversalToken is address destination, address receiver, uint256 amount - ) public payable { + ) public payable whenNotPaused { + if (msg.value == 0) revert ZeroMsgValue(); if (receiver == address(0)) revert InvalidAddress(); _burn(msg.sender, amount); @@ -130,7 +145,7 @@ contract UniversalToken is emit TokenTransfer(destination, receiver, amount); } - function mint(address to, uint256 amount) public onlyOwner { + function mint(address to, uint256 amount) public onlyOwner whenNotPaused { _mint(to, amount); } @@ -190,9 +205,27 @@ contract UniversalToken is emit TokenTransferReverted(sender, amount); } + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + function _authorizeUpgrade( address newImplementation ) internal override onlyOwner {} receive() external payable {} + + // The following functions are overrides required by Solidity. + + function _update( + address from, + address to, + uint256 value + ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { + super._update(from, to, value); + } } diff --git a/contracts/token/hardhat.config.ts b/contracts/token/hardhat.config.ts index 493dc7d..8a5cadd 100644 --- a/contracts/token/hardhat.config.ts +++ b/contracts/token/hardhat.config.ts @@ -1,8 +1,4 @@ -import "./tasks/deploy"; -import "./tasks/mint"; -import "./tasks/transfer"; -import "./tasks/universalSetConnected"; -import "./tasks/connectedSetUniversal"; +import "./tasks"; import "@zetachain/localnet/tasks"; import "@nomicfoundation/hardhat-toolbox"; import "@zetachain/toolkit/tasks"; @@ -17,7 +13,19 @@ const config: HardhatUserConfig = { networks: { ...getHardhatConfigNetworks(), }, - solidity: "0.8.26", + solidity: { + compilers: [ + { + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + version: "0.8.26", + }, + ], + }, }; export default config; diff --git a/contracts/token/scripts/localnet.sh b/contracts/token/scripts/localnet.sh index 61cb5f0..88d878e 100755 --- a/contracts/token/scripts/localnet.sh +++ b/contracts/token/scripts/localnet.sh @@ -29,46 +29,46 @@ GATEWAY_BNB=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="bnb" UNISWAP_ROUTER=$(jq -r '.addresses[] | select(.type=="uniswapRouterInstance" and .chain=="zetachain") | .address' localnet.json) SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -CONTRACT_ZETACHAIN=$(npx hardhat deploy --name ZetaChainUniversalToken --network localhost --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') +CONTRACT_ZETACHAIN=$(npx hardhat token:deploy --name ZetaChainUniversalToken --network localhost --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" --json | jq -r '.contractAddress') echo -e "\nšŸš€ Deployed contract on ZetaChain: $CONTRACT_ZETACHAIN" -CONTRACT_ETHEREUM=$(npx hardhat deploy --name EVMUniversalToken --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') +CONTRACT_ETHEREUM=$(npx hardhat token:deploy --name EVMUniversalToken --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') echo -e "šŸš€ Deployed contract on EVM chain: $CONTRACT_ETHEREUM" -CONTRACT_BNB=$(npx hardhat deploy --name EVMUniversalToken --json --network localhost --gateway "$GATEWAY_BNB" | jq -r '.contractAddress') +CONTRACT_BNB=$(npx hardhat token:deploy --name EVMUniversalToken --json --network localhost --gateway "$GATEWAY_BNB" | jq -r '.contractAddress') echo -e "šŸš€ Deployed contract on BNB chain: $CONTRACT_BNB" echo -e "\nšŸ“® User Address: $SENDER" echo -e "\nšŸ”— Setting universal and connected contracts..." -npx hardhat connected-set-universal --network localhost --contract "$CONTRACT_ETHEREUM" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null -npx hardhat connected-set-universal --network localhost --contract "$CONTRACT_BNB" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null -npx hardhat universal-set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_ETHEREUM" --zrc20 "$ZRC20_ETHEREUM" --json &>/dev/null -npx hardhat universal-set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_BNB" --zrc20 "$ZRC20_BNB" --json &>/dev/null +npx hardhat token:set-universal --network localhost --contract "$CONTRACT_ETHEREUM" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null +npx hardhat token:set-universal --network localhost --contract "$CONTRACT_BNB" --universal "$CONTRACT_ZETACHAIN" --json &>/dev/null +npx hardhat token:set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_ETHEREUM" --zrc20 "$ZRC20_ETHEREUM" --json &>/dev/null +npx hardhat token:set-connected --network localhost --contract "$CONTRACT_ZETACHAIN" --connected "$CONTRACT_BNB" --zrc20 "$ZRC20_BNB" --json &>/dev/null npx hardhat localnet-check balance -TOKEN=$(npx hardhat mint --network localhost --json --contract "$CONTRACT_ZETACHAIN" --to "$SENDER" --amount 10 | jq -r '.contractAddress') +TOKEN=$(npx hardhat token:mint --network localhost --json --contract "$CONTRACT_ZETACHAIN" --to "$SENDER" --amount 10 | jq -r '.contractAddress') echo -e "\nMinted tokens: $TOKEN on ZetaChain." npx hardhat localnet-check balance echo -e "\nTransferring token: ZetaChain ā†’ Ethereum..." -npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" --gas-amount 1 +npx hardhat token:transfer --network localhost --json --amount 10 --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" --gas-amount 1 npx hardhat localnet-check balance echo -e "\nTransferring token: Ethereum ā†’ BNB..." -npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_ETHEREUM" --to "$ZRC20_BNB" --gas-amount 1 +npx hardhat token:transfer --network localhost --json --amount 10 --from "$CONTRACT_ETHEREUM" --to "$ZRC20_BNB" --gas-amount 1 npx hardhat localnet-check balance echo -e "\nTransferring token: BNB ā†’ ZetaChain..." -npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_BNB" +npx hardhat token:transfer --network localhost --json --amount 10 --from "$CONTRACT_BNB" npx hardhat localnet-check balance diff --git a/contracts/token/tasks/deploy.ts b/contracts/token/tasks/deploy.ts index 88ac064..a8725bb 100644 --- a/contracts/token/tasks/deploy.ts +++ b/contracts/token/tasks/deploy.ts @@ -2,6 +2,8 @@ import { task, types } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; + const network = hre.network.name; const [signer] = await hre.ethers.getSigners(); @@ -11,6 +13,13 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if ( + !isAddress(args.gateway) || + (args.uniswapRouter && !isAddress(args.uniswapRouter)) + ) { + throw new Error("Invalid Ethereum address provided."); + } + const factory: any = await hre.ethers.getContractFactory(args.name); const contract = await hre.upgrades.deployProxy(factory, [ @@ -38,7 +47,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("deploy", "Deploy the NFT contract", main) +export const tokenDeploy = task( + "token:deploy", + "Deploy a universal token contract", + main +) .addFlag("json", "Output the result in JSON format") .addOptionalParam("tokenName", "Token name", "Universal Token") .addOptionalParam("tokenSymbol", "Token symbol", "UFT") diff --git a/contracts/token/tasks/index.ts b/contracts/token/tasks/index.ts new file mode 100644 index 0000000..152d929 --- /dev/null +++ b/contracts/token/tasks/index.ts @@ -0,0 +1,5 @@ +export { tokenSetUniversal } from "./setUniversal"; +export { tokenMint } from "./mint"; +export { tokenTransfer } from "./transfer"; +export { tokenSetConnected } from "./setConnected"; +export { tokenDeploy } from "./deploy"; diff --git a/contracts/token/tasks/mint.ts b/contracts/token/tasks/mint.ts index 94e8c00..93b870f 100644 --- a/contracts/token/tasks/mint.ts +++ b/contracts/token/tasks/mint.ts @@ -2,6 +2,8 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; + const [signer] = await hre.ethers.getSigners(); if (signer === undefined) { throw new Error( @@ -9,6 +11,10 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if (!isAddress(args.contract)) { + throw new Error("Invalid Ethereum address provided."); + } + const contract: any = await hre.ethers.getContractAt( args.name, args.contract @@ -17,12 +23,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const recipient = args.to || signer.address; const tx = await contract.mint(recipient, args.amount); - const receipt = await tx.wait(); - - const transferEvent = receipt.events?.find( - (event: any) => event.event === "Transfer" - ); - const tokenId = transferEvent?.args?.tokenId; + await tx.wait(); if (args.json) { console.log( @@ -30,20 +31,17 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { contractAddress: args.contract, mintTransactionHash: tx.hash, recipient: recipient, - tokenURI: args.tokenUri, - tokenId: tokenId?.toString(), }) ); } else { console.log(`šŸš€ Successfully minted NFT. šŸ“œ Contract address: ${args.contract} šŸ‘¤ Recipient: ${recipient} -šŸ†” Token ID: ${tokenId?.toString()} šŸ”— Transaction hash: ${tx.hash}`); } }; -task("mint", "Mint an NFT", main) +export const tokenMint = task("token:mint", "Mint a universal token", main) .addParam("contract", "The address of the deployed NFT contract") .addOptionalParam( "to", diff --git a/contracts/token/tasks/universalSetConnected.ts b/contracts/token/tasks/setConnected.ts similarity index 81% rename from contracts/token/tasks/universalSetConnected.ts rename to contracts/token/tasks/setConnected.ts index 8aaeab8..0cd02fc 100644 --- a/contracts/token/tasks/universalSetConnected.ts +++ b/contracts/token/tasks/setConnected.ts @@ -3,6 +3,8 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { ZetaChainUniversalToken } from "../typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; + const [signer] = await hre.ethers.getSigners(); if (!signer) { throw new Error( @@ -10,6 +12,14 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if ( + !isAddress(args.contract) || + !isAddress(args.zrc20) || + !isAddress(args.connected) + ) { + throw new Error("Invalid Ethereum address provided."); + } + const contract: ZetaChainUniversalToken = await hre.ethers.getContractAt( "ZetaChainUniversalToken", args.contract @@ -35,7 +45,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("universal-set-connected", "Sets the connected contract address", main) +export const tokenSetConnected = task( + "token:set-connected", + "Sets the connected contract address", + main +) .addParam("contract", "The address of the deployed contract") .addParam("zrc20", "The ZRC20 address to link to the connected contract") .addParam("connected", "The address of the connected contract to set") diff --git a/contracts/token/tasks/connectedSetUniversal.ts b/contracts/token/tasks/setUniversal.ts similarity index 80% rename from contracts/token/tasks/connectedSetUniversal.ts rename to contracts/token/tasks/setUniversal.ts index 453a78a..ef24531 100644 --- a/contracts/token/tasks/connectedSetUniversal.ts +++ b/contracts/token/tasks/setUniversal.ts @@ -3,6 +3,8 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { EVMUniversalToken } from "../typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const { isAddress } = hre.ethers.utils; + const [signer] = await hre.ethers.getSigners(); if (!signer) { throw new Error( @@ -10,6 +12,10 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } + if (!isAddress(args.contract) || !isAddress(args.universal)) { + throw new Error("Invalid Ethereum address provided."); + } + const contract: EVMUniversalToken = await hre.ethers.getContractAt( "EVMUniversalToken", args.contract @@ -32,7 +38,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("connected-set-universal", "Sets the universal contract address", main) +export const tokenSetUniversal = task( + "token:set-universal", + "Sets the universal contract address", + main +) .addParam("contract", "The address of the deployed contract") .addParam("universal", "The address of the universal contract to set") .addFlag("json", "Output the result in JSON format"); diff --git a/contracts/token/tasks/transfer.ts b/contracts/token/tasks/transfer.ts index 884e401..80d9c70 100644 --- a/contracts/token/tasks/transfer.ts +++ b/contracts/token/tasks/transfer.ts @@ -5,6 +5,12 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre; const [signer] = await ethers.getSigners(); + const { isAddress } = hre.ethers.utils; + + if (!isAddress(args.to) || !isAddress(args.revertAddress)) { + throw new Error("Invalid Ethereum address provided."); + } + const txOptions = { gasPrice: args.txOptionsGasPrice, gasLimit: args.txOptionsGasLimit, @@ -49,7 +55,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } }; -task("transfer", "Transfer and lock an NFT", main) +export const tokenTransfer = task( + "token:transfer", + "Transfer and lock an NFT", + main +) .addParam("from", "The contract being transferred from") .addOptionalParam( "txOptionsGasPrice",