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.

@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
Comment on lines +50 to +51
* pointer to a view (read-only) signature. This pattern is sometimes used for readonly proxies,
* but it should be used cautiously since any state-modifying logic in the underlying delegate
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I understand this correctly, it will still allow state-modifying logic? Or am I misunderstanding it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, opted against static-delegatecall after putting more thought into it and attempting to implement.

  1. Created infinite loop (broke nested delegatecalls).
  2. Has the same issues as enforcing view keyword (must enforce _staticDelegatecall).
  3. Exposes a delegatecall method that should only be called the contract itself (opening other attack vectors).

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;
    }
}

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.

5 participants