Summary
The KittyBridge::bridgeNftWithData()
function facilitates cross-chain token transfers triggered by KittyConnect::bridgeNftToAnotherChain
.
This process involves utilizing the CCIP router. Despite accurate fee calculation, the function fails to approve the fee token (Link) prior to
invoking router.ccipSend()
.
Vulnerability Details
function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _data, address(s_linkToken));
IRouterClient router = IRouterClient(this.getRouter());
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this))) {
revert KittyBridge__NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
}
@>
@> messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit MessageSent(messageId, _destinationChainSelector, _receiver, _data, address(s_linkToken), fees);
return messageId;
}
Impact
Any attempts to transfer NFTs to another chain will result in a transaction revert.
Tools Used
Foundry and manual review
Recommendations
Ensure approval for the Link token is added before invoking router.ccipSend()
.
function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _data, address(s_linkToken));
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this))) {
revert KittyBridge__NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
}
++ s_linkToken.approve(address(router), fees);
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit MessageSent(messageId, _destinationChainSelector, _receiver, _data, address(s_linkToken), fees);
return messageId;
}