#41624 [SC-Medium] Reward sandwich is possible in `MoneyBrinter` vault by frontrunning `compound`.
Submitted on Mar 17th 2025 at 05:25:51 UTC by @OxAnmol for Audit Comp | Yeet
Report ID: #41624
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/MoneyBrinter.sol
Impacts:
Theft of unclaimed yield
Description
Brief/Intro
A malicious user can front-run the compound
function with large deposits and withdraw immediately in the following block, securing risk-free profits and stealing rewards from other legitimate users.
Vulnerability Details
MoneyBrinter
allows users to deposit Kodiak's island token from the YEET-WBERA pool. The MoneyBrinter then deposits this token into Beradrome for rewards. Any user can call the harvestKodiakRewards
and harvestBeradromeRewards
functions to collect corresponding rewards from beradrome to vault.
The strategyManager
can periodically call the compound
function to compound rewards (swap reward tokens, provide liquidity to Kodiak, and deposit island tokens to Beradrome). This process increases the value of the ERC4626 shares for users.
/**
* @notice Compounds rewards by swapping harvested tokens, staking in Kodiak vault and depositing into Beradrome farm
* @param swapInputTokens Array of input token addresses for swaps
* @param swapToToken0 Array of swap params to swap input tokens to token0
* @param swapToToken1 Array of swap params to swap input tokens to token1
* @param stakingParams Parameters for staking in Kodiak vault
* @param vaultStakingParams Parameters for depositing into vault
* @return uint256 Amount of island tokens minted
* @dev This function is non-reentrant and can only be called by the strategy manager
* @dev It approves tokens, performs swaps, stakes in Kodiak vault, and deposits into farm
* @dev Emits a VaultCompounded event
*/
function compound(
address[] calldata swapInputTokens,
IZapper.SingleTokenSwap[] calldata swapToToken0,
IZapper.SingleTokenSwap[] calldata swapToToken1,
IZapper.KodiakVaultStakingParams calldata stakingParams,
IZapper.VaultDepositParams calldata vaultStakingParams
) public override onlyStrategyManager nonReentrant returns (uint256) {
// By adding the staking params receiver as the vault address, we ensure that zapper returns the island Tokens to the vault
require(
stakingParams.receiver == address(this),
"Invalid staking receiver"
);
require(
swapInputTokens.length == swapToToken0.length + swapToToken1.length,
"Invalid swap data"
);
uint256 initialSupply = totalSupply();
uint256 shareValueBefore = previewRedeem(initialSupply);
_approveTokens(swapInputTokens, swapToToken0, swapToToken1);
IZapper.MultiSwapParams memory swapParams = IZapper.MultiSwapParams({
inputTokens: swapInputTokens,
swapToToken0: swapToToken0,
swapToToken1: swapToToken1
});
(uint256 islandTokensMinted, uint256 vaultSharesMinted) = zapper
.zapInWithMultipleTokens(
swapParams,
stakingParams,
vaultStakingParams
);
require(
vaultSharesMinted == 0,
"MoneyBrinter: vault shares minted while compounding"
);
require(
islandTokensMinted >= stakingParams.amountSharesMin,
"MoneyBrinter: not enough island tokens minted"
);
// deposit into farm
emit VaultCompounded(_msgSender(), islandTokensMinted);
_depositIntoFarm(islandTokensMinted);
uint256 shareValueAfter = previewRedeem(initialSupply);
require(
shareValueAfter >= shareValueBefore,
"MoneyBrinter: Bad Compound"
);
return islandTokensMinted;
}
A malicious actor can monitor the compound
function and front-run it with a large deposit into the vault. This makes them eligible for the compounded rewards. In the next block, they can simply withdraw their island tokens and receive more tokens without any risk.
Because the user deposits a large amount, they will capture most of the rewards from legitimate users who have been depositing in the vault for much longer periods.
Impact Details
Honest users who have been staking in the vault for extended periods will receive significantly reduced rewards, while malicious users can obtain risk-free profits without contributing meaningfully to the vault. This undermines the incentive structure and fairness of the protocol.
i believe based on impact in scope this is theft of unclaimed yield
so it is a high severity.
References
https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/MoneyBrinter.sol
Proof of Concept
Flow
Alice and Bob are honest users who each stake 50 island tokens and receive 50 share tokens each.
Over time, the vault earns rewards in the form of xKDK and oBERO, worth approximately $1,000.
Someone calls the harvest function to collect these rewards.
The
strategyManager
prepares to callcompound
to swap and redeposit island tokens to Beradrome for more rewards.Jack, a malicious user, observes this transaction and front-runs it by calling
deposit
with 1,000 island tokens, receiving 1,000 shares in return.When
compound
is executed, the total assets increase by 100 tokens, bringing the total to 50 (Alice) + 50 (Bob) + 1,000 (Jack) + 100 (newly compounded) = 1,200 tokens.In the next block, when Jack redeems his shares, he receives: 1,000 * 1,200 / 1,100 = 1,090 island tokens.
Even with a 4% withdrawal fee (as mentioned by the sponsor), Jack would pay 43.6 island tokens as a fee (4% of 1,090).
Jack's risk-free profit is therefore 1,090 - 1,000 - 43.6 = 46.4 island tokens.
Was this helpful?