stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: medium
Invalid

Potential Token Locking from Failed Cross-Chain Transfers

Summary

When a user transfers an reSDL NFT from a secondary chain back to the primary chain using RESDLTokenBridge, the NFT is burned on the secondary before the tx is sent to the primary chain.

If the cross-chain tx fails, the user would have lost access on both chains - the NFT was burned on the secondary, but never minted on the primary.

Vulnerability Details

The cross-chain transfer functionality enables users to move their reSDL NFTs between the primary and secondary chains using the RESDLTokenBridge contract.

This process relies on:

  1. Burning the NFT on the source chain

  2. Sending a CCIP message to mint a new NFT on the destination chain

This creates a vulnerability where a failed cross-chain transfer leads to permanent loss of access.

Some potential failure cases:

  • Blocked by frontrunning

  • CCIP tx reverts due to issues with extraArgs

  • Temporary network congestion

An attacker could manipulate conditions to increase the failure rate of cross-chain txs. For example:

  1. Frontrun and manipulate the state to frequently revert

  2. Craft malicious CCIP router to intermittently revert messages

  3. Spam network to create congestion

By forcing failures, attacker could exploit users and cause loss of funds.

Impact

The root cause lies in burning tokens on one chain before the minting operation completes on the other chain. If the cross-chain transaction fails after burning the NFT, the user permanently loses access as the NFT no longer exists on either chain.

A failed cross-chain transfer can lead to irrecoverable locking of user tokens, causing permanent loss of access.

Here is the key section of RESDLTokenBridge.sol that burns the NFT on the secondary chain before attempting the transfer: RESDLTokenBridge.sol#transferRESDL

function transferRESDL(
uint64 _destinationChainSelector,
address _receiver,
uint256 _tokenId,
bool _payNative,
uint256 _maxLINKFee
) external payable returns (bytes32 messageId) {
if (msg.sender != sdlPool.ownerOf(_tokenId)) revert SenderNotAuthorized();
if (_receiver == address(0)) revert InvalidReceiver();
if (_payNative == false && msg.value != 0) revert InvalidMsgValue();
(address destination, ISDLPool.RESDLToken memory reSDLToken) = sdlPoolCCIPController.handleOutgoingRESDL(
_destinationChainSelector,
msg.sender,
_tokenId
);
bytes memory extraArgs = extraArgsByChain[_destinationChainSelector];
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_tokenId,
reSDLToken,
destination,
_payNative ? address(0) : address(linkToken),
extraArgs
);
uint256 fees = IRouterClient(sdlPoolCCIPController.getRouter()).getFee(_destinationChainSelector, evm2AnyMessage);
if (_payNative) {
if (fees > msg.value) revert InsufficientFee();
messageId = sdlPoolCCIPController.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage);
if (fees < msg.value) {
(bool success, ) = msg.sender.call{value: msg.value - fees}("");
if (!success) revert TransferFailed();
}
} else {
if (fees > _maxLINKFee) revert FeeExceedsLimit();
linkToken.safeTransferFrom(msg.sender, address(sdlPoolCCIPController), fees);
messageId = sdlPoolCCIPController.ccipSend(_destinationChainSelector, evm2AnyMessage);
}
emit TokenTransferred(
messageId,
_destinationChainSelector,
msg.sender,
_receiver,
_tokenId,
_payNative ? address(0) : address(linkToken),
fees
);
}

As you can see, it first calls the burn function of the SDLPool to permanently destroy the NFT on the secondary chain.

After that, it builds the CCIP message and sends it cross-chain.

So if ccipSend fails for any reason, the NFT has already been burned and is unrecoverable on that chain. That could lead to permanent loss of access for the user.

Tools Used

Manual Review

Recommendations

  1. Implement retries with exponential backoff

  2. Support withdrawal from minted NFT while in transit

  3. Provide emergency user balance recovery

Adding failure handling logic would help minimize and contain the potential damage from disruptions.

Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.