MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: low
Valid

LayerZeroEndpoint.send() in L1Sender.sol may revert if the user does not provide enough native gas as specified

Summary

When a user wants to claim the MOR token reward on L2 Arbitrum, the user interacts with the claim function on Distribution.sol. Then, after all the claim requirements have been fulfilled by the user, Distribution call the L1Sender.sol which sends a message via the LayerZero endpoint with the send function and if successful it will be received by the L2MessageReceiver.sol and will mint the MOR token to the user's address on L2 Arbitrum as a reward.

The problem here is {value : msg.value} on send function is not set in the codebase so that if the user sends a fee that is less than what is required for execution on the destination chain then the transaction will revert.

Vulnerability Details

Scenario :

Alice has deposited tokens into a pool on Distribution.sol. Over time, Alice has accrued reward tokens through the pool's staking mechanism. Alice then decides to claim her accumulated rewards.

Steps:

  1. Alice calls the claim function:

    • Alice specifies the pool ID and her own address.

    • Alice also need to specify the amount of ether she wants to send for gas or include as part of the reward.

  2. Function execution:

    • The function checks if the claim period has elapsed (e.g., 24 hours since the last claim) and if Alice has any pending rewards.

    • If eligible, the function calculates Alice's current pending rewards based on the pool's reward rate and her deposited tokens.

    • The function updates pool and user data, resetting Alice's pending rewards and recording the current timestamp and rate.

    • Finally, the function calls the L1Sender.sendMintMessage with the following parameters:

      • user_: Alice's address (to mint the reward tokens)

      • pendingRewards_: The calculated amount of rewards

      • _msgSender(): The address of the contract calling the function

  3. Transaction revert:

    • Back to claim step point number 2, Alice did not provide enough native gas fee for executing transaction on destination chain. This can cause transaction revert.

Based on LayerZero docs, {value : msg.value} on send function must be set or it will be revert because user not provide enough gas.

You will note in the topmost example we call send() with {value: msg.value} this is because send() requires a bit of native gas token so the relayer can complete the message delivery on the destination chain. If you don't set this value you might get this error when calling endpoint.send()

POC

In this coded POC , assume the user has passed all the requirements to claim the MOR token reward. But the user does not pay enough native gas to execute the transaction on the destination chain which causes the transaction to revert.

it('should revert if msg.value less than required to pay gas', async () => {
await setNextTime(oneHour * 2);
await distribution.connect(SECOND).stake(poolId, wei(1));
// Claim after 2 days
await setNextTime(oneDay + oneDay * 2);
// User provide less than required and lead to transaction revert
await expect (distribution.claim(poolId, SECOND, { value: wei(0.01) })).to.be.revertedWith('LayerZeroMock: not enough native for fees');
});

Result :

Distribution
#claim
✔ should revert if msg.value less than required to pay gas (96ms)
1 passing (2s)

This coded POC was written using the default environment of the protocol so just input the Distribution.test.ts file and paste it in the claim section then the POC can be executed immediately.

Impact

claim function will revert and can't be used, user loss the gasfee and not receive the reward token yet.

Tools Used

Manual Review

Recommended Mitigation

Consider using the estimateFees() function to estimate fees so that the send() execution not revert because not enough native gas fee.

The implementation :

Contract : L1Sender.sol
// @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery
// @param _dstChainId - the destination chain identifier
// @param _userApplication - the user app address on this EVM chain
// @param _payload - the custom message to send over LayerZero
// @param _payInZRO - if false, user app pays the protocol fee in native token
// @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain
function estimateFees(
uint16 _dstChainId,
address _userApplication,
bytes calldata _payload,
bool _payInZRO,
bytes calldata _adapterParam
) external view returns (uint nativeFee, uint zroFee);
Contract : Distribution.sol
function claim(uint256 poolId_, address user_) external payable poolExists(poolId_) {
Pool storage pool = pools[poolId_];
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];
require(block.timestamp > pool.payoutStart + pool.claimLockPeriod, "DS: pool claim is locked");
uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_);
uint256 pendingRewards_ = _getCurrentUserReward(currentPoolRate_, userData);
require(pendingRewards_ > 0, "DS: nothing to claim");
// Update pool data
poolData.lastUpdate = uint128(block.timestamp);
poolData.rate = currentPoolRate_;
// Update user data
userData.rate = currentPoolRate_;
userData.pendingRewards = 0;
// Transfer rewards
(uint fee, ) = L1Sender.estimateFees;
L1Sender(l1Sender).sendMintMessage{value: fee}(user_, pendingRewards_, _msgSender());
emit UserClaimed(poolId_, user_, pendingRewards_);
}
Updates

Lead Judging Commences

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

LayerZero Integration: `sendMintMessage` doesn't verify the `msg.value` sent by the user facilitating failed transactions.

sovaslava Auditor
over 1 year ago
0xdemon Submitter
over 1 year ago
serialcoder Auditor
over 1 year ago
sovaslava Auditor
over 1 year ago
0xdemon Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

LayerZero Integration: `sendMintMessage` doesn't verify the `msg.value` sent by the user facilitating failed transactions.

Support

FAQs

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