NFTBridge
60,000 USDC
View results
Submission Details
Severity: low
Invalid

Unused return values in function call, resulting in information loss or increased risk

Summary

Several functions within the ethereum::src::Bridge::Starklane contract invoke external contract functions but fail to utilize their return values, leading to potential information loss or increased risk.

  1. The depositTokens function calls sendMessageToL2 on the IStarknetMessaging contract, which returns a tuple (bytes32, uint256) representing the hash of the message sent and its nonce.

  2. The startRequestCancellation function calls startL1ToL2MessageCancellation, also on IStarknetMessaging, which returns a bytes32 representing the hash of the message being canceled.

  3. The cancelRequest function invokes cancelL1ToL2Message, returning a bytes32 hash of the canceled message.

In all these instances, failing to capture and use the return values could lead to incorrect assumptions about the state of the contract or transaction, making it harder to debug or verify their execution.

Vulnerability Details

  1. depositTokens function:

Interface of the sendMessageToL2 function, which returns the hash of the message and its nonce:

/**
Sends a message to an L2 contract.
This function is payable, the payed amount is the message fee.
Returns the hash of the message and the nonce of the message.
*/
function sendMessageToL2(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload
@> ) external payable returns (bytes32, uint256);

The code that calls sendMessageToL2 function in ethereum::src::Bridge::depositTokens doesn't utilize any returned values:

@> IStarknetMessaging(_starknetCoreAddress).sendMessageToL2{value: msg.value}(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload
);
  1. startRequestCancellation function:

Interface of the startL1ToL2MessageCancellation function:

/**
Starts the cancellation of an L1 to L2 message.
A message can be canceled messageCancellationDelay() seconds after this function is called.
Note: This function may only be called for a message that is currently pending and the caller
must be the sender of the that message.
*/
function startL1ToL2MessageCancellation(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload,
uint256 nonce
@> ) external returns (bytes32);

Implementation of the startL1ToL2MessageCancellation function in the StarknetMessaging contract, which returns the message hash:

function startL1ToL2MessageCancellation(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload,
uint256 nonce
) external override returns (bytes32) {
emit MessageToL2CancellationStarted(msg.sender, toAddress, selector, payload, nonce);
bytes32 msgHash = getL1ToL2MsgHash(toAddress, selector, payload, nonce);
uint256 msgFeePlusOne = l1ToL2Messages()[msgHash];
require(msgFeePlusOne > 0, "NO_MESSAGE_TO_CANCEL");
l1ToL2MessageCancellations()[msgHash] = block.timestamp;
@> return msgHash;
}

The code that calls startL1ToL2MessageCancellation function in ethereum::src::Bridge::startRequestCancellation doesn't utilize its returned value:

@> IStarknetMessaging(_starknetCoreAddress).startL1ToL2MessageCancellation(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload,
nonce
);
  1. cancelRequest function:

Interface of the cancelL1ToL2Message function:

/**
Cancels an L1 to L2 message, this function should be called at least
messageCancellationDelay() seconds after the call to startL1ToL2MessageCancellation().
A message may only be cancelled by its sender.
If the message is missing, the call will revert.
Note that the message fee is not refunded.
*/
function cancelL1ToL2Message(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload,
uint256 nonce
@> ) external returns (bytes32);

Implementation of the cancelL1ToL2Message function in the StarknetMessaging contract, which returns the message hash:

function cancelL1ToL2Message(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload,
uint256 nonce
) external override returns (bytes32) {
emit MessageToL2Canceled(msg.sender, toAddress, selector, payload, nonce);
// Note that the message hash depends on msg.sender, which prevents one contract from
// cancelling another contract's message.
// Trying to do so will result in NO_MESSAGE_TO_CANCEL.
bytes32 msgHash = getL1ToL2MsgHash(toAddress, selector, payload, nonce);
uint256 msgFeePlusOne = l1ToL2Messages()[msgHash];
require(msgFeePlusOne != 0, "NO_MESSAGE_TO_CANCEL");
uint256 requestTime = l1ToL2MessageCancellations()[msgHash];
require(requestTime != 0, "MESSAGE_CANCELLATION_NOT_REQUESTED");
uint256 cancelAllowedTime = requestTime + messageCancellationDelay();
require(cancelAllowedTime >= requestTime, "CANCEL_ALLOWED_TIME_OVERFLOW");
require(block.timestamp >= cancelAllowedTime, "MESSAGE_CANCELLATION_NOT_ALLOWED_YET");
l1ToL2Messages()[msgHash] = 0;
@> return (msgHash);
}

The code that calls cancelL1ToL2Message function in ethereum::src::Bridge::cancelRequest doesn't utilize its returned value:

@> IStarknetMessaging(_starknetCoreAddress).cancelL1ToL2Message(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload,
nonce
);

Impact

The returned data is ignored, potentially leading to scenarios where important transactional details are lost.

Tools Used

Manual Review

Recommendations

Capture and use the return values of those function. For example, by using them in emitting events:

  1. depositTokens function:

- IStarknetMessaging(
+ (bytes32 messageHash, uint256 messageNonce) = IStarknetMessaging(
_starknetCoreAddress
).sendMessageToL2{value: msg.value}(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload
);
emit DepositRequestInitiated(
req.hash,
block.timestamp,
payload,
+ messageHash,
+ messageNonce
);
  1. startRequestCancellation function:

- IStarknetMessaging(_starknetCoreAddress).
+ bytes32 messageHash = IStarknetMessaging(_starknetCoreAddress)
.startL1ToL2MessageCancellation(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload,
nonce
);
Request memory req = Protocol.requestDeserialize(payload, 0);
- emit CancelRequestStarted(req.hash, block.timestamp);
+ emit CancelRequestStarted(req.hash, block.timestamp, messageHash);
  1. cancelRequest function:

- IStarknetMessaging(_starknetCoreAddress).cancelL1ToL2Message(
+ bytes32 messageHash = IStarknetMessaging(_starknetCoreAddress).cancelL1ToL2Message(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload,
nonce
);
Request memory req = Protocol.requestDeserialize(payload, 0);
_cancelRequest(req);
- emit CancelRequestCompleted(req.hash, block.timestamp);
+ emit CancelRequestCompleted(req.hash, block.timestamp, messageHash);
Updates

Lead Judging Commences

n0kto Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational / Gas

Please, do not suppose impacts, think about the real impact of the bug and check the CodeHawks documentation to confirm: https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity A PoC always helps to understand the real impact possible.

Support

FAQs

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