MorpheusAI

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

Excessive Token Allowance Risk in `L1Sender` and `L2TokenReceiver` Contracts

Summary

Both the L1Sender and L2TokenReceiver smart contracts set the maximum possible allowance (type(uint256).max) for external contracts.

In L1Sender, this is done for newToken_ and the gateway obtained from IGatewayRouter(newGateway_).getGateway(newToken_).

In L2TokenReceiver, the unlimited allowance is set for the router and nonfungiblePositionManager contracts.

This practice grants an unlimited allowance to these addresses, which could lead to potential security risks if any of these external contracts are compromised.

Vulnerability Details

The contracts use type(uint256).max to set allowances for external contracts, effectively providing them with unlimited access to the contract's token balances.

  1. In L1Sender, the tokens in question are unwrappedDepositToken and newToken_. Ref 1, Ref 2

  2. while in L2TokenReceiver, they are tokenIn and tokenOut. Ref

This approach, while reducing the need for repeated allowance transactions, increases the risk of token loss if the trusted external contracts are exploited.

Impact

An attacker who gains control over any of the external contracts with unlimited allowances could potentially drain the respective contract's token balances by transferring the approved tokens to an unauthorized address. This could result in a significant loss of funds and damage the trust in the contract's security.

Tools Used

Manual review

Recommendations

  • Set Finite Allowances: Implement a mechanism to set required allowances for external contracts in respective functions which interact with external contracts. This reduces the risk associated with granting unlimited allowances and helps to maintain tighter control over the contract's assets.

File: contracts/L1Sender.sol

function _replaceDepositToken(address oldToken_, address newToken_) private {
bool isTokenChanged_ = oldToken_ != newToken_;
- if (oldToken_ != address(0) && isTokenChanged_) {
- // Remove allowance from stETH to wstETH
- IERC20(unwrappedDepositToken).approve(oldToken_, 0);
- }
if (isTokenChanged_) {
- // Get stETH from wstETH
- address unwrappedToken_ = IWStETH(newToken_).stETH();
- // Increase allowance from stETH to wstETH. To exchange stETH for wstETH
- IERC20(unwrappedToken_).approve(newToken_, type(uint256).max);
unwrappedDepositToken = unwrappedToken_;
}
}
function sendDepositToken(
uint256 gasLimit_,
uint256 maxFeePerGas_,
uint256 maxSubmissionCost_
) external payable onlyDistribution returns (bytes memory) {
DepositTokenConfig storage config = depositTokenConfig;
// Get current stETH balance
uint256 amountUnwrappedToken_ = IERC20(unwrappedDepositToken).balanceOf(address(this));
// Wrap all stETH to wstETH
+ IERC20(unwrappedToken_).approve(config.token, amountUnwrappedToken_);
uint256 amount_ = IWStETH(config.token).wrap(amountUnwrappedToken_);
bytes memory data_ = abi.encode(maxSubmissionCost_, "");
return
IGatewayRouter(config.gateway).outboundTransfer{value: msg.value}(
config.token,
config.receiver,
amount_,
gasLimit_,
maxFeePerGas_,
data_
);
}

File: contracts/L2TokenReceiver.sol

function swap(uint256 amountIn_, uint256 amountOutMinimum_) external onlyOwner returns (uint256) {
SwapParams memory params_ = params;
+ TransferHelper.safeApprove(params_.tokenIn, router, amountIn_);
ISwapRouter.ExactInputSingleParams memory swapParams_ = ISwapRouter.ExactInputSingleParams({
tokenIn: params_.tokenIn,
tokenOut: params_.tokenOut,
fee: params_.fee,
recipient: address(this),
deadline: block.timestamp,
amountIn: amountIn_,
amountOutMinimum: amountOutMinimum_,
sqrtPriceLimitX96: params_.sqrtPriceLimitX96
});
uint256 amountOut_ = ISwapRouter(router).exactInputSingle(swapParams_);
emit TokensSwapped(params_.tokenIn, params_.tokenOut, amountIn_, amountOut_, amountOutMinimum_);
return amountOut_;
}
function increaseLiquidityCurrentRange(
uint256 tokenId_,
uint256 depositTokenAmountAdd_,
uint256 rewardTokenAmountAdd_,
uint256 depositTokenAmountMin_,
uint256 rewardTokenAmountMin_
) external onlyOwner returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_) {
uint256 amountAdd0_;
uint256 amountAdd1_;
uint256 amountMin0_;
uint256 amountMin1_;
(, , address token0_, , , , , , , , , ) = INonfungiblePositionManager(nonfungiblePositionManager).positions(
tokenId_
if (token0_ == params.tokenIn) {
amountAdd0_ = depositTokenAmountAdd_;
amountAdd1_ = rewardTokenAmountAdd_;
amountMin0_ = depositTokenAmountMin_;
amountMin1_ = rewardTokenAmountMin_;
+ TransferHelper.safeApprove(params.tokenIn, nonfungiblePositionManager, amountAdd0_);
+ TransferHelper.safeApprove(params.tokenOut, nonfungiblePositionManager, amountAdd1_);
} else {
amountAdd0_ = rewardTokenAmountAdd_;
amountAdd1_ = depositTokenAmountAdd_;
amountMin0_ = rewardTokenAmountMin_;
amountMin1_ = depositTokenAmountMin_;
+ TransferHelper.safeApprove(params.tokenOut, nonfungiblePositionManager, amountAdd0_);
+ TransferHelper.safeApprove(params.tokenIn, nonfungiblePositionManager, amountAdd1_);
}
INonfungiblePositionManager.IncreaseLiquidityParams memory params_ = INonfungiblePositionManager
.IncreaseLiquidityParams({
tokenId: tokenId_,
amount0Desired: amountAdd0_,
amount1Desired: amountAdd1_,
amount0Min: amountMin0_,
amount1Min: amountMin1_,
deadline: block.timestamp
});
(liquidity_, amount0_, amount1_) = INonfungiblePositionManager(nonfungiblePositionManager).increaseLiquidity(
params_
);
emit LiquidityIncreased(tokenId_, amount0_, amount1_, liquidity_, amountMin0_, amountMin1_);
}
function _editParams(SwapParams memory newParams_) private {
require(newParams_.tokenIn != address(0), "L2TR: invalid tokenIn");
require(newParams_.tokenOut != address(0), "L2TR: invalid tokenOut");
- TransferHelper.safeApprove(newParams_.tokenIn, router, type(uint256).max);
- TransferHelper.safeApprove(newParams_.tokenIn, nonfungiblePositionManager, type(uint256).max);
- TransferHelper.safeApprove(newParams_.tokenOut, nonfungiblePositionManager, type(uint256).max);
params = newParams_;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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