For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 V3

  • 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?