58516 sc low inverted min max logic in alchemistallocator operator cap calculation

Submitted on Nov 2nd 2025 at 23:45:26 UTC by @Tomioka for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58516

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistAllocator.sol

  • Impacts:

Description

Brief/Intro

The allocate function in the AlchemistAllocator contract contains a logic error in the operator cap calculation. When a non admin operator calls this function, the code uses max instead of min when combining the calculated cap with the daoTarget. With the current implementation where daoTarget = type(uint256).max, this gives operators unlimited allocation power, making the operator restriction mechanism non functional.

Vulnerability Details

The AlchemistAllocator manages fund allocations to yield strategies through adapters. It has two permission levels:

  1. Admin: Full control, no restrictions

  2. Operator: Limited control, should have MORE restrictive caps

However, the operator cap logic is inverted:

Allocate Function (allocate):

The Logic Error:

Allocate Operator Cap (Line 39):

  • Current: adjusted = adjusted > daoTarget ? adjusted : daoTarget

  • This is: adjusted = max(adjusted, daoTarget)

  • Issue: When daoTarget = type(uint256).max, this gives operators unlimited caps

  • Should be: adjusted = min(adjusted, daoTarget) to restrict operators

Complete Attack Scenario:

Scenario 1: Operator Bypass on Allocate

  1. Strategy has:

    • Absolute cap: 1,000 tokens

    • Relative cap: 2,000 tokens

    • adjusted = max(1000, 2000) = 2000 (line 36)

  2. DAO sets operator limit (daoTarget): 500 tokens

  3. Admin expects operators limited to 500 tokens

  4. Operator calls allocate:

    • Line 39: adjusted = max(2000, 500) = 2000

    • Should be: adjusted = min(2000, 500) = 500

  5. Operator has 2000 token cap instead of 500

  6. Operator bypasses intended 500 token restriction

Scenario 2: Current State with daoTarget = max(uint256)

  1. Currently, daoTarget = type(uint256).max (line 35)

  2. For allocate (line 39):

    • adjusted = max(adjusted, type(uint256).max)

    • Result: adjusted always becomes type(uint256).max

    • Operators have unlimited cap!

Scenario 3: Future Implementation with Real daoTarget

  1. Code comment says: "FIXME get this from the StrategyClassificationProxy"

  2. When implemented, daoTarget will have real values (e.g., 100, 500, 1000 tokens)

  3. With current inverted logic:

    • Operators will get max(strategyLimit, operatorLimit) instead of min

    • High-risk strategies with 10,000 token caps

    • Operator limit set to 100 tokens (safe limit)

    • Operator gets 10,000 token access instead of 100

Real-World Impact:

This vulnerability has severe governance and security consequences:

  • Operator privilege escalation: Operators bypass intended restrictions

  • Risk management broken: Cannot limit operator exposure

  • Governance failure: DAO-set limits are inverted

  • Security degradation: Operators can take excessive risk

  • Insider risk: Malicious operators have more power than intended

Example Real-World Numbers:

Assume protocol with risk-based operator limits:

  • Low-risk strategy: Operator limit = 10,000 tokens, Strategy cap = 100,000 tokens

  • High-risk strategy: Operator limit = 1,000 tokens, Strategy cap = 100,000 tokens

Expected behavior:

  • Operators can allocate up to 1,000 tokens to high-risk (more restrictive)

  • Operators can allocate up to 10,000 tokens to low-risk (less restrictive)

Actual behavior with bug:

  • Operators can allocate up to 100,000 tokens to BOTH (takes max with strategy cap)

  • Operator limits are completely bypassed

  • No difference in permissions between admin and operator

Root Cause:

The bug stems from incorrect ternary operator: Using max where min is needed, making operators less restricted than intended.

Comparison with Correct Implementation:

Correct operator cap enforcement:

AlchemistAllocator (broken pattern):

Impact Details

Primary Impact: Operator Privilege Escalation

  • Operators bypass intended restrictions

  • Operator limits are inverted (more permissive instead of restrictive)

  • No functional difference between admin and operator permissions

  • Governance-set limits are not respected

Secondary Impact: Security and Governance Risks

  • Risk management broken: Cannot limit operator exposure

  • Insider threat: Malicious operators have excessive power

  • Governance failure: DAO votes on limits that don't work

  • Strategy risk: Operators can over-allocate to risky strategies

  • No audit trail: Excessive allocations appear "authorized"

Affected Functionality:

The following critical operations have broken permission enforcement:

  • allocate (line 29) - Operator cap inverted, not restrictive

  • Operator permission system - Non-functional for allocations

  • Risk-based strategy limits - Cannot be enforced per role

Affected Users:

  • Protocol governance: Cannot enforce operator limits

  • Risk managers: Cannot restrict operator actions

  • Admin: Must handle all operations (operators untrusted)

  • All users: Exposed to operator risk-taking

Severity Justification:

  • Current state: Operators have unlimited allocation power

  • Future severity: When implemented correctly, becomes critical privilege escalation

  • Governance violation: Set limits will have opposite effect

  • Silent failure: Code appears to enforce limits but doesn't

References

  • Vulnerable allocate function: src/AlchemistAllocator.sol:29-44

  • Inverted operator cap: src/AlchemistAllocator.sol:39

Proof of Concept

Proof of Concept

To run the PoC for Vulnerability, execute this command: forge test --match-path test/AllocatorInvertedLogic.t.sol -vvv

Fix the inverted logic:

Was this helpful?