MorpheusAI

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

`Distribution.claim` function: if the staker is using an AA wallet, his claimed rewards will be lost

Summary

If the staker is using an account abstraction wallet, his claimed MOR token rewards will be lost on L2 as his address on L2 will be differet from L1.

Vulnerability Details

  • Morpheus protocol allows users to stake/deposit stETH in Distribution contract that is going to be deployed on the Ethereum mainnet, and claim their MOR tokens rewards on Arbitrum network (L2).

  • So any user has made a stake will be able to claim his rewards (or anyone onbehalf of him) via Distribution.claim function, where a mint message is going to be delivered to the L2MessageReceiver contract on Arbitrum via the layer zero endpoint on Ethereum:

    //@notice: claim function on L1:
    // Transfer rewards
    L1Sender(l1Sender).sendMintMessage{value: msg.value}(user_, pendingRewards_, _msgSender());
  • But there's an issue with this claim mechanism:
    Users who interact with the protocol with "EOAs" (externally owned accounts) will be using the same address that is created on all evm chains for these accounts, but users of account abstraction wallets (which are unique smart contract instances deployed on individual chains) will have different addresses on different chains.

  • So as can be noticed; the reward tokens are going to be minted for the address of the staker on L1, and this is not going to be the same address on L2 if the staker uses a wallet:

    //@notice: encoding of the rewards address one via L1Sender.sendMintMessage function:
    bytes memory payload_ = abi.encode(user_, amount_);
    //@notice: minting of rewards on L2
    (address user_, uint256 amount_) = abi.decode(payload_, (address, uint256));
    IMOR(rewardToken).mint(user_, amount_);

Impact

This will result in staker losing his MOR token rewards as they are going to be minted for another address on L2 that's not owned by the staker.

Proof of Concept

Distribution.claim function/ L173

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

Tools Used

Manual Review.

Recommendations

Update Distribution.claim function to have another address argument where the user would like to receive his rewards, and by doing so; it must be ensured that the claim function can be called by the staker himself only and not by anyone onbehalf of him :

-function claim(uint256 poolId_, address user_) external payable poolExists(poolId_) {
+function claim(uint256 poolId_, address user_,address receiver_) external payable poolExists(poolId_) {
+ //to ensure that this function is callable by the staker only:
+ require(user_ == _msgSender());
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
- L1Sender(l1Sender).sendMintMessage{value: msg.value}(user_, pendingRewards_, _msgSender());
+ L1Sender(l1Sender).sendMintMessage{value: msg.value}(receiver_, 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:

Users that interact through smart contracts, account abstaction or multisig wallets lose all rewards because they are not the owners of the same addresses on L2

Support

FAQs

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