Skip to content

feat: handle failed transfers inside onAbort #44

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

Merged
merged 13 commits into from
Mar 13, 2025
Merged

feat: handle failed transfers inside onAbort #44

merged 13 commits into from
Mar 13, 2025

Conversation

fadeev
Copy link
Member

@fadeev fadeev commented Jan 24, 2025

onAbort handles two scenarios:

  • A very common one: transfer from one connected chain to another fails due to insufficient amount, which is also insufficient to make a revert back to the source chain. onCall fails, the protocol cannot make a revert, onAbort is called on ZetaChain and the token is transferred to the original sender on ZetaChain.
  • A less common one: transfer from ZetaChain to a connected chain fails and onRevert on ZetaChain also fails. Since the logic in onRevert and onAbort is identical right now, this will not do much.

Testing with zeta-chain/localnet#103

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced cross-chain transfer handling for both NFTs and tokens, ensuring that assets are automatically recovered if a transfer fails.
    • Added functionality that enables the contracts to receive Ether directly.
    • Introduced new events for tracking aborted token transfers.
  • Chores

    • Upgraded underlying libraries and dependencies to newer release candidate versions for improved stability and performance.
  • Bug Fixes

    • Adjusted error handling mechanisms for improved clarity during transfer failures.

Copy link

coderabbitai bot commented Jan 24, 2025

📝 Walkthrough

Walkthrough

This pull request updates multiple components of the NFT and token contracts along with project configuration files. Changes include adjusting revert option parameters, modifying function visibility, and adding new functions—such as a payable receive function and an onAbort handler—to improve cross-chain transfer error handling. Additionally, new events have been introduced to track aborted transfers. Updates to dependency versions in package files and minor script adjustments are also included.

Changes

File(s) Change Summary
contracts/nft/.gitignore Added the test-ledger entry to the ignored list (unchanged +.openzeppelin remains).
contracts/nft/contracts/evm/UniversalNFTCore.sol
contracts/token/contracts/evm/UniversalTokenCore.sol
Replaced address(0) with universal in RevertOptions; added a new receive() external payable function; changed the visibility of onRevert in the NFT version.
contracts/nft/contracts/shared/UniversalNFTEvents.sol
contracts/token/contracts/shared/UniversalTokenEvents.sol
Added a new event TokenTransferAborted (with parameters specific to NFTs and tokens, respectively).
contracts/nft/contracts/zetachain/UniversalNFTCore.sol
contracts/token/contracts/zetachain/UniversalTokenCore.sol
Updated RevertOptions to include the receiver address and introduced a new onAbort function for handling aborted transfers (with message decoding, minting back assets, and event emission).
contracts/nft/package.json
contracts/token/package.json
Upgraded dependency versions for @zetachain/protocol-contracts from 11.0.0-rc3 to 12.0.0-rc1.
contracts/nft/scripts/localnet.sh
contracts/token/scripts/localnet.sh
Increased sleep duration from 10 to 15 seconds for localnet initialization in the NFT script and from 25 to 15 seconds in the token script.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant C as Contract
    participant G as Gateway

    U->>C: transferCrossChain(parameters)
    C->>G: Forward transfer request
    G-->>C: Error/revert message received
    alt Revert Handling
        C-->>C: Execute onRevert(context)
    else Abort Handling
        C-->>C: Execute onAbort(context)
        C->>U: Mint asset back to sender
        C->>C: Emit TokenTransferAborted event
    end
Loading

Possibly related PRs

Suggested reviewers

  • lumtis
  • 0xM3R
  • skosito
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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 generate docstrings to generate docstrings for this 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.

@fadeev fadeev marked this pull request as ready for review January 30, 2025 16:02
@fadeev fadeev requested a review from a team as a code owner January 30, 2025 16:02
context.revertMessage,
(address, uint256, string, address)
);
_safeMint(sender, tokenId);
Copy link

Choose a reason for hiding this comment

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

do we need some validation of sender or other params here?

Copy link
Member Author

Choose a reason for hiding this comment

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

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

🧹 Nitpick comments (2)
contracts/nft/.gitignore (1)

21-22: Validate the inclusion of configuration directories in ignore rules.

The addition of .openzeppelin and test-ledger ensures that local configuration artifacts and test ledger data are excluded from version control, which aligns with best practices for keeping the repository clean. Please confirm that ignoring .openzeppelin does not omit essential deployment artifacts, since some projects might track parts of it.

contracts/nft/scripts/localnet.sh (1)

7-7: Extended Sleep Duration for Local Network Startup
The change from a 10-second to a 15-second sleep helps ensure that the Hardhat local network has enough time to initialize before subsequent commands are executed. While this works in practice, consider using a readiness check (e.g., polling a specific endpoint or checking port availability) instead of a fixed sleep duration in the future. This would make the startup process more dynamic and potentially reduce unnecessary delays.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32d34c7 and 985dc0e.

⛔ Files ignored due to path filters (2)
  • contracts/nft/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • contracts/token/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • contracts/nft/.gitignore (1 hunks)
  • contracts/nft/contracts/evm/UniversalNFTCore.sol (4 hunks)
  • contracts/nft/contracts/shared/UniversalNFTEvents.sol (1 hunks)
  • contracts/nft/contracts/zetachain/UniversalNFTCore.sol (3 hunks)
  • contracts/nft/package.json (2 hunks)
  • contracts/nft/scripts/localnet.sh (1 hunks)
  • contracts/token/contracts/evm/UniversalTokenCore.sol (3 hunks)
  • contracts/token/contracts/shared/UniversalTokenEvents.sol (1 hunks)
  • contracts/token/contracts/zetachain/UniversalTokenCore.sol (3 hunks)
  • contracts/token/package.json (2 hunks)
🔇 Additional comments (20)
contracts/token/package.json (1)

24-24: Dependency updates align with PR objectives

The upgrades to @zetachain/localnet (5.0.0-rc1) and @zetachain/protocol-contracts (12.0.0-rc1) support the onAbort functionality required for handling failed transfers, as mentioned in the PR objectives. The re-addition of @typechain/ethers-v5 ensures proper TypeScript support for the Ethereum contracts.

Also applies to: 31-31, 60-60

contracts/token/contracts/shared/UniversalTokenEvents.sol (1)

15-15: New event added for tracking aborted transfers

This new event TokenTransferAborted enables tracking when token transfers are aborted, which directly supports the PR's objective of handling failed transfers inside onAbort. The event includes the sender address and amount, providing essential information for monitoring and debugging purposes.

contracts/nft/contracts/shared/UniversalNFTEvents.sol (1)

24-29: New event for tracking aborted NFT transfers

The TokenTransferAborted event is appropriately designed for NFT transfers with parameters for tracking the sender, tokenId, URI, and direction (outgoing). The outgoing boolean parameter helps distinguish between NFTs being transferred from ZetaChain to connected chains versus the opposite direction, which aligns with the two scenarios described in the PR objectives.

contracts/nft/package.json (1)

34-34: Dependency updates consistent with token package

The upgrades to @zetachain/localnet (5.0.0-rc1) and @zetachain/protocol-contracts (12.0.0-rc1) maintain consistency with the token package and support the onAbort functionality needed for handling failed NFT transfers as described in the PR objectives.

Also applies to: 65-65

contracts/token/contracts/evm/UniversalTokenCore.sol (3)

130-130: Good update using universal instead of the zero address

Changing the RevertOptions parameter to use universal instead of address(0) provides better semantic clarity and ensures the revert operation is properly directed to the universal contract.


139-139: Good update using universal instead of the zero address

Similar to the previous change, using universal instead of a zero address in the RevertOptions is a good practice that improves semantics and ensures proper routing of revert operations.


189-189: Good addition of a receive function

Adding a receive function allows the contract to accept Ether transfers, which is necessary for the payable functions like transferCrossChain. Without this function, direct Ether transfers to the contract would fail.

contracts/token/contracts/zetachain/UniversalTokenCore.sol (4)

166-168: Good update to the RevertOptions parameters

The change to use address(this) for the revert destination and including the receiver address in the encoded message provides more context during revert operations, which helps with proper error handling.


235-235: Good update to include the receiver in the encoded message

Including the receiver address in the revert message provides more context for the revert operation and aligns with the changes in other parts of the contract.


248-251: Updated decoding to match new message format

The decoding structure has been correctly updated to match the new message format, which now includes the receiver address as the first parameter.


256-263: Excellent addition of the onAbort function

This new function is a key part of the PR objectives, handling failed transfers by minting tokens back to the original sender on ZetaChain. The implementation correctly decodes the revert message, mints the tokens, and emits an appropriate event.

This function handles the scenario described in the PR where transfers fail due to insufficient tokens and ensures tokens are returned to the sender.

contracts/nft/contracts/zetachain/UniversalNFTCore.sol (5)

172-174: Good update to the RevertOptions parameters

Using address(this) for the revert destination and including complete context (receiver, tokenId, uri, sender) in the encoded message provides more information during revert operations.


248-248: Good update to include complete context in the encoded message

Including the receiver, tokenId, uri, and sender in the revert message provides all necessary information for proper handling during revert operations.


261-264: Updated decoding to match new message format

The decoding structure has been correctly updated to match the new message format, which now includes the receiver address as the first parameter.


270-287: Excellent addition of the onAbort function with detailed documentation

This function is a critical improvement that handles failed transfers by minting NFTs back to the original sender on ZetaChain. The implementation correctly:

  1. Decodes the revert message to extract tokenId, URI, and sender information
  2. Safely mints the NFT back to the sender
  3. Sets the token URI
  4. Emits a detailed event with the context of the aborted transfer

The comprehensive documentation clearly explains the function's purpose and the scenario it addresses, which enhances code maintainability.


284-284: Consider adding sender validation

While ERC-721 does check if the receiver address is zero in _safeMint, you might want to add an explicit check that sender != address(0) before minting to ensure the token isn't lost if the sender address is somehow invalid.

contracts/nft/contracts/evm/UniversalNFTCore.sol (4)

131-131: Good update using universal instead of the zero address

Changing the RevertOptions parameter to use universal instead of address(0) provides better semantic clarity and ensures the revert operation is properly directed to the universal contract.


140-140: Good update using universal instead of the zero address

Similar to the previous change, using universal instead of a zero address in the RevertOptions is a good practice that improves semantics and ensures proper routing of revert operations.


229-229: Good addition of a receive function

Adding a receive function allows the contract to accept Ether transfers, which is necessary for the payable functions like transferCrossChain. Without this function, direct Ether transfers to the contract would fail.


185-185:

❓ Verification inconclusive

Security consideration: onRevert no longer restricted to gateway

Removing the onlyGateway modifier means this function can now be called by any address, not just the gateway contract. This could potentially allow unauthorized users to mint tokens by calling onRevert directly.

Is this intentional? If so, ensure there are other safeguards in place to prevent misuse of this function.


🏁 Script executed:

#!/bin/bash
# Check if onRevert is called from anywhere other than the gateway in the codebase

echo "Searching for onRevert calls in the codebase..."
rg -A 2 -B 2 "onRevert\(" --type sol

Length of output: 171


Security: Confirm onRevert Access Restrictions

The removal of the onlyGateway modifier in contracts/nft/contracts/evm/UniversalNFTCore.sol means that the onRevert function is now callable by any address, which could enable unauthorized minting unless other safeguards are in place. The initial search for invocations of onRevert using rg with the Solidity file type filter did not yield results due to file type recognition issues.

To double-check its usage across the codebase, please run a more inclusive search such as:

#!/bin/bash
echo "Searching for onRevert invocations using fd and grep..."
fd -e sol | xargs grep -R "onRevert(" -n

Ensure that all external calls to onRevert are intentional and that adequate security measures (e.g., alternative access controls) protect against unauthorized use.

@fadeev fadeev marked this pull request as draft March 12, 2025 15:40
@fadeev fadeev linked an issue Mar 12, 2025 that may be closed by this pull request
@fadeev fadeev marked this pull request as ready for review March 12, 2025 16:48
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

🧹 Nitpick comments (3)
contracts/token/scripts/localnet.sh (1)

7-7: Evaluate the Reduced Sleep Duration

The changed line reduces the fixed waiting period from a longer duration (previously 25 seconds) to 15 seconds after launching the local network. While this speeds up the script execution, please verify that 15 seconds is consistently sufficient for the network to initialize. For increased reliability, consider switching to a dynamic readiness check (e.g., polling via npx hardhat localnet-check in a loop) instead of a static sleep.

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

262-269: Add NatSpec documentation to the onAbort function

The newly added onAbort function implements the necessary logic for handling aborted transfers, but it's missing NatSpec documentation that explains its purpose and parameters, unlike other functions in the contract.

+    /**
+     * @notice Handles a cross-chain call abortion and returns tokens to the sender.
+     * @dev This function is called by the Gateway contract when a cross-chain transfer is aborted.
+     * @param context Metadata about the aborted call.
+     */
     function onAbort(AbortContext calldata context) external onlyGateway {

Also, consider whether the onAbort function should handle the case where context.amount > 0 && context.asset != address(0) similar to onRevert, or if there's a specific reason for the different behavior.


248-251: Consider extracting common decoding logic

The decoding logic for context.revertMessage is duplicated in both onRevert and onAbort. Consider extracting this into a private helper function to improve maintainability and reduce duplication.

+    /**
+     * @dev Helper function to decode revert message.
+     * @param revertMessage The encoded revert message.
+     * @return receiver The address of the intended receiver.
+     * @return amount The amount of tokens to be returned.
+     * @return sender The address of the original sender.
+     */
+    function _decodeRevertMessage(bytes memory revertMessage) 
+        private 
+        pure 
+        returns (address receiver, uint256 amount, address sender) 
+    {
+        return abi.decode(
+            revertMessage,
+            (address, uint256, address)
+        );
+    }
+
     function onRevert(RevertContext calldata context) external onlyGateway {
-        (, uint256 amount, address sender) = abi.decode(
-            context.revertMessage,
-            (address, uint256, address)
-        );
+        (, uint256 amount, address sender) = _decodeRevertMessage(context.revertMessage);
         _mint(sender, amount);
         emit TokenTransferReverted(sender, amount);

         if (context.amount > 0 && context.asset != address(0)) {
             if (!IZRC20(context.asset).transfer(sender, context.amount)) {
                 revert TransferFailed();
             }
         }
     }

     function onAbort(AbortContext calldata context) external onlyGateway {
-        (, uint256 amount, address sender) = abi.decode(
-            context.revertMessage,
-            (address, uint256, address)
-        );
+        (, uint256 amount, address sender) = _decodeRevertMessage(context.revertMessage);
         _mint(sender, amount);
         emit TokenTransferAborted(sender, amount);
     }

Also applies to: 262-269

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 985dc0e and 56353cc.

⛔ Files ignored due to path filters (2)
  • contracts/nft/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • contracts/token/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • contracts/nft/contracts/evm/UniversalNFTCore.sol (3 hunks)
  • contracts/nft/contracts/zetachain/UniversalNFTCore.sol (4 hunks)
  • contracts/nft/package.json (1 hunks)
  • contracts/token/contracts/evm/UniversalToken.sol (0 hunks)
  • contracts/token/contracts/zetachain/UniversalTokenCore.sol (4 hunks)
  • contracts/token/package.json (1 hunks)
  • contracts/token/scripts/localnet.sh (1 hunks)
💤 Files with no reviewable changes (1)
  • contracts/token/contracts/evm/UniversalToken.sol
🚧 Files skipped from review as they are similar to previous changes (4)
  • contracts/token/package.json
  • contracts/nft/package.json
  • contracts/nft/contracts/evm/UniversalNFTCore.sol
  • contracts/nft/contracts/zetachain/UniversalNFTCore.sol
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: slither (contracts/token, token.sarif)
  • GitHub Check: slither (contracts/nft, nft.sarif)
🔇 Additional comments (3)
contracts/token/contracts/zetachain/UniversalTokenCore.sol (3)

166-167: Enhanced revert handling with proper context

The updated RevertOptions now uses address(this) instead of address(0) for the revertToAddress parameter, ensuring that the revert message is properly routed back to this contract. The encoded parameters now include the receiver address, which provides more comprehensive context for debugging and transaction tracking.


235-235: Consistent revert message pattern

The encoded parameters in the RevertOptions have been updated to match the structure used in transferCrossChain, which provides consistency in error handling across different functions.


248-251: Updated decoding to match new message format

The decoding of context.revertMessage has been updated to extract the receiver address that was added to the encoded message, ensuring proper handling of reverted transfers.

@fadeev fadeev requested review from skosito and a team and removed request for skosito March 12, 2025 17:20
@fadeev
Copy link
Member Author

fadeev commented Mar 12, 2025

@s2imonovic please, review.

Copy link

@s2imonovic s2imonovic left a comment

Choose a reason for hiding this comment

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

looks good, just one question

@@ -128,7 +128,7 @@ abstract contract UniversalNFTCore is
gateway.call(

Choose a reason for hiding this comment

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

whats going on with the native tokens in the case if destination == address(0) and msg.value > 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. Created an issue to handle this in a separate PR.

#59

@@ -127,7 +127,7 @@ abstract contract UniversalTokenCore is
gateway.call(

Choose a reason for hiding this comment

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

same question here: whats going on with the native tokens in the case if destination == address(0) and msg.value > 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will address both questions in a separate PR, see #59

@fadeev fadeev merged commit 231cd17 into main Mar 13, 2025
13 checks passed
@fadeev fadeev deleted the on-abort branch March 13, 2025 13:02
@fadeev fadeev added the feat label May 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants