From 57bc55a92118fcf8025736d876f767dfa89eb809 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 10:37:45 +0300 Subject: [PATCH 01/13] NFT: fixes --- examples/nft/contracts/Connected.sol | 6 ++++-- examples/nft/contracts/Universal.sol | 6 ++++-- examples/nft/scripts/test.sh | 22 ++++++++++------------ examples/nft/tasks/deploy.ts | 9 ++++++++- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index 8e86c867..a0c1dc13 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -25,8 +25,10 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { constructor( address payable gatewayAddress, - address initialOwner - ) ERC721("MyToken", "MTK") Ownable(initialOwner) { + address owner, + string memory name, + string memory symbol + ) ERC721(name, symbol) Ownable(owner) { gateway = GatewayEVM(gatewayAddress); } diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index a6f05a19..b5c5f6a0 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -40,8 +40,10 @@ contract Universal is constructor( address payable gatewayAddress, - address initialOwner - ) ERC721("MyToken", "MTK") Ownable(initialOwner) { + address owner, + string memory name, + string memory symbol + ) ERC721(name, symbol) Ownable(owner) { gateway = GatewayZEVM(gatewayAddress); } diff --git a/examples/nft/scripts/test.sh b/examples/nft/scripts/test.sh index 88c4f1c6..af8bba7b 100755 --- a/examples/nft/scripts/test.sh +++ b/examples/nft/scripts/test.sh @@ -2,18 +2,16 @@ set -e -if [ "$1" = "localnet" ]; then - npx hardhat localnet --exit-on-error & sleep 10 -fi +if [ "$1" = "localnet" ]; then npx hardhat localnet --exit-on-error & sleep 10; fi -function nft_balance() { +function balance() { local ZETACHAIN=$(cast call "$CONTRACT_ZETACHAIN" "balanceOf(address)(uint256)" "$SENDER") local ETHEREUM=$(cast call "$CONTRACT_ETHEREUM" "balanceOf(address)(uint256)" "$SENDER") local BNB=$(cast call "$CONTRACT_BNB" "balanceOf(address)(uint256)" "$SENDER") echo -e "\nšŸ–¼ļø NFT Balance" echo "---------------------------------------------" echo "🟢 ZetaChain: $ZETACHAIN" - echo "šŸ”µ EVM Chain: $ETHEREUM" + echo "šŸ”µ Ethereum: $ETHEREUM" echo "🟔 BNB Chain: $BNB" echo "---------------------------------------------" } @@ -31,7 +29,7 @@ CONTRACT_ZETACHAIN=$(npx hardhat deploy --network localhost --json | jq -r '.con echo -e "\nšŸš€ Deployed NFT contract on ZetaChain: $CONTRACT_ZETACHAIN" CONTRACT_ETHEREUM=$(npx hardhat deploy --name Connected --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') -echo -e "šŸš€ Deployed NFT contract on EVM chain: $CONTRACT_ETHEREUM" +echo -e "šŸš€ Deployed NFT contract on Ethereum: $CONTRACT_ETHEREUM" CONTRACT_BNB=$(npx hardhat deploy --name Connected --json --network localhost --gateway "$GATEWAY_BNB" | jq -r '.contractAddress') echo -e "šŸš€ Deployed NFT contract on BNB chain: $CONTRACT_BNB" @@ -45,30 +43,30 @@ npx hardhat universal-set-counterparty --network localhost --contract "$CONTRACT npx hardhat universal-set-counterparty --network localhost --contract "$CONTRACT_ZETACHAIN" --counterparty "$CONTRACT_BNB" --zrc20 "$ZRC20_BNB" --json &>/dev/null npx hardhat localnet-check -nft_balance +balance NFT_ID=$(npx hardhat 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 -nft_balance +balance echo -e "\nTransferring NFT: ZetaChain → Ethereum..." npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" npx hardhat localnet-check -nft_balance +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 0.1 npx hardhat localnet-check -nft_balance +balance echo -e "\nTransferring NFT: BNB → ZetaChain..." npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_BNB" npx hardhat localnet-check -nft_balance +balance -npx hardhat localnet-stop \ No newline at end of file +if [ "$1" = "localnet" ]; then npx hardhat localnet-stop; fi \ No newline at end of file diff --git a/examples/nft/tasks/deploy.ts b/examples/nft/tasks/deploy.ts index 1421fab3..54ea346c 100644 --- a/examples/nft/tasks/deploy.ts +++ b/examples/nft/tasks/deploy.ts @@ -12,7 +12,12 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { } const factory = await hre.ethers.getContractFactory(args.name); - const contract = await factory.deploy(args.gateway, signer.address); + const contract = await factory.deploy( + args.gateway, + signer.address, + args.nftName, + args.nftSymbol + ); await contract.deployed(); if (args.json) { @@ -33,6 +38,8 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { task("deploy", "Deploy the NFT contract", main) .addFlag("json", "Output the result in JSON format") + .addOptionalParam("nftName", "NFT name", "Universal NFT") + .addOptionalParam("nftSymbol", "NFT symbol", "UNFT") .addOptionalParam("name", "The contract name to deploy", "Universal") .addOptionalParam( "gateway", From a674ace960f1e4bcb90e9459957a753d90282503 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 10:48:13 +0300 Subject: [PATCH 02/13] refactor --- examples/nft/contracts/Connected.sol | 17 ++++++----------- examples/nft/contracts/Universal.sol | 6 +++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index a0c1dc13..34513668 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -52,37 +52,32 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { ) external payable { string memory uri = tokenURI(tokenId); _burn(tokenId); - bytes memory encodedData = abi.encode( - tokenId, - receiver, - uri, - destination - ); + bytes memory message = abi.encode(tokenId, receiver, uri, destination); RevertOptions memory revertOptions = RevertOptions( address(this), true, address(0), - encodedData, + message, 0 ); if (destination == address(0)) { - gateway.call(counterparty, encodedData, revertOptions); + gateway.call(counterparty, message, revertOptions); } else { gateway.depositAndCall{value: msg.value}( counterparty, - encodedData, + message, revertOptions ); } } function onCall( - MessageContext calldata messageContext, + MessageContext calldata context, bytes calldata message ) external payable onlyGateway returns (bytes4) { - if (messageContext.sender != counterparty) revert("Unauthorized"); + if (context.sender != counterparty) revert("Unauthorized"); (uint256 tokenId, address receiver, string memory uri) = abi.decode( message, diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index b5c5f6a0..83898337 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -70,7 +70,7 @@ contract Universal is !IZRC20(destination).transferFrom(msg.sender, address(this), gasFee) ) revert TransferFailed(); IZRC20(destination).approve(address(gateway), gasFee); - bytes memory encodedData = abi.encode(tokenId, receiver, uri); + bytes memory message = abi.encode(tokenId, receiver, uri); CallOptions memory callOptions = CallOptions(gasLimit, false); @@ -78,14 +78,14 @@ contract Universal is address(this), true, address(0), - encodedData, + message, gasLimit ); gateway.call( counterparty[destination], destination, - encodedData, + message, callOptions, revertOptions ); From 3c638dc117a1eae39e4116c541901bfed18d3e64 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 11:16:13 +0300 Subject: [PATCH 03/13] should throw an error --- examples/nft/contracts/Connected.sol | 21 +++++---- examples/nft/contracts/Universal.sol | 6 ++- examples/nft/scripts/test.sh | 2 +- examples/nft/tasks/transfer.ts | 66 ++++++++++++++++------------ 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index 34513668..936b2128 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -14,12 +14,16 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { uint256 private _nextTokenId; address public counterparty; + error InvalidAddress(); + error Unauthorized(); + function setCounterparty(address contractAddress) external onlyOwner { + if (contractAddress == address(0)) revert InvalidAddress(); counterparty = contractAddress; } modifier onlyGateway() { - require(msg.sender == address(gateway), "Caller is not the gateway"); + if (msg.sender != address(gateway)) revert Unauthorized(); _; } @@ -29,17 +33,15 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { string memory name, string memory symbol ) ERC721(name, symbol) Ownable(owner) { + if (gatewayAddress == address(0) || owner == address(0)) + revert InvalidAddress(); gateway = GatewayEVM(gatewayAddress); } function safeMint(address to, string memory uri) public onlyOwner { - uint256 hash = uint256( - keccak256( - abi.encodePacked(address(this), block.number, _nextTokenId++) - ) - ); + if (to == address(0)) revert InvalidAddress(); - uint256 tokenId = hash & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); @@ -50,6 +52,8 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { address receiver, address destination ) external payable { + if (receiver == address(0)) revert InvalidAddress(); + string memory uri = tokenURI(tokenId); _burn(tokenId); bytes memory message = abi.encode(tokenId, receiver, uri, destination); @@ -77,12 +81,13 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { MessageContext calldata context, bytes calldata message ) external payable onlyGateway returns (bytes4) { - if (context.sender != counterparty) revert("Unauthorized"); + if (context.sender != counterparty) revert Unauthorized(); (uint256 tokenId, address receiver, string memory uri) = abi.decode( message, (uint256, address, string) ); + _safeMint(receiver, tokenId); _setTokenURI(tokenId, uri); return ""; diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index 83898337..e25fbb31 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -28,13 +28,15 @@ contract Universal is uint256 public gasLimit = 700000; error TransferFailed(); + error Unauthorized(); + error InvalidAddress(); mapping(address => bytes) public counterparty; event CounterpartySet(address indexed zrc20, bytes indexed contractAddress); modifier onlyGateway() { - require(msg.sender == address(gateway), "Caller is not the gateway"); + if (msg.sender != address(gateway)) revert Unauthorized(); _; } @@ -44,6 +46,8 @@ contract Universal is string memory name, string memory symbol ) ERC721(name, symbol) Ownable(owner) { + if (gatewayAddress == address(0) || owner == address(0)) + revert InvalidAddress(); gateway = GatewayZEVM(gatewayAddress); } diff --git a/examples/nft/scripts/test.sh b/examples/nft/scripts/test.sh index af8bba7b..e2ac979b 100755 --- a/examples/nft/scripts/test.sh +++ b/examples/nft/scripts/test.sh @@ -52,7 +52,7 @@ 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" +npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" --receiver 0x0000000000000000000000000000000000000000 npx hardhat localnet-check balance diff --git a/examples/nft/tasks/transfer.ts b/examples/nft/tasks/transfer.ts index 484f8cb3..854d12cd 100644 --- a/examples/nft/tasks/transfer.ts +++ b/examples/nft/tasks/transfer.ts @@ -5,17 +5,21 @@ import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre; const [signer] = await ethers.getSigners(); - const nftContract = await ethers.getContractAt("IERC721", args.from); - const approveTx = await nftContract - .connect(signer) - .approve(args.from, args.tokenId); - await approveTx.wait(); + + try { + const nftContract = await ethers.getContractAt("IERC721", args.from); + const approveTx = await nftContract + .connect(signer) + .approve(args.from, args.tokenId); + await approveTx.wait(); + } catch (error: any) { + throw new Error(`Approval transaction failed: ${error.message}`); + } const txOptions = { gasPrice: args.txOptionsGasPrice, gasLimit: args.txOptionsGasLimit, }; - let tx; let contract; try { @@ -24,39 +28,47 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const gasLimit = hre.ethers.BigNumber.from(args.txOptionsGasLimit); const zrc20 = new ethers.Contract(args.to, ZRC20ABI.abi, signer); const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit); - const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions); - await zrc20TransferTx.wait(); + + try { + const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions); + await zrc20TransferTx.wait(); + } catch (error: any) { + throw new Error(`ZRC-20 transfer approval failed: ${error.message}`); + } } catch (e) { contract = await ethers.getContractAt("Connected", args.from); } const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18); - const receiver = args.receiver || signer.address; - tx = await (contract as any).transferCrossChain( - args.tokenId, - receiver, - args.to, - { ...txOptions, value: gasAmount } - ); - - await tx.wait(); - if (args.json) { - console.log( - JSON.stringify({ - contractAddress: args.from, - transferTransactionHash: tx.hash, - sender: signer.address, - tokenId: args.tokenId, - }) + try { + const tx = await (contract as any).transferCrossChain( + args.tokenId, + receiver, + args.to, + { ...txOptions, value: gasAmount } ); - } else { - console.log(`šŸš€ Successfully transferred NFT to the contract. + await tx.wait(); + + if (args.json) { + console.log( + JSON.stringify({ + contractAddress: args.from, + transferTransactionHash: tx.hash, + sender: signer.address, + tokenId: args.tokenId, + }) + ); + } else { + console.log(`šŸš€ Successfully transferred NFT to the contract. šŸ“œ Contract address: ${args.from} šŸ–¼ NFT Contract address: ${args.nftContract} šŸ†” Token ID: ${args.tokenId} šŸ”— Transaction hash: ${tx.hash}`); + } + } catch (error: any) { + throw new Error(`NFT transfer transaction failed: ${error.message}`); } }; From d54d7ca4900d79d64229f773e0b4d832c91403bb Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 11:22:15 +0300 Subject: [PATCH 04/13] should also throw an error, because address is zero --- examples/nft/contracts/Universal.sol | 1 + examples/nft/tasks/transfer.ts | 67 ++++++++++++---------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index e25fbb31..bcd68599 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -64,6 +64,7 @@ contract Universal is address receiver, address destination ) public { + if (receiver == address(0)) revert InvalidAddress(); string memory uri = tokenURI(tokenId); _burn(tokenId); diff --git a/examples/nft/tasks/transfer.ts b/examples/nft/tasks/transfer.ts index 854d12cd..e0e47abe 100644 --- a/examples/nft/tasks/transfer.ts +++ b/examples/nft/tasks/transfer.ts @@ -5,21 +5,17 @@ import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre; const [signer] = await ethers.getSigners(); - - try { - const nftContract = await ethers.getContractAt("IERC721", args.from); - const approveTx = await nftContract - .connect(signer) - .approve(args.from, args.tokenId); - await approveTx.wait(); - } catch (error: any) { - throw new Error(`Approval transaction failed: ${error.message}`); - } + const nftContract = await ethers.getContractAt("IERC721", args.from); + const approveTx = await nftContract + .connect(signer) + .approve(args.from, args.tokenId); + await approveTx.wait(); const txOptions = { gasPrice: args.txOptionsGasPrice, gasLimit: args.txOptionsGasLimit, }; + let tx; let contract; try { @@ -28,51 +24,44 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const gasLimit = hre.ethers.BigNumber.from(args.txOptionsGasLimit); const zrc20 = new ethers.Contract(args.to, ZRC20ABI.abi, signer); const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit); - - try { - const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions); - await zrc20TransferTx.wait(); - } catch (error: any) { - throw new Error(`ZRC-20 transfer approval failed: ${error.message}`); - } + const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions); + await zrc20TransferTx.wait(); } catch (e) { contract = await ethers.getContractAt("Connected", args.from); } const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18); + const receiver = args.receiver || signer.address; - try { - const tx = await (contract as any).transferCrossChain( - args.tokenId, - receiver, - args.to, - { ...txOptions, value: gasAmount } - ); - await tx.wait(); + tx = await (contract as any).transferCrossChain( + args.tokenId, + receiver, + args.to, + { ...txOptions, value: gasAmount } + ); - if (args.json) { - console.log( - JSON.stringify({ - contractAddress: args.from, - transferTransactionHash: tx.hash, - sender: signer.address, - tokenId: args.tokenId, - }) - ); - } else { - console.log(`šŸš€ Successfully transferred NFT to the contract. + await tx.wait(); + if (args.json) { + console.log( + JSON.stringify({ + contractAddress: args.from, + transferTransactionHash: tx.hash, + sender: signer.address, + tokenId: args.tokenId, + }) + ); + } else { + console.log(`šŸš€ Successfully transferred NFT to the contract. šŸ“œ Contract address: ${args.from} šŸ–¼ NFT Contract address: ${args.nftContract} šŸ†” Token ID: ${args.tokenId} šŸ”— Transaction hash: ${tx.hash}`); - } - } catch (error: any) { - throw new Error(`NFT transfer transaction failed: ${error.message}`); } }; task("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") .addOptionalParam( From 14dd3e2d2c0f5e8d1eaa08f1972bac8f56b955ff Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 11:25:09 +0300 Subject: [PATCH 05/13] should be passing the test --- examples/nft/scripts/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nft/scripts/test.sh b/examples/nft/scripts/test.sh index e2ac979b..e3a69016 100755 --- a/examples/nft/scripts/test.sh +++ b/examples/nft/scripts/test.sh @@ -52,7 +52,7 @@ 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" --receiver 0x0000000000000000000000000000000000000000 +npx hardhat transfer --network localhost --json --token-id "$NFT_ID" --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" npx hardhat localnet-check balance From 73347ae6472032deaf2f340554bcf73a2da4f5a2 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 11:25:43 +0300 Subject: [PATCH 06/13] remove unused imports --- examples/nft/tasks/connectedSetCounterparty.ts | 1 - examples/nft/tasks/deploy.ts | 2 +- examples/nft/tasks/mint.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/nft/tasks/connectedSetCounterparty.ts b/examples/nft/tasks/connectedSetCounterparty.ts index d9bec15b..4bf28e91 100644 --- a/examples/nft/tasks/connectedSetCounterparty.ts +++ b/examples/nft/tasks/connectedSetCounterparty.ts @@ -1,6 +1,5 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { ethers } from "ethers"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); diff --git a/examples/nft/tasks/deploy.ts b/examples/nft/tasks/deploy.ts index 54ea346c..72cbc67b 100644 --- a/examples/nft/tasks/deploy.ts +++ b/examples/nft/tasks/deploy.ts @@ -1,4 +1,4 @@ -import { task, types } from "hardhat/config"; +import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { diff --git a/examples/nft/tasks/mint.ts b/examples/nft/tasks/mint.ts index 515ba6bb..30458166 100644 --- a/examples/nft/tasks/mint.ts +++ b/examples/nft/tasks/mint.ts @@ -1,6 +1,5 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { ethers } from "ethers"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); From edca64ca28f2604eaee9e1c25165d699eeb59c26 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 11:46:35 +0300 Subject: [PATCH 07/13] refactor --- examples/nft/tasks/connectedSetCounterparty.ts | 7 +++++-- examples/nft/tasks/deploy.ts | 2 +- examples/nft/tasks/mint.ts | 7 +++++-- examples/nft/tasks/universalSetCounterparty.ts | 8 +++++--- examples/nft/tsconfig.json | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/examples/nft/tasks/connectedSetCounterparty.ts b/examples/nft/tasks/connectedSetCounterparty.ts index 4bf28e91..92281b4d 100644 --- a/examples/nft/tasks/connectedSetCounterparty.ts +++ b/examples/nft/tasks/connectedSetCounterparty.ts @@ -1,5 +1,6 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { Connected } from "@/typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); @@ -9,7 +10,10 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } - const contract = await hre.ethers.getContractAt(args.name, args.contract); + const contract: Connected = await hre.ethers.getContractAt( + "Connected", + args.contract + ); const tx = await contract.setCounterparty(args.counterparty); const receipt = await tx.wait(); @@ -33,5 +37,4 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { task("connected-set-counterparty", "Sets the universal contract address", main) .addParam("contract", "The address of the deployed contract") .addParam("counterparty", "The address of the universal contract to set") - .addOptionalParam("name", "The contract name to interact with", "Connected") .addFlag("json", "Output the result in JSON format"); diff --git a/examples/nft/tasks/deploy.ts b/examples/nft/tasks/deploy.ts index 72cbc67b..4a33dff5 100644 --- a/examples/nft/tasks/deploy.ts +++ b/examples/nft/tasks/deploy.ts @@ -11,7 +11,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } - const factory = await hre.ethers.getContractFactory(args.name); + const factory: any = await hre.ethers.getContractFactory(args.name); const contract = await factory.deploy( args.gateway, signer.address, diff --git a/examples/nft/tasks/mint.ts b/examples/nft/tasks/mint.ts index 30458166..7806e3d4 100644 --- a/examples/nft/tasks/mint.ts +++ b/examples/nft/tasks/mint.ts @@ -9,7 +9,10 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } - const contract = await hre.ethers.getContractAt(args.name, args.contract); + const contract = await hre.ethers.getContractAt( + args.name as "Universal" | "Connected", + args.contract + ); const recipient = args.to || signer.address; @@ -17,7 +20,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const receipt = await tx.wait(); const transferEvent = receipt.events?.find( - (event) => event.event === "Transfer" + (event: any) => event.event === "Transfer" ); const tokenId = transferEvent?.args?.tokenId; diff --git a/examples/nft/tasks/universalSetCounterparty.ts b/examples/nft/tasks/universalSetCounterparty.ts index 6a3ff107..9fd2e13e 100644 --- a/examples/nft/tasks/universalSetCounterparty.ts +++ b/examples/nft/tasks/universalSetCounterparty.ts @@ -1,5 +1,6 @@ import { task } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { Universal } from "@/typechain-types"; const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const [signer] = await hre.ethers.getSigners(); @@ -9,10 +10,12 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ); } - const contract = await hre.ethers.getContractAt(args.name, args.contract); + const contract: Universal = await hre.ethers.getContractAt( + "Universal", + args.contract + ); const tx = await contract.setCounterparty(args.zrc20, args.counterparty); - const receipt = await tx.wait(); if (args.json) { console.log( @@ -36,5 +39,4 @@ task("universal-set-counterparty", "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("counterparty", "The address of the connected contract to set") - .addOptionalParam("name", "The contract name to interact with", "Universal") .addFlag("json", "Output the result in JSON format"); diff --git a/examples/nft/tsconfig.json b/examples/nft/tsconfig.json index fb0567b3..705790bf 100644 --- a/examples/nft/tsconfig.json +++ b/examples/nft/tsconfig.json @@ -1,5 +1,9 @@ { "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + }, "module": "nodenext", "moduleResolution": "nodenext", "esModuleInterop": true, From bddf609864c93d93e17e8e7915e5e661936d525e Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 12:12:21 +0300 Subject: [PATCH 08/13] set gasLimit in constructor --- examples/nft/contracts/Universal.sol | 10 ++++++---- examples/nft/scripts/test.sh | 2 +- examples/nft/tasks/deploy.ts | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index bcd68599..c4310dfa 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -25,7 +25,7 @@ contract Universal is SystemContract(0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9); uint256 private _nextTokenId; bool public isUniversal = true; - uint256 public gasLimit = 700000; + uint256 public gasLimit; error TransferFailed(); error Unauthorized(); @@ -44,11 +44,13 @@ contract Universal is address payable gatewayAddress, address owner, string memory name, - string memory symbol + string memory symbol, + uint256 gas ) ERC721(name, symbol) Ownable(owner) { if (gatewayAddress == address(0) || owner == address(0)) revert InvalidAddress(); gateway = GatewayZEVM(gatewayAddress); + gasLimit = gas; } function setCounterparty( @@ -130,7 +132,7 @@ contract Universal is _setTokenURI(tokenId, uri); } else { (, uint256 gasFee) = IZRC20(destination).withdrawGasFeeWithGasLimit( - 700000 + gasLimit ); SwapHelperLib.swapExactTokensForTokens( @@ -146,7 +148,7 @@ contract Universal is counterparty[destination], destination, abi.encode(tokenId, sender, uri), - CallOptions(700000, false), + CallOptions(gasLimit, false), RevertOptions(address(0), false, address(0), "", 0) ); } diff --git a/examples/nft/scripts/test.sh b/examples/nft/scripts/test.sh index e3a69016..4b357c5b 100755 --- a/examples/nft/scripts/test.sh +++ b/examples/nft/scripts/test.sh @@ -25,7 +25,7 @@ GATEWAY_ETHEREUM=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain== GATEWAY_BNB=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="bnb") | .address' localnet.json) SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -CONTRACT_ZETACHAIN=$(npx hardhat deploy --network localhost --json | jq -r '.contractAddress') +CONTRACT_ZETACHAIN=$(npx hardhat deploy --network localhost --json --gas-limit 1000000 | jq -r '.contractAddress') echo -e "\nšŸš€ Deployed NFT contract on ZetaChain: $CONTRACT_ZETACHAIN" CONTRACT_ETHEREUM=$(npx hardhat deploy --name Connected --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') diff --git a/examples/nft/tasks/deploy.ts b/examples/nft/tasks/deploy.ts index 4a33dff5..895512b0 100644 --- a/examples/nft/tasks/deploy.ts +++ b/examples/nft/tasks/deploy.ts @@ -16,7 +16,8 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { args.gateway, signer.address, args.nftName, - args.nftSymbol + args.nftSymbol, + ...(args.gasLimit ? [args.gasLimit] : []) ); await contract.deployed(); @@ -41,6 +42,7 @@ task("deploy", "Deploy the NFT contract", main) .addOptionalParam("nftName", "NFT name", "Universal NFT") .addOptionalParam("nftSymbol", "NFT symbol", "UNFT") .addOptionalParam("name", "The contract name to deploy", "Universal") + .addOptionalParam("gasLimit", "Gas limit for the transaction") .addOptionalParam( "gateway", "Gateway address (default: ZetaChain Gateway)", From f191e372cd6d28e4774f88f0306a842ba61cbb83 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 12:33:58 +0300 Subject: [PATCH 09/13] add events --- examples/nft/contracts/Connected.sol | 25 +++++++++++++++++++++++++ examples/nft/contracts/Universal.sol | 28 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index 936b2128..bcefe264 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -17,9 +17,29 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { error InvalidAddress(); error Unauthorized(); + event SetCounterparty(address indexed newCounterparty); + event TokenMinted(address indexed to, uint256 indexed tokenId, string uri); + event TokenTransfer( + uint256 indexed tokenId, + address indexed receiver, + address indexed destination, + string uri + ); + event TokenTransferReceived( + uint256 indexed tokenId, + address indexed receiver, + string uri + ); + event TokenTransferReverted( + uint256 indexed tokenId, + address indexed sender, + string uri + ); + function setCounterparty(address contractAddress) external onlyOwner { if (contractAddress == address(0)) revert InvalidAddress(); counterparty = contractAddress; + emit SetCounterparty(contractAddress); } modifier onlyGateway() { @@ -45,6 +65,7 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { _safeMint(to, tokenId); _setTokenURI(tokenId, uri); + emit TokenMinted(to, tokenId, uri); } function transferCrossChain( @@ -75,6 +96,8 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { revertOptions ); } + + emit TokenTransfer(tokenId, receiver, destination, uri); } function onCall( @@ -90,6 +113,7 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { _safeMint(receiver, tokenId); _setTokenURI(tokenId, uri); + emit TokenTransferReceived(tokenId, receiver, uri); return ""; } @@ -101,6 +125,7 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { _safeMint(sender, tokenId); _setTokenURI(tokenId, uri); + emit TokenTransferReverted(tokenId, sender, uri); } receive() external payable {} diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index c4310dfa..05803e59 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -34,6 +34,29 @@ contract Universal is mapping(address => bytes) public counterparty; event CounterpartySet(address indexed zrc20, bytes indexed contractAddress); + event TokenTransfer( + uint256 indexed tokenId, + address indexed receiver, + address indexed destination, + string uri + ); + event TokenTransferReceived( + uint256 indexed tokenId, + address indexed receiver, + string uri + ); + event TokenTransferReverted( + uint256 indexed tokenId, + address indexed sender, + string uri + ); + + event TokenTransferToDestination( + uint256 indexed tokenId, + address indexed sender, + address indexed destination, + string uri + ); modifier onlyGateway() { if (msg.sender != address(gateway)) revert Unauthorized(); @@ -96,6 +119,8 @@ contract Universal is callOptions, revertOptions ); + + emit TokenTransfer(tokenId, receiver, destination, uri); } function safeMint(address to, string memory uri) public onlyOwner { @@ -130,6 +155,7 @@ contract Universal is if (destination == address(0)) { _safeMint(sender, tokenId); _setTokenURI(tokenId, uri); + emit TokenTransferReceived(tokenId, sender, uri); } else { (, uint256 gasFee) = IZRC20(destination).withdrawGasFeeWithGasLimit( gasLimit @@ -151,6 +177,7 @@ contract Universal is CallOptions(gasLimit, false), RevertOptions(address(0), false, address(0), "", 0) ); + emit TokenTransferToDestination(tokenId, sender, destination, uri); } } @@ -162,6 +189,7 @@ contract Universal is _safeMint(sender, tokenId); _setTokenURI(tokenId, uri); + emit TokenTransferReverted(tokenId, sender, uri); } // The following functions are overrides required by Solidity. From bed82302ecad1c5a35fcd05aeedc470f12290ffc Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 12:42:55 +0300 Subject: [PATCH 10/13] events in a separate file --- examples/nft/contracts/Connected.sol | 30 +++++++----------------- examples/nft/contracts/Universal.sol | 29 +++-------------------- examples/nft/contracts/shared/Events.sol | 30 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 examples/nft/contracts/shared/Events.sol diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index bcefe264..7033e395 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -8,8 +8,15 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol"; import {RevertContext} from "@zetachain/protocol-contracts/contracts/Revert.sol"; - -contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { +import "./shared/Events.sol"; + +contract Connected is + ERC721, + ERC721Enumerable, + ERC721URIStorage, + Ownable, + Events +{ GatewayEVM public immutable gateway; uint256 private _nextTokenId; address public counterparty; @@ -17,25 +24,6 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { error InvalidAddress(); error Unauthorized(); - event SetCounterparty(address indexed newCounterparty); - event TokenMinted(address indexed to, uint256 indexed tokenId, string uri); - event TokenTransfer( - uint256 indexed tokenId, - address indexed receiver, - address indexed destination, - string uri - ); - event TokenTransferReceived( - uint256 indexed tokenId, - address indexed receiver, - string uri - ); - event TokenTransferReverted( - uint256 indexed tokenId, - address indexed sender, - string uri - ); - function setCounterparty(address contractAddress) external onlyOwner { if (contractAddress == address(0)) revert InvalidAddress(); counterparty = contractAddress; diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index 05803e59..62ea0d8d 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -12,13 +12,15 @@ 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 "./Events.sol"; contract Universal is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable, - UniversalContract + UniversalContract, + Events { GatewayZEVM public immutable gateway; SystemContract public immutable systemContract = @@ -33,31 +35,6 @@ contract Universal is mapping(address => bytes) public counterparty; - event CounterpartySet(address indexed zrc20, bytes indexed contractAddress); - event TokenTransfer( - uint256 indexed tokenId, - address indexed receiver, - address indexed destination, - string uri - ); - event TokenTransferReceived( - uint256 indexed tokenId, - address indexed receiver, - string uri - ); - event TokenTransferReverted( - uint256 indexed tokenId, - address indexed sender, - string uri - ); - - event TokenTransferToDestination( - uint256 indexed tokenId, - address indexed sender, - address indexed destination, - string uri - ); - modifier onlyGateway() { if (msg.sender != address(gateway)) revert Unauthorized(); _; diff --git a/examples/nft/contracts/shared/Events.sol b/examples/nft/contracts/shared/Events.sol new file mode 100644 index 00000000..a0beba21 --- /dev/null +++ b/examples/nft/contracts/shared/Events.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract Events { + event SetCounterparty(address indexed newCounterparty); + event TokenMinted(address indexed to, uint256 indexed tokenId, string uri); + event TokenTransfer( + uint256 indexed tokenId, + address indexed receiver, + address indexed destination, + string uri + ); + event TokenTransferReceived( + uint256 indexed tokenId, + address indexed receiver, + string uri + ); + event TokenTransferReverted( + uint256 indexed tokenId, + address indexed sender, + string uri + ); + event CounterpartySet(address indexed zrc20, bytes indexed contractAddress); + event TokenTransferToDestination( + uint256 indexed tokenId, + address indexed sender, + address indexed destination, + string uri + ); +} From c779848038848d579cfb7ebdd5a05b6ad4fc6c6d Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 12:48:06 +0300 Subject: [PATCH 11/13] events in a separate file --- examples/nft/contracts/Universal.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index 62ea0d8d..4ccc06c8 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -12,7 +12,7 @@ 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 "./Events.sol"; +import "./shared/Events.sol"; contract Universal is ERC721, From b77396f0fc2bbf77c3064cd623af0fefcd6c81ac Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 14:24:27 +0300 Subject: [PATCH 12/13] Ownable2Step --- examples/nft/contracts/Connected.sol | 4 ++-- examples/nft/contracts/Universal.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/nft/contracts/Connected.sol b/examples/nft/contracts/Connected.sol index 7033e395..6b561403 100644 --- a/examples/nft/contracts/Connected.sol +++ b/examples/nft/contracts/Connected.sol @@ -4,8 +4,8 @@ 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/access/Ownable.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 "./shared/Events.sol"; @@ -14,7 +14,7 @@ contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, - Ownable, + Ownable2Step, Events { GatewayEVM public immutable gateway; diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index 4ccc06c8..82f192cb 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -4,8 +4,8 @@ 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/access/Ownable.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"; @@ -18,7 +18,7 @@ contract Universal is ERC721, ERC721Enumerable, ERC721URIStorage, - Ownable, + Ownable2Step, UniversalContract, Events { From 6ba78a04872312e71271633c577ca7b8dae05a15 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 7 Nov 2024 14:32:08 +0300 Subject: [PATCH 13/13] invalid gas limit error --- examples/nft/contracts/Universal.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/nft/contracts/Universal.sol b/examples/nft/contracts/Universal.sol index 82f192cb..f323f5f4 100644 --- a/examples/nft/contracts/Universal.sol +++ b/examples/nft/contracts/Universal.sol @@ -32,6 +32,7 @@ contract Universal is error TransferFailed(); error Unauthorized(); error InvalidAddress(); + error InvalidGasLimit(); mapping(address => bytes) public counterparty; @@ -49,6 +50,7 @@ contract Universal is ) ERC721(name, symbol) Ownable(owner) { if (gatewayAddress == address(0) || owner == address(0)) revert InvalidAddress(); + if (gas == 0) revert InvalidGasLimit(); gateway = GatewayZEVM(gatewayAddress); gasLimit = gas; }