-
Notifications
You must be signed in to change notification settings - Fork 449
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
base: release-dev/slashing-ux-improvements
Are you sure you want to change the base?
feat: split AllocationManager
#1643
Conversation
e64c0f7
to
7dc8bb1
Compare
AllocationManager
AllocationManager
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
* 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 |
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.
If I understand this correctly, it will still allow state-modifying logic? Or am I misunderstanding it?
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.
Yes, opted against static-delegatecall after putting more thought into it and attempting to implement.
- Created infinite loop (broke nested delegatecalls).
- Has the same issues as enforcing view keyword (must enforce _staticDelegatecall).
- Exposes a delegatecall method that should only be called the contract itself (opening other attack vectors).
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
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;
}
}
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 onAllocationManager
.Modifications:
SplitContractMixin
that uses a fallback to delegate unmatched function calls viadelegatecall
to a secondary contractAllocationManager
into two contracts: the main contract handles state-mutating operations, whileAllocationManagerView
handles all read-only view functionsAllocationManagerStorage
to share the same storage layout, enabling the view contract to read from the main contract's storageAllocationManager
constructor to accept and store theAllocationManagerView
addressExistingDeploymentParser
, test harnesses, and unit/integration tests to work with the split architectureResult:
AllocationManager
is now about ~ 4.8KB under the 24KB limit with room to grow.AllocationManagerView
contract has 16KB of available space for future view functions.