MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Invalid

retryMessage deletes a failedMessage permanently

Summary

retryMessage deletes a failedMessage permanently which may lead to loss of rewards/funds/pending-rewards for a staker/user.

Vulnerability Details

  1. The gateway will call this function to run a payload/message:

function lzReceive(
uint16 senderChainId_,
bytes memory senderAndReceiverAddresses_,
uint64 nonce_,
bytes memory payload_
) external {
require(_msgSender() == config.gateway, "L2MR: invalid gateway");
_blockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_);
}
  1. Then it goes to this function:

function _blockingLzReceive(
uint16 senderChainId_,
bytes memory senderAndReceiverAddresses_,
uint64 nonce_,
bytes memory payload_
) private {
try
IL2MessageReceiver(address(this)).nonblockingLzReceive(
senderChainId_,
senderAndReceiverAddresses_,
payload_
)
{
emit MessageSuccess(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_);
} catch (bytes memory reason_) {
failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_] = keccak256(payload_);
emit MessageFailed(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_, reason_);
}
}

This calls the nonblockingLzReceive in a try-catch block, it means if it is unable to mint the MOR tokens for staker, then that message/payload will be added into failedMessages,

  1. Now the user is able to call this function to retry to run that message:

function retryMessage(
uint16 senderChainId_,
bytes memory senderAndReceiverAddresses_,
uint64 nonce_,
bytes memory payload_
) external {
bytes32 payloadHash_ = failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_];
require(payloadHash_ != bytes32(0), "L2MR: no stored message");
require(keccak256(payload_) == payloadHash_, "L2MR: invalid payload");
_nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, payload_); // <------- problem is here
delete failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_]; // <---------- and here
emit RetryMessageSuccess(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_);
}

The problem is that, if the _nonblockingLzReceive again fails, that payload/message will be deleted without any other chance for user to execute his/her mint-message and the user will lose the pending MOR rewards.

  1. due to that the Distribution#claim is setting the pending reward to zero (before sending the message), then if the user is unable to run his message in retry, then the user will lose that pending rewards:

userData.pendingRewards = 0;
// Transfer rewards
L1Sender(l1Sender).sendMintMessage{value: msg.value}(user_, pendingRewards_, _msgSender());

Impact

User may lose the rewards in the retryMessage.

Tools Used

Manual Review

Recommendations

Consider adding a try-catch block inside the retryMessage like the _blockingLzReceive, and consider not deleting the message/payload while the code jumps to catch block.

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
ubl4nk Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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