Skip to content

Conversation

@0xClandestine
Copy link
Member

@0xClandestine 0xClandestine commented Sep 30, 2025

Motivation:

The AllocationManager contract was hitting the 24KB bytecode size limit, which would have blocked deployment. We needed a solution to reduce the contract size while maintaining backwards compatibility with existing integrations that call view functions on AllocationManager.

Modifications:

  • Created a new SplitContractMixin that uses a fallback to delegate unmatched function calls via delegatecall to a secondary contract
  • Split AllocationManager into two contracts: the main contract handles state-mutating operations, while AllocationManagerView handles all read-only view functions
  • Both contracts inherit from AllocationManagerStorage to share the same storage layout, enabling the view contract to read from the main contract's storage
  • Updated AllocationManager constructor to accept and store the AllocationManagerView address
  • Modified all deployment scripts (devnet, local, integration) to deploy both proxies and implementations
  • Updated ExistingDeploymentParser, test harnesses, and unit/integration tests to work with the split architecture

Result:

  • The AllocationManager is now about ~ 4.8KB under the 24KB limit with room to grow.
  • The AllocationManagerView contract has 16KB of available space for future view functions.

TODO: Add a ci check (I don't have privs but the code is written locally)

  1. Get list of all contracts ending in View.sol in the repo.
  2. Use forge inspect abi and check all mutability fields == view|pure.
  3. Basic search over the file to see if sstore or delegatecall is used on a for additional sanity.

@ypatil12 ypatil12 changed the base branch from main to release/slashing-ux-improvements October 1, 2025 21:56
@ypatil12 ypatil12 changed the base branch from release/slashing-ux-improvements to release-dev/slashing-ux-improvements October 2, 2025 14:55
@0xClandestine 0xClandestine changed the title draft: split AllocationManager feat: split AllocationManager Oct 7, 2025
@0xClandestine 0xClandestine requested a review from ypatil12 October 9, 2025 19:18
@ypatil12 ypatil12 force-pushed the release-dev/slashing-ux-improvements branch from bce9cbb to c572ec5 Compare October 15, 2025 18:39
@0xClandestine 0xClandestine force-pushed the refactor/split-alm branch 2 times, most recently from 99855a2 to c676cd8 Compare October 15, 2025 19:53
feat: split alm

wip

wip

wip

wip

wip

wip

wip

unit tests passing

remove extsload

wip

tests passing

chore: git mv storage to folder

wip

ci

docs

todo

cleanup

ci

ci

rename `secondHalf`

rm extsload

move storage

add protocol registry to zeus
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get some tests here to ensure that only view functions can be delegate called? And revert otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of an odd thing to test, since the compiler explicitly does this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for this would actually fail. If the delegate implements a non-view function, it can be called with delegateView successfully. _delegateView does not enforce that delegate calls do not modify state; it's more of a suggestion that the underlying function doesn't modify state.

If you're curious, try plugging the below code block into SplitContractMixin.t.sol locally, and run forge t SplitContractMixin.t.sol. You'll see that test_setValue passes, indicating that setValue (which is a view function and uses delegateView) is able to execute a state-modifying operation.

    // rest of SplitContractMixinTest above

    function setValue(uint) public view returns (uint result) {
        _delegateView(address(delegate));
        result;
    }

    function test_setValue() public {
        (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(this.setValue.selector, 100));
        assertTrue(success);

        (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(this.getValue.selector));
        assertTrue(success);
        uint result = abi.decode(data, (uint));
        assertEq(result, 100);
    }
}

// Mock contract to test delegation
contract Delegate is Test {
    uint value;

    function getValue() public view returns (uint result) {
        return value;
    }

    function setValue(uint _value) public {
        value = _value;
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nadir-akhtar We're talking about different things here, @0xrajath was suggesting adding tests to confirm that if we send calldata with a selector that's unknown to the view contract it reverts. I'm saying it's standard solidity behavior for a contract to revert if the functions unrecognized.

Copy link
Collaborator

@nadir-akhtar nadir-akhtar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split looks like a solid start! Especially with the layout at <storage slot> syntax making this super convenient.

A few notes on lingering TODOs, as well as some room for clarification in our docs on _delegateView allowing for delegatecall effects that may change state if the delegate contract implements such a function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for this would actually fail. If the delegate implements a non-view function, it can be called with delegateView successfully. _delegateView does not enforce that delegate calls do not modify state; it's more of a suggestion that the underlying function doesn't modify state.

If you're curious, try plugging the below code block into SplitContractMixin.t.sol locally, and run forge t SplitContractMixin.t.sol. You'll see that test_setValue passes, indicating that setValue (which is a view function and uses delegateView) is able to execute a state-modifying operation.

    // rest of SplitContractMixinTest above

    function setValue(uint) public view returns (uint result) {
        _delegateView(address(delegate));
        result;
    }

    function test_setValue() public {
        (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(this.setValue.selector, 100));
        assertTrue(success);

        (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(this.getValue.selector));
        assertTrue(success);
        uint result = abi.decode(data, (uint));
        assertEq(result, 100);
    }
}

// Mock contract to test delegation
contract Delegate is Test {
    uint value;

    function getValue() public view returns (uint result) {
        return value;
    }

    function setValue(uint _value) public {
        value = _value;
    }
}

Copy link
Collaborator

@nadir-akhtar nadir-akhtar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Much needed improvement to our contract codesize 💪

Copy link
Collaborator

@ypatil12 ypatil12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, left one non-blocking comment

check_IncrAlloc_State_Slashable(operator, allocateParams);

// 7. Roll blocks to complete allocation
_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also slash here just for completeness to write to every (I think?) slot

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do in semver removal pr for sake of avoiding collecting approvals again

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@0xClandestine 0xClandestine merged commit 82a9ddd into release-dev/slashing-ux-improvements Oct 23, 2025
17 checks passed
@0xClandestine 0xClandestine deleted the refactor/split-alm branch October 23, 2025 20:01
ypatil12 pushed a commit that referenced this pull request Nov 5, 2025
**Motivation:**

The `AllocationManager` contract was hitting the 24KB bytecode size
limit, which would have blocked deployment. We needed a solution to
reduce the contract size while maintaining backwards compatibility with
existing integrations that call view functions on `AllocationManager`.

**Modifications:**

- Created a new `SplitContractMixin` that uses a fallback to delegate
unmatched function calls via `delegatecall` to a secondary contract
- Split `AllocationManager` into two contracts: the main contract
handles state-mutating operations, while `AllocationManagerView` handles
all read-only view functions
- Both contracts inherit from `AllocationManagerStorage` to share the
same storage layout, enabling the view contract to read from the main
contract's storage
- Updated `AllocationManager` constructor to accept and store the
`AllocationManagerView` address
- Modified all deployment scripts (devnet, local, integration) to deploy
both proxies and implementations
- Updated `ExistingDeploymentParser`, test harnesses, and
unit/integration tests to work with the split architecture

**Result:**

- The `AllocationManager` is now about ~ 4.8KB under the 24KB limit with
room to grow.
- The `AllocationManagerView` contract has 16KB of available space for
future view functions.

TODO: Add a ci check (I don't have privs but the code is written
locally)
1) Get list of all contracts ending in View.sol in the repo.
2) Use forge inspect abi and check all mutability fields == view|pure.
3) Basic search over the file to see if sstore or delegatecall is used
on a for additional sanity.

---------

Co-authored-by: eigenmikem <[email protected]>

squash

squash

feat: split alm

wip

wip

wip

wip

wip

wip

wip

unit tests passing

remove extsload

wip

tests passing

chore: git mv storage to folder

wip

ci

docs

todo

cleanup

ci

ci

rename `secondHalf`

rm extsload

move storage

add protocol registry to zeus

squash

feat: split alm

wip

wip

wip

wip

wip

wip

wip

unit tests passing

remove extsload

wip

tests passing

chore: git mv storage to folder

wip

ci

wip

wip

wip

wip

wip

remove extsload

wip

tests passing

wip

passing

perf: wrap modifier logic to save codesize

chore: forge fmt

refactor: remove semver (#1641)

**Motivation:**

We want to consolidate semver logic into a single purpose built
contract.

**Modifications:**

- Removed `SemVerMixin` import from all contracts.

**Result:**

More codesize savings.

make fmt

refactor: expand `ProtocolRegistry` to list all contracts + pause

rm gas snapshots

rm extsload

rebase

ci

revert eigen changes

remove stale scripts
ypatil12 pushed a commit that referenced this pull request Nov 5, 2025
**Motivation:**

The `AllocationManager` contract was hitting the 24KB bytecode size
limit, which would have blocked deployment. We needed a solution to
reduce the contract size while maintaining backwards compatibility with
existing integrations that call view functions on `AllocationManager`.

**Modifications:**

- Created a new `SplitContractMixin` that uses a fallback to delegate
unmatched function calls via `delegatecall` to a secondary contract
- Split `AllocationManager` into two contracts: the main contract
handles state-mutating operations, while `AllocationManagerView` handles
all read-only view functions
- Both contracts inherit from `AllocationManagerStorage` to share the
same storage layout, enabling the view contract to read from the main
contract's storage
- Updated `AllocationManager` constructor to accept and store the
`AllocationManagerView` address
- Modified all deployment scripts (devnet, local, integration) to deploy
both proxies and implementations
- Updated `ExistingDeploymentParser`, test harnesses, and
unit/integration tests to work with the split architecture

**Result:**

- The `AllocationManager` is now about ~ 4.8KB under the 24KB limit with
room to grow.
- The `AllocationManagerView` contract has 16KB of available space for
future view functions.

TODO: Add a ci check (I don't have privs but the code is written
locally)
1) Get list of all contracts ending in View.sol in the repo.
2) Use forge inspect abi and check all mutability fields == view|pure.
3) Basic search over the file to see if sstore or delegatecall is used
on a for additional sanity.

---------

Co-authored-by: eigenmikem <[email protected]>

squash

squash

feat: split alm

wip

wip

wip

wip

wip

wip

wip

unit tests passing

remove extsload

wip

tests passing

chore: git mv storage to folder

wip

ci

docs

todo

cleanup

ci

ci

rename `secondHalf`

rm extsload

move storage

add protocol registry to zeus

squash

feat: split alm

wip

wip

wip

wip

wip

wip

wip

unit tests passing

remove extsload

wip

tests passing

chore: git mv storage to folder

wip

ci

wip

wip

wip

wip

wip

remove extsload

wip

tests passing

wip

passing

perf: wrap modifier logic to save codesize

chore: forge fmt

refactor: remove semver (#1641)

**Motivation:**

We want to consolidate semver logic into a single purpose built
contract.

**Modifications:**

- Removed `SemVerMixin` import from all contracts.

**Result:**

More codesize savings.

make fmt

refactor: expand `ProtocolRegistry` to list all contracts + pause

rm gas snapshots

rm extsload

rebase

ci

revert eigen changes

remove stale scripts
ypatil12 pushed a commit that referenced this pull request Nov 6, 2025
**Motivation:**

The `AllocationManager` contract was hitting the 24KB bytecode size
limit, which would have blocked deployment. We needed a solution to
reduce the contract size while maintaining backwards compatibility with
existing integrations that call view functions on `AllocationManager`.

**Modifications:**

- Created a new `SplitContractMixin` that uses a fallback to delegate
unmatched function calls via `delegatecall` to a secondary contract
- Split `AllocationManager` into two contracts: the main contract
handles state-mutating operations, while `AllocationManagerView` handles
all read-only view functions
- Both contracts inherit from `AllocationManagerStorage` to share the
same storage layout, enabling the view contract to read from the main
contract's storage
- Updated `AllocationManager` constructor to accept and store the
`AllocationManagerView` address
- Modified all deployment scripts (devnet, local, integration) to deploy
both proxies and implementations
- Updated `ExistingDeploymentParser`, test harnesses, and
unit/integration tests to work with the split architecture

**Result:**

- The `AllocationManager` is now about ~ 4.8KB under the 24KB limit with
room to grow.
- The `AllocationManagerView` contract has 16KB of available space for
future view functions.

TODO: Add a ci check (I don't have privs but the code is written
locally)
1) Get list of all contracts ending in View.sol in the repo.
2) Use forge inspect abi and check all mutability fields == view|pure.
3) Basic search over the file to see if sstore or delegatecall is used
on a for additional sanity.

---------

Co-authored-by: eigenmikem <[email protected]>
ypatil12 pushed a commit that referenced this pull request Dec 1, 2025
**Motivation:**

The `AllocationManager` contract was hitting the 24KB bytecode size
limit, which would have blocked deployment. We needed a solution to
reduce the contract size while maintaining backwards compatibility with
existing integrations that call view functions on `AllocationManager`.

**Modifications:**

- Created a new `SplitContractMixin` that uses a fallback to delegate
unmatched function calls via `delegatecall` to a secondary contract
- Split `AllocationManager` into two contracts: the main contract
handles state-mutating operations, while `AllocationManagerView` handles
all read-only view functions
- Both contracts inherit from `AllocationManagerStorage` to share the
same storage layout, enabling the view contract to read from the main
contract's storage
- Updated `AllocationManager` constructor to accept and store the
`AllocationManagerView` address
- Modified all deployment scripts (devnet, local, integration) to deploy
both proxies and implementations
- Updated `ExistingDeploymentParser`, test harnesses, and
unit/integration tests to work with the split architecture

**Result:**

- The `AllocationManager` is now about ~ 4.8KB under the 24KB limit with
room to grow.
- The `AllocationManagerView` contract has 16KB of available space for
future view functions.

TODO: Add a ci check (I don't have privs but the code is written
locally)
1) Get list of all contracts ending in View.sol in the repo.
2) Use forge inspect abi and check all mutability fields == view|pure.
3) Basic search over the file to see if sstore or delegatecall is used
on a for additional sanity.

---------

Co-authored-by: eigenmikem <[email protected]>
ypatil12 added a commit that referenced this pull request Dec 2, 2025
# v1.9.0 Slashing UX Improvements

## Release Manager

@ypatil12 @eigenmikem @0xClandestine

# Overview

The Slashing UX improvement release is a tech debt-focused release that
improves key parts of the Eigenlayer Core UX. This release will upgrade
every core contract.

The below release notes cover Core Contracts.

## Highlights

🚀 New Features

- The `AllocationManager` has been split into two contracts to address
size limitations of the contract. The main contract handles
state-mutating operations, while `AllocationManagerView` handles all
read-only view functions. **This is not a breaking change for
introspection as previous introspection calls fallback to `delegateCall`
into the `AllocationManagerView` contract.**. For more information, see
the [contract
architecture](../docs/core/AllocationManager.md#contract-architecture).
- The `ProtocolRegistry` is a new contract that stores all proxy
contract addresses, global semver, and has the ability to pause the
entire protocol. This contract will be deployed on all chains.
- Two new `createOperatorSets` functions (for redistributing and non
redistributing operatorSets) have been added that take in a slasher
address. This address is the *only* address that can slash an
operatorSet. Changing the address is behind a
`ALLOCATION_CONFIGURATION_DELAY` (17.5 days on mainnet).

⛔ Breaking Changes

- The slasher permissions are set and stored in the `AllocationManager`
instead of the `PermissionController`. Only one address can slash an
operatorSet; the address is initially set upon creation of the
operatorSet. OperatorSets created prior to this release will have their
slasher migrated based on the following rules:
- If there is no slasher set or the slasher in the
`PermissionController` is the 0 address, the AVS address will be set as
the slasher
- If there are multiple slashers set in the `PermissionController`, the
first address will be set as the slasher
- Semver (`SemverMixin.sol`) has been removed from all contracts, except
from those that inherit the `SignatureUtilsMixin`. The version of the
core protocol can be introspected via the `ProtocolRegistry`.

📌 Deprecations

The old `createOperatorSets` functions in the leftmost column will be
deprecated in Q2 2026 in favor of the newly specified functions. The old
functions do not pass in a slasher. The new functions do pass in a
slasher. If the old function is used, the slasher of the operatorSet is
set to the avs address.

| Function | MigrateTo | Notes |
| -------- | -------- | -------- |
| `createOperatorSets(avs, CreateSetParams[])` |
`createOperatorSets(address avs, CreateSetParamsV2[])` | New function
takes in a slasher address |
| `createRedistributingOperatorSets(avs, CreateSetParams[],
redistributionRecipients[])` | `createRedistributingOperatorSets(avs,
CreateSetParamsV2[], redistributionRecipients[])` | New function takes
in a slasher address |

🔧 Improvements

- Added a non-revert `_canCall` in the `PermissionControllerMixin` for
space savings. This function is used in the `AllocationManager` and
`DelegationManager`.
- The allocation delay for a newly created operator is active
immediately. This allows operators to make allocations instantly after
registering in the core.
- The internal `SlashingLib.scaleForBurning` function has been
deprecated in favor of `SlashingLib.calcSlashedAmount`, standardizing
the calculation of slashed shares across the withdrawal queue and
storage. See [PR
#1502](#1502) for
more information.


# Changelog

- feat: re-enable forge fmt + foundry v1.5.0 [PR
#1669](#1669)
- feat: substitute calcSlashedAmount for scaleForBurning [PR
#1502](#1502)
- fix: `v1.9.0` upgrade script [PR
#1666](#1666)
- feat: `v1.9.0` upgrade scripts + reusable upgrade helpers [PR
#1665](#1665)
- chore: update interface natspec for DM [PR
#1664](#1664)
- feat: slashing commitments [PR
#1645](#1645)
- feat: remove semver + minor optimizations [PR
#1654](#1654)
- feat: split `AllocationManager` [PR
#1643](#1643)
- feat: add protocol registry [PR
#1655](#1655)
- feat: instant alloc delay from dm [PR
#1646](#1646)
- chore: remove holesky [PR
#1662](#1662)
- chore: hardcode foundry ci to v1.3.5 [PR
#1658](#1658)
- chore: hardcode foundry to v1.3.5 in ci [PR
#1657](#1657)
- feat(audit): publish Hourglass + Multichain + RMS audit reports [PR
#1644](#1644)
- docs: add transport frequency for multichain [PR
#1642](#1642)
- chore: update readMe for multichain/hourglass [PR
#1637](#1637)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants