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: ERC20Custody and ZetaConnector uups upgradable #385

Merged
merged 6 commits into from
Oct 14, 2024

Conversation

skosito
Copy link
Contributor

@skosito skosito commented Oct 10, 2024

NOTE: will run yarn generate before merge to make review a bit easier

  • custody and connectors upgradable since we will have to add some changes in v21
  • commented out echidna tests with issue added to fix them, they are failing and out of scope of this work, currently not used at all

Summary by CodeRabbit

  • New Features

    • Transitioned multiple contracts to be upgradeable, enhancing flexibility and security.
    • Introduced initialization functions for contract setup, replacing constructors.
  • Bug Fixes

    • Improved contract upgrade authorization to ensure only designated roles can perform upgrades.
  • Tests

    • Updated testing contracts to utilize UUPS proxies for better contract management.
    • Modified setup logic in tests to align with new proxy deployment mechanisms.
    • Added new events and tests to verify upgrade functionalities.
  • Chores

    • Updated import paths for contracts to reflect new directory structures.

@skosito skosito linked an issue Oct 10, 2024 that may be closed by this pull request
@codecov-commenter
Copy link

codecov-commenter commented Oct 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 84.69%. Comparing base (cbbcf27) to head (8084b72).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #385      +/-   ##
==========================================
- Coverage   84.77%   84.69%   -0.09%     
==========================================
  Files           8        8              
  Lines         394      405      +11     
  Branches      134      134              
==========================================
+ Hits          334      343       +9     
- Misses         60       62       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@skosito skosito marked this pull request as ready for review October 11, 2024 12:51
Copy link
Contributor

coderabbitai bot commented Oct 11, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces significant changes across multiple contracts to transition them from non-upgradeable to upgradeable architectures using OpenZeppelin's upgradeable contracts. Key modifications include replacing constructors with initialize functions, altering variable declarations from immutable to regular state variables, and adding internal functions for upgrade authorization. The deployment scripts have also been updated to support the new upgradeable structure, and several test contracts have been modified to reflect these changes.

Changes

File Path Change Summary
v2/contracts/evm/ERC20Custody.sol Transitioned to upgradeable contract; added initialize and _authorizeUpgrade functions; changed gateway to a regular state variable.
v2/contracts/evm/ZetaConnectorBase.sol Made upgradeable; added initialize and _authorizeUpgrade functions; changed gateway and zetaToken to regular state variables.
v2/contracts/evm/ZetaConnectorNative.sol Replaced constructor with initialize function; no logic changes.
v2/contracts/evm/ZetaConnectorNonNative.sol Replaced constructor with initialize function; integrated initialization logic for maxSupply.
v2/scripts/deploy/deterministic/DeployERC20Custody.s.sol Updated import path; altered deployment logic to use CREATE2 for proxy contracts.
v2/scripts/deploy/deterministic/DeployGatewayEVM.s.sol Updated import path; no logic changes.
v2/scripts/deploy/deterministic/DeployZetaConnectorNonNative.s.sol Updated import path; altered deployment logic to a two-step process involving proxy contracts.
v2/scripts/deploy/deterministic/UpgradeGatewayEVM.s.sol Updated import path; no logic changes.
v2/test/ERC20Custody.t.sol Updated to use UUPS proxies; modified test functions for new architecture.
v2/test/GatewayEVM.t.sol Updated to use UUPS proxies; modified setUp function for deployment.
v2/test/GatewayEVMUpgrade.t.sol Removed entire contract responsible for testing upgrade functionality.
v2/test/GatewayEVMZEVM.t.sol Updated initialization for multiple proxies; streamlined setUp function.
v2/test/ZetaConnectorNative.t.sol Updated to use UUPS proxies; modified setUp function.
v2/test/ZetaConnectorNonNative.t.sol Updated to use UUPS proxies; modified setUp function.
v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol Added new contract for testing ERC20Custody upgrades with various functionalities.
v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol Modified import paths and event naming; added new event ExecutedV2.
v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol Introduced new contract for testing GatewayZEVM upgrades with initialization and event logging.
v2/test/utils/upgrades/ZetaConnectorNativeUpgradeTest.sol Added new contract for testing ZetaConnectorNative upgrades with initialization and withdrawal functions.
v2/test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol Added new contract for testing ZetaConnectorNonNative upgrades with initialization and event logging.

Possibly related PRs

Suggested reviewers

  • fadeev
  • CharlieMc0
  • andresaiello
  • brewmaster012
  • lumtis
  • fbac

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.

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.

Copy link
Contributor

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

🧹 Outside diff range and nitpick comments (3)
v2/scripts/deploy/deterministic/DeployERC20Custody.s.sol (1)

51-53: Add a verification step for the admin address

While you are verifying that tssAddress and gateway are correctly set, it would be beneficial to also confirm that the admin address is set as expected. This ensures that all initialization parameters have been properly configured.

Apply this addition to include the admin verification:

 require(erc20Custody.tssAddress() == tss, "tss not set");
 require(address(erc20Custody.gateway()) == gateway, "gateway not set");
+require(erc20Custody.hasRole(DEFAULT_ADMIN_ROLE, admin), "admin not set");
v2/test/ZetaConnectorNonNative.t.sol (1)

67-71: Consider using distinct proxy variables for clarity

Reusing the proxy variable for multiple contract deployments may reduce code readability. Consider using separate variables for each proxy deployment to enhance clarity.

Apply this diff to implement the suggestion:

-        proxy = Upgrades.deployUUPSProxy(
+        address custodyProxy = Upgrades.deployUUPSProxy(
             "ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
         );
-        custody = ERC20Custody(proxy);
+        custody = ERC20Custody(custodyProxy);

-        proxy = Upgrades.deployUUPSProxy(
+        address connectorProxy = Upgrades.deployUUPSProxy(
             "ZetaConnectorNonNative.sol",
             abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zetaToken), tssAddress, owner))
         );
-        zetaConnector = ZetaConnectorNonNative(proxy);
+        zetaConnector = ZetaConnectorNonNative(connectorProxy);
v2/test/ERC20Custody.t.sol (1)

65-69: Suggestion: Add comments to explain proxy deployments

Adding brief comments before each proxy deployment can help future readers understand the purpose of each deployment step, improving the maintainability of the test setup.

Apply this diff to include explanatory comments:

+// Deploy and initialize the ERC20Custody contract proxy
 proxy = Upgrades.deployUUPSProxy(
     "ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
 );
 custody = ERC20Custody(proxy);

+// Deploy and initialize the ZetaConnectorNonNative contract proxy
 proxy = Upgrades.deployUUPSProxy(
     "ZetaConnectorNonNative.sol",
     abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zeta), tssAddress, owner))
 );
 zetaConnector = ZetaConnectorNonNative(proxy);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 475acfa and 0638977.

📒 Files selected for processing (16)
  • v2/contracts/evm/ERC20Custody.sol (3 hunks)
  • v2/contracts/evm/ZetaConnectorBase.sol (4 hunks)
  • v2/contracts/evm/ZetaConnectorNative.sol (1 hunks)
  • v2/contracts/evm/ZetaConnectorNonNative.sol (1 hunks)
  • v2/scripts/deploy/deterministic/DeployERC20Custody.s.sol (1 hunks)
  • v2/scripts/deploy/deterministic/DeployGatewayEVM.s.sol (1 hunks)
  • v2/scripts/deploy/deterministic/DeployZetaConnectorNonNative.s.sol (2 hunks)
  • v2/scripts/deploy/deterministic/UpgradeGatewayEVM.s.sol (1 hunks)
  • v2/test/ERC20Custody.t.sol (1 hunks)
  • v2/test/GatewayEVM.t.sol (2 hunks)
  • v2/test/GatewayEVMUpgrade.t.sol (1 hunks)
  • v2/test/GatewayEVMZEVM.t.sol (1 hunks)
  • v2/test/ZetaConnectorNative.t.sol (1 hunks)
  • v2/test/ZetaConnectorNonNative.t.sol (1 hunks)
  • v2/test/fuzz/ERC20CustodyEchidnaTest.sol (1 hunks)
  • v2/test/fuzz/GatewayEVMEchidnaTest.sol (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • v2/scripts/deploy/deterministic/DeployGatewayEVM.s.sol
  • v2/scripts/deploy/deterministic/UpgradeGatewayEVM.s.sol
  • v2/test/fuzz/ERC20CustodyEchidnaTest.sol
  • v2/test/fuzz/GatewayEVMEchidnaTest.sol
🧰 Additional context used
🔇 Additional comments (24)
v2/test/GatewayEVMZEVM.t.sol (4)

74-77: LGTM: Proper initialization of upgradeable GatewayEVM proxy

The changes correctly implement the deployment of an upgradeable GatewayEVM proxy using Upgrades.deployUUPSProxy. The initialization parameters are correctly passed, and the gatewayEVM variable is properly assigned the proxy address. This approach allows for future upgrades of the contract.


78-81: LGTM: Efficient initialization of upgradeable ERC20Custody proxy

The changes correctly implement the deployment of an upgradeable ERC20Custody proxy using Upgrades.deployUUPSProxy. The initialization parameters are correctly passed, and the custody variable is properly assigned the proxy address. The reuse of the proxy variable is an efficient approach.


82-88: LGTM: Well-structured initialization of upgradeable ZetaConnectorNonNative proxy

The changes correctly implement the deployment of an upgradeable ZetaConnectorNonNative proxy using Upgrades.deployUUPSProxy. The initialization parameters are correctly passed, and the zetaConnector variable is properly assigned the proxy address. The multi-line format of the initialization call improves readability without affecting functionality.


74-88: Summary: Successful implementation of upgradeable proxies

The changes in this file successfully implement upgradeable proxies for GatewayEVM, ERC20Custody, and ZetaConnectorNonNative using Upgrades.deployUUPSProxy. This aligns well with the PR objective of making the custody and connectors upgradable in anticipation of upcoming changes in version 21. The implementation is consistent across all three contracts, and the code is well-structured for readability and efficiency.

v2/scripts/deploy/deterministic/DeployZetaConnectorNonNative.s.sol (3)

5-6: Updated import statements are correct

The import paths for ZetaConnectorNonNative.sol and ERC1967Proxy have been appropriately updated to reflect the new locations and dependencies.


52-55: Confirm proper casting of proxy to implementation contract

Casting the proxy address to ZetaConnectorNonNative may have implications if the proxy does not expose all implementation functions directly. Ensure that the methods called are accessible and behave as expected when interacting through the proxy.

You can verify the functions are callable via the proxy with the following script:

#!/bin/bash
# Description: Verify that the functions used are accessible through the proxy

grep -E 'function\s+(tssAddress|gateway|zetaToken)\(' contracts/evm/ZetaConnectorNonNative.sol

39-40: Verify the parameter order in the initialize function

Ensure that the parameters passed to ZetaConnectorNonNative.initialize match the expected order in the contract's initialize function to prevent initialization errors.

Run the following script to confirm the initialize function's signature:

v2/contracts/evm/ZetaConnectorNative.sol (1)

14-25: Initialization function is correctly implemented

The initialize function appropriately replaces the constructor for an upgradeable contract. It correctly uses the initializer modifier and calls super.initialize with the necessary parameters. The use of override is appropriate since it overrides the parent contract's initialize function.

v2/contracts/evm/ZetaConnectorNonNative.sol (2)

17-19: Initialization of maxSupply moved to initialize function

The maxSupply variable is now declared without an initial value and is initialized in the initialize function. This change aligns with the upgradeable contract pattern and ensures flexibility in initialization.


20-33: Proper implementation of the initialize function for upgradeability

The initialize function correctly replaces the constructor, includes the initializer modifier to prevent multiple initializations, and calls super.initialize. Setting maxSupply within this function ensures that it's set during contract initialization.

v2/contracts/evm/ZetaConnectorBase.sol (5)

4-8: Updated imports to use upgradeable contracts.

The import statements have been correctly updated to use the upgradeable versions of OpenZeppelin contracts, which is essential for implementing upgradeable contract patterns.


19-26: Inherited from upgradeable base contracts appropriately.

The contract now inherits from the upgradeable versions of OpenZeppelin contracts, enabling upgradeability and consistent with the use of proxy patterns.


33-35: Modified state variables for upgradeability compliance.

The gateway and zetaToken variables have been changed from immutable to regular state variables. This modification is necessary because immutable variables are set during construction and are not compatible with upgradeable proxy contracts.


46-66: Initialize function correctly replaces constructor.

The initialize function appropriately replaces the constructor for an upgradeable contract. It includes necessary checks for zero addresses, initializes state variables, and sets up access control roles correctly.


77-79: Implemented secure _authorizeUpgrade function.

The _authorizeUpgrade function correctly restricts the upgrade authorization to accounts with the DEFAULT_ADMIN_ROLE, ensuring that only authorized users can upgrade the contract implementation.

v2/contracts/evm/ERC20Custody.sol (5)

9-14: Imports updated for upgradeable contracts

The imports have been correctly updated to use OpenZeppelin's upgradeable contracts, which is appropriate for implementing upgradeable functionality.


21-28: Contract inheritance modified for upgradeability

The contract now inherits from OpenZeppelin's upgradeable base contracts, enabling upgradeable functionality as intended.


32-32: Changed 'gateway' variable to suit upgradeable contract

The gateway variable has been changed from immutable to a regular state variable, which is necessary since immutable variables are not compatible with upgradeable contracts.


48-57: Initializer function implemented correctly

The initialize function replaces the constructor and appropriately initializes the contract's state and roles. All necessary parent initializers are called in the correct order.


69-70: Upgrade authorization method added correctly

The _authorizeUpgrade function is implemented to restrict upgrades to only those with the DEFAULT_ADMIN_ROLE, adhering to the recommended pattern for UUPS upgradeable contracts.

v2/test/ZetaConnectorNonNative.t.sol (2)

58-60: LGTM: GatewayEVM UUPS proxy deployment is correctly implemented

The GatewayEVM contract is correctly deployed as a UUPS proxy and initialized with the appropriate parameters.


63-66: LGTM: ERC20Custody UUPS proxy deployment is correctly implemented

The ERC20Custody contract is properly deployed as a UUPS proxy and initialized with the correct parameters.

v2/test/ERC20Custody.t.sol (1)

57-69: Verify the initialization parameters for the proxies

Ensure that the initialization parameters provided for each proxy deployment are correct and correspond to the expected constructor arguments of the respective contracts. Incorrect parameters could lead to unexpected behavior during tests.

Run the following script to confirm the constructor signatures and the initialization parameters:

v2/test/ZetaConnectorNative.t.sol (1)

58-70: Good practice: Limiting the scope of the proxy variable

Reducing the scope of the proxy variable from a state variable to a local variable within the setUp function enhances encapsulation and prevents unintended side effects. This change improves code organization and maintainability.

Comment on lines +17 to +30
bytes32 implSalt = keccak256("ERC20Custody");
bytes32 proxySalt = keccak256("ERC20CustodyProxy");

vm.startBroadcast();

ERC20Custody custody = new ERC20Custody{salt: salt}(gateway, tss, admin);
require(address(custody) != address(0), "deployment failed");
expectedImplAddress = vm.computeCreate2Address(
implSalt,
hashInitCode(type(ERC20Custody).creationCode)
);

ERC20Custody erc20CustodyImpl = new ERC20Custody{salt: implSalt}();
require(address(erc20CustodyImpl) != address(0), "erc20CustodyImpl deployment failed");

require(expectedImplAddress == address(erc20CustodyImpl), "impl address doesn't match expected address");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor repetitive deployment logic into helper functions

The code for computing expected addresses and deploying both the implementation and proxy contracts is repetitive. To enhance maintainability and reduce duplication, consider abstracting this logic into reusable helper functions.

For example, you could create helper functions like computeExpectedAddress and deployContract:

function computeExpectedAddress(bytes32 salt, bytes memory bytecode) internal view returns (address) {
    return vm.computeCreate2Address(salt, hashInitCode(bytecode));
}

function deployContract(bytes32 salt, bytes memory bytecode) internal returns (address deployedAddress) {
    assembly {
        deployedAddress := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }
    require(deployedAddress != address(0), "Deployment failed");
}

Refactored implementation deployment:

-expectedImplAddress = vm.computeCreate2Address(
-    implSalt,
-    hashInitCode(type(ERC20Custody).creationCode)
-);
-
-ERC20Custody erc20CustodyImpl = new ERC20Custody{salt: implSalt}();
-require(address(erc20CustodyImpl) != address(0), "erc20CustodyImpl deployment failed");
-
-require(expectedImplAddress == address(erc20CustodyImpl), "impl address doesn't match expected address");
+bytes memory implBytecode = type(ERC20Custody).creationCode;
+expectedImplAddress = computeExpectedAddress(implSalt, implBytecode);
+address implAddress = deployContract(implSalt, implBytecode);
+require(expectedImplAddress == implAddress, "Implementation address mismatch");
+ERC20Custody erc20CustodyImpl = ERC20Custody(implAddress);

Refactored proxy deployment:

-expectedProxyAddress = vm.computeCreate2Address(
-    proxySalt,
-    hashInitCode(
-        type(ERC1967Proxy).creationCode,
-        abi.encode(
-            address(erc20CustodyImpl),
-            abi.encodeWithSelector(ERC20Custody.initialize.selector, gateway, tss, admin)
-        )
-    )
-);
-
-ERC1967Proxy erc20CustodyProxy = new ERC1967Proxy{salt: proxySalt}(
-    address(erc20CustodyImpl),
-    abi.encodeWithSelector(ERC20Custody.initialize.selector, gateway, tss, admin)
-);
-require(address(erc20CustodyProxy) != address(0), "erc20CustodyProxy deployment failed");
-
-require(expectedProxyAddress == address(erc20CustodyProxy), "Proxy address mismatch");
+bytes memory proxyInitCode = abi.encodePacked(
+    type(ERC1967Proxy).creationCode,
+    abi.encode(
+        address(erc20CustodyImpl),
+        abi.encodeWithSelector(ERC20Custody.initialize.selector, gateway, tss, admin)
+    )
+);
+expectedProxyAddress = computeExpectedAddress(proxySalt, proxyInitCode);
+address proxyAddress = deployContract(proxySalt, proxyInitCode);
+require(expectedProxyAddress == proxyAddress, "Proxy address mismatch");
+ERC1967Proxy erc20CustodyProxy = ERC1967Proxy(payable(proxyAddress));

Also applies to: 32-49

Comment on lines +18 to +19
bytes32 implSalt = keccak256("ZetaConnectorNonNative");
bytes32 proxySalt = keccak256("ZetaConnectorNonNativeProxy");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Define salt strings as constants for clarity

Consider defining the salt strings used in keccak256 as constants to enhance readability and maintainability.

Apply this diff to define the salt strings as constants:

+    string constant IMPL_SALT_STRING = "ZetaConnectorNonNative";
+    string constant PROXY_SALT_STRING = "ZetaConnectorNonNativeProxy";

-    bytes32 implSalt = keccak256("ZetaConnectorNonNative");
-    bytes32 proxySalt = keccak256("ZetaConnectorNonNativeProxy");
+    bytes32 implSalt = keccak256(abi.encodePacked(IMPL_SALT_STRING));
+    bytes32 proxySalt = keccak256(abi.encodePacked(PROXY_SALT_STRING));
📝 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
bytes32 implSalt = keccak256("ZetaConnectorNonNative");
bytes32 proxySalt = keccak256("ZetaConnectorNonNativeProxy");
string constant IMPL_SALT_STRING = "ZetaConnectorNonNative";
string constant PROXY_SALT_STRING = "ZetaConnectorNonNativeProxy";
bytes32 implSalt = keccak256(abi.encodePacked(IMPL_SALT_STRING));
bytes32 proxySalt = keccak256(abi.encodePacked(PROXY_SALT_STRING));

Comment on lines 55 to 64
address erc20CustodyProxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(erc20CustodyProxy);
address connectorProxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNonNative.sol",
abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zeta), tssAddress, owner))
);
zetaConnector = ZetaConnectorNonNative(connectorProxy);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor proxy deployment into a helper function to reduce code duplication

The code for deploying UUPS proxies for ERC20Custody and ZetaConnectorNonNative contracts is repetitive. Refactoring this into a reusable helper function will enhance maintainability and make the setUp function cleaner.

Consider adding a private function to handle proxy deployment:

function deployProxy(string memory contractName, bytes memory initData) private returns (address) {
    return Upgrades.deployUUPSProxy(contractName, initData);
}

Then update the proxy deployments as follows:

-address erc20CustodyProxy = Upgrades.deployUUPSProxy(
-    "ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
-);
+address erc20CustodyProxy = deployProxy(
+    "ERC20Custody.sol",
+    abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
+);
 custody = ERC20Custody(erc20CustodyProxy);

-address connectorProxy = Upgrades.deployUUPSProxy(
-    "ZetaConnectorNonNative.sol",
-    abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zeta), tssAddress, owner))
-);
+address connectorProxy = deployProxy(
+    "ZetaConnectorNonNative.sol",
+    abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zeta), tssAddress, owner))
+);
 zetaConnector = ZetaConnectorNonNative(connectorProxy);

v2/test/ERC20Custody.t.sol Show resolved Hide resolved
Comment on lines +61 to +70
proxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(proxy);
proxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNonNative.sol",
abi.encodeCall(ZetaConnectorNonNative.initialize, (address(gateway), address(zeta), tssAddress, owner))
);
zetaConnector = ZetaConnectorNonNative(proxy);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using distinct variables for each proxy instance to improve readability

The proxy variable is reused for deploying multiple proxies (GatewayEVM, ERC20Custody, and ZetaConnectorNonNative), which could lead to confusion and reduce code clarity. Using distinct variable names like gatewayProxy, custodyProxy, and connectorProxy can enhance the readability and maintainability of the test setup code.

Comment on lines +397 to +407

proxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(proxy);
proxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNonNative.sol",
abi.encodeCall(ZetaConnectorBase.initialize, (address(gateway), address(zeta), tssAddress, owner))
);
zetaConnector = ZetaConnectorNonNative(proxy);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent initializer used for ZetaConnectorNonNative

In line 404, the initialize function of ZetaConnectorBase is called instead of ZetaConnectorNonNative.initialize. Ensure that the correct initializer is used to properly initialize ZetaConnectorNonNative. This inconsistency might lead to incorrect initialization and unexpected behavior during testing.

Comment on lines +58 to +70
address proxy = Upgrades.deployUUPSProxy(
"GatewayEVM.sol", abi.encodeCall(GatewayEVM.initialize, (tssAddress, address(zetaToken), owner))
);
gateway = GatewayEVM(proxy);
custody = new ERC20Custody(address(gateway), tssAddress, owner);
zetaConnector = new ZetaConnectorNative(address(gateway), address(zetaToken), tssAddress, owner);
proxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(proxy);
proxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNative.sol",
abi.encodeCall(ZetaConnectorNative.initialize, (address(gateway), address(zetaToken), tssAddress, owner))
);
zetaConnector = ZetaConnectorNative(proxy);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using distinct variable names for each proxy to enhance readability

Reusing the proxy variable for deploying multiple contracts can lead to confusion and reduce code clarity. Assigning unique variable names to each proxy improves maintainability and helps prevent potential errors.

Apply this diff to use distinct proxy variables:

-address proxy = Upgrades.deployUUPSProxy(
+address gatewayProxy = Upgrades.deployUUPSProxy(
    "GatewayEVM.sol", abi.encodeCall(GatewayEVM.initialize, (tssAddress, address(zetaToken), owner))
);
-gateway = GatewayEVM(proxy);
+gateway = GatewayEVM(gatewayProxy);

-proxy = Upgrades.deployUUPSProxy(
+address custodyProxy = Upgrades.deployUUPSProxy(
    "ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
-custody = ERC20Custody(proxy);
+custody = ERC20Custody(custodyProxy);

-proxy = Upgrades.deployUUPSProxy(
+address connectorProxy = Upgrades.deployUUPSProxy(
    "ZetaConnectorNative.sol",
    abi.encodeCall(ZetaConnectorNative.initialize, (address(gateway), address(zetaToken), tssAddress, owner))
);
-zetaConnector = ZetaConnectorNative(proxy);
+zetaConnector = ZetaConnectorNative(connectorProxy);
📝 Committable suggestion

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

Suggested change
address proxy = Upgrades.deployUUPSProxy(
"GatewayEVM.sol", abi.encodeCall(GatewayEVM.initialize, (tssAddress, address(zetaToken), owner))
);
gateway = GatewayEVM(proxy);
custody = new ERC20Custody(address(gateway), tssAddress, owner);
zetaConnector = new ZetaConnectorNative(address(gateway), address(zetaToken), tssAddress, owner);
proxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(proxy);
proxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNative.sol",
abi.encodeCall(ZetaConnectorNative.initialize, (address(gateway), address(zetaToken), tssAddress, owner))
);
zetaConnector = ZetaConnectorNative(proxy);
address gatewayProxy = Upgrades.deployUUPSProxy(
"GatewayEVM.sol", abi.encodeCall(GatewayEVM.initialize, (tssAddress, address(zetaToken), owner))
);
gateway = GatewayEVM(gatewayProxy);
address custodyProxy = Upgrades.deployUUPSProxy(
"ERC20Custody.sol", abi.encodeCall(ERC20Custody.initialize, (address(gateway), tssAddress, owner))
);
custody = ERC20Custody(custodyProxy);
address connectorProxy = Upgrades.deployUUPSProxy(
"ZetaConnectorNative.sol",
abi.encodeCall(ZetaConnectorNative.initialize, (address(gateway), address(zetaToken), tssAddress, owner))
);
zetaConnector = ZetaConnectorNative(connectorProxy);

Copy link
Member

@lumtis lumtis left a comment

Choose a reason for hiding this comment

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

LGTM to me
Maybe we can add a test for the upgrade for each contracts?

@skosito
Copy link
Contributor Author

skosito commented Oct 11, 2024

LGTM to me Maybe we can add a test for the upgrade for each contracts?

yes good point, will add, useful to see if some change in contract makes it non-upgradable (eg importing some 3rd party contract etc), i will add tests in this PR

@skosito
Copy link
Contributor Author

skosito commented Oct 11, 2024

LGTM to me Maybe we can add a test for the upgrade for each contracts?

@lumtis added

Copy link
Contributor

@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: 22

🧹 Outside diff range and nitpick comments (7)
v2/test/ZetaConnectorNonNative.t.sol (3)

48-49: LGTM: New event for upgraded contract

The addition of the WithdrawnV2 event is appropriate for testing the upgraded version of the contract. It maintains consistency with the original Withdrawn event.

Consider adding a comment explaining the purpose of this new event to improve code readability.


61-74: LGTM: Upgradeable proxy setup

The modifications to use Upgrades.deployUUPSProxy for deploying GatewayEVM, ERC20Custody, and ZetaConnectorNonNative are correct and align with the PR objective of making these contracts upgradeable.

Consider extracting the initialization parameters for each contract into separate variables to improve readability and maintainability.


Potential Untested Functions Identified

The following public/external functions in ZetaConnectorNonNative.sol are not covered by existing test cases:

  • receiveTokens
  • setMaxSupply

Recommendations:

  • Add Test Cases: Implement comprehensive tests for receiveTokens and setMaxSupply to ensure their correct behavior and security.
  • Verify Functionality: Ensure that these functions handle all possible edge cases and adhere to the intended logic.
🔗 Analysis chain

Line range hint 1-383: Overall LGTM with suggestions for improvement

The changes to this test file successfully implement upgradeable contract testing for ZetaConnectorNonNative. The modifications align well with the PR objectives of making the custody and connectors upgradable.

Consider the following improvements to enhance the test coverage and maintainability:

  1. Add more test cases to cover edge cases in the upgrade process (e.g., failed upgrades, unauthorized upgrades).
  2. Create helper functions for common setup steps to reduce code duplication.
  3. Add tests to verify that the storage layout is preserved after upgrades.
  4. Include tests for the upgradeTo and upgradeToAndCall functions of the UUPS upgradeable pattern.

To ensure comprehensive test coverage, let's check for any untested public or external functions in the ZetaConnectorNonNative contract:

This script will help identify any functions in ZetaConnectorNonNative that might not have corresponding test cases.





🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find potentially untested functions in ZetaConnectorNonNative

# Test: Extract public and external functions from ZetaConnectorNonNative
ast-grep --lang solidity --pattern $'contract ZetaConnectorNonNative {
  $$$
  function $FUNC_NAME($_) $VISIBILITY $$$
  $$$
}' | grep -E 'function.*\b(public|external)\b' | awk '{print $2}' | sort > /tmp/all_functions.txt

# Test: Extract tested functions from the test file
rg --type solidity 'function test' | awk '{print $2}' | sed 's/test//g' | sed 's/()//g' | sort > /tmp/tested_functions.txt

# Test: Find untested functions
comm -23 /tmp/all_functions.txt /tmp/tested_functions.txt

Length of output: 576


Script:

#!/bin/bash
# Description: Find potentially untested public and external functions in ZetaConnectorNonNative.sol

# Locate the ZetaConnectorNonNative.sol file
FILE_PATH=$(fd "ZetaConnectorNonNative\.sol$" .)
if [ -z "$FILE_PATH" ]; then
  echo "ZetaConnectorNonNative.sol not found."
  exit 1
fi

# Extract all public and external function names
rg -oP 'function\s+\K\w+(?=\s*\()(?=.*\b(public|external)\b)' "$FILE_PATH" | sort > /tmp/all_functions.txt

# Extract tested functions from the test file by removing 'test' prefix and parentheses
TEST_FILE="v2/test/ZetaConnectorNonNative.t.sol"
if [ ! -f "$TEST_FILE" ]; then
  echo "Test file v2/test/ZetaConnectorNonNative.t.sol not found."
  exit 1
fi

rg -oP 'function\s+test\K\w+(?=\s*\()' "$TEST_FILE" | sort > /tmp/tested_functions.txt

# Find untested functions
if command -v comm >/dev/null 2>&1; then
  comm -23 /tmp/all_functions.txt /tmp/tested_functions.txt
else
  echo "comm command not found. Please ensure it is available in the environment."
  exit 1
fi

Length of output: 960


Script:

#!/bin/bash
# Description: Find potentially untested public and external functions in ZetaConnectorNonNative.sol

# Locate the ZetaConnectorNonNative.sol file within the contracts directory
FILE_PATH=$(fd "ZetaConnectorNonNative\.sol$" ./v2/contracts/evm/)
if [ -z "$FILE_PATH" ]; then
  echo "ZetaConnectorNonNative.sol not found in ./v2/contracts/evm/."
  exit 1
fi

# Ensure only one file is found
FILE_COUNT=$(echo "$FILE_PATH" | wc -l)
if [ "$FILE_COUNT" -gt 1 ]; then
  echo "Multiple ZetaConnectorNonNative.sol files found. Please ensure only one exists in ./v2/contracts/evm/."
  exit 1
fi

# Extract all public and external function names
rg -oP 'function\s+\K\w+(?=\s*\()(?=.*\b(public|external)\b)' "$FILE_PATH" | sort > /tmp/all_functions.txt

# Extract tested functions from the test file by removing 'test' prefix and parentheses
TEST_FILE="v2/test/ZetaConnectorNonNative.t.sol"
if [ ! -f "$TEST_FILE" ]; then
  echo "Test file v2/test/ZetaConnectorNonNative.t.sol not found."
  exit 1
fi

rg -oP 'function\s+test\K\w+(?=\s*\()' "$TEST_FILE" | sort > /tmp/tested_functions.txt

# Find untested functions using grep
grep -Fxv -f /tmp/tested_functions.txt /tmp/all_functions.txt

Length of output: 665

v2/test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (1)

125-127: Add nonReentrant modifier to receiveTokens function to enhance security.

The receiveTokens function interacts with external contracts and alters the state by burning tokens. While there is no apparent reentrancy risk at the moment, adding the nonReentrant modifier is a good defensive practice to prevent potential vulnerabilities in future code changes.

Update the function signature:

function receiveTokens(uint256 amount) external override nonReentrant whenNotPaused {
    IZetaNonEthNew(zetaToken).burnFrom(msg.sender, amount);
}
v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol (1)

70-84: Consider Initializing Parent Contracts in Correct Order

According to OpenZeppelin's recommendations, it's best practice to initialize parent contracts in the order they are inherited to avoid any potential issues.

Apply this diff to reorder the initializers:

     __UUPSUpgradeable_init();
+    __AccessControl_init();
     __Pausable_init();
     __ReentrancyGuard_init();
+    __AccessControl_init();

Ensure that __AccessControl_init() is called before __UUPSUpgradeable_init() if necessary.

v2/test/ERC20Custody.t.sol (1)

580-580: Fix typo in comment: 'substracted' should be 'subtracted'

There is a typographical error in the comment on line 580. The word "substracted" should be corrected to "subtracted" for proper spelling.

Apply this diff to fix the typo:

-// Verify that the tokens were substracted from custody
+// Verify that the tokens were subtracted from custody
v2/test/GatewayEVM.t.sol (1)

43-43: Consider Renaming ExecutedV2 Event for Clarity

The newly added ExecutedV2 event serves to distinguish post-upgrade executions. To enhance readability, consider renaming it to something more descriptive like ExecutedAfterUpgrade or UpgradedExecuted to clearly convey its purpose.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0638977 and ec2fd2a.

📒 Files selected for processing (11)
  • v2/test/ERC20Custody.t.sol (4 hunks)
  • v2/test/GatewayEVM.t.sol (5 hunks)
  • v2/test/GatewayEVMUpgrade.t.sol (0 hunks)
  • v2/test/GatewayZEVM.t.sol (3 hunks)
  • v2/test/ZetaConnectorNative.t.sol (4 hunks)
  • v2/test/ZetaConnectorNonNative.t.sol (4 hunks)
  • v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol (1 hunks)
  • v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol (1 hunks)
  • v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol (1 hunks)
  • v2/test/utils/upgrades/ZetaConnectorNativeUpgradeTest.sol (1 hunks)
  • v2/test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (1 hunks)
💤 Files with no reviewable changes (1)
  • v2/test/GatewayEVMUpgrade.t.sol
🧰 Additional context used
🔇 Additional comments (23)
v2/test/ZetaConnectorNonNative.t.sol (2)

10-10: LGTM: New import for upgrade testing

The addition of the import statement for ZetaConnectorNonNativeUpgradeTest is appropriate for testing the upgrade functionality of the ZetaConnectorNonNative contract.


364-383: LGTM: Upgrade and withdrawal test

The new testUpgradeAndWithdraw function effectively tests the upgrade process and verifies that the withdrawal functionality is maintained after the upgrade.

Consider the following improvements:

  1. Add assertions to verify the upgrade was successful (e.g., check the implementation address).
  2. Test more functionalities of the upgraded contract to ensure complete compatibility.
  3. Add comments explaining the purpose of each step in the test for better readability.

To ensure the upgrade process is correctly implemented, let's verify the upgrade-related functions in the ZetaConnectorNonNative contract:

v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol (5)

4-7: LGTM: Import paths updated consistently

The import paths have been updated from "../../contracts/" to "../../../contracts/" consistently across all modified import statements. This change reflects a restructuring of the project directory.


Line range hint 1-498: Summary of changes and recommendations

The main changes in this upgrade test contract include:

  1. Updated import paths
  2. Addition of a new ExecutedV2 event
  3. Modifications to the execute functions

Recommendations:

  1. Ensure consistency in event usage across the contract. The execute function with MessageContext should emit ExecutedV2 instead of Executed.
  2. Clearly document the differences between the two execute functions and their intended use cases.
  3. Consider adding deprecation notices if any functions or events are planned to be phased out in future upgrades.
  4. Verify that the changes don't introduce any security vulnerabilities or break existing functionality, especially regarding the handling of MessageContext.
  5. Update any external systems or off-chain processes that may be affected by these changes.

Please address these points to ensure a smooth and consistent upgrade process.


Line range hint 138-159: Function signature updated, but event usage is inconsistent

The execute function signature has been updated to include a MessageContext parameter, which enhances the message handling capability of the contract. However, the function still emits the old Executed event instead of the new ExecutedV2 event. This is inconsistent with the event versioning approach introduced earlier.

Consider updating the event emission to use ExecutedV2 for consistency:

-        emit Executed(destination, msg.value, data);
+        emit ExecutedV2(destination, msg.value, data);

Also, ensure that any external systems or off-chain processes that depend on this function are updated to handle the new MessageContext parameter.

To ensure that the MessageContext is properly used within the function, please run the following verification:

#!/bin/bash
# Check for proper usage of MessageContext in execute function
rg -U "function execute.*MessageContext.*\n.*\n.*\n.*\n.*messageContext" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Line range hint 36-37: New event ExecutedV2 added for versioning

A new event ExecutedV2 has been added, which is similar to the existing Executed event. This approach allows for versioning of events, which can be beneficial for tracking changes in event structure across contract upgrades. However, ensure that any external systems or off-chain processes that depend on these events are updated accordingly.

To ensure that the new event is properly implemented and doesn't conflict with existing functionality, please run the following verification:

✅ Verification successful

ExecutedV2 Event Properly Implemented

The ExecutedV2 event is correctly declared and emitted in v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol. This implementation aligns with the versioning approach for events, ensuring consistent tracking across contract upgrades.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for proper implementation of ExecutedV2 event
rg -U "event Executed.*\n.*\n.*emit Executed" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol
rg -U "event ExecutedV2.*\n.*\n.*emit ExecutedV2" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 217


Script:

#!/bin/bash
# Check for the declaration of the ExecutedV2 event
rg -U 'event ExecutedV2\s*\(.*\);' v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Check for the emission of the ExecutedV2 event
rg -U 'emit ExecutedV2\s*\(.*\);' v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 303


Line range hint 161-182: New overloaded execute function added

A new overloaded version of the execute function has been added without the MessageContext parameter. This provides backward compatibility for calls that don't include the MessageContext. The function correctly emits the new ExecutedV2 event.

While this approach maintains compatibility, it's important to consider the following:

  1. Ensure that the documentation clearly explains the difference between the two execute functions and when to use each.
  2. Consider adding a deprecation notice to this overloaded function if the intention is to phase it out in future upgrades.
  3. Verify that the absence of MessageContext doesn't lead to any security or functionality issues in this version.

To ensure that both execute functions are properly implemented and don't conflict, please run the following verification:

✅ Verification successful

To ensure the execute functions are correctly implemented and emit the appropriate events, please run the following verification scripts:


To further verify the implementation of the execute functions and their corresponding emit statements, please run the following verification scripts:


To accurately associate each emit statement with its corresponding execute function, please run the following verification scripts:


Verification Successful

Both execute functions in v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol are correctly implemented and emit their respective events (Executed and ExecutedV2). No issues were found with the current implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for proper implementation of both execute functions
rg -U "function execute.*\n.*\n.*\n.*\n.*emit Executed" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol
rg -U "function execute.*\n.*\n.*\n.*\n.*emit ExecutedV2" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 215


Script:

#!/bin/bash
# Verify execute function emits Executed event
rg -U "function execute.*\n.*\nemit Executed" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Verify execute function emits ExecutedV2 event
rg -U "function execute.*\n.*\nemit ExecutedV2" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Alternatively, use ast-grep for more precise AST-based matching
ast-grep --lang solidity --pattern '
    FunctionDeclaration
        name: "execute"
        body: 
            EmitEvent[name: "Executed"]
' v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

ast-grep --lang solidity --pattern '
    FunctionDeclaration
        name: "execute"
        body: 
            EmitEvent[name: "ExecutedV2"]
' v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 817


Script:

#!/bin/bash
# Search for emit Executed event in GatewayEVMUpgradeTest.sol
rg "emit\s+Executed" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Search for emit ExecutedV2 event in GatewayEVMUpgradeTest.sol
rg "emit\s+ExecutedV2" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Additionally, list all execute functions to verify their implementations
rg "function\s+execute\s*\(" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 489


Script:

#!/bin/bash
# Extract context around 'emit Executed'
rg -A5 "emit\s+Executed" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Extract context around 'emit ExecutedV2'
rg -A5 "emit\s+ExecutedV2" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

# Extract context around 'emit ExecutedWithERC20'
rg -A5 "emit\s+ExecutedWithERC20" v2/test/utils/upgrades/GatewayEVMUpgradeTest.sol

Length of output: 1263

v2/test/utils/upgrades/ZetaConnectorNonNativeUpgradeTest.sol (4)

24-37: Ensure proper usage of override keyword in initialize function.

The initialize function is marked with the override keyword, but it's not clear if the parent contract ZetaConnectorBase declares an initialize function that is virtual. If not, using override may cause a compilation error.

Please confirm that super.initialize is intended and that the parent contract's initialize function is appropriately declared for overriding.


47-51: ⚠️ Potential issue

Ensure consistency between function access control and Natspec comments in withdraw.

The Natspec comment states that this function can only be called by the TSS address, but the function uses onlyRole(WITHDRAWER_ROLE) as an access control modifier. This could lead to confusion if WITHDRAWER_ROLE is not exclusively assigned to the TSS address.

Please verify if WITHDRAWER_ROLE is intended to represent the TSS address. If so, consider updating the Natspec comment to reflect the role:

/// @dev This function can only be called by accounts with the WITHDRAWER_ROLE.

Alternatively, if the function should be restricted to the TSS address, update the access control modifier accordingly.


67-73: ⚠️ Potential issue

Ensure consistency between function access control and Natspec comments in withdrawAndCall.

Similar to the previous comment, the Natspec states the function can only be called by the TSS address, but it uses onlyRole(WITHDRAWER_ROLE). Confirm that the access control aligns with the intended design.

Update the Natspec comment or the access control modifier for clarity and accuracy.


94-101: ⚠️ Potential issue

Ensure consistency between function access control and Natspec comments in withdrawAndRevert.

Again, there is a mismatch between the Natspec comment and the access control modifier. The function uses onlyRole(WITHDRAWER_ROLE) while the comment mentions it can only be called by the TSS address.

Please reconcile the discrepancy by updating the comment or the modifier to accurately reflect who can call the function.

v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol (1)

87-99: Verify Role Updates in updateTSSAddress Function

The updateTSSAddress function correctly revokes and grants roles when updating the tssAddress. Ensure that there are no missed permissions and that the order of operations maintains contract security.

Run the following script to verify that all roles associated with the old tssAddress are properly revoked and that the new tssAddress has been granted the necessary roles:

✅ Verification successful

Role Updates Verified Successfully

The updateTSSAddress function in v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol correctly revokes WITHDRAWER_ROLE and WHITELISTER_ROLE from the old tssAddress and grants them to the newTSSAddress. The order of operations ensures contract security by revoking roles before granting them to the new address.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check role assignments for tssAddress updates

# Expected: No remaining roles assigned to the old tssAddress and correct roles for newTSSAddress

# Find all role assignments to the old tssAddress
rg --type solidity "grantRole|revokeRole" -A 2

# Verify role constants are correct
rg --type solidity "bytes32 public constant" -A 1

Length of output: 166


Script:

#!/bin/bash
# Description: Check role assignments for tssAddress updates using file extensions

# Expected: No remaining roles assigned to the old tssAddress and correct roles for newTSSAddress

# Find all role assignments to the old tssAddress
rg "grantRole|revokeRole" --glob "*.sol" -A 2

# Verify role constants are correct
rg "bytes32 public constant" --glob "*.sol" -A 1

Length of output: 11100


Script:

#!/bin/bash
# Description: Verify the updateTSSAddress function in ERC20CustodyUpgradeTest.sol

# Extract the updateTSSAddress function
rg "function updateTSSAddress" --glob "*.sol" -A 15 ERC20CustodyUpgradeTest.sol

# Check for revocation of WITHDRAWER_ROLE and WHITELISTER_ROLE from the old tssAddress
rg "_revokeRole(WITHDRAWER_ROLE, tssAddress)" --glob "*.sol" ERC20CustodyUpgradeTest.sol
rg "_revokeRole(WHITELISTER_ROLE, tssAddress)" --glob "*.sol" ERC20CustodyUpgradeTest.sol

# Check for granting of WITHDRAWER_ROLE and WHITELISTER_ROLE to the newTSSAddress
rg "_grantRole(WITHDRAWER_ROLE, newTSSAddress)" --glob "*.sol" ERC20CustodyUpgradeTest.sol
rg "_grantRole(WHITELISTER_ROLE, newTSSAddress)" --glob "*.sol" ERC20CustodyUpgradeTest.sol

# Verify that tssAddress is updated after revoking and granting roles
rg "tssAddress = newTSSAddress" --glob "*.sol" ERC20CustodyUpgradeTest.sol

Length of output: 936


Script:

#!/bin/bash
# Description: Verify the updateTSSAddress function in v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol

# Define the correct file path
FILE_PATH="v2/test/utils/upgrades/ERC20CustodyUpgradeTest.sol"

# Check if the file exists
if [ ! -f "$FILE_PATH" ]; then
    echo "Error: File $FILE_PATH does not exist."
    exit 1
fi

# Extract the updateTSSAddress function with surrounding lines for context
rg "function updateTSSAddress" --glob "*.sol" -A 15 "$FILE_PATH"

# Check for revocation of WITHDRAWER_ROLE and WHITELISTER_ROLE from the old tssAddress
rg "_revokeRole(WITHDRAWER_ROLE, tssAddress);" "$FILE_PATH"
rg "_revokeRole(WHITELISTER_ROLE, tssAddress);" "$FILE_PATH"

# Check for granting of WITHDRAWER_ROLE and WHITELISTER_ROLE to the newTSSAddress
rg "_grantRole(WITHDRAWER_ROLE, newTSSAddress);" "$FILE_PATH"
rg "_grantRole(WHITELISTER_ROLE, newTSSAddress);" "$FILE_PATH"

# Verify that tssAddress is updated after revoking and granting roles
rg "tssAddress = newTSSAddress;" "$FILE_PATH"

Length of output: 1353

v2/test/ZetaConnectorNative.t.sol (3)

10-10: Import statement added correctly

The import of ZetaConnectorNativeUpgradeTest.sol is appropriately included and ensures access to the upgraded contract for testing purposes.


47-47: Event WithdrawnV2 declared appropriately

The new event WithdrawnV2 is correctly declared with the intended parameters and will facilitate tracking withdrawals post-upgrade.


61-73: Deployment and initialization of proxies are correctly implemented

The UUPS proxies for GatewayEVM, ERC20Custody, and ZetaConnectorNative are properly deployed and initialized with the correct parameters. This ensures that each contract is upgradeable and maintains the expected functionality.

v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol (4)

90-93: Ensure All Custom Errors Are Declared

The OnlyWZETAOrProtocol error used in the receive function is not declared. This will cause a compilation error.


58-63: Ensure the 'CallerIsNotProtocol' Error Is Declared

The CallerIsNotProtocol error used in the onlyProtocol modifier is not declared.


134-142: Confirm Correct Implementation of WETH9 Interface

Ensure that the IWETH9 interface correctly matches the WETH9 contract, especially the withdraw and transferFrom functions, to avoid unexpected behaviors.

Run the following script to check the IWETH9 interface:

✅ Verification successful

IWETH9 Interface Implementation Confirmed

The IWETH9 interface correctly includes both transferFrom and withdraw functions as per the standard WETH9 interface.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that IWETH9 interface matches the standard WETH9 interface.

# Expectation: The IWETH9 interface should have `transferFrom` and `withdraw` functions.

ast-grep --lang solidity --pattern $'interface IWETH9 {
    function transferFrom($_) external returns ($_);
    function withdraw($_) external;
}'

Length of output: 265


Script:

#!/bin/bash
# Description: Verify that IWETH9 interface includes `transferFrom` and `withdraw` functions using ripgrep.

# Search for the IWETH9 interface
rg 'interface\s+IWETH9' -A 10

# Check for the transferFrom function within the interface
rg 'interface\s+IWETH9' -A 10 | rg 'function\s+transferFrom\s*\(.*\)\s+external\s+returns'

# Check for the withdraw function within the interface
rg 'interface\s+IWETH9' -A 10 | rg 'function\s+withdraw\s*\(.*\)\s+external'

Length of output: 5682


70-84: Validate Admin Role Assignment

When assigning roles during initialization, ensure that the admin role is correctly set and that role hierarchy is appropriately defined.

Run the following script to verify role assignments:

✅ Verification successful

Admin Role Assignment Verified

The admin address has been correctly assigned both DEFAULT_ADMIN_ROLE and PAUSER_ROLE as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that DEFAULT_ADMIN_ROLE and PAUSER_ROLE are correctly assigned.

# Expectation: The admin address should have both DEFAULT_ADMIN_ROLE and PAUSER_ROLE assigned.

ast-grep --lang solidity --pattern $'function initialize($_) public initializer {
    $$$
    _grantRole(DEFAULT_ADMIN_ROLE, $_);
    _grantRole(PAUSER_ROLE, $_);
    $$$
}'

Length of output: 291


Script:

#!/bin/bash
# Description: Verify that DEFAULT_ADMIN_ROLE and PAUSER_ROLE are correctly assigned using ripgrep.

# Expectation: The admin address should have both DEFAULT_ADMIN_ROLE and PAUSER_ROLE assigned within the initialize function.

rg -A3 -B1 "_grantRole\(DEFAULT_ADMIN_ROLE," v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol
rg -A3 -B1 "_grantRole\(PAUSER_ROLE," v2/test/utils/upgrades/GatewayZEVMUpgradeTest.sol

Length of output: 442

v2/test/GatewayEVM.t.sol (1)

10-10: Import Statement Added for Upgrade Testing

The addition of import "./utils/upgrades/GatewayEVMUpgradeTest.sol"; correctly includes the necessary contract for upgrade testing.

v2/test/GatewayZEVM.t.sol (3)

12-12: Added import for GatewayZEVMUpgradeTest

The import of GatewayZEVMUpgradeTest.sol is necessary for the upgrade tests introduced in this file.


603-628: ⚠️ Potential issue

Ensure proper initialization during proxy upgrade

In the testUpgradeAndWithdrawZRC20 function, the proxy is upgraded using:

Upgrades.upgradeProxy(proxy, "GatewayZEVMUpgradeTest.sol", "", owner);

Passing an empty string "" as the initialization data might result in the upgraded contract not being properly initialized. If the new implementation contract requires initialization, consider encoding the initializer function call and passing it as the third argument.

Please verify whether the upgraded contract requires initialization, and update the upgrade call accordingly.


36-48: New event WithdrawnV2 introduced with additional parameters

The WithdrawnV2 event adds new parameters gasfee, protocolFlatFee, and message, extending the original Withdrawn event. Ensure that all relevant parts of the code emit WithdrawnV2 where these additional parameters are needed. Also, update any systems that listen for this event to handle the new parameters.

To confirm that WithdrawnV2 is emitted correctly throughout the codebase, you can run the following shell script:

Comment on lines +36 to +46
function withdraw(
address to,
uint256 amount,
bytes32 internalSendHash
)
external
override
nonReentrant
onlyRole(WITHDRAWER_ROLE)
whenNotPaused
{
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused parameter internalSendHash in withdraw function

The parameter internalSendHash in the withdraw function is declared but not used within the function body. If it's not needed, consider removing it to simplify the function signature. If it's intended for future use or to maintain interface consistency, adding a comment explaining its purpose would improve code clarity.

Comment on lines +57 to +68
function withdrawAndCall(
address to,
uint256 amount,
bytes calldata data,
bytes32 internalSendHash
)
external
override
nonReentrant
onlyRole(WITHDRAWER_ROLE)
whenNotPaused
{
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused parameter internalSendHash in withdrawAndCall function

The parameter internalSendHash in the withdrawAndCall function is declared but not utilized within the function body. To enhance code readability, either remove the unused parameter or implement it appropriately if it's required for tracking or logging purposes.

Comment on lines +85 to +97
function withdrawAndRevert(
address to,
uint256 amount,
bytes calldata data,
bytes32 internalSendHash,
RevertContext calldata revertContext
)
external
override
nonReentrant
onlyRole(WITHDRAWER_ROLE)
whenNotPaused
{
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused parameter internalSendHash in withdrawAndRevert function

In the withdrawAndRevert function, the parameter internalSendHash is not used within the function body. Consider removing it if it's unnecessary, or ensure it's used correctly if it's meant for internal tracking.

// Forward the call to the Gateway contract
gateway.executeWithERC20(address(zetaToken), to, amount, data);

emit WithdrawnAndCalled(to, amount, data);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Event WithdrawnAndCalled is not declared

The function withdrawAndCall emits an event WithdrawnAndCalled, but this event is not declared in the contract. To prevent compilation errors and ensure proper event logging, please declare the event before emitting it.

Consider adding the event declaration:

event WithdrawnAndCalled(address indexed to, uint256 amount, bytes data);

// Forward the call to the Gateway contract
gateway.revertWithERC20(address(zetaToken), to, amount, data, revertContext);

emit WithdrawnAndReverted(to, amount, data, revertContext);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Event WithdrawnAndReverted is not declared

The function withdrawAndRevert emits an event WithdrawnAndReverted, but this event is not declared in the contract. Ensure that you declare the event to avoid compilation issues and to facilitate accurate event tracking.

Consider adding the event declaration:

event WithdrawnAndReverted(address indexed to, uint256 amount, bytes data, RevertContext revertContext);

Comment on lines +415 to +416
emit Called(msg.sender, zrc20, receiver, message, callOptions, revertOptions);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Declare the 'Called' Event Before Emitting It

The Called event is emitted in the _call function but is not declared in the contract. This will result in a compilation error.

Declare the Called event with the appropriate parameters:

+    /// @notice Event emitted when a cross-chain call is initiated.
+    event Called(
+        address indexed sender,
+        address zrc20,
+        bytes receiver,
+        bytes message,
+        CallOptions callOptions,
+        RevertOptions revertOptions
+    );

Committable suggestion was skipped due to low confidence.

@@ -40,6 +40,8 @@ contract ERC20CustodyTest is Test, IGatewayEVMErrors, IGatewayEVMEvents, IReceiv
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
error LegacyMethodsNotSupported();

event WithdrawnV2(address indexed to, address indexed token, uint256 amount);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider renaming the WithdrawnV2 event for clarity

The event name WithdrawnV2 indicates versioning by appending V2. To enhance readability and maintainability, consider renaming the event to better reflect its purpose in the upgraded contract. A more descriptive name can provide clarity on the event's functionality without relying on version numbers.

Comment on lines +558 to +587

function testUpgradeAndWithdraw() public {
// upgrade
Upgrades.upgradeProxy(address(custody), "ERC20CustodyUpgradeTest.sol", "", owner);
ERC20CustodyUpgradeTest custodyV2 = ERC20CustodyUpgradeTest(address(custody));
// withdraw
uint256 amount = 100_000;
uint256 balanceBefore = token.balanceOf(destination);
assertEq(balanceBefore, 0);
uint256 balanceBeforeCustody = token.balanceOf(address(custodyV2));

bytes memory transferData = abi.encodeWithSignature("transfer(address,uint256)", address(destination), amount);
vm.expectCall(address(token), 0, transferData);
vm.expectEmit(true, true, true, true, address(custodyV2));
emit WithdrawnV2(destination, address(token), amount);
vm.prank(tssAddress);
custodyV2.withdraw(destination, address(token), amount);

// Verify that the tokens were transferred to the destination address
uint256 balanceAfter = token.balanceOf(destination);
assertEq(balanceAfter, amount);

// Verify that the tokens were substracted from custody
uint256 balanceAfterCustody = token.balanceOf(address(custodyV2));
assertEq(balanceAfterCustody, balanceBeforeCustody - amount);

// Verify that gateway doesn't hold any tokens
uint256 balanceGateway = token.balanceOf(address(gateway));
assertEq(balanceGateway, 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add assertions to verify successful contract upgrade

In the testUpgradeAndWithdraw function, consider adding assertions to confirm that the contract has been successfully upgraded to ERC20CustodyUpgradeTest. This could include:

  • Checking new functions or variables introduced in the upgraded contract.
  • Verifying that the contract's storage layout matches expectations after the upgrade.

Adding these assertions ensures that the upgrade process works correctly and the contract behaves as intended post-upgrade.

Comment on lines +370 to +394
function testUpgradeAndForwardCallToReceivePayable() public {
// upgrade
Upgrades.upgradeProxy(address(gateway), "GatewayEVMUpgradeTest.sol", "", owner);
GatewayEVMUpgradeTest gatewayUpgradeTest = GatewayEVMUpgradeTest(address(gateway));
// call
address custodyBeforeUpgrade = gateway.custody();
address tssBeforeUpgrade = gateway.tssAddress();

string memory str = "Hello, Foundry!";
uint256 num = 42;
bool flag = true;
uint256 value = 1 ether;

bytes memory data = abi.encodeWithSignature("receivePayable(string,uint256,bool)", str, num, flag);
vm.expectCall(address(receiver), value, data);
vm.expectEmit(true, true, true, true, address(receiver));
emit ReceivedPayable(address(gateway), value, str, num, flag);
vm.expectEmit(true, true, true, true, address(gateway));
emit ExecutedV2(address(receiver), value, data);
vm.prank(tssAddress);
gatewayUpgradeTest.execute{ value: value }(address(receiver), data);

assertEq(custodyBeforeUpgrade, gateway.custody());
assertEq(tssBeforeUpgrade, gateway.tssAddress());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance State Validation in Upgrade Test

In the testUpgradeAndForwardCallToReceivePayable function, while you verify that custody and tssAddress remain unchanged after the upgrade, consider extending your assertions to include other critical state variables and roles. This ensures comprehensive validation of the contract's state integrity post-upgrade.

Comment on lines +603 to +628
function testUpgradeAndWithdrawZRC20() public {
// upgrade
Upgrades.upgradeProxy(proxy, "GatewayZEVMUpgradeTest.sol", "", owner);
GatewayZEVMUpgradeTest gatewayUpgradeTest = GatewayZEVMUpgradeTest(proxy);
// withdraw
uint256 amount = 1;
uint256 ownerBalanceBefore = zrc20.balanceOf(owner);

vm.expectEmit(true, true, true, true, address(gatewayUpgradeTest));
emit WithdrawnV2(
owner,
0,
abi.encodePacked(addr1),
address(zrc20),
amount,
0,
zrc20.PROTOCOL_FLAT_FEE(),
"",
CallOptions({ gasLimit: 0, isArbitraryCall: true }),
revertOptions
);
gatewayUpgradeTest.withdraw(abi.encodePacked(addr1), amount, address(zrc20), revertOptions);

uint256 ownerBalanceAfter = zrc20.balanceOf(owner);
assertEq(ownerBalanceBefore - amount, ownerBalanceAfter);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expand test coverage for upgraded contract

While the testUpgradeAndWithdrawZRC20 function tests the withdrawal functionality post-upgrade, consider adding tests for other functionalities of the upgraded contract to ensure comprehensive coverage.

@skosito skosito merged commit 282fdaa into main Oct 14, 2024
11 checks passed
@skosito skosito deleted the erc20custody-connector-upgradable branch October 14, 2024 08:59
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.

Make erc20 custody and connectors upgradable
4 participants