-
Notifications
You must be signed in to change notification settings - Fork 461
feat: split AllocationManager
#1643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: split AllocationManager
#1643
Conversation
e64c0f7 to
7dc8bb1
Compare
AllocationManagerAllocationManager
bec851a to
7dc8bb1
Compare
7dc8bb1 to
b480b22
Compare
bce9cbb to
c572ec5
Compare
99855a2 to
c676cd8
Compare
42af1f1 to
ecc69cf
Compare
src/test/integration/tests/upgrade/AllocationManagerUpgrade.t.sol
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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;
}
}There was a problem hiding this comment.
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.
src/test/integration/tests/upgrade/AllocationManagerUpgrade.t.sol
Outdated
Show resolved
Hide resolved
nadir-akhtar
left a comment
There was a problem hiding this 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.
There was a problem hiding this comment.
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;
}
}
nadir-akhtar
left a comment
There was a problem hiding this 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 💪
ypatil12
left a comment
There was a problem hiding this 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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
82a9ddd
into
release-dev/slashing-ux-improvements
**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
**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
**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]>
**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]>
# 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)
Motivation:
The
AllocationManagercontract 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 onAllocationManager.Modifications:
SplitContractMixinthat uses a fallback to delegate unmatched function calls viadelegatecallto a secondary contractAllocationManagerinto two contracts: the main contract handles state-mutating operations, whileAllocationManagerViewhandles all read-only view functionsAllocationManagerStorageto share the same storage layout, enabling the view contract to read from the main contract's storageAllocationManagerconstructor to accept and store theAllocationManagerViewaddressExistingDeploymentParser, test harnesses, and unit/integration tests to work with the split architectureResult:
AllocationManageris now about ~ 4.8KB under the 24KB limit with room to grow.AllocationManagerViewcontract 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)