Raspy Black Giraffe
Medium
The lack of slippage controls in the rebalance function will cause financial losses for vault users as the coordinator's transactions can be front-run or executed under unfavorable market conditions.
In SolverVault.sol there is no slippage checks
function rebalance(IMarket from, IMarket to, UFixed6 amount) external onlyCoordinator {
if (!_isRegistered(from) || !_isRegistered(to)) revert SolverVaultNotRegisteredError();
from.update(address(this), Fixed6Lib.ZERO, Fixed6Lib.from(-1, amount), address(0));
to.update(address(this), Fixed6Lib.ZERO, Fixed6Lib.from(1, amount), address(0));
}
1- Coordinator needs to call rebalance() to move funds between markets. 2- Vault must hold sufficient collateral in the from market.
1- Market prices for the involved assets must be volatile 2- The rebalance transaction is delayed, allowing market conditions to change unfavorably.
1- Coordinator calls rebalance(fromMarket, toMarket, 1000 USDC) to adjust positions.
2- Attacker detects the transaction in the mempool and front-runs it by: Artificially inflating the price of toMarket via a large buy order. Artificially deflating the price of fromMarket via a large sell order.
3- The coordinator’s rebalance executes at manipulated prices: Vault sells fromMarket at a lower-than-expected price. Vault buys toMarket at a higher-than-expected price.
Vault users suffer losses proportional to the manipulated price difference during rebalancing. For example:
If the attacker causes a 5% price impact, users lose ~5% of the rebalanced amount. Attacker gains profit by closing their manipulated positions after the vault’s trade.
// Simplified Foundry test showcasing front-running
function testFrontrunRebalance() public {
// 1. Setup: Vault holds 1000 USDC in MarketA
_depositToVault(1000e6);
// 2. Attacker manipulates MarketB’s price upward
vm.startPrank(attacker);
marketB.trade(attacker, 1_000_000e6); // Inflate price
vm.stopPrank();
// 3. Coordinator’s rebalance executes at bad prices
vm.prank(coordinator);
solverVault.rebalance(marketA, marketB, 1000e6);
// 4. Verify loss: Vault’s total assets decrease
assertLt(solverVault.totalAssets(), 1000e6);
}
Add slippage controls to the rebalance function :
function rebalance(
IMarket from,
IMarket to,
UFixed6 amount,
UFixed6 minAmountFrom, // Minimum received from `from` market
UFixed6 minAmountTo, // Minimum received to `to` market
uint256 deadline // Transaction expiry timestamp
) external onlyCoordinator {
if (block.timestamp > deadline) revert Expired();
(UFixed6 actualFrom, UFixed6 actualTo) = _executeRebalance(from, to, amount);
if (actualFrom < minAmountFrom || actualTo < minAmountTo) revert SlippageExceeded();
}