First Flight #12: Kitty Connect

First Flight #12: Kitty Connect
Beginner FriendlyFoundryNFTGameFi
100 EXP
View results
Submission Details
Severity: high
Valid

Missing fee token approval for `IRouterClient` in `bridgeNftWithData`

Summary

The bridgeNftWithData function in the KittyBridge contract attempts to bridge Kitties across chains using Chainlink's CCIP and requires a fee in LINK tokens to be paid to the IRouterClient contract. However, it does not include a step to approve the router contract to spend the required LINK fee on behalf of the KittyBridge contract.

Vulnerability Details

ERC-20 tokens, such as LINK, require an owner to approve a spender to transfer tokens up to a specified allowance. The bridgeNftWithData function calculates the necessary fees in LINK tokens for the bridging operation and checks if the contract has sufficient LINK token balance. However, it overlooks the need to set an allowance for the router contract, assuming the contract's balance is sufficient for the fee. This assumption fails when the router contract attempts to run transfer LINK tokens from the KittyBridge contract to itself without the necessary approval, causing the transaction to revert.

Impact

This vulnerability renders the bridgeNftWithData function inoperative, as it will always fail when attempting to bridge NFTs due to the inability to transfer LINK tokens as fees.

Tools Used

Foundry, Manual Inspection

Proof of Code

Code Add the following code to the `KittyTest.t.sol` file:
function test_bridgeNftWithData_FailsIfGasTokenNotApproved() public {
vm.prank(kittyConnectOwner);
kittyBridge.allowlistSender(networkConfig.router, true);
// ******************** Bridge NFT with Data ********************
string
memory _catImage = "ipfs://QmbxwGgBGrNdXPm84kqYskmcMT3jrzBN8LzQjixvkz4c62";
string memory _catName = "Meowdy";
string memory _catBreed = "Ragdoll";
uint256 _catDob = block.timestamp;
uint256 _tokenId = kittyConnect.getTokenCounter();
vm.prank(partnerA);
kittyConnect.mintCatToNewOwner(
user,
_catImage,
_catName,
_catBreed,
_catDob
);
// Before bridging, confirm that the Bridge does not have enough LINK allowance for fees
uint256 _kittyBridgeLinkAllowanceToRouter = IERC20(
kittyBridge.getLinkToken()
).allowance(address(kittyBridge), address(networkConfig.router));
assertEq(_kittyBridgeLinkAllowanceToRouter, 0);
vm.prank(user);
vm.expectRevert("ERC20: insufficient allowance");
kittyConnect.bridgeNftToAnotherChain(
networkConfig.otherChainSelector,
address(kittyBridge),
_tokenId
);
// *******************************************************
}

Recommended Mitigation

Implement a LINK token approval step within the bridgeNftWithData function or as part of the initial setup/configuration of the KittyBridge contract to grant the router contract an allowance to spend LINK tokens on its behalf. This approval should be for an amount at least equal to the anticipated fee for the operation.

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);
// Code below stays the same
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Missing fee token approval

Support

FAQs

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