57644 sc low unenforced cap logic in alchemistallocator allows not controlled allocations
Submitted on Oct 27th 2025 at 20:27:33 UTC by @Paludo0x for Audit Comp | Alchemix V3
Report ID: #57644
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistAllocator.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Vulnerability Details
AlchemistAllocator::allocate() and deallocate() compute a capped allowed amount from the configured absolute and elative caps and an optional DAO target, but never enforce it.
The code ultimately calls the vault with the original amount. As a result, a privileged caller (admin or operator) can allocate or deallocate beyond intended limits whenever the vault does not also enforce caps.
Vulnerability Details
In allocate(address adapter, uint256 amount) and similarly in deallocate, the function:
reads cap inputs (absolute, relative and a “DAO target” placeholder),
computes an adjusted bound,
but then ignores it and forwards the unbounded amount to the vault
Relevant snippet:
There are 3 issues in these line of codes:
cap is computed but never used to cap the forwarded amount
cap combination is inverted: MAX(absoluteCap, relativeCap) relaxes limits; if the intent is “both must hold,” you want MIN.
Operator branch inverted: MAX(adjusted, daoTarget) (with daoTarget = uint256.max) makes operators less constrained than admin; typically, the DAO target should tighten limits
About issue 1.: actually the allocate/deallocate flows rely on cap check implemented on VaultV2
Impact Details
The criticality is LOW: altough the absence of capping system could lead to allocate or deallocate beyond intended limits, the call comes from a priviliged user
Proof of Concept
Proof of Concept
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20;
import "forge-std/Test.sol"; // Adjust the relative path below if your test lives elsewhere. import "./AlchemistAllocator.t.sol";
/// @dev Narrow interfaces to avoid importing full contracts. interface IVaultLike { function allocate(address adapter, uint256 oldAllocation, uint256 amount) external; }
interface IAllocatorLike { function allocate(address adapter, uint256 amount) external; }
contract AllocatorCapIgnored_FromBase is AlchemistAllocatorTest { // ==== Helpers to access base fixtures (adjust if your base uses different names) ==== function _vaultAddr() internal view returns (address) { // assumes the base exposes a public vault instance return address(vault); }
}
Was this helpful?