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

ERC-20 token transfer example and improvements to NFT #209

Merged
merged 27 commits into from
Nov 8, 2024
Merged

Conversation

fadeev
Copy link
Member

@fadeev fadeev commented Oct 31, 2024

./scripts/test.sh
🚀 Deployed contract on ZetaChain: 0xC976c932092ECcD8f328FfD85066C0c05ED54044
🚀 Deployed contract on EVM chain: 0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec
🚀 Deployed contract on BNB chain: 0xa779C1D17bC5230c07afdC51376CAC1cb3Dd5314

📮 User Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

🔗 Setting counterparty contracts...

🖼️  Balance
---------------------------------------------
🟢 ZetaChain: 0
🔵 EVM Chain: 0
🟡 BNB Chain: 0
---------------------------------------------

Minted tokens: 0xC976c932092ECcD8f328FfD85066C0c05ED54044 on ZetaChain.

🖼️  Balance
---------------------------------------------
🟢 ZetaChain: 10
🔵 EVM Chain: 0
🟡 BNB Chain: 0
---------------------------------------------

Transferring token: ZetaChain → Ethereum...
{"contractAddress":"0xC976c932092ECcD8f328FfD85066C0c05ED54044","transferTransactionHash":"0x7eeb932d45ea1ac283273a0dd8c45efe978a4f403df48baed72ec2c0bb64da80","sender":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}

🖼️  Balance
---------------------------------------------
🟢 ZetaChain: 0
🔵 EVM Chain: 10
🟡 BNB Chain: 0
---------------------------------------------

Transferring token: Ethereum → BNB...
{"contractAddress":"0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec","transferTransactionHash":"0x5565a923a576bbb0a422e596c0cb10d7b9f9d0b558a0dc7455315fd1d89aac15","sender":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}

🖼️  Balance
---------------------------------------------
🟢 ZetaChain: 0
🔵 EVM Chain: 0
🟡 BNB Chain: 10
---------------------------------------------

Transferring token: BNB → ZetaChain...
{"contractAddress":"0xa779C1D17bC5230c07afdC51376CAC1cb3Dd5314","transferTransactionHash":"0x64f92cbffb2a52153a4624be6734160f5c0ab362ae4a47a295b345bfc27f7326","sender":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}

🖼️  Balance
---------------------------------------------
🟢 ZetaChain: 10
🔵 EVM Chain: 0
🟡 BNB Chain: 0
---------------------------------------------

Depends on zeta-chain/localnet#52

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced cross-chain token transfer functionality with two new contracts: Connected and Universal.
    • Added tasks for deploying contracts, minting NFTs, and transferring tokens across chains.
    • New section in README.md detailing compatibility with localnet version v4.0.0-rc*.
  • Configuration Changes

    • New configuration files added: .gitignore, .eslintignore, foundry.toml, hardhat.config.ts, tsconfig.json, and LICENSE.
    • Updated package.json to include scripts for testing and linting.
  • Documentation

    • Enhanced documentation in README.md to include new features and usage instructions.
    • Added a new section in README.md specifying compatibility with localnet version v4.0.0-rc*.

Copy link

coderabbitai bot commented Oct 31, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

This pull request introduces several significant changes to the examples/token directory, including the addition of new configuration files for ESLint, Hardhat, and TypeScript, as well as new Solidity contracts designed for cross-chain token transfers. A README.md update adds compatibility information, while various tasks for deploying and managing NFTs are also included. Additionally, a new LICENSE file is added, and a package.json file is created to manage project dependencies and scripts.

Changes

File Path Change Summary
examples/hello/package.json Removed trailing newline at the end of the file.
examples/token/.eslintignore New file added to specify directories for ESLint to ignore during linting.
examples/token/.eslintrc.js New ESLint configuration file added with settings for TypeScript and various plugins.
examples/token/.gitignore New file added to specify files and directories to be ignored by Git.
examples/token/LICENSE New file added containing the MIT License text.
examples/token/README.md New section added titled "NFT Example" specifying compatibility with localnet version v4.0.0-rc*.
examples/token/contracts/Connected.sol New Solidity contract added for cross-chain token transfers with various functions for minting and transferring tokens.
examples/token/contracts/Universal.sol New Solidity contract added for cross-chain token transfers, implementing the UniversalContract interface.
examples/token/foundry.toml New configuration file added for Foundry project settings.
examples/token/hardhat.config.ts New Hardhat configuration file added for project setup and network configurations.
examples/token/package.json New package.json file created for managing project dependencies and scripts.
examples/token/scripts/test.sh New Bash script added for testing smart contracts on a local blockchain network.
examples/token/tasks/connectedSetCounterparty.ts New task added for setting the universal contract address for a specified deployed contract.
examples/token/tasks/deploy.ts New deployment task added for deploying an NFT contract using Hardhat.
examples/token/tasks/mint.ts New task added for minting NFTs using the Hardhat framework.
examples/token/tasks/transfer.ts New task added for transferring and locking NFTs with various parameters.
examples/token/tasks/universalSetCounterparty.ts New task added for setting a connected contract address for a specified deployed contract.
examples/token/tsconfig.json New TypeScript configuration file added with various compiler options for improved development.
examples/token/contracts/shared/Events.sol New file added defining events related to token operations.

Possibly related PRs

  • Added tasks to Hello example and migrated Swap to gateway #195: The changes in the package.json file in this PR involve updates to dependencies, which is similar to the modification of the package.json file in the main PR where a trailing newline was removed. Both PRs involve the package.json file for projects related to the example-template.
  • Swap: swap from EVM task #203: This PR also updates the package.json file, specifically changing dependency versions. This is related to the main PR's changes to the package.json file, as both involve modifications to the same file type within the context of project dependencies.

Suggested reviewers

  • andresaiello

Warning

Tool Failures:

Tool Failure Count:


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@fadeev fadeev changed the base branch from main to gateway-testnet October 31, 2024 09:46
Base automatically changed from gateway-testnet to main November 1, 2024 06:32
@fadeev fadeev marked this pull request as ready for review November 4, 2024 08:42
@fadeev fadeev requested a review from andresaiello as a code owner November 4, 2024 08:42
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 31

🧹 Outside diff range and nitpick comments (15)
examples/token/hardhat.config.ts (1)

14-19: Consider adding compiler optimization settings.

The configuration would benefit from additional compiler settings to optimize gas usage and enable source maps for debugging.

Consider enhancing the configuration with these settings:

 const config: HardhatUserConfig = {
   networks: {
     ...getHardhatConfigNetworks(),
   },
-  solidity: "0.8.24",
+  solidity: {
+    version: "0.8.24",
+    settings: {
+      optimizer: {
+        enabled: true,
+        runs: 200,
+      },
+      outputSelection: {
+        "*": {
+          "*": ["storageLayout"],
+        },
+      },
+    },
+  },
 };
examples/token/.eslintrc.js (3)

7-17: Consider enhancing the base configuration for better TypeScript support.

The current configuration could be improved by:

  1. Extending recommended TypeScript configurations
  2. Reconsidering the browser environment restriction
   extends: [
-    "plugin:prettier/recommended"
+    "plugin:prettier/recommended",
+    "plugin:@typescript-eslint/recommended",
+    "plugin:@typescript-eslint/recommended-requiring-type-checking"
   ],
   env: {
-    browser: false,
+    browser: true,
     es2021: true,

18-24: Enhance code quality with additional ESLint plugins.

Consider adding security and best practice plugins to strengthen the codebase:

   plugins: [
     "@typescript-eslint",
+    "security",
+    "sonarjs",
+    "promise",
     "prettier",
     "simple-import-sort",
     "sort-keys-fix",
     "typescript-sort-keys",
   ],

34-46: Enhance module resolution with path aliases.

Consider adding path alias support to improve import readability and maintainability:

     "import/resolver": {
       typescript: {
         project: path.join(__dirname, "tsconfig.json"),
+        alwaysTryTypes: true
       },
+      alias: {
+        map: [
+          ["@contracts", "./contracts"],
+          ["@test", "./test"]
+        ],
+        extensions: [".ts", ".js", ".json"]
+      }
     },

Make sure to add corresponding path aliases in your tsconfig.json.

examples/token/tasks/deploy.ts (2)

1-3: Consider adding strict typing for task arguments.

Replace the any type with a proper interface to improve type safety and code maintainability.

+interface DeployTaskArgs {
+  name: string;
+  gateway: string;
+  json: boolean;
+}

-const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
+const main = async (args: DeployTaskArgs, hre: HardhatRuntimeEnvironment) => {

Also applies to: 4-4


34-41: Improve configuration management and parameter validation.

The hardcoded gateway address and lack of contract name validation could cause issues in different environments.

Consider these improvements:

  1. Move the gateway address to a configuration file or environment variable.
  2. Add validation for the contract name parameter.
  3. Add network-specific gateway addresses.
+import { config } from "../config";
+
+const SUPPORTED_CONTRACTS = ["Universal", "Connected"];
+
 task("deploy", "Deploy the NFT contract", main)
   .addFlag("json", "Output the result in JSON format")
-  .addOptionalParam("name", "The contract name to deploy", "Universal")
+  .addOptionalParam(
+    "name",
+    `The contract name to deploy (${SUPPORTED_CONTRACTS.join(", ")})`,
+    "Universal",
+    types.string
+  )
   .addOptionalParam(
     "gateway",
     "Gateway address (default: ZetaChain Gateway)",
-    "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"
+    config.networks[hre.network.name]?.gatewayAddress
   );
examples/token/tasks/universalSetCounterparty.ts (2)

1-3: Enhance type safety with proper parameter typing.

The imports look good, but we should improve type safety for better maintainability and developer experience.

Add a properly typed interface for the task arguments:

interface UniversalSetCounterpartyArgs {
  contract: string;
  zrc20: string;
  counterparty: string;
  name: string;
  json: boolean;
}

35-40: Add parameter validation in task definition.

While the task parameters are well-defined, we should add validation at the task definition level.

Consider adding these validations:

 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");
+  .addFlag("json", "Output the result in JSON format")
+  .setAction(async (args, hre) => {
+    // Validate contract name
+    if (args.name && typeof args.name !== "string") {
+      throw new Error("Contract name must be a string");
+    }
+    return main(args, hre);
+  });
examples/token/tasks/mint.ts (1)

25-42: Improve output handling and null safety

The output handling could be more robust and consistent.

Consider these improvements:

+  const output = {
+    contractAddress: args.contract,
+    mintTransactionHash: tx.hash,
+    recipient,
+    tokenId: tokenId?.toString() ?? 'unknown'
+  };

   if (args.json) {
-    console.log(
-      JSON.stringify({
-        contractAddress: args.contract,
-        mintTransactionHash: tx.hash,
-        recipient: recipient,
-        tokenURI: args.tokenUri,
-        tokenId: tokenId?.toString(),
-      })
-    );
+    console.log(JSON.stringify(output, null, 2));
   } else {
-    console.log(`🚀 Successfully minted NFT.
-📜 Contract address: ${args.contract}
-👤 Recipient: ${recipient}
-🆔 Token ID: ${tokenId?.toString()}
-🔗 Transaction hash: ${tx.hash}`);
+    const { contractAddress, mintTransactionHash, recipient, tokenId } = output;
+    console.log(
+      [
+        '🚀 Successfully minted NFT.',
+        `📜 Contract address: ${contractAddress}`,
+        `👤 Recipient: ${recipient}`,
+        `🆔 Token ID: ${tokenId}`,
+        `🔗 Transaction hash: ${mintTransactionHash}`
+      ].join('\n')
+    );
   }
examples/token/tasks/transfer.ts (1)

40-48: Enhance transfer execution reliability.

The current implementation lacks address validation and transfer confirmation through events.

Consider these improvements:

-const receiver = args.receiver || signer.address;
+const receiver = args.receiver ? ethers.utils.getAddress(args.receiver) : signer.address;
+if (receiver === ethers.constants.AddressZero) {
+  throw new Error("Invalid receiver address");
+}

-tx = await (contract as any).transferCrossChain(
+const tx = await (contract as any).transferCrossChain(
   receiver,
   args.to,
   args.amount,
   { ...txOptions, value: gasAmount }
 );

-await tx.wait();
+const receipt = await tx.wait();
+const transferEvent = receipt.events?.find(e => e.event === 'CrossChainTransfer');
+if (!transferEvent) {
+  throw new Error('Transfer event not emitted - transaction may have failed silently');
+}
examples/token/contracts/Connected.sol (3)

33-58: Validate msg.value based on destination in transferCrossChain

In the transferCrossChain function, when destination is the zero address, you call gateway.call without utilizing msg.value. Conversely, when destination is non-zero, you call gateway.depositAndCall{value: msg.value}. To prevent unintended Ether transfers or failures due to insufficient funds, consider adding validation for msg.value based on the destination parameter.

Apply this diff to add validation:

 ) external payable {
+    if (destination == address(0)) {
+        require(msg.value == 0, "No ETH should be sent when destination is zero address");
+    } else {
+        require(msg.value > 0, "ETH required for depositAndCall");
+    }
     _burn(msg.sender, amount);
     bytes memory encodedData = abi.encode(receiver, amount, destination);

72-73: Implement handling or logging in the onRevert function

The onRevert function is currently empty. For better observability and debugging, consider handling the revert scenario by adding logic or emitting an event that logs the revert context.

Apply this diff to enhance the function:

 function onRevert(RevertContext calldata context) external onlyGateway {
+    // Emit an event with revert details
+    emit CrossChainRevert(context.zetaTxSenderAddress, context.message);
 }

+event CrossChainRevert(address sender, bytes message);

This addition will help in tracking cross-chain call failures and diagnosing issues effectively.


29-31: Optimize the visibility of the mint function

The mint function is marked as public, but if it's only intended to be called externally and not used internally or by derived contracts, changing the visibility to external can save gas and optimize performance.

Apply this diff to update the function visibility:

-function mint(address to, uint256 amount) public onlyOwner {
+function mint(address to, uint256 amount) external onlyOwner {
     _mint(to, amount);
 }
examples/token/contracts/Universal.sol (2)

118-118: Implement logic in onRevert function

The onRevert function is currently empty. To handle failed cross-chain transactions effectively, consider implementing logic to manage reversions, such as refunding tokens or notifying users of the failure.


19-19: Restrict exposure of gasLimit variable

Declaring gasLimit as a public variable may expose internal configuration details. Consider making it private or internal, and provide controlled access through getter and setter functions with proper access modifiers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9a852e6 and 5de7d5f.

⛔ Files ignored due to path filters (3)
  • examples/hello/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • examples/swap/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • examples/token/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (18)
  • examples/hello/package.json (1 hunks)
  • examples/token/.eslintignore (1 hunks)
  • examples/token/.eslintrc.js (1 hunks)
  • examples/token/.gitignore (1 hunks)
  • examples/token/LICENSE (1 hunks)
  • examples/token/README.md (1 hunks)
  • examples/token/contracts/Connected.sol (1 hunks)
  • examples/token/contracts/Universal.sol (1 hunks)
  • examples/token/foundry.toml (1 hunks)
  • examples/token/hardhat.config.ts (1 hunks)
  • examples/token/package.json (1 hunks)
  • examples/token/scripts/test.sh (1 hunks)
  • examples/token/tasks/connectedSetCounterparty.ts (1 hunks)
  • examples/token/tasks/deploy.ts (1 hunks)
  • examples/token/tasks/mint.ts (1 hunks)
  • examples/token/tasks/transfer.ts (1 hunks)
  • examples/token/tasks/universalSetCounterparty.ts (1 hunks)
  • examples/token/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • examples/hello/package.json
  • examples/token/.eslintignore
  • examples/token/.gitignore
  • examples/token/LICENSE
  • examples/token/README.md
  • examples/token/foundry.toml
  • examples/token/package.json
  • examples/token/tsconfig.json
🧰 Additional context used
🪛 Shellcheck
examples/token/scripts/test.sh

[warning] 12-12: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 13-13: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 14-14: Declare and assign separately to avoid masking return values.

(SC2155)

🔇 Additional comments (5)
examples/token/tasks/deploy.ts (1)

5-6: Verify cross-chain deployment compatibility.

Since this PR aims to demonstrate cross-chain token transfers, ensure the deployment task works correctly across all target networks (ZetaChain, Ethereum, and BNB Chain).

✅ Verification successful

Cross-chain deployment configuration is properly implemented

The deployment task is correctly configured for cross-chain compatibility:

  1. The network configuration is properly sourced from @zetachain/networks using getHardhatConfigNetworks() in hardhat.config.ts
  2. The deployment task correctly retrieves the network context using hre.network.name
  3. The codebase includes necessary ZetaChain protocol contracts and cross-chain gateway integrations
  4. The deployment script handles gateway addresses appropriately through the optional gateway parameter
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify network configurations and deployment parameters for all target chains

# Test: Check if network configurations exist for all target chains
rg -A 5 "networks.*=.*{" .

# Test: Check for chain-specific deployment parameters or overrides
rg -A 10 "deploy.*task" .

Length of output: 148


Script:

#!/bin/bash
# Let's fix the regex and try again with proper escaping

# Check network configurations
rg -A 5 "networks\s*=\s*\{" .

# Check hardhat config for network definitions
rg -A 10 "module\.exports\s*=\s*\{" hardhat.config.*

# Check deployment tasks and their network handling
rg -A 5 "task\(.*deploy" .

# Look for any network-specific logic in deployment files
fd "deploy" --type f --exec rg -l "network|chain"

Length of output: 2473


Script:

#!/bin/bash
# Let's examine the network handling in the token deployment task and related files

# Check the specific implementation of the token deployment
cat ./examples/token/tasks/deploy.ts

# Look for network configurations in the example directory
fd "hardhat.config" --type f --exec cat {} \; ./examples/token/

# Check for any chain-specific constants or configurations
rg -A 3 "chain|network" ./examples/token/

Length of output: 24751

examples/token/tasks/mint.ts (2)

1-4: LGTM: Clean and necessary imports

The imports are well-organized and include all required dependencies for the task implementation.


44-52: LGTM: Well-structured task definition

The task parameters are well-defined with clear descriptions and appropriate optionality.

examples/token/tasks/transfer.ts (1)

1-4: LGTM! Clean and focused imports.

The imports are well-organized and specific to the requirements.

examples/token/contracts/Connected.sol (1)

22-27: ⚠️ Potential issue

Correct the usage of the Ownable constructor

The Ownable contract from OpenZeppelin does not accept an initialOwner parameter in its constructor. Instead, it sets the owner to msg.sender by default. To assign a different initial owner, you should call transferOwnership(initialOwner) within the constructor after initializing the parent contracts.

Apply this diff to fix the constructor:

 ) ERC20("MyToken", "MTK") {
     gateway = GatewayEVM(gatewayAddress);
+    transferOwnership(initialOwner);
 }

Likely invalid or redundant comment.

examples/token/hardhat.config.ts Show resolved Hide resolved
examples/token/hardhat.config.ts Outdated Show resolved Hide resolved
examples/token/.eslintrc.js Show resolved Hide resolved
examples/token/tasks/connectedSetCounterparty.ts Outdated Show resolved Hide resolved
examples/token/tasks/connectedSetCounterparty.ts Outdated Show resolved Hide resolved
Comment on lines 60 to 70
function onCall(
MessageContext calldata messageContext,
bytes calldata message
) external payable onlyGateway returns (bytes4) {
if (messageContext.sender != counterparty) revert("Unauthorized");
(address receiver, uint256 amount) = abi.decode(
message,
(address, uint256)
);
_mint(receiver, amount);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add the missing return statement in the onCall function

The onCall function declares a bytes4 return type but does not currently return any value. This will result in a compilation error and may cause unexpected behavior. Ensure that you return the appropriate selector as per the interface requirements of the GatewayEVM.

Apply this diff to add the missing return statement:

     _mint(receiver, amount);
+    return _zetaclient.onCallSuccess(
+        messageContext.zetaTxSenderAddress,
+        messageContext.zetaTxOrigin,
+        messageContext.zetaValueAndGas
+    );
 }

Ensure that you replace the return statement with the correct value expected by the GatewayEVM interface.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function onCall(
MessageContext calldata messageContext,
bytes calldata message
) external payable onlyGateway returns (bytes4) {
if (messageContext.sender != counterparty) revert("Unauthorized");
(address receiver, uint256 amount) = abi.decode(
message,
(address, uint256)
);
_mint(receiver, amount);
}
function onCall(
MessageContext calldata messageContext,
bytes calldata message
) external payable onlyGateway returns (bytes4) {
if (messageContext.sender != counterparty) revert("Unauthorized");
(address receiver, uint256 amount) = abi.decode(
message,
(address, uint256)
);
_mint(receiver, amount);
return _zetaclient.onCallSuccess(
messageContext.zetaTxSenderAddress,
messageContext.zetaTxOrigin,
messageContext.zetaValueAndGas
);
}

contract Universal is ERC20, Ownable, UniversalContract {
GatewayZEVM public immutable gateway;
SystemContract public immutable systemContract =
SystemContract(0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid hardcoding the system contract address

Hardcoding the systemContract address reduces flexibility and can lead to issues if the address changes across different networks or environments. Consider passing the address as a constructor parameter or providing a setter function with appropriate access control to allow for configuration.

Comment on lines 90 to 91
if (keccak256(messageContext.origin) != keccak256(counterparty[zrc20]))
revert("Unauthorized");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify origin verification in onCall function

Comparing keccak256 hashes of byte arrays is unnecessary and gas-inefficient. Since both messageContext.origin and counterparty[zrc20] are of type bytes, consider using a utility function to compare the byte arrays directly or, if possible, standardize on a fixed-size type like bytes32 for direct comparison. This will improve efficiency and readability.

revert TransferFailed();
IZRC20(zrc20).approve(address(gateway), gasFee);

bytes memory encodedData = abi.encode(receiver, amount);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure consistent message encoding and decoding

In transferCrossChain, the message is encoded with two parameters:

bytes memory encodedData = abi.encode(receiver, amount);

However, in onCall, the message is decoded expecting three parameters:

(address receiver, uint256 tokenAmount, address destination) = abi.decode(message, (address, uint256, address));

This inconsistency will cause decoding errors. Ensure that the encoding and decoding formats match between these functions.

Apply this diff to align the encoding and decoding:

  • If destination is intended to be part of the message:
function transferCrossChain(
    address receiver,
    address zrc20,
    uint256 amount,
+   address destination
) public {
    _burn(msg.sender, amount);

    // ... [omitted code] ...

-   bytes memory encodedData = abi.encode(receiver, amount);
+   bytes memory encodedData = abi.encode(receiver, amount, destination);

    // ... [omitted code] ...
}
  • If destination is not required, adjust the decoding in onCall:
function onCall(
    MessageContext calldata messageContext,
    address zrc20,
    uint256 amount,
    bytes calldata message
) external override onlyGateway {
    // ... [omitted code] ...

-   (address receiver, uint256 tokenAmount, address destination) = abi.decode(message, (address, uint256, address));
+   (address receiver, uint256 tokenAmount) = abi.decode(message, (address, uint256));

    // ... [omitted code] ...
}

Also applies to: 92-93

Comment on lines 47 to 78
function transferCrossChain(
address receiver,
address zrc20,
uint256 amount
) public {
_burn(msg.sender, amount);

(, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit(gasLimit);
if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), gasFee))
revert TransferFailed();
IZRC20(zrc20).approve(address(gateway), gasFee);

bytes memory encodedData = abi.encode(receiver, amount);

CallOptions memory callOptions = CallOptions(gasLimit, false);

RevertOptions memory revertOptions = RevertOptions(
address(this),
true,
address(0),
encodedData,
gasLimit
);

gateway.call(
counterparty[zrc20],
zrc20,
encodedData,
callOptions,
revertOptions
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance security in transferCrossChain function

The transferCrossChain function performs multiple external calls and token transfers, which could introduce reentrancy risks. To strengthen security:

  • Utilize the checks-effects-interactions pattern to order operations safely.
  • Consider incorporating OpenZeppelin's ReentrancyGuard to prevent reentrant calls.
  • Ensure that all external calls are properly validated and that state changes occur before these calls when appropriate.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

🧹 Outside diff range and nitpick comments (9)
examples/token/contracts/shared/Events.sol (1)

4-24: Consider enhancing event parameters for better traceability.

The events are well-structured with appropriate indexing on address parameters. However, consider adding these parameters to enhance traceability:

  1. timestamp to TokenTransfer events for temporal tracking
  2. chainId to cross-chain transfer events for explicit chain identification
  3. metadata or reason string to TokenTransferReverted for debugging

Here's how you could enhance the events:

 event TokenTransfer(
     address indexed destination,
     address indexed receiver,
     uint256 amount
+    uint256 timestamp,
+    uint256 chainId
 );

 event TokenTransferReverted(
     address indexed sender,
-    uint256 amount
+    uint256 amount,
+    string reason
 );
examples/token/contracts/Connected.sol (1)

11-16: Enhance error messages with more context.

While the error definitions are good, they could be more descriptive to aid in debugging.

Consider this improvement:

-    error InvalidAddress();
-    error Unauthorized();
+    error InvalidAddress(string reason);
+    error Unauthorized(address sender, address expected);

Then update the usage:

-    if (gatewayAddress == address(0) || owner == address(0))
-        revert InvalidAddress();
+    if (gatewayAddress == address(0))
+        revert InvalidAddress("gateway address is zero");
+    if (owner == address(0))
+        revert InvalidAddress("owner address is zero");

-    if (context.sender != counterparty) revert Unauthorized();
+    if (context.sender != counterparty) 
+        revert Unauthorized(context.sender, counterparty);
examples/token/scripts/test.sh (1)

76-76: Improve cleanup handling.

Add error handling for the network stop operation to ensure proper cleanup.

-npx hardhat localnet-stop
+echo -e "\n🛑 Stopping local network..."
+if ! npx hardhat localnet-stop; then
+  echo "Warning: Failed to stop local network gracefully"
+  # Attempt force cleanup
+  pkill -f "hardhat localnet" || true
+fi
examples/token/contracts/Universal.sol (3)

1-2: Consider using a more flexible pragma version

The current pragma ^0.8.26 is very specific. While this ensures consistency, it might cause compatibility issues with other contracts or development environments. Consider using a more flexible range like ^0.8.0 unless there are specific features from 0.8.26 that are required.


14-28: Add NatSpec documentation for contract, variables, and errors

Consider adding comprehensive NatSpec documentation to improve code readability and maintainability. This should include:

  • Contract description with @title and @notice
  • Public variable documentation with @notice
  • Error documentation with @notice

Example:

/// @title Universal Token Contract for Cross-chain Transfers
/// @notice Implements ERC20 token with cross-chain transfer capabilities
contract Universal is ERC20, Ownable2Step, UniversalContract, Events {
    /// @notice ZetaChain gateway interface for cross-chain communication
    GatewayZEVM public immutable gateway;
    
    /// @notice Error thrown when a transfer operation fails
    error TransferFailed();

93-95: Add event emission for minting

The mint function should emit an event to track token creation.

     function mint(address to, uint256 amount) public onlyOwner {
+        if (to == address(0)) revert InvalidAddress();
+        if (amount == 0) revert("Zero amount mint");
         _mint(to, amount);
+        emit TokenMinted(to, amount);
     }
examples/token/tasks/deploy.ts (3)

7-12: Validate the presence of a signer more robustly.

The current check may not cover all cases where a signer is invalid. Ensure that the signer object exists and has the necessary properties.

Modify the condition as follows:

-if (signer === undefined) {
+if (!signer || !signer.address) {

24-37: Ensure consistent output formatting.

To enhance maintainability and readability, consider using a structured logging library or utilities to format the output. This approach promotes consistency across different output formats (JSON and plain text).

For example, you might abstract the logging:

function logDeploymentResult(contract: Contract, network: string, signer: Signer, isJson: boolean) {
  const result = {
    contractAddress: contract.address,
    deployer: await signer.getAddress(),
    network: network,
    transactionHash: contract.deployTransaction.hash,
  };

  if (isJson) {
    console.log(JSON.stringify(result));
  } else {
    console.log(`🚀 Successfully deployed "${contract.contractName}" contract on ${network}.
📜 Contract address: ${result.contractAddress}
🔗 Transaction hash: ${result.transactionHash}`);
  }
}

And then call:

await logDeploymentResult(contract, network, signer, args.json);

40-50: Provide default values using environment variables or configuration.

Hardcoding addresses and default parameter values can lead to issues when deploying to different environments. Use environment variables or a configuration file to manage default values dynamically.

For instance, utilize process.env:

-.addOptionalParam("gateway", "Gateway address (default: ZetaChain Gateway)", "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707");
+.addOptionalParam("gateway", "Gateway address", process.env.GATEWAY_ADDRESS || "default_gateway_address");

Ensure to document required environment variables in your README or project documentation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5de7d5f and 3bbcab0.

📒 Files selected for processing (6)
  • examples/token/contracts/Connected.sol (1 hunks)
  • examples/token/contracts/Universal.sol (1 hunks)
  • examples/token/contracts/shared/Events.sol (1 hunks)
  • examples/token/scripts/test.sh (1 hunks)
  • examples/token/tasks/deploy.ts (1 hunks)
  • examples/token/tasks/transfer.ts (1 hunks)
🧰 Additional context used
🪛 Shellcheck
examples/token/scripts/test.sh

[warning] 13-13: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 14-14: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 15-15: Declare and assign separately to avoid masking return values.

(SC2155)

🔇 Additional comments (4)
examples/token/contracts/shared/Events.sol (2)

1-2: LGTM! Modern Solidity version selected.

The use of Solidity 0.8.26 is appropriate as it includes important safety features and optimizations.


5-8: Verify the byte array indexing implementation.

The contractAddress parameter is indexed as bytes. Ensure that the contract addresses across different chains are properly encoded and decoded when using this event.

✅ Verification successful

Byte array indexing implementation is correctly handled

The implementation shows proper handling of contract addresses across chains:

  • In Universal.sol, the contractAddress is stored as bytes in the counterparty mapping and emitted in the event
  • The event is emitted in a consistent manner when setting counterparty mappings
  • The bytes format allows for flexibility in handling different address formats across chains
  • Both NFT and Token implementations follow the same pattern for cross-chain address handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for implementations that emit CounterpartyMappingSet
# to verify proper bytes encoding

rg -l "emit CounterpartyMappingSet" --type solidity

Length of output: 86


Script:

#!/bin/bash
# Search for implementations that emit CounterpartyMappingSet
# using ripgrep without file type filter
rg "emit CounterpartyMappingSet" -A 3

# Also search for any encoding/decoding of contract addresses
rg "contractAddress" -A 3

Length of output: 8774

examples/token/contracts/Connected.sol (1)

1-10: LGTM! Well-structured contract setup with secure inheritance choices.

The contract follows best practices by:

  • Using a recent Solidity version with built-in overflow checks
  • Implementing OpenZeppelin's Ownable2Step for secure ownership transfers
  • Following a clear inheritance pattern
examples/token/contracts/Universal.sol (1)

29-46: LGTM! Well-structured constructor and modifier

The constructor properly validates inputs and the modifier has clear error messaging.

return "";
}

function onRevert(RevertContext calldata context) external onlyGateway {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement revert handling logic.

The empty onRevert function could lead to lost tokens in case of cross-chain transfer failures. Consider implementing proper revert handling logic to ensure tokens can be recovered.

Would you like me to provide an implementation for handling cross-chain transfer reverts?

Comment on lines +22 to +25
function setCounterparty(address contractAddress) external onlyOwner {
counterparty = contractAddress;
emit CounterpartySet(contractAddress);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add address validation and improve event emission.

The function should validate the new counterparty address and include the old value in the event for better traceability.

Apply this improvement:

 function setCounterparty(address contractAddress) external onlyOwner {
+    if (contractAddress == address(0)) revert InvalidAddress("counterparty address is zero");
+    address oldCounterparty = counterparty;
     counterparty = contractAddress;
-    emit CounterpartySet(contractAddress);
+    emit CounterpartySet(oldCounterparty, contractAddress);
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +70 to +82
function onCall(
MessageContext calldata context,
bytes calldata message
) external payable onlyGateway returns (bytes4) {
if (context.sender != counterparty) revert Unauthorized();
(address receiver, uint256 amount) = abi.decode(
message,
(address, uint256)
);
_mint(receiver, amount);
emit TokenTransferReceived(receiver, amount);
return "";
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add amount validation in onCall function.

The function should validate the minting amount to prevent potential issues.

Apply this improvement:

 function onCall(
     MessageContext calldata context,
     bytes calldata message
 ) external payable onlyGateway returns (bytes4) {
     if (context.sender != counterparty) revert Unauthorized();
     (address receiver, uint256 amount) = abi.decode(
         message,
         (address, uint256)
     );
+    if (receiver == address(0)) revert InvalidAddress("receiver is zero");
+    if (amount == 0) revert("amount cannot be zero");
     _mint(receiver, amount);
     emit TokenTransferReceived(receiver, amount);
     return "";
 }

Committable suggestion skipped: line range outside the PR's diff.

examples/token/contracts/Connected.sol Show resolved Hide resolved
Comment on lines +42 to +68
function transferCrossChain(
address destination,
address receiver,
uint256 amount
) external payable {
_burn(msg.sender, amount);
bytes memory message = abi.encode(destination, receiver, amount);

RevertOptions memory revertOptions = RevertOptions(
address(this),
true,
address(0),
message,
0
);

if (destination == address(0)) {
gateway.call(counterparty, message, revertOptions);
} else {
gateway.depositAndCall{value: msg.value}(
counterparty,
message,
revertOptions
);
}
emit TokenTransfer(destination, receiver, amount);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add security checks and follow CEI pattern.

The function has several security considerations that should be addressed:

  1. No validation if counterparty is set
  2. No validation of amount
  3. External calls after state modification (potential reentrancy)

Apply these security improvements:

 function transferCrossChain(
     address destination,
     address receiver,
     uint256 amount
 ) external payable {
+    if (counterparty == address(0)) revert InvalidAddress("counterparty not set");
+    if (receiver == address(0)) revert InvalidAddress("receiver is zero");
+    if (amount == 0) revert("amount cannot be zero");
+    
+    // Cache msg.value to follow CEI pattern
+    uint256 value = msg.value;
     _burn(msg.sender, amount);
     bytes memory message = abi.encode(destination, receiver, amount);

     RevertOptions memory revertOptions = RevertOptions(
         address(this),
         true,
         address(0),
         message,
         0
     );

     if (destination == address(0)) {
         gateway.call(counterparty, message, revertOptions);
     } else {
-        gateway.depositAndCall{value: msg.value}(
+        gateway.depositAndCall{value: value}(
             counterparty,
             message,
             revertOptions
         );
     }
     emit TokenTransfer(destination, receiver, amount);
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +56 to +91
function transferCrossChain(
address destination,
address receiver,
uint256 amount
) public {
if (receiver == address(0)) revert InvalidAddress();
_burn(msg.sender, amount);

(, uint256 gasFee) = IZRC20(destination).withdrawGasFeeWithGasLimit(
gasLimit
);
if (
!IZRC20(destination).transferFrom(msg.sender, address(this), gasFee)
) revert TransferFailed();
IZRC20(destination).approve(address(gateway), gasFee);
bytes memory message = abi.encode(receiver, amount);

CallOptions memory callOptions = CallOptions(gasLimit, false);

RevertOptions memory revertOptions = RevertOptions(
address(this),
true,
address(0),
message,
gasLimit
);

gateway.call(
counterparty[destination],
destination,
message,
callOptions,
revertOptions
);
emit TokenTransfer(destination, receiver, amount);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and reentrancy protection

The transferCrossChain function needs additional security measures:

  1. Add a check for zero amount transfers
  2. Implement reentrancy protection using OpenZeppelin's ReentrancyGuard
  3. Consider implementing a maximum amount check

Apply this diff:

+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

-contract Universal is ERC20, Ownable2Step, UniversalContract, Events {
+contract Universal is ERC20, Ownable2Step, UniversalContract, Events, ReentrancyGuard {
    // ...
-    function transferCrossChain(
+    function transferCrossChain(
         address destination,
         address receiver,
         uint256 amount
-    ) public {
+    ) public nonReentrant {
+        if (amount == 0) revert("Zero amount transfer");
         if (receiver == address(0)) revert InvalidAddress();

Committable suggestion skipped: line range outside the PR's diff.

const [signer] = await hre.ethers.getSigners();
if (signer === undefined) {
throw new Error(
`Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the command in the error message.

The command npx hardhat account --save may be incorrect or non-standard. Provide accurate instructions to prevent user confusion.

Update the error message:

-`Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)`
+`Wallet not found. Please set the PRIVATE_KEY environment variable (e.g., in a .env file) or ensure your Hardhat configuration includes a valid account.`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)`
`Wallet not found. Please set the PRIVATE_KEY environment variable (e.g., in a .env file) or ensure your Hardhat configuration includes a valid account.`

);
}

const factory: any = await hre.ethers.getContractFactory(args.name);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Specify the correct type for the contract factory.

Using any diminishes type checking for the contract factory. Replace any with the appropriate type.

Adjust the declaration:

-const factory: any = await hre.ethers.getContractFactory(args.name);
+const factory = await hre.ethers.getContractFactory(args.name);

TypeScript can infer the type, or you can specify it explicitly if necessary.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const factory: any = await hre.ethers.getContractFactory(args.name);
const factory = await hre.ethers.getContractFactory(args.name);

Comment on lines +15 to +21
const contract = await factory.deploy(
args.gateway,
signer.address,
args.tokenName,
args.tokenSymbol,
...(args.gasLimit ? [args.gasLimit] : [])
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Pass deployment overrides correctly to include gas limit.

The gas limit should be provided as an override, not as a constructor parameter. Including it in the constructor arguments may lead to unexpected behavior or deployment failures.

Refactor the deployment call:

-  const contract = await factory.deploy(
-    args.gateway,
-    signer.address,
-    args.tokenName,
-    args.tokenSymbol,
-    ...(args.gasLimit ? [args.gasLimit] : [])
-  );
+  const deployArgs = [
+    args.gateway,
+    signer.address,
+    args.tokenName,
+    args.tokenSymbol,
+  ];
+
+  const deployOptions = args.gasLimit ? { gasLimit: args.gasLimit } : {};
+
+  const contract = await factory.deploy(...deployArgs, deployOptions);

Committable suggestion skipped: line range outside the PR's diff.

import { task, types } from "hardhat/config";
import { HardhatRuntimeEnvironment } from "hardhat/types";

const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Define specific types for function parameters.

Using any for the args parameter reduces type safety and may lead to runtime errors. Consider defining a proper interface for args to enhance code reliability.

Apply this change:

-const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
+interface DeployArgs {
+  json?: boolean;
+  tokenName?: string;
+  tokenSymbol?: string;
+  name?: string;
+  gasLimit?: string;
+  gateway?: string;
+}
+
+const main = async (args: DeployArgs, hre: HardhatRuntimeEnvironment) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
interface DeployArgs {
json?: boolean;
tokenName?: string;
tokenSymbol?: string;
name?: string;
gasLimit?: string;
gateway?: string;
}
const main = async (args: DeployArgs, hre: HardhatRuntimeEnvironment) => {

@fadeev
Copy link
Member Author

fadeev commented Nov 8, 2024

In this PR I've also modified the NFT example (events and parameter order) to make the two tutorials more consistent.

@fadeev fadeev changed the title ERC-20 token transfer example ERC-20 token transfer example and improvements to NFT Nov 8, 2024
@fadeev fadeev requested a review from andresaiello November 8, 2024 10:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (4)
examples/nft/contracts/Connected.sol (1)

Line range hint 1-156: Consider gas optimization for cross-chain operations.

Cross-chain operations can be expensive. Consider implementing the following optimizations:

  1. Cache the URI data off-chain and only transfer a reference/hash in the cross-chain message
  2. Implement batch transfer functionality to amortize cross-chain messaging costs
  3. Consider implementing a message compression mechanism for URIs

Would you like assistance in implementing any of these optimizations?

examples/nft/contracts/Universal.sol (3)

128-132: Consider optimizing variable declarations for gas efficiency.

The message decoding and event emissions follow the correct parameter order. However, we can optimize gas usage by combining related operations.

Consider this optimization:

-            address destination,
-            address sender,
-            uint256 tokenId,
-            string memory uri
+            address destination, address sender,
+            uint256 tokenId, string memory uri

Also applies to: 137-137, 155-155, 159-159


164-166: Consider enhancing error handling with specific revert reasons.

While the revert handling is functionally correct, consider adding specific error messages for different failure scenarios to improve debugging.

Add custom errors:

+    error InvalidRevertMessage();
+    error TokenRecoveryFailed();

     function onRevert(RevertContext calldata context) external onlyGateway {
+        if (context.revertMessage.length == 0) revert InvalidRevertMessage();
         (address sender, uint256 tokenId, string memory uri) = abi.decode(
             context.revertMessage,
             (address, uint256, string)
         );

Also applies to: 171-171


Line range hint 1-171: Consider implementing additional security measures.

While the contract has good security foundations, consider these enhancements:

  1. Add reentrancy guards for external calls
  2. Implement emergency pause functionality
  3. Add events for critical state changes (e.g., gasLimit updates)

Would you like me to provide implementation details for these security enhancements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3bbcab0 and 246361d.

📒 Files selected for processing (9)
  • examples/nft/contracts/Connected.sol (3 hunks)
  • examples/nft/contracts/Universal.sol (4 hunks)
  • examples/nft/contracts/shared/Events.sol (1 hunks)
  • examples/token/hardhat.config.ts (1 hunks)
  • examples/token/scripts/test.sh (1 hunks)
  • examples/token/tasks/connectedSetCounterparty.ts (1 hunks)
  • examples/token/tasks/mint.ts (1 hunks)
  • examples/token/tasks/transfer.ts (1 hunks)
  • examples/token/tasks/universalSetCounterparty.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • examples/token/hardhat.config.ts
  • examples/token/tasks/connectedSetCounterparty.ts
  • examples/token/tasks/mint.ts
  • examples/token/tasks/transfer.ts
  • examples/token/tasks/universalSetCounterparty.ts
🧰 Additional context used
🪛 Shellcheck
examples/token/scripts/test.sh

[warning] 12-12: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 13-13: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 14-14: Declare and assign separately to avoid masking return values.

(SC2155)

🔇 Additional comments (2)
examples/nft/contracts/shared/Events.sol (1)

8-29: LGTM! Parameter ordering follows a consistent pattern.

The reordering of event parameters follows a logical and consistent pattern across all transfer-related events:

  1. Network/destination parameters first
  2. Address-related parameters (sender/receiver)
  3. Token-specific parameters
  4. Additional metadata (uri)

This standardization improves code readability and maintains proper indexing for efficient event filtering.

examples/nft/contracts/Connected.sol (1)

68-68: Verify cross-chain message protocol compatibility.

The parameter ordering in message encoding and event emission has been changed. This modification requires careful verification to ensure compatibility with counterparty contracts across all chains.

Let's verify the counterparty contract implementation:

Also applies to: 88-88

✅ Verification successful

The previous ast-grep patterns might have been too specific. Let's try a broader search to understand the message encoding/decoding patterns and cross-chain message handling across the codebase.


Parameter reordering is consistent with cross-chain message protocol

The codebase analysis shows that the message encoding/decoding patterns are consistent across the contracts:

  1. In Connected.sol (the contract under review):

    • Encoding: abi.encode(destination, receiver, tokenId, uri)
    • Decoding: abi.decode(message, (address, uint256, string))
  2. In Universal.sol (the counterparty contract):

    • Encoding: abi.encode(receiver, tokenId, uri)
    • Decoding: abi.decode(message, (address, address, uint256, string))

The parameter ordering is properly aligned between the contracts, with Universal.sol correctly handling the additional destination parameter in the cross-chain message protocol.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for message encoding/decoding patterns in other contracts
ast-grep --pattern 'abi.encode($$$destination$$$, $$$receiver$$$, $$$tokenId$$$, $$$uri$$$)'
ast-grep --pattern 'abi.decode($$$, ($$$address$$$, $$$uint256$$$, $$$string$$$))'

Length of output: 179


Script:

#!/bin/bash
# Let's search for all abi.encode and abi.decode patterns in the codebase
rg "abi\.(encode|decode)" -A 3 -B 3

# Also search for any files that might contain cross-chain message handling
fd -e sol | xargs rg "(onCall|onRevert|transferCrossChain)" -A 5 -B 5

Length of output: 25867

Comment on lines 8 to 29
address indexed destination,
address indexed receiver,
uint256 indexed tokenId,
string uri
);
event TokenTransferReceived(
uint256 indexed tokenId,
address indexed receiver,
uint256 indexed tokenId,
string uri
);
event TokenTransferReverted(
uint256 indexed tokenId,
address indexed sender,
uint256 indexed tokenId,
string uri
);
event CounterpartySet(address indexed zrc20, bytes indexed contractAddress);
event TokenTransferToDestination(
uint256 indexed tokenId,
address indexed sender,
address indexed destination,
address indexed sender,
uint256 indexed tokenId,
string uri
);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Parameter mismatch detected in TokenTransfer event between declaration and usage

The verification reveals inconsistencies in event parameter ordering:

  • In examples/nft/contracts/Universal.sol, TokenTransfer is emitted with parameters (receiver, destination, tokenId, uri)
  • However, in Events.sol, it's declared as (destination, receiver, tokenId, uri)
  • Similar parameter ordering should be maintained across the codebase to prevent event handling issues

This mismatch could lead to incorrect event handling by external listeners and should be corrected to maintain consistency with the event declaration in Events.sol.

🔗 Analysis chain

Verify event parameter alignment with external contracts.

The parameter reordering appears to align with changes in the Connected and Universal contracts mentioned in the summary. However, it's crucial to verify this alignment to prevent any potential event handling issues.


Let me gather more information about the event declarations to complete the verification.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify event parameter ordering consistency across related contracts
# Expected: Parameter ordering should match in event emissions and declarations

# Search for event emissions in related contracts
echo "Checking event emissions in contracts:"
rg -A 2 "emit Token(Transfer|TransferReceived|TransferReverted|TransferToDestination)"

# Search for interface definitions that might be listening to these events
echo "Checking interface definitions:"
ast-grep --pattern 'interface $_ {
  $$$
  event Token$_(address indexed $_, $$$);
  $$$
}'

Length of output: 2702


Script:

#!/bin/bash
# Search for event declarations in all contracts
echo "Checking event declarations in contracts:"
rg -A 3 "event Token(Transfer|TransferReceived|TransferReverted|TransferToDestination)" --type sol

# Search specifically in the shared Events.sol files
echo -e "\nChecking Events.sol files:"
fd "Events.sol" --exec cat {}

Length of output: 1911

balance

echo -e "\nTransferring token: ZetaChain → Ethereum..."
npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add transaction receipt validation for token transfers.

The current implementation doesn't validate the transaction receipts. Consider adding receipt validation to ensure transfers are successful.

-npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM" 
+# Function to validate transaction receipt
+validate_receipt() {
+  local receipt=$1
+  local status
+  status=$(echo "$receipt" | jq -r '.status')
+  if [ "$status" != "1" ]; then
+    echo "Transaction failed with status: $status"
+    exit 1
+  fi
+}
+
+# Execute transfer with receipt validation
+receipt=$(npx hardhat transfer --network localhost --json --amount 10 --from "$CONTRACT_ZETACHAIN" --to "$ZRC20_ETHEREUM")
+validate_receipt "$receipt"

Also applies to: 64-64, 70-70

npx hardhat localnet-check
balance

npx hardhat localnet-stop
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance network cleanup reliability.

The cleanup process should handle potential failures and ensure all resources are properly released.

-npx hardhat localnet-stop
+cleanup() {
+  echo "Cleaning up..."
+  if ! npx hardhat localnet-stop; then
+    echo "Warning: Failed to stop local network gracefully"
+    # Force cleanup as fallback
+    pkill -f "hardhat localnet" || true
+  fi
+}
+
+trap cleanup EXIT
+cleanup
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npx hardhat localnet-stop
cleanup() {
echo "Cleaning up..."
if ! npx hardhat localnet-stop; then
echo "Warning: Failed to stop local network gracefully"
# Force cleanup as fallback
pkill -f "hardhat localnet" || true
fi
}
trap cleanup EXIT
cleanup

Comment on lines +109 to +116
(address sender, uint256 tokenId, string memory uri) = abi.decode(
context.revertMessage,
(uint256, address, string)
(address, uint256, string)
);

_safeMint(sender, tokenId);
_setTokenURI(tokenId, uri);
emit TokenTransferReverted(tokenId, sender, uri);
emit TokenTransferReverted(sender, tokenId, uri);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add address validation in onRevert function.

For consistency with onCall and other functions, add validation for the decoded sender address.

 (address sender, uint256 tokenId, string memory uri) = abi.decode(
     context.revertMessage,
     (address, uint256, string)
 );
+if (sender == address(0)) revert InvalidAddress();
 
 _safeMint(sender, tokenId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(address sender, uint256 tokenId, string memory uri) = abi.decode(
context.revertMessage,
(uint256, address, string)
(address, uint256, string)
);
_safeMint(sender, tokenId);
_setTokenURI(tokenId, uri);
emit TokenTransferReverted(tokenId, sender, uri);
emit TokenTransferReverted(sender, tokenId, uri);
(address sender, uint256 tokenId, string memory uri) = abi.decode(
context.revertMessage,
(address, uint256, string)
);
if (sender == address(0)) revert InvalidAddress();
_safeMint(sender, tokenId);
_setTokenURI(tokenId, uri);
emit TokenTransferReverted(sender, tokenId, uri);

Comment on lines +97 to +104
(address receiver, uint256 tokenId, string memory uri) = abi.decode(
message,
(uint256, address, string)
(address, uint256, string)
);

_safeMint(receiver, tokenId);
_setTokenURI(tokenId, uri);
emit TokenTransferReceived(tokenId, receiver, uri);
emit TokenTransferReceived(receiver, tokenId, uri);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add address validation in onCall function.

While the parameter reordering is correct, consider adding validation for the decoded receiver address to maintain consistency with other functions that check for zero addresses.

 (address receiver, uint256 tokenId, string memory uri) = abi.decode(
     message,
     (address, uint256, string)
 );
+if (receiver == address(0)) revert InvalidAddress();
 
 _safeMint(receiver, tokenId);

Committable suggestion skipped: line range outside the PR's diff.

@@ -79,7 +79,7 @@ contract Universal is
!IZRC20(destination).transferFrom(msg.sender, address(this), gasFee)
) revert TransferFailed();
IZRC20(destination).approve(address(gateway), gasFee);
bytes memory message = abi.encode(tokenId, receiver, uri);
bytes memory message = abi.encode(receiver, tokenId, uri);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Parameter ordering inconsistency detected between Universal and Connected contracts

The parameter ordering differs between the contracts:

  • Universal.sol: abi.encode(receiver, tokenId, uri)
  • Connected.sol: abi.encode(destination, receiver, tokenId, uri)

This inconsistency in message encoding could lead to cross-chain transfer failures. The Connected contract expects an additional destination parameter in the encoded message that Universal contract does not provide.

🔗 Analysis chain

Parameter reordering looks good, verify cross-chain compatibility.

The reordering of parameters in message encoding and event emission maintains internal consistency. The sequence (receiver, tokenId, uri) is now uniform throughout the contract.

Let's verify the parameter order consistency in connected contracts:

Also applies to: 102-102

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for message encoding patterns in connected contracts
rg -A 2 "abi\.encode.*receiver.*tokenId.*uri" --type sol

Length of output: 86


Script:

#!/bin/bash
# Search for message encoding patterns in all files
rg -A 2 "abi\.encode.*receiver.*tokenId.*uri"

# Also search for message decoding patterns
rg -A 2 "abi\.decode.*receiver.*tokenId.*uri"

# Search for any abi.encode patterns to analyze parameter ordering
rg -A 2 "abi\.encode"

Length of output: 3355

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 246361d and a98517d.

📒 Files selected for processing (1)
  • examples/token/tasks/transfer.ts (1 hunks)
🔇 Additional comments (2)
examples/token/tasks/transfer.ts (2)

36-41: ⚠️ Potential issue

Add validation for cross-chain transfer

The cross-chain transfer lacks validation for critical parameters and proper error handling.

Consider this implementation:

-  tx = await (contract as any).transferCrossChain(
-    args.to,
-    receiver,
-    args.amount,
-    { ...txOptions, value: gasAmount }
-  );
+  if (!contract.transferCrossChain) {
+    throw new Error("Contract does not support cross-chain transfers");
+  }
+  
+  if (!ethers.utils.isAddress(args.to)) {
+    throw new Error("Invalid destination address");
+  }
+  
+  tx = await contract.transferCrossChain(
+    args.to,
+    receiver,
+    args.amount,
+    { ...txOptions, value: gasAmount }
+  ).catch((error: Error) => {
+    throw new Error(`Cross-chain transfer failed: ${error.message}`);
+  });

Likely invalid or redundant comment.


67-68: Review default gas values

The default gas values might need adjustment based on the network and contract complexity:

  • Gas price: 10 Gwei might be too low for some networks
  • Gas limit: 7M might be excessive for token transfers

Let's analyze typical gas usage:

Also applies to: 73-74

✅ Verification successful

The previous search might have failed due to incorrect pattern matching. Let's try a broader search to understand the gas configuration context and transfer implementations in the codebase.


Gas configuration values are appropriate for the use case

Based on the codebase analysis:

  • The 10 Gwei (10000000000 wei) gas price is used consistently across examples and is reasonable for test/development environments
  • The 7M gas limit is appropriate as the contracts perform complex operations:
    • Cross-chain transfers via ZetaChain gateway
    • Gas fee calculations using withdrawGasFeeWithGasLimit
    • Multiple contract interactions in a single transaction

The values align with similar configurations in other parts of the codebase (e.g., NFT, Hello, and Swap examples) and are suitable for the cross-chain token transfer operations being performed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check typical gas usage for token transfers
rg -A 2 "transferCrossChain.*\{" | grep -A 2 "gasLimit"

Length of output: 55


Script:

#!/bin/bash
# Let's search for gas-related configurations and transfer functions
rg -i "gas.*limit|gas.*price" -A 2

# Also search for transfer-related functions to understand the context
ast-grep --pattern 'function $_ ($$$) {
  $$$
  transfer$$$($$$)
  $$$
}'

Length of output: 17023

Comment on lines +76 to +88
.addFlag("callOnRevert", "Whether to call on revert")
.addOptionalParam(
"revertAddress",
"The address to call on revert",
"0x0000000000000000000000000000000000000000"
)
.addOptionalParam("revertMessage", "The message to send on revert", "0x")
.addOptionalParam(
"onRevertGasLimit",
"The gas limit for the revert transaction",
7000000,
types.int
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused revert parameters

The following parameters are defined but never used in the implementation:

  • callOnRevert
  • revertAddress
  • revertMessage
  • onRevertGasLimit

Either implement the revert functionality or remove these unused parameters to avoid confusion.

}
};

task("transfer", "Transfer and lock an NFT", main)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent task description

The task description mentions "Transfer and lock an NFT" but the implementation is for ERC-20 tokens.

Update the task description:

-task("transfer", "Transfer and lock an NFT", main)
+task("transfer", "Transfer ERC-20 tokens cross-chain", main)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
task("transfer", "Transfer and lock an NFT", main)
task("transfer", "Transfer ERC-20 tokens cross-chain", main)

Comment on lines +25 to +29
const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions);
await zrc20TransferTx.wait();
const tokenApprove = await contract.approve(args.from, value);
await tokenApprove.wait();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add proper error handling for token approvals

The approval transactions lack proper error handling and validation. A failed approval could lead to a stuck transaction.

Consider this implementation:

-    const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
-    const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions);
-    await zrc20TransferTx.wait();
-    const tokenApprove = await contract.approve(args.from, value);
-    await tokenApprove.wait();
+    const [success, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
+    if (!success) {
+      throw new Error("Failed to calculate gas fee");
+    }
+    
+    const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions);
+    const zrc20Receipt = await zrc20TransferTx.wait();
+    if (!zrc20Receipt.status) {
+      throw new Error("ZRC20 approval failed");
+    }
+    
+    const tokenApprove = await contract.approve(args.from, value);
+    const tokenReceipt = await tokenApprove.wait();
+    if (!tokenReceipt.status) {
+      throw new Error("Token approval failed");
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions);
await zrc20TransferTx.wait();
const tokenApprove = await contract.approve(args.from, value);
await tokenApprove.wait();
const [success, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
if (!success) {
throw new Error("Failed to calculate gas fee");
}
const zrc20TransferTx = await zrc20.approve(args.from, gasFee, txOptions);
const zrc20Receipt = await zrc20TransferTx.wait();
if (!zrc20Receipt.status) {
throw new Error("ZRC20 approval failed");
}
const tokenApprove = await contract.approve(args.from, value);
const tokenReceipt = await tokenApprove.wait();
if (!tokenReceipt.status) {
throw new Error("Token approval failed");
}

@fadeev fadeev merged commit c394e92 into main Nov 8, 2024
12 checks passed
@fadeev fadeev deleted the example-token branch November 8, 2024 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants