stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: low
Valid

Wrong CCIP fee calculation in `WrappedTokenBridge` for bridging `wstLink`

Summary

The fee to transfer to the CCIP router to send a message is calculated with a hardcoded amount of 1000 wstLink tokens. However, a user can send any amount of tokens.

That leads to either the user providing more tokens than needed and the WrappedTokenBridge getting the refunded tokens, or not sufficient amount of tokens for the fee and the user not being able to send such big amount of tokens.

Vulnerability Details

Inside WrappedTokenBridge we can see the following function to obtain the fee needed to pay to the CCIP router to send a message:

function getFee(uint64 _destinationChainSelector, bool _payNative) external view returns (uint256) {
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
address(this),
1000 ether, <-
_payNative ? address(0) : address(linkToken)
);
return IRouterClient(this.getRouter()).getFee(_destinationChainSelector, evm2AnyMessage);
}

Here the user can encounter 2 different situations:

  1. The amount of wstLink that the user wants to bridge requires LESS amount of CCIP fee

In this situation the following will happen.
- The user will initiate the transfer via transferAndCall() on the stLink token
- The contract will wrap the transfered tokens into wstLink
- The contract will compute the fee to transfer to the router in order to execute the CCIP transfer taking into account 1000 tokens
- The contract will transfer the computed amount from the user to the router
- Since the real CCIP message needed less amount of fee than the computed, the router will refund the remaining linkToken to the contract
The result will be the user having paid more than needed to transfer his tokens

  1. The amount of wstLink that the user wants to bridge requires MORE amount of CCIP fee

In this situation the following will happen.
- The user will initiate the transfer via transferAndCall() on the stLink token
- The contract will wrap the transfered tokens into wstLink
- The contract will compute the fee to transfer to the router in order to execute the CCIP transfer taking into account 1000 tokens
- The contract will transfer the computed amount from the user to the router
- Since the real CCIP message requires more funds to execute the message, the CCIP call will fail because of not providing enough funds to pay the fee
The result will be the user getting his transaction reverted

Impact

Medium

Tools Used

Manual review

Recommendations

Compute the fee to send the CCIP message with the real amount of tokens to transfer.

To do that we need a function that simulates the wrapping of the tokens in order to know the real amount to transfer and then calculate the fee.

+ function simulateWrapping(uint256 _amount) public pure {
+ uint256 preWrapBalance = wrappedToken.balanceOf(address(this));
+ wrappedToken.wrap(_amount);
+ uint256 amountToTransfer = wrappedToken.balanceOf(address(this)) - preWrapBalance;
+ assembly {
+ let fmp := mload(0x40)
+ mstore(fmp, amountToTransfer)
+ revert(fmp, 32)
+ }
+ }
- function getFee(uint64 _destinationChainSelector, bool _payNative) external view returns (uint256) {
+ function getFee(uint64 _destinationChainSelector, bool _payNative, uint256 _amountOfStlink) external view returns (uint256) {
+ ( , bytes memory data) = address(this).call(abi.encodeWithSignature("simulateWrapping(uint256)", _amountOfStlink));
+ uint256 amountOfWstlink = abi.decode(data, (uint256));
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
address(this),
- 1000 ether,
+ amountOfWstlink,
_payNative ? address(0) : address(linkToken)
);
return IRouterClient(this.getRouter()).getFee(_destinationChainSelector, evm2AnyMessage);
}
Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0kage Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

1000 ether

Support

FAQs

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