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

NFT: improvements to make production-ready #217

Merged
merged 13 commits into from
Nov 8, 2024
Merged

NFT: improvements to make production-ready #217

merged 13 commits into from
Nov 8, 2024

Conversation

fadeev
Copy link
Member

@fadeev fadeev commented Nov 7, 2024

  • moved symbol and name into constructor, updated deploy script
  • test: if started with localnet kill it in the end
  • set gas limit in constructor
  • added events
  • switched to Ownable2Step

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced NFT contract deployment with customizable name and symbol.
    • Improved flexibility in token creation and cross-chain functionality.
    • New events for better tracking of token management and transfer processes.
    • Added optional receiver parameter for NFT transfers.
  • Bug Fixes

    • Updated checks in cross-chain transfer functions to ensure proper handling of addresses.
  • Documentation

    • Updated output messages for consistency in deployment scripts.
  • Refactor

    • Simplified control flow in the deployment script for improved readability.
    • Improved type safety and error handling in various tasks.
    • Enhanced module resolution capabilities in TypeScript configuration.

Copy link

coderabbitai bot commented Nov 7, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces significant updates to the Connected and Universal contracts, modifying their constructors to accept dynamic parameters for token naming and symbol assignment. The transferCrossChain function in both contracts has been adjusted to enhance cross-chain functionality. Additionally, the test.sh script has been refined for improved readability, and the deploy.ts task has been updated to allow for customizable NFT names and symbols during deployment. These changes collectively enhance the flexibility and functionality of the NFT contracts and their deployment processes.

Changes

File Change Summary
examples/nft/contracts/Connected.sol Constructor updated to accept name and symbol parameters; added custom errors InvalidAddress and Unauthorized; improved error handling in various functions.
examples/nft/contracts/Universal.sol Constructor updated to accept name, symbol, and gas; added custom errors InvalidAddress and Unauthorized; improved error handling in transferCrossChain.
examples/nft/contracts/shared/Events.sol New contract created to define events for token management and transfer processes, including TokenMinted, TokenTransfer, and others.
examples/nft/scripts/test.sh Simplified control flow; renamed function from nft_balance to balance; updated output messages for consistency.
examples/nft/tasks/deploy.ts Added optional parameters nftName and nftSymbol with default values; updated deployment method to accept new parameters.
examples/nft/tasks/connectedSetCounterparty.ts Updated contract reference for type safety; removed optional name parameter from task definition.
examples/nft/tasks/mint.ts Enhanced type safety for args.name and event parameters; retained original minting logic.
examples/nft/tasks/transfer.ts Introduced new optional parameter receiver for specifying NFT transfer recipient.
examples/nft/tasks/universalSetCounterparty.ts Updated contract reference for type safety; removed optional name parameter from task definition.
examples/nft/tsconfig.json Added baseUrl and paths properties to enhance module resolution capabilities.

Possibly related PRs

  • feat: added ability to swap token on and from ZetaChain #186: The changes in the Connected contract regarding the introduction of the onlyGateway modifier are relevant as they enhance access control, similar to the modifications made in the Swap contract which also implements the onlyGateway modifier for security.
  • fix: withdraw gas tokens from swap #198: The onlyGateway modifier introduced in the Swap and SwapToAnyToken contracts aligns with the changes in the Connected contract, reinforcing access control across multiple contracts.
  • onlyGateway modifier #213: The addition of the onlyGateway modifier in the Echo, Hello, and Connected contracts directly relates to the changes made in the main PR, as they all aim to restrict access to critical functions, enhancing security across the contracts.

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
Copy link
Member Author

fadeev commented Nov 7, 2024

@coderabbitai review

Copy link

coderabbitai bot commented Nov 7, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 2

🧹 Outside diff range and nitpick comments (8)
examples/nft/scripts/test.sh (3)

5-5: Consider adding timeout and health check for localnet startup

The current implementation uses a fixed sleep duration which might be insufficient on slower systems. Consider implementing a health check loop.

-if [ "$1" = "localnet" ]; then npx hardhat localnet --exit-on-error & sleep 10; fi
+if [ "$1" = "localnet" ]; then
+  npx hardhat localnet --exit-on-error &
+  ATTEMPTS=0
+  until npx hardhat localnet-check &>/dev/null || [ $ATTEMPTS -eq 30 ]; do
+    sleep 1
+    ((ATTEMPTS++))
+  done
+  if [ $ATTEMPTS -eq 30 ]; then
+    echo "Error: Localnet failed to start within 30 seconds"
+    exit 1
+  fi
+fi

Line range hint 21-35: Add error handling for contract deployment operations

The script should validate the contract addresses after deployment and handle potential failures gracefully.

+function check_address() {
+  local addr=$1
+  local name=$2
+  if [[ ! $addr =~ ^0x[a-fA-F0-9]{40}$ ]]; then
+    echo "Error: Invalid contract address for $name: $addr"
+    exit 1
+  fi
+}

 CONTRACT_ZETACHAIN=$(npx hardhat deploy --network localhost --json | jq -r '.contractAddress')
+check_address "$CONTRACT_ZETACHAIN" "ZetaChain NFT"
 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')
+check_address "$CONTRACT_ETHEREUM" "Ethereum NFT"
 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')
+check_address "$CONTRACT_BNB" "BNB NFT"
 echo -e "🚀 Deployed NFT contract on BNB chain: $CONTRACT_BNB"

72-72: Enhance cleanup process

Consider adding a trap to ensure localnet is stopped even if the script fails.

+if [ "$1" = "localnet" ]; then
+  trap 'npx hardhat localnet-stop' EXIT
+fi
-if [ "$1" = "localnet" ]; then npx hardhat localnet-stop; fi
examples/nft/contracts/Connected.sol (2)

28-31: Verify inheritance initialization order

The constructor properly initializes both ERC721 and Ownable base contracts. However, consider documenting the inheritance order in a comment for better maintainability, as Solidity's C3 linearization can be complex with multiple inheritance.

Add a comment above the contract:

+/// @dev Inheritance order: Connected -> ERC721URIStorage -> ERC721Enumerable -> ERC721 -> Ownable
 contract Connected is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable {

Line range hint 1-156: Review payable functions security

The contract includes payable receive() and fallback() functions without access controls or accounting logic. This could lead to trapped ETH if not handled properly.

Consider implementing one of these solutions:

-    receive() external payable {}
-    fallback() external payable {}
+    receive() external payable {
+        require(msg.sender == address(gateway), "Only gateway can send ETH");
+    }
+    
+    fallback() external payable {
+        require(msg.sender == address(gateway), "Only gateway can send ETH");
+    }

Or if ETH acceptance is not needed:

-    receive() external payable {}
-    fallback() external payable {}
+    receive() external payable {
+        revert("ETH not accepted");
+    }
+    
+    fallback() external payable {
+        revert("ETH not accepted");
+    }
examples/nft/contracts/Universal.sol (3)

43-46: LGTM! Consider adding input validation.

The constructor changes improve contract flexibility by allowing custom NFT names and symbols. The parameter naming is clear and follows best practices.

Consider adding input validation for the name and symbol parameters:

    constructor(
        address payable gatewayAddress,
        address owner,
        string memory name,
        string memory symbol
-    ) ERC721(name, symbol) Ownable(owner) {
+    ) ERC721(name, symbol) Ownable(owner) {
+        require(bytes(name).length > 0, "Name cannot be empty");
+        require(bytes(symbol).length > 0, "Symbol cannot be empty");
+        require(gatewayAddress != address(0), "Invalid gateway address");
        gateway = GatewayZEVM(gatewayAddress);
    }

Line range hint 69-95: Security: Enhance cross-chain transfer safety.

The transferCrossChain function follows good practices but could benefit from additional safety measures:

  1. Consider implementing a reentrancy guard
  2. Add events for better transaction tracking
  3. Validate the destination chain's counterparty contract before transfer

Consider implementing these improvements:

+    event CrossChainTransferInitiated(uint256 tokenId, address receiver, address destination);
+
+    modifier nonReentrant() {
+        require(!_locked, "ReentrancyGuard: reentrant call");
+        _locked = true;
+        _;
+        _locked = false;
+    }
+
-    function transferCrossChain(
+    function transferCrossChain(
         uint256 tokenId,
         address receiver,
         address destination
-    ) public {
+    ) public nonReentrant {
+        require(counterparty[destination].length > 0, "Invalid destination chain");
         string memory uri = tokenURI(tokenId);
         _burn(tokenId);
+        emit CrossChainTransferInitiated(tokenId, receiver, destination);

Line range hint 96-146: Optimize gas usage and improve configuration flexibility.

The onCall function uses hardcoded gas values and could benefit from gas optimizations:

  1. Use the contract's gasLimit state variable instead of hardcoding 700000
  2. Cache frequently accessed state variables
  3. Consider implementing a configurable gas limit

Implement these optimizations:

-            (, uint256 gasFee) = IZRC20(destination).withdrawGasFeeWithGasLimit(
-                700000
-            );
+            (, uint256 gasFee) = IZRC20(destination).withdrawGasFeeWithGasLimit(
+                gasLimit
+            );

             SwapHelperLib.swapExactTokensForTokens(
                 systemContract,
                 zrc20,
                 amount,
                 destination,
                 0
             );

             IZRC20(destination).approve(address(gateway), gasFee);
             gateway.call(
                 counterparty[destination],
                 destination,
                 abi.encode(tokenId, sender, uri),
-                CallOptions(700000, false),
+                CallOptions(gasLimit, false),
                 RevertOptions(address(0), false, address(0), "", 0)
             );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b324d3 and 57bc55a.

📒 Files selected for processing (4)
  • examples/nft/contracts/Connected.sol (1 hunks)
  • examples/nft/contracts/Universal.sol (1 hunks)
  • examples/nft/scripts/test.sh (3 hunks)
  • examples/nft/tasks/deploy.ts (2 hunks)
🧰 Additional context used
🪛 Shellcheck
examples/nft/scripts/test.sh

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

(SC2155)


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

(SC2155)


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

(SC2155)

🔇 Additional comments (4)
examples/nft/tasks/deploy.ts (2)

41-42: LGTM: Well-structured task parameter definitions.

The optional parameters are appropriately defined with meaningful default values that align with standard NFT naming conventions.


15-20: LGTM: Constructor parameter updates properly implemented.

The deployment call correctly includes the new NFT name and symbol parameters, maintaining consistency with the updated contract constructors.

Let's verify the contract constructors match these parameters:

✅ Verification successful

This script will:

  1. Find all Solidity files
  2. For each file, search for constructor definitions
  3. Show surrounding context to verify parameter alignment

The previous command might have failed due to syntax issues with tree-sitter patterns. This approach using fd and rg should provide better visibility into the constructor implementations.


The previous search revealed that the NFT contract files exist but didn't show their constructors, possibly due to regex limitations. Let's examine their complete content to verify the constructor parameters.


Constructor parameters correctly aligned with deployment call

The deployment parameters in deploy.ts perfectly match the constructor signatures in both NFT contracts:

  • Connected.sol:
constructor(
    address payable gatewayAddress,
    address owner,
    string memory name,
    string memory symbol
)
  • Universal.sol:
constructor(
    address payable gatewayAddress,
    address owner,
    string memory name,
    string memory symbol
)

The deployment call:

const contract = await factory.deploy(
    args.gateway,
    signer.address,
    args.nftName,
    args.nftSymbol
);
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify constructor signatures in NFT contracts
# Expect: Constructors accepting gateway, signer, name, and symbol parameters

# Search for constructor definitions in Connected and Universal contracts
ast-grep --pattern 'constructor($$$) {
  $$$
}'

Length of output: 49


Script:

#!/bin/bash
# Let's try a different approach to find the constructor definitions
# First, let's locate the NFT contract files
fd -e sol | while read -r file; do
  echo "=== $file ==="
  # Search for constructor definitions with surrounding context
  rg -U "constructor.*?\{" -A 5 "$file"
done

Length of output: 3320


Script:

#!/bin/bash
# Let's specifically examine the NFT contract files content
echo "=== Connected.sol ==="
cat examples/nft/contracts/Connected.sol

echo -e "\n=== Universal.sol ==="
cat examples/nft/contracts/Universal.sol

Length of output: 10321

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

28-31: Constructor signature change improves contract flexibility

The updated constructor parameters allow for dynamic NFT naming and symbol assignment, which is a good practice for reusable contracts. The change from initialOwner to owner better aligns with OpenZeppelin's Ownable pattern.

Let's verify if there are any direct contract deployments that need updating:

✅ Verification successful

Let me search for any references to the Connected contract to ensure we don't miss any deployments or tests that might need updating.


Let me examine the deployment script and test files in the NFT example directory to verify how the Connected contract is being deployed.


Constructor changes are properly supported in deployment scripts

The deployment script in examples/nft/tasks/deploy.ts already handles the updated constructor signature correctly, passing all required parameters including gateway, owner (as signer.address), nftName, and nftSymbol. The script provides default values and optional parameters for all constructor arguments, maintaining backward compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct contract deployments that might need updates
rg -A 5 "new Connected\(" 

Length of output: 27


Script:

#!/bin/bash
# Search for contract deployments and tests using different patterns
rg -l "Connected" --type ts --type js --type sol

# Search for deploy scripts or test files
fd -e ts -e js -e sol -E "node_modules" -E "dist" -E "build" | rg -i "deploy|test"

# Look for contract factory patterns
rg -A 3 "ContractFactory.*Connected" 

Length of output: 497


Script:

#!/bin/bash
# Check the deploy script content for NFT example
cat examples/nft/tasks/deploy.ts

# Look for any test files specific to NFT example
fd -e ts -e js "test" examples/nft/

Length of output: 1608

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

Line range hint 1-238: Verify cross-chain compatibility.

Please ensure that the contract has been tested with all supported destination chains and their respective gas requirements.

examples/nft/scripts/test.sh Outdated Show resolved Hide resolved
@fadeev
Copy link
Member Author

fadeev commented Nov 7, 2024

@andresaiello moved gas limit into the constructor, but something tells me for production you'd want different gas limits for each connected chain 🤔 so having a mapping and a way to update these values.

@fadeev
Copy link
Member Author

fadeev commented Nov 7, 2024

@coderabbitai review

Copy link

coderabbitai bot commented Nov 7, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@fadeev fadeev marked this pull request as ready for review November 7, 2024 09:55
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: 8

🧹 Outside diff range and nitpick comments (15)
examples/nft/tsconfig.json (1)

3-6: Consider enhancing type checking configuration.

While the path configuration is solid, you might want to add "exactOptionalPropertyTypes": true to strengthen type checking for optional properties in your NFT-related interfaces.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    },
+   "exactOptionalPropertyTypes": true,
    "module": "nodenext",
examples/nft/contracts/shared/Events.sol (1)

1-2: Consider using a more flexible Solidity version range.

The current pragma ^0.8.26 is very specific. For better compatibility and maintenance, consider using a more flexible version range while maintaining security.

-pragma solidity ^0.8.26;
+pragma solidity ^0.8.0;
examples/nft/tasks/connectedSetCounterparty.ts (2)

13-16: Enhance contract instantiation with constants and error handling

While the type safety improvement is good, consider these production-ready enhancements:

+const CONNECTED_CONTRACT_NAME = "Connected" as const;
+
 const main = async (args: any, hre: HardhatRuntimeEnvironment) => {
   // ... existing code ...
-  const contract: Connected = await hre.ethers.getContractAt(
-    "Connected",
-    args.contract
-  );
+  const contract = await hre.ethers.getContractAt(
+    CONNECTED_CONTRACT_NAME,
+    args.contract
+  ).then((contract): Connected => {
+    if (!contract) throw new Error(`Failed to get contract at ${args.contract}`);
+    return contract as Connected;
+  });

This refactor:

  1. Eliminates magic strings
  2. Adds explicit error handling for contract instantiation
  3. Maintains type safety while being more explicit

5-5: Improve type safety and input validation

Consider these production-critical improvements:

  1. Define proper types for task arguments:
interface ConnectedSetCounterpartyArgs {
  contract: string;
  counterparty: string;
  json?: boolean;
}
  1. Add address validation:
const main = async (args: ConnectedSetCounterpartyArgs, hre: HardhatRuntimeEnvironment) => {
  if (!hre.ethers.utils.isAddress(args.contract)) {
    throw new Error(`Invalid contract address: ${args.contract}`);
  }
  if (!hre.ethers.utils.isAddress(args.counterparty)) {
    throw new Error(`Invalid counterparty address: ${args.counterparty}`);
  }
  // ... rest of the code
};
  1. Consider adding gas estimation and management:
const tx = await contract.setCounterparty(args.counterparty, {
  gasLimit: await contract.estimateGas.setCounterparty(args.counterparty).then(limit => 
    limit.mul(120).div(100) // Add 20% buffer
  )
});
examples/nft/tasks/universalSetCounterparty.ts (1)

Line range hint 33-33: Fix inconsistent parameter reference in console output

The console output references args.contractAddress but the parameter is named args.counterparty in the task definition. This inconsistency could confuse users.

Apply this fix:

-🔗 Connected contract address: ${args.contractAddress}
+🔗 Connected contract address: ${args.counterparty}
examples/nft/tasks/mint.ts (1)

19-21: Enhance transaction error handling and add gas estimation.

For a production NFT system, consider adding proper error handling and gas estimation to prevent failed transactions and improve user experience.

-  const tx = await contract.safeMint(recipient, args.tokenUri);
-  const receipt = await tx.wait();
+  try {
+    const gasEstimate = await contract.safeMint.estimateGas(recipient, args.tokenUri);
+    const tx = await contract.safeMint(recipient, args.tokenUri, {
+      gasLimit: Math.ceil(gasEstimate * 1.2), // Add 20% buffer
+    });
+    const receipt = await tx.wait();
+    if (!receipt.status) {
+      throw new Error('Transaction failed');
+    }
+  } catch (error) {
+    throw new Error(`Failed to mint NFT: ${error.message}`);
+  }
examples/nft/tasks/transfer.ts (3)

64-64: Consider adding TypeScript type information in the parameter description.

The receiver parameter is well-documented, but adding type information would improve developer experience.

-.addOptionalParam("receiver", "The address to receive the NFT")
+.addOptionalParam("receiver", "The address to receive the NFT (type: string, format: hex address)")

Line range hint 41-41: Add address validation for the receiver parameter.

The receiver address should be validated before making contract calls to prevent failed transactions.

-const receiver = args.receiver || signer.address;
+const receiver = args.receiver || signer.address;
+if (receiver && !ethers.utils.isAddress(receiver)) {
+  throw new Error(`Invalid receiver address format: ${receiver}`);
+}

Line range hint 15-38: Refactor for better separation of concerns and error handling.

The current implementation mixes contract detection, gas calculations, and approvals in a single try-catch block. Consider restructuring for better maintainability and error handling.

Consider splitting into separate functions:

async function detectContractType(address: string, ethers: any) {
  try {
    const contract = await ethers.getContractAt("Universal", address);
    await contract.isUniversal();
    return { type: "Universal", contract };
  } catch {
    const contract = await ethers.getContractAt("Connected", address);
    return { type: "Connected", contract };
  }
}

async function handleGasApproval(
  contract: any,
  zrc20Address: string,
  gasLimit: any,
  signer: any,
  txOptions: any
) {
  const zrc20 = new ethers.Contract(zrc20Address, ZRC20ABI.abi, signer);
  const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit);
  const zrc20TransferTx = await zrc20.approve(contract.address, gasFee, txOptions);
  await zrc20TransferTx.wait();
}
examples/nft/scripts/test.sh (1)

28-32: Improve contract deployment configuration and verification

The hardcoded gas limit of 1000000 should be configurable, and deployments should be verified.

Consider:

  1. Moving the gas limit to a configuration file
  2. Adding deployment verification
  3. Adding retry logic for failed deployments

Example configuration approach:

+# Add to config.sh
+declare -A CHAIN_GAS_LIMITS=(
+    ["zetachain"]=1000000
+    ["ethereum"]=2000000
+    ["bnb"]=2000000
+)
+
+function verify_deployment() {
+    local contract=$1
+    local chain=$2
+    
+    if ! cast code "$contract" &>/dev/null; then
+        echo "Error: Failed to verify contract deployment on $chain"
+        return 1
+    fi
+}
examples/nft/contracts/Connected.sol (3)

24-25: LGTM: Gas-efficient custom errors.

Using custom errors instead of string messages is gas-efficient. Consider adding error parameters to provide more context about which address or operation was invalid.

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

50-56: Consider using a more robust token ID generation mechanism.

While the current implementation is simple, it could be vulnerable to front-running. Consider using a deterministic approach based on sender address and nonce, or implementing a queue-based system.

-uint256 tokenId = _nextTokenId++;
+uint256 tokenId = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, _nextTokenId++)));

92-104: Enhance error handling in callback functions.

Consider providing more context in error messages and validating the decoded parameters.

 function onCall(
     MessageContext calldata context,
     bytes calldata message
 ) external payable onlyGateway returns (bytes4) {
-    if (context.sender != counterparty) revert Unauthorized();
+    if (context.sender != counterparty) revert Unauthorized(context.sender, counterparty);
     
     (uint256 tokenId, address receiver, string memory uri) = abi.decode(
         message,
         (uint256, address, string)
     );
+    if (receiver == address(0)) revert InvalidAddress(receiver, "Invalid receiver in cross-chain callback");

Also applies to: 108-116

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

69-69: Consider adding gas limit validation in transferCrossChain.

While the function includes proper address validation and event emission, it should validate that the current gasLimit is sufficient for the target chain.

Consider implementing a chain-specific gas limit mapping that can be updated by the owner:

mapping(address => uint256) public chainGasLimits;

function setChainGasLimit(address destination, uint256 limit) external onlyOwner {
    if (limit == 0) revert("Invalid gas limit");
    chainGasLimits[destination] = limit;
}

Also applies to: 80-80, 100-100


135-135: Add message format validation in onCall and onRevert.

While the functions properly emit events and use dynamic gas limits, they should validate the format of incoming messages to prevent potential issues with malformed data.

Consider adding try-catch blocks around message decoding:

function onCall(...) external override onlyGateway {
    try abi.decode(message, (uint256, address, string, address)) returns (
        uint256 tokenId,
        address sender,
        string memory uri,
        address destination
    ) {
        // existing logic
    } catch {
        revert("Invalid message format");
    }
}

Also applies to: 138-138, 154-154, 169-169

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 57bc55a and c779848.

📒 Files selected for processing (10)
  • examples/nft/contracts/Connected.sol (2 hunks)
  • examples/nft/contracts/Universal.sol (6 hunks)
  • examples/nft/contracts/shared/Events.sol (1 hunks)
  • examples/nft/scripts/test.sh (3 hunks)
  • examples/nft/tasks/connectedSetCounterparty.ts (2 hunks)
  • examples/nft/tasks/deploy.ts (3 hunks)
  • examples/nft/tasks/mint.ts (1 hunks)
  • examples/nft/tasks/transfer.ts (1 hunks)
  • examples/nft/tasks/universalSetCounterparty.ts (2 hunks)
  • examples/nft/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/nft/tasks/deploy.ts
🧰 Additional context used
🪛 Shellcheck
examples/nft/scripts/test.sh

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

(SC2155)


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

(SC2155)


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

(SC2155)

🔇 Additional comments (9)
examples/nft/tsconfig.json (1)

3-6: Path aliasing configuration looks good.

The addition of path aliasing with @/* will improve import readability and maintainability. However, let's verify that all build tools support this configuration.

✅ Verification successful

Path alias configuration is correctly integrated and in use

The path alias configuration is properly set up and already being utilized in the codebase:

  • Found active usage of the @/* alias in NFT tasks (e.g., import { Universal } from "@/typechain-types")
  • The configuration is compatible with the project's TypeScript and build setup
  • No conflicting path configurations found in other build tools
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if any existing imports need to be updated to use the new alias
# and verify build tool configurations support the path alias

# Check for relative imports that could be simplified
rg -g '*.ts' -g '*.tsx' '\.\./\.\.' 

# Check build tool configs for path alias support
fd -e json -e js -e yaml -e yml -x grep -l "paths\|alias"

Length of output: 148


Script:

#!/bin/bash
# Let's check the actual content of the build configuration files
# and look for any existing imports in TypeScript files

# Check content of build configs
echo "=== tsconfig.json ==="
cat examples/nft/tsconfig.json

echo -e "\n=== slither.config.json ==="
cat slither.config.json

# Look for any imports in TypeScript files
echo -e "\n=== TypeScript imports ==="
rg -g '*.ts' -g '*.tsx' '^import .+ from' -A 1

Length of output: 8802

examples/nft/contracts/shared/Events.sol (1)

7-12: Verify the necessity of duplicate transfer events.

The TokenTransfer and TokenTransferToDestination events appear to capture very similar information. This might lead to confusion in event handling and unnecessary gas costs.

Let's check how these events are used in the codebase:

Also applies to: 24-29

examples/nft/tasks/connectedSetCounterparty.ts (1)

3-3: LGTM: Type-safe import addition

The addition of the Connected type import enhances type safety and compiler checks.

examples/nft/tasks/universalSetCounterparty.ts (1)

3-3: Excellent type safety improvements!

The addition of explicit typing through the Universal import and contract type annotation enhances code reliability and IDE support.

Also applies to: 13-16

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

11-12: LGTM: Clean separation of events into a shared contract.

The separation of events into a shared contract promotes code reusability and maintains a single source of truth for event definitions across related contracts.

Also applies to: 13-19


28-30: LGTM: Proper validation and event emission.

The function correctly validates the input and emits an event for off-chain tracking.


64-88: 🛠️ Refactor suggestion

Enhance cross-chain transfer validation and gas handling.

The function should validate msg.value against minimum required gas and handle chain-specific gas limits.

 function transferCrossChain(
     uint256 tokenId,
     address receiver,
     address destination
 ) external payable {
     if (receiver == address(0)) revert InvalidAddress();
+    uint256 requiredGas = destination == address(0) ? 
+        _defaultGasLimit : 
+        _chainGasLimits[block.chainid] != 0 ? _chainGasLimits[block.chainid] : _defaultGasLimit;
+    if (msg.value < requiredGas) revert("Insufficient gas");
examples/nft/contracts/Universal.sol (2)

15-15: LGTM: Clean integration of Events contract.

The Events contract integration follows proper inheritance patterns and import organization.

Also applies to: 22-23


30-30: LGTM: Well-structured state variable and error definitions.

The gasLimit variable and custom errors enhance contract maintainability and gas efficiency. Custom errors are preferred over require statements for gas optimization.

Also applies to: 33-34

examples/nft/contracts/shared/Events.sol Show resolved Hide resolved
examples/nft/tasks/mint.ts Show resolved Hide resolved
examples/nft/tasks/mint.ts Show resolved Hide resolved
examples/nft/scripts/test.sh Show resolved Hide resolved
examples/nft/scripts/test.sh Show resolved Hide resolved
examples/nft/scripts/test.sh Show resolved Hide resolved
examples/nft/contracts/Connected.sol Show resolved Hide resolved
examples/nft/contracts/Universal.sol Outdated Show resolved Hide resolved
@fadeev fadeev changed the title NFT: fixes NFT: improvements to make production-ready Nov 7, 2024
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

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

24-25: Consider enhancing error debugging capabilities.

While the custom errors are gas-efficient, consider adding parameters to provide more context during failures.

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

39-46: Enhance address validation specificity.

While the address validation is good, consider separating the checks to provide more specific error context.

-if (gatewayAddress == address(0) || owner == address(0))
-    revert InvalidAddress();
+if (gatewayAddress == address(0)) revert InvalidAddress(gatewayAddress);
+if (owner == address(0)) revert InvalidAddress(owner);

27-30: Improve setCounterparty validation and event emission.

Consider adding the old value to the event for better traceability.

 function setCounterparty(address contractAddress) external onlyOwner {
     if (contractAddress == address(0)) revert InvalidAddress();
+    address oldCounterparty = counterparty;
     counterparty = contractAddress;
-    emit SetCounterparty(contractAddress);
+    emit SetCounterparty(oldCounterparty, contractAddress);
 }
examples/nft/contracts/Universal.sol (2)

69-69: Cross-chain transfer security improvements noted.

The addition of zero-address validation for the receiver enhances security. Consider adding validation for the destination address as well.

     function transferCrossChain(
         uint256 tokenId,
         address receiver,
         address destination
     ) public {
         if (receiver == address(0)) revert InvalidAddress();
+        if (destination == address(0)) revert InvalidAddress();
         string memory uri = tokenURI(tokenId);

Also applies to: 80-80, 88-88, 95-95


138-138: Verify gas limit usage across chains.

The dynamic gas limit is now used consistently across the contract, but consider implementing chain-specific gas limits as suggested in the PR comments.

Consider implementing a mapping for chain-specific gas limits:

mapping(address => uint256) public chainGasLimits;

function setChainGasLimit(address chain, uint256 limit) external onlyOwner {
    if (limit < MIN_GAS_LIMIT || limit > MAX_GAS_LIMIT) revert InvalidGasLimit();
    chainGasLimits[chain] = limit;
}

Also applies to: 154-154

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c779848 and b77396f.

📒 Files selected for processing (2)
  • examples/nft/contracts/Connected.sol (2 hunks)
  • examples/nft/contracts/Universal.sol (6 hunks)
🔇 Additional comments (5)
examples/nft/contracts/Connected.sol (1)

8-8: Excellent security improvement with Ownable2Step!

The switch from Ownable to Ownable2Step enhances security by implementing a two-step ownership transfer process. The addition of the Events contract and proper inheritance structure provides better event handling capabilities.

Also applies to: 11-11, 13-19

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

8-8: Inheritance structure improvements noted.

The switch to Ownable2Step from Ownable enhances security by requiring a two-step process for ownership transfers. The addition of the Events contract promotes better event organization.

Also applies to: 15-15, 21-23


39-39: Improved gateway authorization check.

The change from require to a custom error improves gas efficiency and provides better error reporting.


44-53: Constructor validation can be enhanced.

While the constructor properly validates addresses and initializes parameters, it should also validate the gas limit parameter.


100-100: Event emissions enhance transparency.

The addition of events for token transfers, receipts, and reverts improves the contract's observability and enables better tracking of cross-chain operations.

Also applies to: 135-135, 157-157, 169-169

examples/nft/contracts/Connected.sol Show resolved Hide resolved
examples/nft/contracts/Connected.sol Show resolved Hide resolved
examples/nft/contracts/Universal.sol Show resolved Hide resolved
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: 1

🧹 Outside diff range and nitpick comments (2)
examples/nft/contracts/Universal.sol (2)

45-55: Consider additional parameter validation.

While address and gas validation are implemented, consider adding validation for name and symbol parameters to prevent empty strings.

 constructor(
     address payable gatewayAddress,
     address owner,
     string memory name,
     string memory symbol,
     uint256 gas
 ) ERC721(name, symbol) Ownable(owner) {
     if (gatewayAddress == address(0) || owner == address(0))
         revert InvalidAddress();
     if (gas == 0) revert InvalidGasLimit();
+    if (bytes(name).length == 0 || bytes(symbol).length == 0)
+        revert("Invalid name or symbol");
     gateway = GatewayZEVM(gatewayAddress);
     gasLimit = gas;
 }

71-71: Good improvements to cross-chain transfer logic!

The address validation and event emission enhance security and traceability. Consider adding validation for the destination address as well.

 function transferCrossChain(
     uint256 tokenId,
     address receiver,
     address destination
 ) public {
     if (receiver == address(0)) revert InvalidAddress();
+    if (destination == address(0)) revert InvalidAddress();
     string memory uri = tokenURI(tokenId);

Also applies to: 82-82, 90-90, 97-97, 102-102

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b77396f and 6ba78a0.

📒 Files selected for processing (1)
  • examples/nft/contracts/Universal.sol (6 hunks)
🔇 Additional comments (4)
examples/nft/contracts/Universal.sol (4)

8-8: Excellent security and architectural improvements!

The switch to Ownable2Step enhances security by requiring a two-step process for ownership transfers, and the separation of events into a dedicated contract improves modularity.

Also applies to: 15-15, 21-23


30-35: Well-structured error handling implementation!

The custom errors improve gas efficiency and provide clear error messages compared to require statements with strings.


40-40: Improved modifier implementation!

The use of custom errors in the modifier is more gas-efficient than string-based require statements.


171-171: Good addition of event emission!

The event emission for reverted transfers improves transaction traceability.

@@ -121,9 +134,10 @@ contract Universal is
if (destination == address(0)) {
_safeMint(sender, tokenId);
_setTokenURI(tokenId, uri);
emit TokenTransferReceived(tokenId, sender, 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

Reentrancy protection is recommended for external calls

The onCall function in Universal.sol contains external calls through gateway.call after state modifications (_safeMint, _setTokenURI). While the function is protected by onlyGateway modifier, implementing the following safeguards is recommended:

  • Add OpenZeppelin's ReentrancyGuard and use nonReentrant modifier
  • Alternatively, strictly follow checks-effects-interactions pattern by:
    • Moving the TokenTransferReceived event emission after all state changes
    • Ensuring gateway.call is the last operation in all execution paths

Example implementation:

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

contract Universal is ..., ReentrancyGuard {
    function onCall(...) external override onlyGateway nonReentrant {
        // existing implementation
    }
}
🔗 Analysis chain

Consider reentrancy protection for external calls.

The function makes external calls to swapExactTokensForTokens and gateway.call. Consider implementing a reentrancy guard or ensuring proper checks-effects-interactions pattern.

Also applies to: 140-140, 156-156, 159-159

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for ReentrancyGuard usage in the codebase
ast-grep --pattern 'contract $_ is $$$, ReentrancyGuard, $$$ {'

Length of output: 65


Script:

#!/bin/bash
# Let's check for ReentrancyGuard imports and usage with different patterns
rg -i "reentrancy|ReentrancyGuard" -A 3

# Check the specific onCall function implementation
ast-grep --pattern 'function onCall($$$) {
  $$$
}'

# Check for nonReentrant modifier usage
ast-grep --pattern 'nonReentrant'

# Look for the gateway.call pattern to understand the external call context
rg "gateway.*call" -A 3

Length of output: 1514


Script:

#!/bin/bash
# Let's examine the full context of the onCall function to understand the control flow
rg "function onCall" -A 20

# Check for existing modifiers on the contract and function
ast-grep --pattern 'function onCall($$$) $_ {
  $$$
}'

# Look for existing checks-effects-interactions pattern
rg "require\(.*\)" -A 2 examples/nft/contracts/Universal.sol

Length of output: 7522

@fadeev fadeev merged commit 9a852e6 into main Nov 8, 2024
11 checks passed
@fadeev fadeev deleted the nft-fixes branch November 8, 2024 07:21
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.

NFT: move name and description into the constructor NFT: make gas customizable
2 participants