Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: NFT and FT upgradeable using OpenZeppelin's UUPS #6

Merged
merged 32 commits into from
Dec 23, 2024
Merged

Conversation

fadeev
Copy link
Member

@fadeev fadeev commented Dec 3, 2024

  • Make contract upgradeable
  • Rename contracts to EVMUniversalNFT and ZetaChainUniversalNFT so that they have unique names (useful for ThirdWeb)
  • Use ZETA for gas when transferring a token from ZetaChain to a connected chain. Before, a user actually had to have ZRC-20 of gas of the destination chain, which was bad UX

Summary by CodeRabbit

  • New Features

    • Introduced new upgradeable NFT contracts: EVMUniversalNFT and ZetaChainUniversalNFT.
    • Added new token contracts: EVMUniversalToken and ZetaChainUniversalToken.
    • New functions for setting gas limits and authorizing upgrades in contracts.
  • Bug Fixes

    • Enhanced error handling for Ethereum address validations across various tasks.
  • Documentation

    • Updated task names and parameters to reflect new contract structures and functionalities.
  • Chores

    • Consolidated import statements in configuration files for improved clarity.
    • Updated deployment scripts for better error handling and contract naming conventions.

@fadeev

This comment was marked as resolved.

@fadeev fadeev changed the title upgradeable feat: upgradeable Dec 3, 2024
@fadeev

This comment was marked as resolved.

@fadeev fadeev linked an issue Dec 4, 2024 that may be closed by this pull request
@fadeev

This comment was marked as resolved.

@fadeev fadeev changed the base branch from main to v2.0.0 December 12, 2024 08:56
@fadeev fadeev changed the title feat: upgradeable feat: NFT and FT upgradeable using OpenZeppelin's UUPS Dec 12, 2024
@fadeev fadeev marked this pull request as ready for review December 12, 2024 08:59
@fadeev
Copy link
Member Author

fadeev commented Dec 12, 2024

@andresaiello @skosito please, review.

@fadeev fadeev changed the base branch from v2.0.0 to main December 18, 2024 08:08
@fadeev fadeev requested a review from 0xM3R December 18, 2024 08:14
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 comments (2)
contracts/nft/contracts/evm/UniversalNFT.sol (1)

Line range hint 77-91: Enhance tokenId generation security.

The current tokenId generation method using block.number could be predictable. Consider using a more secure random number generation method.

     function safeMint(address to, string memory uri) public onlyOwner {
         uint256 hash = uint256(
             keccak256(
-                abi.encodePacked(address(this), block.number, _nextTokenId++)
+                abi.encodePacked(
+                    address(this),
+                    block.prevrandao,
+                    block.timestamp,
+                    _nextTokenId++
+                )
             )
         );
contracts/nft/contracts/zetachain/UniversalNFT.sol (1)

Line range hint 169-209: Add input validation for amount parameter.

The onCall function should validate that the amount parameter is non-zero before proceeding with the swap.

     function onCall(
         MessageContext calldata context,
         address zrc20,
         uint256 amount,
         bytes calldata message
     ) external override onlyGateway {
+        if (amount == 0) revert("Zero amount");
         if (context.sender != connected[zrc20]) revert Unauthorized();
♻️ Duplicate comments (2)
contracts/token/contracts/zetachain/UniversalToken.sol (1)

29-32: ⚠️ Potential issue

Remove unused state variable _nextTokenId.

The _nextTokenId variable appears to be unused in this contract and should be removed to optimize gas costs.

contracts/token/contracts/evm/UniversalToken.sol (1)

139-141: 🛠️ Refactor suggestion

Enhance upgrade security with timelock mechanism.

The current upgrade mechanism could be enhanced with a timelock to provide users time to review and react to pending upgrades.

🧹 Nitpick comments (5)
contracts/token/contracts/zetachain/UniversalToken.sol (3)

73-76: Add event emission for gas limit changes.

Consider emitting an event when the gas limit is updated to maintain transparency and enable off-chain tracking of important state changes.

 function setGasLimit(uint256 gas) external onlyOwner {
     if (gas == 0) revert InvalidGasLimit();
     gasLimitAmount = gas;
+    emit GasLimitUpdated(gas);
 }

90-119: Consider extracting gas fee handling logic.

The transferCrossChain function handles multiple concerns. Consider extracting the gas fee handling logic into a separate internal function for better maintainability and readability.

+    function _handleGasFeePayment(address destination) internal returns (uint256) {
+        (address gasZRC20, uint256 gasFee) = IZRC20(destination)
+            .withdrawGasFeeWithGasLimit(gasLimitAmount);
+        if (destination != gasZRC20) revert InvalidAddress();
+
+        address WZETA = gateway.zetaToken();
+        IWETH9(WZETA).deposit{value: msg.value}();
+        IWETH9(WZETA).approve(uniswapRouter, msg.value);
+
+        uint256 out = SwapHelperLib.swapTokensForExactTokens(
+            uniswapRouter,
+            WZETA,
+            gasFee,
+            gasZRC20,
+            msg.value
+        );
+
+        uint256 remaining = msg.value - out;
+        if (remaining > 0) {
+            IWETH9(WZETA).withdraw(remaining);
+            (bool success, ) = msg.sender.call{value: remaining}("");
+            if (!success) revert TransferFailed();
+        }
+        return gasFee;
+    }

203-207: Add NatSpec documentation for upgrade authorization.

The empty _authorizeUpgrade function should include NatSpec documentation explaining that only the owner can upgrade the contract and why the function body is intentionally empty.

+    /// @notice Function that should revert when `msg.sender` is not authorized to upgrade the contract.
+    /// @dev This function is called by {upgradeTo} and {upgradeToAndCall}. Empty implementation as access
+    /// control is handled by the `onlyOwner` modifier.
+    /// @param newImplementation Address of the new implementation contract
     function _authorizeUpgrade(
         address newImplementation
     ) internal override onlyOwner {}
contracts/nft/contracts/evm/UniversalNFT.sol (1)

47-64: Consider emitting an initialization event.

For better transparency and tracking, consider emitting an event after successful initialization.

+    event NFTInitialized(address indexed owner, string name, string symbol);

     function initialize(
         address initialOwner,
         string memory name,
         string memory symbol,
         address payable gatewayAddress,
         uint256 gas
     ) public initializer {
         __ERC721_init(name, symbol);
         __ERC721Enumerable_init();
         __ERC721URIStorage_init();
         __Ownable_init(initialOwner);
         __ERC721Burnable_init();
         __UUPSUpgradeable_init();
         if (gatewayAddress == address(0)) revert InvalidAddress();
         if (gas == 0) revert InvalidGasLimit();
         gasLimitAmount = gas;
         gateway = GatewayEVM(gatewayAddress);
+        emit NFTInitialized(initialOwner, name, symbol);
     }
contracts/nft/contracts/zetachain/UniversalNFT.sol (1)

94-126: Refactor complex transfer logic into smaller functions.

The transferCrossChain function handles multiple responsibilities. Consider splitting it into smaller, more focused functions for better maintainability and testing.

+    function _handleGasToken(uint256 msgValue, address gasZRC20, uint256 gasFee) private returns (uint256) {
+        address WZETA = gateway.zetaToken();
+        IWETH9(WZETA).deposit{value: msgValue}();
+        IWETH9(WZETA).approve(uniswapRouter, msgValue);
+        
+        return SwapHelperLib.swapTokensForExactTokens(
+            uniswapRouter,
+            WZETA,
+            gasFee,
+            gasZRC20,
+            msgValue
+        );
+    }

     function transferCrossChain(
         uint256 tokenId,
         address receiver,
         address destination
     ) public payable {
         if (msg.value == 0) revert ZeroMsgValue();
         if (receiver == address(0)) revert InvalidAddress();
         string memory uri = tokenURI(tokenId);
         _burn(tokenId);

         (address gasZRC20, uint256 gasFee) = IZRC20(destination)
             .withdrawGasFeeWithGasLimit(gasLimitAmount);
         if (destination != gasZRC20) revert InvalidAddress();

-        address WZETA = gateway.zetaToken();
-        IWETH9(WZETA).deposit{value: msg.value}();
-        IWETH9(WZETA).approve(uniswapRouter, msg.value);
-
-        uint256 out = SwapHelperLib.swapTokensForExactTokens(
-            uniswapRouter,
-            WZETA,
-            gasFee,
-            gasZRC20,
-            msg.value
-        );
+        uint256 out = _handleGasToken(msg.value, gasZRC20, gasFee);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76b5c25 and 9d9834e.

📒 Files selected for processing (8)
  • contracts/nft/contracts/evm/UniversalNFT.sol (3 hunks)
  • contracts/nft/contracts/shared/UniversalNFTEvents.sol (1 hunks)
  • contracts/nft/contracts/zetachain/UniversalNFT.sol (7 hunks)
  • contracts/nft/tasks/mint.ts (3 hunks)
  • contracts/token/contracts/evm/UniversalToken.sol (3 hunks)
  • contracts/token/contracts/shared/UniversalTokenEvents.sol (1 hunks)
  • contracts/token/contracts/zetachain/UniversalToken.sol (6 hunks)
  • contracts/token/tasks/mint.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • contracts/token/contracts/shared/UniversalTokenEvents.sol
  • contracts/nft/contracts/shared/UniversalNFTEvents.sol
🚧 Files skipped from review as they are similar to previous changes (2)
  • contracts/nft/tasks/mint.ts
  • contracts/token/tasks/mint.ts
🔇 Additional comments (2)
contracts/token/contracts/zetachain/UniversalToken.sol (2)

Line range hint 53-71: LGTM: Initialization pattern follows UUPS best practices.

The initialize function correctly:

  • Initializes all inherited contracts in the proper order
  • Validates critical parameters
  • Sets up the initial state

10-25: LGTM: Inheritance chain follows OpenZeppelin's recommendations.

The contract correctly inherits from upgradeable base contracts in the recommended order: Initializable first, followed by the upgradeable variants of ERC20, ERC20Burnable, Ownable, and UUPS.

✅ Verification successful

Inheritance chain follows OpenZeppelin's best practices

The codebase consistently follows OpenZeppelin's recommended inheritance patterns for upgradeable contracts. Both UniversalToken and UniversalNFT contracts:

  • Correctly list Initializable as the first inherited contract
  • Properly order the OpenZeppelin upgradeable contracts
  • Maintain consistent UUPS upgrade pattern implementation

The inheritance structure is uniform across all contract variants (ZetaChain, EVM) and follows production-grade practices for proxy-based upgradeability.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify no other inheritance patterns exist in the codebase
rg "contract.*is.*Initializable" --type sol

Length of output: 73


Script:

#!/bin/bash
# Search for contract declarations with Initializable inheritance
rg "contract.*is.*Initializable"

# Also check for any other UUPS upgrade patterns
rg "contract.*is.*UUPSUpgradeable"

Length of output: 71


Script:

#!/bin/bash
# First, find all Solidity files
fd -e sol -t f -x cat {} \; | grep -A 5 "contract.*is"

# Also check for specific imports of upgradeable contracts
fd -e sol -t f -x cat {} \; | grep -B 2 -A 2 "import.*Upgradeable"

Length of output: 5002

Comment on lines 14 to 21
contract UniversalToken is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable,
UniversalTokenEvents
{
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding ReentrancyGuard for enhanced security.

The contract handles value transfers and should implement ReentrancyGuardUpgradeable to prevent potential reentrancy attacks.

+import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

 contract UniversalToken is
     Initializable,
     ERC20Upgradeable,
     ERC20BurnableUpgradeable,
     OwnableUpgradeable,
     UUPSUpgradeable,
+    ReentrancyGuardUpgradeable,
     UniversalTokenEvents
📝 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
contract UniversalToken is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable,
UniversalTokenEvents
{
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract UniversalToken is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable,
ReentrancyGuardUpgradeable,
UniversalTokenEvents
{

Comment on lines +41 to 56
function initialize(
address initialOwner,
string memory name,
string memory symbol,
address payable gatewayAddress,
uint256 gas
) public initializer {
__ERC20_init(name, symbol);
__ERC20Burnable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
if (gatewayAddress == address(0)) revert InvalidAddress();
gasLimitAmount = gas;
gateway = GatewayEVM(gatewayAddress);
}

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 gas validation in initialize function.

The gas parameter should be validated to prevent setting an invalid gas limit during initialization.

     function initialize(
         address initialOwner,
         string memory name,
         string memory symbol,
         address payable gatewayAddress,
         uint256 gas
     ) public initializer {
         __ERC20_init(name, symbol);
         __ERC20Burnable_init();
         __Ownable_init(initialOwner);
         __UUPSUpgradeable_init();
         if (gatewayAddress == address(0)) revert InvalidAddress();
+        if (gas == 0) revert InvalidGasLimit();
         gasLimitAmount = gas;
         gateway = GatewayEVM(gatewayAddress);
     }

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

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

🧹 Nitpick comments (1)
contracts/token/tasks/mint.ts (1)

45-49: Update parameter documentation to reflect specific contract types.

The name parameter description should clearly indicate the supported contract types as per the PR changes.

Consider updating the parameter documentation:

   .addOptionalParam(
     "name",
-    "The contract name to interact with",
+    "The contract name to interact with (ZetaChainUniversalToken or EVMUniversalToken)",
     "ZetaChainUniversalToken"
   )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9834e and 3b9a093.

⛔ Files ignored due to path filters (1)
  • contracts/nft/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • contracts/nft/package.json (2 hunks)
  • contracts/nft/tasks/mint.ts (4 hunks)
  • contracts/token/tasks/mint.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • contracts/nft/tasks/mint.ts
  • contracts/nft/package.json

(event: any) => event.event === "Transfer"
);
const tokenId = transferEvent?.args?.tokenId;
await tx.wait();
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 transaction receipt handling for production reliability.

The current implementation lacks proper error handling and event verification for the mint operation.

Consider implementing this more robust approach:

- await tx.wait();
+ const receipt = await tx.wait();
+ const mintEvent = receipt.events?.find(e => e.event === 'Transfer' && e.args?.from === ethers.constants.AddressZero);
+ if (!mintEvent) {
+   throw new Error('Mint transaction completed but no Transfer event found');
+ }
+ // Verify mint amount matches requested amount
+ if (mintEvent.args?.value.toString() !== args.amount.toString()) {
+   throw new Error(`Minted amount ${mintEvent.args?.value} differs from requested amount ${args.amount}`);
+ }

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

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

🔭 Outside diff range comments (3)
contracts/token/tasks/connectedSetUniversal.ts (1)

Line range hint 24-25: Add gas limit for upgradeable contract interaction

Upgradeable contracts typically require more gas due to proxy delegation. Consider adding a gas limit to prevent transaction failures.

-const tx = await contract.setUniversal(args.universal);
+const tx = await contract.setUniversal(args.universal, {
+  gasLimit: 1000000
+});
contracts/nft/tasks/connectedSetUniversal.ts (1)

Line range hint 35-39: Fix inconsistent variable usage in log message

The log message uses args.universalContract which doesn't exist in the args object.

 console.log(`🚀 Successfully set the universal contract.
 📜 Contract address: ${args.contract}
-🔗 Universal contract address: ${args.universalContract}
+🔗 Universal contract address: ${args.universal}
 🔗 Transaction hash: ${tx.hash}`);
contracts/nft/tasks/universalSetConnected.ts (1)

Line range hint 39-43: Fix inconsistent variable usage in log message

The log message uses args.contractAddress which should be args.connected.

 console.log(`🚀 Successfully set the connected contract.
 📜 Contract address: ${args.contract}
 🔗 ZRC20 address: ${args.zrc20}
-🔗 Connected contract address: ${args.contractAddress}
+🔗 Connected contract address: ${args.connected}
 🔗 Transaction hash: ${tx.hash}`);
♻️ Duplicate comments (1)
contracts/nft/tasks/connectedSetUniversal.ts (1)

18-21: 🛠️ Refactor suggestion

Add proxy contract validation

When working with upgradeable contracts, it's important to verify we're interacting with the proxy contract and not the implementation directly.

 const contract: EVMUniversalNFT = await hre.ethers.getContractAt(
   "EVMUniversalNFT",
   args.contract
 );
+const implementationAddress = await hre.upgrades.erc1967.getImplementationAddress(args.contract);
+if (args.contract === implementationAddress) {
+  throw new Error('Provided address is implementation contract, not proxy');
+}
🧹 Nitpick comments (6)
contracts/token/tasks/universalSetConnected.ts (2)

15-21: Enhance error message specificity for address validation

While the address validation is a good security measure, the error message could be more specific to help users identify which address is invalid.

Consider implementing more granular validation:

-  if (
-    !isAddress(args.contract) ||
-    !isAddress(args.zrc20) ||
-    !isAddress(args.connected)
-  ) {
-    throw new Error("Invalid Ethereum address provided.");
-  }
+  if (!isAddress(args.contract)) {
+    throw new Error("Invalid contract address provided.");
+  }
+  if (!isAddress(args.zrc20)) {
+    throw new Error("Invalid ZRC20 address provided.");
+  }
+  if (!isAddress(args.connected)) {
+    throw new Error("Invalid connected contract address provided.");
+  }

Line range hint 28-45: Consider adding debug logging for contract interactions

While the success logging is comprehensive, consider adding debug logs before the contract interaction to aid in troubleshooting.

Add debug logging before the transaction:

+ console.log(`Attempting to set connected contract...
+ Contract: ${args.contract}
+ ZRC20: ${args.zrc20}
+ Connected: ${args.connected}`);
  const tx = await contract.setConnected(args.zrc20, args.connected);
contracts/token/tasks/transfer.ts (1)

8-12: Enhance error message specificity for address validation

While the address validation is a good addition, the error message could be more specific to help users identify which address is invalid.

Consider this improvement:

-  if (!isAddress(args.to) || !isAddress(args.revertAddress)) {
-    throw new Error("Invalid Ethereum address provided.");
+  if (!isAddress(args.to)) {
+    throw new Error(`Invalid 'to' address provided: ${args.to}`);
+  }
+  if (!isAddress(args.revertAddress)) {
+    throw new Error(`Invalid 'revertAddress' provided: ${args.revertAddress}`);
+  }
contracts/nft/tasks/connectedSetUniversal.ts (1)

23-25: Consider making gas limit configurable

While adding a gas limit is good, hardcoding it might not be optimal for different networks or contract states.

+const DEFAULT_GAS_LIMIT = 1000000;
+
 const tx = await contract.setUniversal(args.universal, {
-  gasLimit: 1000000,
+  gasLimit: args.gasLimit || DEFAULT_GAS_LIMIT,
 });

Update task parameters:

 task("connected-set-universal", "Sets the universal contract address", main)
   .addParam("contract", "The address of the deployed contract")
   .addParam("universal", "The address of the universal contract to set")
+  .addOptionalParam("gasLimit", "Custom gas limit for the transaction")
   .addFlag("json", "Output the result in JSON format");
contracts/token/tasks/deploy.ts (1)

5-6: Enhance error message specificity for address validation.

The address validation is well-implemented, but the error message could be more informative by indicating which address is invalid.

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(
+      `Invalid address provided: ${
+        !isAddress(args.gateway)
+          ? `gateway=${args.gateway}`
+          : `uniswapRouter=${args.uniswapRouter}`
+      }`
+    );

Also applies to: 16-22

contracts/nft/tasks/transfer.ts (1)

8-12: Maintain consistent error message format across files.

For consistency with the token deployment task, enhance the error message specificity.

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(
+      `Invalid address provided: ${
+        !isAddress(args.to)
+          ? `to=${args.to}`
+          : `revertAddress=${args.revertAddress}`
+      }`
+    );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b9a093 and 0b208f1.

📒 Files selected for processing (10)
  • contracts/nft/tasks/connectedSetUniversal.ts (1 hunks)
  • contracts/nft/tasks/deploy.ts (3 hunks)
  • contracts/nft/tasks/mint.ts (3 hunks)
  • contracts/nft/tasks/transfer.ts (3 hunks)
  • contracts/nft/tasks/universalSetConnected.ts (1 hunks)
  • contracts/token/tasks/connectedSetUniversal.ts (1 hunks)
  • contracts/token/tasks/deploy.ts (3 hunks)
  • contracts/token/tasks/mint.ts (3 hunks)
  • contracts/token/tasks/transfer.ts (2 hunks)
  • contracts/token/tasks/universalSetConnected.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • contracts/token/tasks/mint.ts
  • contracts/nft/tasks/deploy.ts
  • contracts/nft/tasks/mint.ts
🔇 Additional comments (10)
contracts/token/tasks/universalSetConnected.ts (2)

3-3: LGTM: Import statement aligns with contract renaming strategy

The updated import reflects the new contract naming convention, maintaining consistency with the broader refactoring effort.


23-25: Verify UUPS implementation at the specified address

The contract retrieval has been updated for the new UUPS pattern. Ensure that the contract at the specified address is indeed a UUPS implementation.

✅ Verification successful

UUPS implementation verified successfully

The contract implementation follows the UUPS pattern correctly:

  • ZetaChainUniversalToken inherits from UniversalToken which implements UUPSUpgradeable
  • Proper initialization pattern with initialize function and disabled initializers in constructor
  • Required _authorizeUpgrade function implementation with owner-only modifier
  • All necessary UUPS-related imports from OpenZeppelin
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the contract at the specified address is a UUPS implementation

# Search for UUPS implementation in the contract
ast-grep --pattern 'contract ZetaChainUniversalToken is UUPSUpgradeable {
  $$$
}'

# Search for initialize function required by UUPS
ast-grep --pattern 'function initialize($$$) {
  $$$
}'

Length of output: 142


Script:

#!/bin/bash
# Let's search for the contract implementation with a broader pattern
rg -l "contract ZetaChainUniversalToken" --type solidity

# Then examine the contract inheritance and implementation
rg "contract ZetaChainUniversalToken" -A 5 --type solidity

# Also search for initialize function in Solidity files
rg "function initialize" -A 5 --type solidity

# Check for UUPS related imports
rg "import.*UUPSUpgradeable" --type solidity

Length of output: 346


Script:

#!/bin/bash
# Search for contract files and implementation
fd -e sol . | xargs cat

# Alternative search for UUPS implementation
rg "contract ZetaChainUniversalToken"

# Search for initialize function
rg "function initialize"

# Search for UUPS imports
rg "import.*UUPSUpgradeable"

Length of output: 30775

contracts/token/tasks/connectedSetUniversal.ts (1)

15-17: LGTM: Address validation implementation

The addition of address validation using isAddress is a good practice for preventing invalid inputs.

contracts/nft/tasks/universalSetConnected.ts (2)

14-21: LGTM: Comprehensive address validation

The implementation properly validates all required addresses: contract, zrc20, and connected addresses.


22-25: 🛠️ Refactor suggestion

Add proxy validation and gas limit

Similar to other tasks, this needs proxy validation and gas limit configuration for upgradeable contracts.

 const contract: ZetaChainUniversalNFT = await hre.ethers.getContractAt(
   "ZetaChainUniversalNFT",
   args.contract
 );
+const implementationAddress = await hre.upgrades.erc1967.getImplementationAddress(args.contract);
+if (args.contract === implementationAddress) {
+  throw new Error('Provided address is implementation contract, not proxy');
+}

-const tx = await contract.setConnected(args.zrc20, args.connected);
+const tx = await contract.setConnected(args.zrc20, args.connected, {
+  gasLimit: args.gasLimit || 1000000
+});

Likely invalid or redundant comment.

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

25-32: Add UUPS-specific proxy deployment options.

The proxy deployment should specify the UUPS implementation type and initializer function.

 const contract = await hre.upgrades.deployProxy(factory, [
   signer.address,
   args.tokenName,
   args.tokenSymbol,
   args.gateway,
   args.gasLimit,
   ...(args.uniswapRouter ? [args.uniswapRouter] : []),
-]);
+], {
+  initializer: 'initialize',
+  kind: 'uups'
+});

54-58: LGTM: Contract name change aligns with PR objectives.

The rename to "ZetaChainUniversalToken" provides a unique identifier, which is beneficial for ThirdWeb integration as mentioned in the PR objectives.

contracts/nft/tasks/transfer.ts (3)

25-28: LGTM: Contract name change maintains consistency.

The rename to "ZetaChainUniversalNFT" maintains consistency with the token contract changes and supports unique identification.


73-73: LGTM: Gas limit aligned with proxy deployment requirements.

The increased gas limit of 10000000 is appropriate for proxy contract interactions.


34-39: Verify gas handling in cross-chain transfer.

The implementation allows ZETA for gas payment as intended, but we should verify the contract handles this correctly.

✅ Verification successful

The previous pattern didn't yield results, so I'm broadening the search to find the contract implementation and any gas-related handling. This will help us verify how the gas payment is processed in the cross-chain transfer.


Gas handling in cross-chain transfer is properly implemented

The contract correctly handles the gas payment mechanism:

  • Validates non-zero msg.value with if (msg.value == 0) revert ZeroMsgValue()
  • Converts ZETA to wrapped ZETA (WZETA) for gas fee processing
  • Performs gas token swap through Uniswap router
  • Handles remaining gas refunds to the sender
  • Includes safety checks with proper revert conditions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for gas handling in the NFT contract
ast-grep --pattern 'function transferCrossChain($_, $_, $_) {
  $$$
  value
  $$$
}'

Length of output: 86


Script:

#!/bin/bash
# Search for transferCrossChain function implementation
rg -U "function transferCrossChain.*?\{.*?\}" -A 20

# Also search for any gas or value handling patterns
rg -U "(msg\.value|gasAmount|value:)" -A 2 -B 2

Length of output: 8475

Comment on lines +19 to +22
const contract = await ethers.getContractAt(
"ZetaChainUniversalToken",
args.from
);
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 contract existence validation

The code assumes the contract exists at the given address. Given the issues mentioned in PR comments about contract not existing at specified addresses, we should add validation.

Add this validation before proceeding with the transfer:

   const contract = await ethers.getContractAt(
     "ZetaChainUniversalToken",
     args.from
   );
+  const code = await ethers.provider.getCode(args.from);
+  if (code === '0x') {
+    throw new Error(`No contract found at address ${args.from}`);
+  }
📝 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 contract = await ethers.getContractAt(
"ZetaChainUniversalToken",
args.from
);
const contract = await ethers.getContractAt(
"ZetaChainUniversalToken",
args.from
);
const code = await ethers.provider.getCode(args.from);
if (code === '0x') {
throw new Error(`No contract found at address ${args.from}`);
}

Comment on lines +93 to 94
.addParam("gasAmount", "The amount of gas to transfer", "0")
.addParam("amount", "The amount of gas to transfer", "0");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update gas limit and improve parameter descriptions

  1. There's an inconsistency in the gas limit value (7000000 in definition vs 10000000 mentioned in PR)
  2. The parameter descriptions for gasAmount and amount are identical and not descriptive enough

Consider these improvements:

   .addOptionalParam(
     "txOptionsGasLimit",
     "The gas limit for the transaction",
-    7000000,
+    10000000,
     types.int
   )
   // ... other params ...
   .addParam("gasAmount", "The amount of gas to transfer", "0")
-  .addParam("amount", "The amount of gas to transfer", "0");
+  .addParam("amount", "The amount of tokens to transfer", "0");

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

Comment on lines +24 to 34
const value = ethers.utils.parseUnits(args.amount, 18);
const tokenApprove = await contract.approve(args.from, value);
await tokenApprove.wait();

const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);

const receiver = args.receiver || signer.address;

tx = await (contract as any).transferCrossChain(
const tx = await (contract as any).transferCrossChain(
args.to,
receiver,
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 error handling and add decimal validation

The current implementation has several potential issues:

  1. The approval transaction could fail silently
  2. The gas amount parsing assumes 18 decimals
  3. No error handling for the transfer operation

Consider these improvements:

+  const decimals = await contract.decimals().catch(() => 18);
   const value = ethers.utils.parseUnits(args.amount, decimals);
-  const tokenApprove = await contract.approve(args.from, value);
-  await tokenApprove.wait();
+  try {
+    const tokenApprove = await contract.approve(args.from, value);
+    await tokenApprove.wait();
+  } catch (error) {
+    throw new Error(`Token approval failed: ${error.message}`);
+  }

   const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
   const receiver = args.receiver || signer.address;

-  const tx = await (contract as any).transferCrossChain(
-    args.to,
-    receiver,
-    args.amount,
-    { ...txOptions, value: gasAmount }
-  );
+  try {
+    const tx = await (contract as any).transferCrossChain(
+      args.to,
+      receiver,
+      args.amount,
+      { ...txOptions, value: gasAmount }
+    );
+    await tx.wait();
+  } catch (error) {
+    throw new Error(`Cross-chain transfer failed: ${error.message}`);
+  }
📝 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 value = ethers.utils.parseUnits(args.amount, 18);
const tokenApprove = await contract.approve(args.from, value);
await tokenApprove.wait();
const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
const receiver = args.receiver || signer.address;
tx = await (contract as any).transferCrossChain(
const tx = await (contract as any).transferCrossChain(
args.to,
receiver,
const decimals = await contract.decimals().catch(() => 18);
const value = ethers.utils.parseUnits(args.amount, decimals);
try {
const tokenApprove = await contract.approve(args.from, value);
await tokenApprove.wait();
} catch (error) {
throw new Error(`Token approval failed: ${error.message}`);
}
const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
const receiver = args.receiver || signer.address;
try {
const tx = await (contract as any).transferCrossChain(
args.to,
receiver,
args.amount,
{ ...txOptions, value: gasAmount }
);
await tx.wait();
} catch (error) {
throw new Error(`Cross-chain transfer failed: ${error.message}`);
}

Comment on lines +19 to 22
const contract: EVMUniversalToken = await hre.ethers.getContractAt(
"EVMUniversalToken",
args.contract
);
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 proxy contract validation

When working with upgradeable contracts, it's important to verify we're interacting with the proxy contract and not the implementation directly.

 const contract: EVMUniversalToken = await hre.ethers.getContractAt(
   "EVMUniversalToken",
   args.contract
 );
+const implementationAddress = await hre.upgrades.erc1967.getImplementationAddress(args.contract);
+if (args.contract === implementationAddress) {
+  throw new Error('Provided address is implementation contract, not proxy');
+}
📝 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 contract: EVMUniversalToken = await hre.ethers.getContractAt(
"EVMUniversalToken",
args.contract
);
const contract: EVMUniversalToken = await hre.ethers.getContractAt(
"EVMUniversalToken",
args.contract
);
const implementationAddress = await hre.upgrades.erc1967.getImplementationAddress(args.contract);
if (args.contract === implementationAddress) {
throw new Error('Provided address is implementation contract, not proxy');
}

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

🧹 Nitpick comments (3)
contracts/nft/tasks/transfer.ts (1)

8-12: Enhance error message specificity for address validation.

While the address validation is a good addition, the error message could be more specific to help users identify which address is invalid.

Consider this improvement:

-  if (!isAddress(args.to) || !isAddress(args.revertAddress)) {
-    throw new Error("Invalid Ethereum address provided.");
+  if (!isAddress(args.to)) {
+    throw new Error(`Invalid destination address provided: ${args.to}`);
+  }
+  if (!isAddress(args.revertAddress)) {
+    throw new Error(`Invalid revert address provided: ${args.revertAddress}`);
+  }
contracts/nft/tasks/setConnected.ts (1)

14-20: Enhance error messages with specific validation failures

While the address validation is good, the error message could be more specific about which address is invalid.

Consider implementing more detailed error messages:

-  if (
-    !isAddress(args.contract) ||
-    !isAddress(args.zrc20) ||
-    !isAddress(args.connected)
-  ) {
-    throw new Error("Invalid Ethereum address provided.");
-  }
+  for (const [key, value] of Object.entries({ 
+    contract: args.contract, 
+    zrc20: args.zrc20, 
+    connected: args.connected 
+  })) {
+    if (!isAddress(value)) {
+      throw new Error(`Invalid Ethereum address provided for ${key}: ${value}`);
+    }
+  }
contracts/nft/tasks/mint.ts (1)

15-17: Enhance error message for address validation

While the validation is correct, the error message could be more helpful by including the invalid address.

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(`Invalid Ethereum address provided: ${args.contract}`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b208f1 and 565a6fe.

📒 Files selected for processing (8)
  • contracts/nft/hardhat.config.ts (1 hunks)
  • contracts/nft/scripts/localnet.sh (2 hunks)
  • contracts/nft/tasks/deploy.ts (2 hunks)
  • contracts/nft/tasks/index.ts (1 hunks)
  • contracts/nft/tasks/mint.ts (2 hunks)
  • contracts/nft/tasks/setConnected.ts (2 hunks)
  • contracts/nft/tasks/setUniversal.ts (2 hunks)
  • contracts/nft/tasks/transfer.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • contracts/nft/tasks/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • contracts/nft/scripts/localnet.sh
  • contracts/nft/tasks/deploy.ts
🔇 Additional comments (11)
contracts/nft/tasks/transfer.ts (1)

25-28: Verify error handling for contract interaction.

The direct contract interaction without try-catch could potentially lead to unhandled errors. Consider how failed contract interactions should be handled.

✅ Verification successful

Error handling follows task-level pattern

Based on the codebase analysis, the absence of try-catch blocks in contract interactions is consistent across all task files (setUniversal.ts, transfer.ts, setConnected.ts, mint.ts). This appears to be an intentional design pattern where error handling is managed at the task execution level rather than individual contract calls.

  • The pattern is uniform across all contract interactions in the tasks directory
  • Similar contract calls in setConnected.ts and mint.ts also use direct interactions
  • Task runners typically handle errors at a higher level, which is a valid architectural choice
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling patterns in similar contract interactions
rg -A 5 "getContractAt.*ZetaChainUniversalNFT" --type ts

Length of output: 58


Script:

#!/bin/bash
# Let's first check for any contract interactions in the tasks directory
rg -A 5 "getContractAt" contracts/nft/tasks/ --type ts

# Also check for error handling patterns in tasks
rg -A 5 "try.*catch" contracts/nft/tasks/ --type ts

Length of output: 2017

contracts/nft/hardhat.config.ts (1)

4-5: LGTM: Import changes align with upgradeability requirements

The imports have been correctly updated to support OpenZeppelin's UUPS upgradeability pattern and the new task structure.

Also applies to: 13-14

contracts/nft/tasks/setUniversal.ts (2)

14-16: LGTM: Enhanced input validation

The addition of address validation using isAddress improves the robustness of the task.


44-48: LGTM: Task naming follows new convention

The task naming convention nft:set-universal is consistent with the new structure.

contracts/nft/tasks/setConnected.ts (2)

22-24: LGTM: Contract type correctly updated

The contract type has been properly updated to ZetaChainUniversalNFT.


47-51: LGTM: Task naming follows new convention

The task naming convention nft:set-connected is consistent with the new structure.

contracts/nft/tasks/mint.ts (5)

3-7: LGTM: Enhanced input validation setup

The addition of URL validation and ethers utilities strengthens the input validation, which is crucial for smart contract interactions.


19-32: LGTM: Robust token URI validation

The implementation provides thorough validation with clear error messaging and proper protocol restrictions.


70-79: LGTM: Consistent token ID formatting

The addition of toString() ensures consistent string representation of BigNumber values in both JSON and text output formats.


83-94: LGTM: Improved task organization

The task definition changes enhance clarity through:

  • Namespaced task name (nft:mint)
  • Updated default contract name to align with the new contract structure

34-36: Verify contract type consistency

The contract types have been updated to use specific NFT contract names. Let's verify the consistency across the codebase.

✅ Verification successful

Contract type usage is consistent across the codebase

The contract types ZetaChainUniversalNFT and EVMUniversalNFT are used consistently throughout the codebase:

  • Contract definitions exist in their respective .sol files
  • Type imports and contract interactions in tasks are properly aligned
  • Deployment scripts correctly reference the contract names
  • The type union in mint.ts accurately reflects the available contract types
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for contract type references
rg -t typescript '"(ZetaChainUniversalNFT|EVMUniversalNFT)"' contracts/

Length of output: 108


Script:

#!/bin/bash
# Search for contract type references without file type restriction
rg "\"(ZetaChainUniversalNFT|EVMUniversalNFT)\"" contracts/

# Also search for contract names without quotes
rg "(ZetaChainUniversalNFT|EVMUniversalNFT)" contracts/

# Search for contract artifacts
fd "ZetaChainUniversalNFT|EVMUniversalNFT" contracts/

Length of output: 2880

Comment on lines +34 to +39
const tx = await contract.transferCrossChain(
args.tokenId,
receiver,
args.to,
{ ...txOptions, value: gasAmount }
);
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 validation for gas amount parameter.

The gas amount is parsed but not validated. Consider adding checks for negative values or amounts exceeding reasonable limits.

Add this validation before the transaction:

  const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
+ if (gasAmount.lte(0)) {
+   throw new Error("Gas amount must be greater than 0");
+ }
+ const maxGasAmount = ethers.utils.parseUnits("1000", 18); // Adjust limit as needed
+ if (gasAmount.gt(maxGasAmount)) {
+   throw new Error(`Gas amount exceeds maximum limit of 1000 ZETA`);
+ }
📝 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 tx = await contract.transferCrossChain(
args.tokenId,
receiver,
args.to,
{ ...txOptions, value: gasAmount }
);
const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
if (gasAmount.lte(0)) {
throw new Error("Gas amount must be greater than 0");
}
const maxGasAmount = ethers.utils.parseUnits("1000", 18); // Adjust limit as needed
if (gasAmount.gt(maxGasAmount)) {
throw new Error(`Gas amount exceeds maximum limit of 1000 ZETA`);
}
const tx = await contract.transferCrossChain(
args.tokenId,
receiver,
args.to,
{ ...txOptions, value: gasAmount }
);

@@ -70,7 +74,7 @@
.addOptionalParam(
"txOptionsGasLimit",
"The gas limit for the transaction",
7000000,
10000000,
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Gas limit value appears to be inconsistent with codebase patterns

The hardcoded gas limit of 10000000 is significantly higher than other gas limits found in the codebase:

  • Other tasks use configurable gas limits via arguments (e.g., args.txOptionsGasLimit)
  • Similar task setUniversal.ts uses a much lower gas limit of 1000000
  • Previous version of this file used a configurable gas limit through args.txOptionsGasLimit

Consider:

  • Keeping the gas limit configurable via args.txOptionsGasLimit as it was before
  • If a fixed value is required, align it with other similar operations (~1000000)
  • Document the reason for such a high gas limit if it's absolutely necessary
🔗 Analysis chain

Verify the increased gas limit requirement.

The gas limit has been increased from 7000000 to 10000000. Please ensure this increase is necessary and doesn't lead to excessive gas costs.


Let me search for gas limit patterns and their context to verify if this increase is justified based on the codebase patterns and history.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar gas limit patterns
rg "gasLimit.*10000000" --type ts
# Search for deployment transaction receipts
rg "receipt.*gasUsed" --type ts

Length of output: 69


Script:

#!/bin/bash
# Search for gas limit configurations in the codebase
rg "gasLimit.*\d+" --type ts -A 2

# Search for any gas-related configurations or constants
rg "GAS_LIMIT|gas.*limit" --type ts -i

# Look for the previous version of this file in git history
git show HEAD~1:contracts/nft/tasks/transfer.ts | grep -A 5 -B 5 "gasLimit"

Length of output: 1655

Comment on lines +20 to +32
solidity: {
compilers: [
{
settings: {
optimizer: {
enabled: true,
runs: 1000,
},
},
version: "0.8.26",
},
],
},
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent compiler settings detected between contracts

The verification revealed inconsistent Solidity compiler configurations across the project:

  • contracts/nft/hardhat.config.ts: Uses detailed configuration with optimization enabled (1000 runs)
  • contracts/token/hardhat.config.ts: Uses simplified configuration without optimization settings

This inconsistency could lead to:

  • Different bytecode optimization levels between contracts
  • Potential verification issues on block explorers
  • Varying gas costs across deployments

Recommendation: Align the compiler settings by applying the same optimization configuration to both contracts, preferably using the more detailed configuration from the NFT contract.

🔗 Analysis chain

Verify compiler optimization settings across all contracts

The compiler settings look appropriate for production deployment. However, ensure these settings are consistent across all contract deployments to prevent any potential verification issues on block explorers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent compiler settings across all Hardhat configs
fd -e ts -e js 'hardhat.config' | xargs rg -A 10 'solidity.*{' 

Length of output: 148


Script:

#!/bin/bash
# Check for consistent compiler settings across all Hardhat configs
fd -e ts -e js 'hardhat.config' | xargs rg -l 'solidity'

# If files are found, examine their compiler settings
fd -e ts -e js 'hardhat.config' | while read -r file; do
    echo "=== $file ==="
    rg -A 15 'solidity:' "$file" || true
done

Length of output: 768

Comment on lines +23 to +25
const tx = await contract.setUniversal(args.universal, {
gasLimit: 1000000,
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider dynamic gas limit calculation

Using a fixed gas limit of 1,000,000 might not be optimal across different networks and could lead to failed transactions.

Consider implementing dynamic gas estimation:

-  const tx = await contract.setUniversal(args.universal, {
-    gasLimit: 1000000,
-  });
+  const gasEstimate = await contract.estimateGas.setUniversal(args.universal);
+  const tx = await contract.setUniversal(args.universal, {
+    gasLimit: Math.ceil(gasEstimate.mul(120).div(100)), // Add 20% buffer
+  });
📝 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 tx = await contract.setUniversal(args.universal, {
gasLimit: 1000000,
});
const gasEstimate = await contract.estimateGas.setUniversal(args.universal);
const tx = await contract.setUniversal(args.universal, {
gasLimit: Math.ceil(gasEstimate.mul(120).div(100)), // Add 20% buffer
});

Comment on lines +44 to +62
const transferEvent = receipt.logs
.map((log: any) => {
try {
return contract.interface.parseLog(log);
} catch {
return null;
}
})
.find((parsedLog: any) => parsedLog?.name === "Transfer");

if (!transferEvent) {
throw new Error("Transfer event not found in transaction logs.");
}

const tokenId = transferEvent?.args?.tokenId;

if (!tokenId) {
throw new Error("Transfer event not found in transaction logs.");
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Differentiate error messages and enhance error handling

The event parsing is more robust, but there are two issues:

  1. The same error message is used for different error conditions
  2. The error handling could be more informative
-    throw new Error("Transfer event not found in transaction logs.");
+    throw new Error("Failed to find Transfer event in transaction logs.");
 }

 const tokenId = transferEvent?.args?.tokenId;

 if (!tokenId) {
-    throw new Error("Transfer event not found in transaction logs.");
+    throw new Error("Token ID not found in Transfer event.");
 }
📝 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 transferEvent = receipt.logs
.map((log: any) => {
try {
return contract.interface.parseLog(log);
} catch {
return null;
}
})
.find((parsedLog: any) => parsedLog?.name === "Transfer");
if (!transferEvent) {
throw new Error("Transfer event not found in transaction logs.");
}
const tokenId = transferEvent?.args?.tokenId;
if (!tokenId) {
throw new Error("Transfer event not found in transaction logs.");
}
const transferEvent = receipt.logs
.map((log: any) => {
try {
return contract.interface.parseLog(log);
} catch {
return null;
}
})
.find((parsedLog: any) => parsedLog?.name === "Transfer");
if (!transferEvent) {
throw new Error("Failed to find Transfer event in transaction logs.");
}
const tokenId = transferEvent?.args?.tokenId;
if (!tokenId) {
throw new Error("Token ID not found in Transfer event.");
}

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

♻️ Duplicate comments (3)
contracts/token/tasks/mint.ts (1)

26-26: 🛠️ Refactor suggestion

Enhance transaction receipt handling

The current implementation lacks proper error handling and event verification for the mint operation.

- await tx.wait();
+ const receipt = await tx.wait();
+ const mintEvent = receipt.events?.find(e => e.event === 'Transfer' && e.args?.from === ethers.constants.AddressZero);
+ if (!mintEvent) {
+   throw new Error('Mint transaction completed but no Transfer event found');
+ }
+ // Verify mint amount matches requested amount
+ if (mintEvent.args?.value.toString() !== args.amount.toString()) {
+   throw new Error(`Minted amount ${mintEvent.args?.value} differs from requested amount ${args.amount}`);
+ }
contracts/token/tasks/deploy.ts (1)

25-32: ⚠️ Potential issue

Configure UUPS proxy deployment

The proxy deployment should be configured for UUPS upgradeability pattern.

 const contract = await hre.upgrades.deployProxy(factory, [
   signer.address,
   args.tokenName,
   args.tokenSymbol,
   args.gateway,
   args.gasLimit,
   ...(args.uniswapRouter ? [args.uniswapRouter] : []),
-]);
+], {
+  initializer: 'initialize',
+  kind: 'uups'
+});
contracts/token/tasks/transfer.ts (1)

19-23: ⚠️ Potential issue

Add contract existence validation

Validate contract existence at the specified address.

   const contract = await ethers.getContractAt(
     "ZetaChainUniversalToken",
     args.from
   );
+  const code = await ethers.provider.getCode(args.from);
+  if (code === '0x') {
+    throw new Error(`No contract found at address ${args.from}`);
+  }
🧹 Nitpick comments (7)
contracts/token/tasks/setUniversal.ts (2)

6-7: Enhance error message specificity

While the address validation is a good security measure, the error message could be more informative by indicating which address is invalid.

Consider this improvement:

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(
+      `Invalid Ethereum address provided. Contract: ${args.contract}, Universal: ${args.universal}`
+    );

Also applies to: 15-17


19-22: Consider adding gas limit configuration

While the contract interaction is correct, consider adding a gas limit configuration to prevent potential transaction failures during complex operations.

Consider adding gas configuration:

-  const tx = await contract.setUniversal(args.universal);
+  const tx = await contract.setUniversal(args.universal, {
+    gasLimit: 100000 // Adjust based on your needs
+  });
contracts/token/tasks/mint.ts (1)

14-16: Enhance address validation error message

The error message should specify which address is invalid for better debugging.

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(`Invalid contract address: ${args.contract}`);
contracts/token/tasks/deploy.ts (1)

16-21: Enhance address validation error messages

Specify which address is invalid for better debugging.

   if (
     !isAddress(args.gateway) ||
     (args.uniswapRouter && !isAddress(args.uniswapRouter))
   ) {
-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(
+      !isAddress(args.gateway)
+        ? `Invalid gateway address: ${args.gateway}`
+        : `Invalid uniswap router address: ${args.uniswapRouter}`
+    );
   }
contracts/token/tasks/transfer.ts (1)

10-12: Enhance address validation error messages

Specify which address is invalid for better debugging.

-  if (!isAddress(args.to) || !isAddress(args.revertAddress)) {
-    throw new Error("Invalid Ethereum address provided.");
+  if (!isAddress(args.to)) {
+    throw new Error(`Invalid destination address: ${args.to}`);
+  }
+  if (!isAddress(args.revertAddress)) {
+    throw new Error(`Invalid revert address: ${args.revertAddress}`);
+  }
contracts/token/tasks/setConnected.ts (2)

15-21: Enhance error message specificity

While the address validation is good, the error message could be more helpful by indicating which specific address is invalid.

Consider this improvement:

-    throw new Error("Invalid Ethereum address provided.");
+    throw new Error(
+      `Invalid Ethereum address provided. Please verify:\n` +
+      `Contract: ${args.contract}\n` +
+      `ZRC20: ${args.zrc20}\n` +
+      `Connected: ${args.connected}`
+    );

23-24: Consider using type-safe contract factory

Instead of using a string literal for the contract name, consider using the contract factory for better type safety.

-  const contract: ZetaChainUniversalToken = await hre.ethers.getContractAt(
-    "ZetaChainUniversalToken",
+  const contract = await hre.ethers.getContractAt(
+    (await hre.ethers.getContractFactory("ZetaChainUniversalToken")).interface.format(),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 565a6fe and ae1b1d8.

📒 Files selected for processing (11)
  • contracts/nft/hardhat.config.ts (1 hunks)
  • contracts/token/contracts/example/EVMUniversalToken.sol (1 hunks)
  • contracts/token/contracts/example/ZetaChainUniversalToken.sol (1 hunks)
  • contracts/token/hardhat.config.ts (1 hunks)
  • contracts/token/scripts/localnet.sh (2 hunks)
  • contracts/token/tasks/deploy.ts (3 hunks)
  • contracts/token/tasks/index.ts (1 hunks)
  • contracts/token/tasks/mint.ts (2 hunks)
  • contracts/token/tasks/setConnected.ts (2 hunks)
  • contracts/token/tasks/setUniversal.ts (2 hunks)
  • contracts/token/tasks/transfer.ts (3 hunks)
✅ Files skipped from review due to trivial changes (3)
  • contracts/token/contracts/example/EVMUniversalToken.sol
  • contracts/token/contracts/example/ZetaChainUniversalToken.sol
  • contracts/token/tasks/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • contracts/token/hardhat.config.ts
  • contracts/nft/hardhat.config.ts
  • contracts/token/scripts/localnet.sh
🔇 Additional comments (8)
contracts/token/tasks/setUniversal.ts (2)

3-3: LGTM: Import change aligns with contract upgrades

The import change from Connected to EVMUniversalToken correctly reflects the architectural shift to upgradeable contracts.


41-45: LGTM: Task registration follows improved naming convention

The task registration changes improve code organization through:

  1. Clear namespace separation with "token:" prefix
  2. Better modularity with explicit export
contracts/token/tasks/deploy.ts (1)

Line range hint 63-67: Align gas limit with NFT deployment

The gas limit should be consistent with the NFT deployment since both use proxy deployment.

   .addOptionalParam(
     "gasLimit",
     "Gas limit for the transaction",
-    1000000,
+    10000000,
     types.int
   )
contracts/token/tasks/transfer.ts (2)

Line range hint 24-36: Enhance error handling and add decimal validation

The current implementation lacks proper error handling for approval and transfer operations.

+  const decimals = await contract.decimals().catch(() => 18);
   const value = ethers.utils.parseUnits(args.amount, decimals);
-  const tokenApprove = await contract.approve(args.from, value);
-  await tokenApprove.wait();
+  try {
+    const tokenApprove = await contract.approve(args.from, value);
+    await tokenApprove.wait();
+  } catch (error) {
+    throw new Error(`Token approval failed: ${error.message}`);
+  }

   const gasAmount = ethers.utils.parseUnits(args.gasAmount, 18);
   const receiver = args.receiver || signer.address;

-  const tx = await (contract as any).transferCrossChain(
-    args.to,
-    receiver,
-    args.amount,
-    { ...txOptions, value: gasAmount }
-  );
+  try {
+    const tx = await (contract as any).transferCrossChain(
+      args.to,
+      receiver,
+      args.amount,
+      { ...txOptions, value: gasAmount }
+    );
+    await tx.wait();
+  } catch (error) {
+    throw new Error(`Cross-chain transfer failed: ${error.message}`);
+  }

97-98: Update parameter descriptions

The parameter descriptions for gasAmount and amount are identical and not descriptive enough.

   .addParam("gasAmount", "The amount of gas to transfer", "0")
-  .addParam("amount", "The amount of gas to transfer", "0");
+  .addParam("amount", "The amount of tokens to transfer", "0");
contracts/token/tasks/setConnected.ts (3)

3-3: LGTM: Type import aligns with contract renaming

The import statement correctly references the renamed contract type, maintaining type safety.


48-52: LGTM: Improved task naming and modularity

The task registration changes improve the codebase in two ways:

  1. The new naming convention "token:set-connected" is more consistent and descriptive
  2. Exporting the task enhances modularity and reusability

Line range hint 1-57: Verify integration with upgrade system

Since this PR implements upgradeable contracts, we should verify that this task properly integrates with the upgrade system.

✅ Verification successful

Let me analyze the deployment and contract implementation to understand how setConnected integrates with the upgrade system before making a final determination.


Integration with upgrade system verified successfully

The task properly integrates with the upgrade system:

  • The contract is deployed as a proxy using hre.upgrades.deployProxy in the deployment task
  • The setConnected function is part of the upgradeable contract implementation and includes proper access control (onlyOwner)
  • Address validation in the task provides an additional security layer for proxy interactions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related upgrade tasks and proxy implementations
echo "Searching for related upgrade tasks..."
rg -l "UUPSUpgradeable|ERC1967Proxy" contracts/

echo "Checking for other tasks that might need similar address validation..."
rg -l "task\(" contracts/token/tasks/

Length of output: 699


Script:

#!/bin/bash
# Check if other tasks handle proxy interactions
echo "Checking deploy task for proxy setup..."
cat contracts/token/tasks/deploy.ts

echo "Checking if setConnected is used in proxy context..."
rg -C 5 "setConnected" contracts/token/contracts/zetachain/UniversalToken.sol contracts/token/contracts/evm/UniversalToken.sol

Length of output: 3410

Copy link

@0xM3R 0xM3R left a comment

Choose a reason for hiding this comment

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

LGTM

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 comments (3)
contracts/nft/contracts/evm/UniversalNFT.sol (1)

Line range hint 79-93: Improve token ID generation mechanism.

The current token ID generation using block number and keccak256 could lead to collisions in high-frequency minting scenarios. Consider using OpenZeppelin's Counters:

+    using Counters for Counters.Counter;
+    Counters.Counter private _tokenIdCounter;
+
     function safeMint(
         address to,
         string memory uri
     ) public whenNotPaused onlyOwner {
-        uint256 hash = uint256(
-            keccak256(
-                abi.encodePacked(address(this), block.number, _nextTokenId++)
-            )
-        );
-        uint256 tokenId = hash & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
+        uint256 tokenId = _tokenIdCounter.current();
+        _tokenIdCounter.increment();
         _safeMint(to, tokenId);
         _setTokenURI(tokenId, uri);
contracts/nft/contracts/zetachain/UniversalNFT.sol (2)

Line range hint 59-79: Enhance initializer security

The initialize function should include additional safeguards:

  1. Add nonReentrant modifier
  2. Validate gas limit against reasonable bounds
+    uint256 public constant MIN_GAS_LIMIT = 21000;
+    uint256 public constant MAX_GAS_LIMIT = 1000000;

     function initialize(
         address initialOwner,
         string memory name,
         string memory symbol,
         address payable gatewayAddress,
         uint256 gas,
         address uniswapRouterAddress
-    ) public initializer {
+    ) public initializer nonReentrant {
         __ERC721_init(name, symbol);
         __ERC721Enumerable_init();
         __ERC721URIStorage_init();
         __Ownable_init(initialOwner);
         __ERC721Burnable_init();
         __UUPSUpgradeable_init();
         if (gatewayAddress == address(0) || uniswapRouterAddress == address(0))
             revert InvalidAddress();
-        if (gas == 0) revert InvalidGasLimit();
+        if (gas < MIN_GAS_LIMIT || gas > MAX_GAS_LIMIT) revert InvalidGasLimit();
         gateway = GatewayZEVM(gatewayAddress);
         uniswapRouter = uniswapRouterAddress;
         gasLimitAmount = gas;
     }

Line range hint 94-127: Refactor complex transfer logic and enhance error handling

The transferCrossChain function handles multiple concerns and could benefit from:

  1. Extracting swap logic to a separate internal function
  2. Adding more specific error messages
  3. Implementing slippage protection
+    error SlippageExceeded(uint256 expected, uint256 received);
+    error InsufficientGasFee(uint256 required, uint256 provided);

+    function _handleGasSwap(
+        address WZETA,
+        uint256 gasFee,
+        address gasZRC20
+    ) internal returns (uint256 remaining) {
+        IWETH9(WZETA).deposit{value: msg.value}();
+        IWETH9(WZETA).approve(uniswapRouter, msg.value);
+
+        uint256 minOut = gasFee * 98 / 100; // 2% max slippage
+        uint256 out = SwapHelperLib.swapTokensForExactTokens(
+            uniswapRouter,
+            WZETA,
+            gasFee,
+            gasZRC20,
+            msg.value
+        );
+        if (out < minOut) revert SlippageExceeded(minOut, out);
+        
+        return msg.value - out;
+    }

     function transferCrossChain(
         uint256 tokenId,
         address receiver,
         address destination
     ) public payable whenNotPaused {
         if (msg.value == 0) revert ZeroMsgValue();
+        if (msg.value < gasFee) revert InsufficientGasFee(gasFee, msg.value);
         // ... rest of the function
     }
🧹 Nitpick comments (2)
contracts/token/contracts/evm/UniversalToken.sol (1)

Line range hint 82-100: Optimize gas fee handling in cross-chain transfers.

The transferCrossChain function could benefit from:

  1. Minimum value validation
  2. Gas fee estimation helper
  3. Clearer error messages
+    error InsufficientValue(uint256 required, uint256 provided);
+
+    function estimateGasFee(address destination) public view returns (uint256) {
+        return gateway.estimateGasFee(destination, gasLimitAmount);
+    }
+
     function transferCrossChain(
         address destination,
         address receiver,
         uint256 amount
     ) external payable whenNotPaused {
+        uint256 requiredGas = estimateGasFee(destination);
+        if (msg.value < requiredGas) {
+            revert InsufficientValue(requiredGas, msg.value);
+        }
         if (receiver == address(0)) revert InvalidAddress();
contracts/nft/contracts/evm/UniversalNFT.sol (1)

56-61: Optimize initialization sequence.

The initialization sequence is correct but could be more gas-efficient by combining related initializations:

-    __ERC721_init(name, symbol);
-    __ERC721Enumerable_init();
-    __ERC721URIStorage_init();
-    __Ownable_init(initialOwner);
-    __ERC721Burnable_init();
-    __UUPSUpgradeable_init();
+    __ERC721_init_unchained(name, symbol);
+    __ERC721Enumerable_init_unchained();
+    __ERC721URIStorage_init_unchained();
+    __Ownable_init_unchained(initialOwner);
+    __ERC721Burnable_init_unchained();
+    __UUPSUpgradeable_init_unchained();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae1b1d8 and 5dbab72.

📒 Files selected for processing (5)
  • contracts/nft/contracts/evm/UniversalNFT.sol (4 hunks)
  • contracts/nft/contracts/zetachain/UniversalNFT.sol (8 hunks)
  • contracts/token/contracts/evm/UniversalToken.sol (3 hunks)
  • contracts/token/contracts/zetachain/UniversalToken.sol (7 hunks)
  • contracts/token/hardhat.config.ts (1 hunks)
🔇 Additional comments (8)
contracts/token/hardhat.config.ts (3)

9-10: LGTM! Required plugins for UUPS implementation

The addition of hardhat-ethers and hardhat-upgrades plugins is necessary for implementing upgradeable contracts using the UUPS pattern.

✅ Verification successful

Required plugins are properly configured in package.json

Both @nomiclabs/hardhat-ethers (v2.0.0) and @openzeppelin/hardhat-upgrades (v1.28.0) are correctly declared as devDependencies in the contracts/token/package.json file. The imports in hardhat.config.ts are properly supported by the project's dependencies.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify package dependencies

# Check if dependencies are properly declared in package.json
rg -A 1 '"@nomiclabs/hardhat-ethers"' package.json
rg -A 1 '"@openzeppelin/hardhat-upgrades"' package.json

Length of output: 110


Script:

#!/bin/bash
# Let's check the package.json content directly to see all dependencies
cat package.json

# Also check if there are any package.json files in subdirectories
fd package.json

Length of output: 369


Script:

#!/bin/bash
# Check the package.json in the token directory since the file under review is in contracts/token/
cat contracts/token/package.json

Length of output: 2129


1-1: Verify tasks directory structure after import consolidation

The consolidation of task imports improves maintainability. However, we should verify that all tasks are properly exported from the tasks directory.

✅ Verification successful

Import consolidation is properly implemented

The tasks directory structure is well-organized with all task files properly exported through the index.ts. The consolidation includes:

  • All 5 task files (deploy.ts, mint.ts, setConnected.ts, setUniversal.ts, transfer.ts)
  • Proper named exports in index.ts matching each task file
  • Correct import statement in hardhat.config.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify tasks directory structure and exports

# Check if tasks/index.ts exists and exports all tasks
fd -t f "index.ts" tasks/

# List all task files to ensure they're properly organized
fd -t f . tasks/

Length of output: 232


Script:

#!/bin/bash
# Let's first check if the tasks directory exists relative to the token directory
fd -t d "tasks" contracts/token/

# Check for any .ts files in the token directory that might be task-related
fd -e ts . contracts/token/

# Use ripgrep to find any task-related imports or exports
rg -g "*.ts" "import.*task" contracts/token/

Length of output: 941


Script:

#!/bin/bash
# Let's verify the index.ts exports to ensure all tasks are properly consolidated
cat contracts/token/tasks/index.ts

# Check if all task files are properly imported in index.ts
rg -g "index.ts" "import.*" contracts/token/tasks/

Length of output: 311


16-28: LGTM! Optimized compiler settings for production

The compiler optimization settings are well-configured for production use:

  • Optimizer enabled for better gas efficiency
  • 1000 runs is a good balance between deployment cost and runtime efficiency
✅ Verification successful

Compiler settings are consistent and well-configured across contracts

The verification confirms that both NFT and token contracts use identical, production-grade compiler settings:

  • Both use Solidity 0.8.26
  • Both have optimizer enabled with 1000 runs
  • Both use OpenZeppelin upgrades plugin

This configuration is appropriate for the contract complexity, considering the universal token and NFT implementations across EVM and ZetaChain.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify compiler settings consistency across related contracts

# Check for consistent compiler settings across contract configurations
rg -A 10 'solidity.*version.*0\.8\.26' .

Length of output: 42


Script:

#!/bin/bash
# Let's try a broader search for compiler settings
rg -A 10 'solidity.*compilers' .

# Also search for any hardhat config files
fd 'hardhat.config' .

# Check for any other solidity version specifications
rg -A 5 'version.*0\.' .

Length of output: 67195


Script:

#!/bin/bash
# Let's check the content of both hardhat config files to compare compiler settings
cat ./contracts/nft/hardhat.config.ts
cat ./contracts/token/hardhat.config.ts

# Also check for any solidity files to understand the contract complexity
fd -e sol . 

Length of output: 1935

contracts/token/contracts/evm/UniversalToken.sol (2)

54-61: ⚠️ Potential issue

Initialize all inherited contracts.

The initialize function is missing initialization calls for ERC20PausableUpgradeable and ERC20PermitUpgradeable. Add the following initializations:

 function initialize(
     address initialOwner,
     string memory name,
     string memory symbol,
     address payable gatewayAddress,
     uint256 gas
 ) public initializer {
     __ERC20_init(name, symbol);
     __ERC20Burnable_init();
+    __ERC20Pausable_init();
+    __ERC20Permit_init(name);
     __Ownable_init(initialOwner);
     __UUPSUpgradeable_init();

Likely invalid or redundant comment.


145-147: 🛠️ Refactor suggestion

Enhance upgrade security mechanism.

The current _authorizeUpgrade implementation lacks additional security measures. Consider implementing:

  1. Timelock mechanism
  2. Upgrade validation checks
  3. Events for upgrade transparency
+    uint256 private constant UPGRADE_TIMELOCK = 2 days;
+    uint256 private upgradeProposalTime;
+    address private proposedImplementation;
+
+    event UpgradeProposed(address indexed implementation);
+    event UpgradeExecuted(address indexed implementation);
+
+    function proposeUpgrade(address newImplementation) external onlyOwner {
+        proposedImplementation = newImplementation;
+        upgradeProposalTime = block.timestamp;
+        emit UpgradeProposed(newImplementation);
+    }
+
     function _authorizeUpgrade(
         address newImplementation
-    ) internal override onlyOwner {}
+    ) internal override onlyOwner {
+        require(newImplementation == proposedImplementation, "Invalid implementation");
+        require(block.timestamp >= upgradeProposalTime + UPGRADE_TIMELOCK, "Timelock active");
+        emit UpgradeExecuted(newImplementation);
+    }

Likely invalid or redundant comment.

contracts/token/contracts/zetachain/UniversalToken.sol (2)

36-36: ⚠️ Potential issue

Remove unused state variable.

The _nextTokenId variable appears to be accidentally copied from the NFT contract and is never used.

-    uint256 private _nextTokenId;

Likely invalid or redundant comment.


104-122: 🛠️ Refactor suggestion

Enhance security and efficiency of token swaps.

The current implementation of token swaps could be improved:

  1. Add slippage protection
  2. Implement checks-effects-interactions pattern
  3. Add reentrancy protection
+    uint256 private constant MAX_SLIPPAGE = 100; // 1%
+
     address WZETA = gateway.zetaToken();
     
+    uint256 minOut = (gasFee * (10000 - MAX_SLIPPAGE)) / 10000;
     IWETH9(WZETA).deposit{value: msg.value}();
     IWETH9(WZETA).approve(uniswapRouter, msg.value);
     
     uint256 out = SwapHelperLib.swapTokensForExactTokens(
         uniswapRouter,
         WZETA,
         gasFee,
         gasZRC20,
-        msg.value
+        msg.value,
+        minOut
     );

Likely invalid or redundant comment.

contracts/nft/contracts/zetachain/UniversalNFT.sol (1)

22-32: Review inheritance order for diamond problem mitigation

The contract inherits from multiple base contracts that share common ancestors. While the current order works, it's recommended to document the inheritance hierarchy to prevent future maintenance issues.

Run this script to analyze the inheritance hierarchy:

✅ Verification successful

Let me analyze the inheritance hierarchy by first getting the content of the relevant files. The previous command might have failed due to pattern matching issues with Solidity's syntax, so let's examine the raw files first.


The inheritance order is correctly implemented with no diamond inheritance issues

The inheritance hierarchy follows OpenZeppelin's recommended order for upgradeable contracts, with proper initialization sequence:

  1. Initializable is first, as required
  2. Base ERC721 implementation (ERC721Upgradeable)
  3. Storage extensions (ERC721URIStorageUpgradeable, ERC721EnumerableUpgradeable)
  4. Behavioral extensions (ERC721PausableUpgradeable)
  5. Access control (OwnableUpgradeable)
  6. Additional features (ERC721BurnableUpgradeable, UniversalContract, UUPSUpgradeable)
  7. Events interface (UniversalNFTEvents)

All override implementations are properly handled with correct super calls, particularly in _update, _increaseBalance, tokenURI, and supportsInterface functions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify inheritance hierarchy using ast-grep
ast-grep --pattern 'contract $_ is $_' 

Length of output: 40


Script:

#!/bin/bash
# Search for the contract file and its content
fd "UniversalNFT.sol" --type f --exec cat {}

# Also search for any related interface or base contract files
fd "Universal.*\.sol" --type f --exec cat {}

Length of output: 49485

import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
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 duplicate imports.

The following imports appear multiple times:

  • Initializable (lines 10 and 16)
  • ERC20BurnableUpgradeable (lines 7 and 17)

Also applies to: 13-13, 16-16, 17-17

Comment on lines +34 to +38
GatewayZEVM public gateway;
address public uniswapRouter;
uint256 private _nextTokenId;
bool public constant isUniversal = true;
uint256 public immutable gasLimitAmount;
uint256 public gasLimitAmount;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider implementing state variable protection mechanisms

The removal of immutable modifiers is necessary for upgradeability, but it introduces potential security risks. Consider:

  1. Adding a setter function with timelock for critical state variables
  2. Implementing events to track state changes
+    event GatewayUpdated(address indexed oldGateway, address indexed newGateway);
+    event UniswapRouterUpdated(address indexed oldRouter, address indexed newRouter);

+    function setGateway(address newGateway) external onlyOwner {
+        if (newGateway == address(0)) revert InvalidAddress();
+        address oldGateway = address(gateway);
+        gateway = GatewayZEVM(newGateway);
+        emit GatewayUpdated(oldGateway, newGateway);
+    }

+    function setUniswapRouter(address newRouter) external onlyOwner {
+        if (newRouter == address(0)) revert InvalidAddress();
+        address oldRouter = uniswapRouter;
+        uniswapRouter = newRouter;
+        emit UniswapRouterUpdated(oldRouter, newRouter);
+    }

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

Comment on lines +290 to +292
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
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 upgrade authorization security

The _authorizeUpgrade function should implement additional security measures:

  1. Add a timelock mechanism
  2. Require multi-signature approval
  3. Emit an event for transparency
+    event UpgradeAuthorized(address indexed implementation);
+    uint256 private constant UPGRADE_TIMELOCK = 2 days;
+    mapping(address => uint256) private _pendingUpgrades;

     function _authorizeUpgrade(
         address newImplementation
-    ) internal override onlyOwner {}
+    ) internal override onlyOwner {
+        require(_pendingUpgrades[newImplementation] > 0, "Upgrade not initiated");
+        require(block.timestamp >= _pendingUpgrades[newImplementation], "Timelock active");
+        emit UpgradeAuthorized(newImplementation);
+    }
+
+    function initiateUpgrade(address newImplementation) external onlyOwner {
+        _pendingUpgrades[newImplementation] = block.timestamp + UPGRADE_TIMELOCK;
+    }

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

@fadeev fadeev merged commit 785f106 into main Dec 23, 2024
13 checks passed
@fadeev fadeev deleted the upgradeable branch December 23, 2024 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants