Deposit/stake functions are front-runnable
Description
Safety Module and Rewards Manager provide deposit/stake functions. These functions expect that transfer or approval has already been executed prior to a call.
In Depositor(SafetyModule), for example, the function depositReserveAssets
is used to deposit with transfer. But, in this function, safeTransferFrom
uses the arbitrary parameter from_
, not msg.sender
. In this case, if there is a user who has approved this contract, the other user could call this function by using from_
as the approved address. If this function is not called with approval in a single transaction, it could be vulnerable to a front-run attack.
function depositReserveAssets(uint8 reservePoolId_, uint256 reserveAssetAmount_, address receiver_, address from_)
external
returns (uint256 depositReceiptTokenAmount_)
{
// ...
! underlyingToken_.safeTransferFrom(from_, address(this), reserveAssetAmount_);
depositReceiptTokenAmount_ =
_executeReserveDeposit(reservePoolId_, underlyingToken_, reserveAssetAmount_, receiver_, assetPool_, reservePool_);
}
Impact
An attacker could execute a front-run attack when the user uses deposit/stake functions if safeTransferFrom
is not called in a single transaction with approval.
In this case, the attacker could use receiver_
as their address and from_
as the victim's address, and the attacker could succeed in depositing the victim's tokens.
Recommendations
We recommend changing _from
to msg.sender
in depositReserveAssets
, depositRewardAssets
, and depositRewardAssets
. This could prevent an attacker from using other's approval even when the user does not use a single transaction.
Remediation
Cozy Finance acknowledged the risk of front-running when not using a single transaction. Cozy Finance will provide router design that will have a multicall function for building the single transaction.
Cozy Finance fixed depositReserveAssets
, depositRewardAssets
, and depositRewardAssets
for the users who do not use the multicall router.