Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/helpers/DelegateAnything.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;

import { IDelegatedExtension } from "../modules/interfaces/IDelegatedExtension.sol";
import { LibBytes } from "../utils/LibBytes.sol";
import { LibOptim } from "../utils/LibOptim.sol";

/// @title DelegateAnything
/// @author Michael Standen
/// @notice Helper for delegating calls to any contract
contract DelegateAnything is IDelegatedExtension {

address internal immutable _SELF = address(this);

/// @notice Error thrown when not called via delegatecall
error NotDelegateCall();

/// @notice Error thrown when a delegate call fails
error DelegateCallFailed(bytes returnData);

/// @inheritdoc IDelegatedExtension
function handleSequenceDelegateCall(
bytes32,
uint256,
uint256,
uint256,
uint256,
bytes calldata data
) external {
if (address(this) == _SELF) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we could simplify it a bit, I don't think this is needed

revert NotDelegateCall();
}

(address to, uint256 pointer) = LibBytes.readAddress(data, 0);

bool success;
(success) = LibOptim.delegatecall(to, gasleft(), data[pointer:]);
if (!success) {
revert DelegateCallFailed(LibOptim.returnData());
}
}

}
106 changes: 106 additions & 0 deletions test/helpers/DelegateAnything.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;

import { DelegateAnything } from "../../src/helpers/DelegateAnything.sol";
import { AdvTest } from "../utils/TestUtils.sol";

contract Target {

function setStorageSlot(
bytes32 slot,
uint256 value
) external {
assembly {
sstore(slot, value)
}
}

function revertCall() external pure {
revert("Revert message");
}

}

contract DelegateCallWrapper {

DelegateAnything public delegateAnything;

constructor(
DelegateAnything _delegateAnything
) {
delegateAnything = _delegateAnything;
}

function delegateCall(
bytes32 opHash,
uint256 startingGas,
uint256 index,
uint256 numCalls,
uint256 space,
bytes calldata data
) external {
(bool success, bytes memory returnData) = address(delegateAnything)
.delegatecall(
abi.encodeWithSelector(
DelegateAnything.handleSequenceDelegateCall.selector, opHash, startingGas, index, numCalls, space, data
)
);
if (!success) {
assembly {
revert(add(returnData, 0x20), mload(returnData))
}
}
}

}

contract DelegateAnythingTest is AdvTest {

DelegateAnything public delegateAnything;
DelegateCallWrapper public wrapper;
Target public target;

// Known random hash for storage slot
bytes32 constant STORAGE_SLOT = keccak256("DelegateAnything.test.storage.slot");

function setUp() public {
delegateAnything = new DelegateAnything();
wrapper = new DelegateCallWrapper(delegateAnything);
target = new Target();
}

function test_directCallReverts() external {
bytes memory inner = abi.encodeWithSelector(Target.setStorageSlot.selector, STORAGE_SLOT, 42);
bytes memory data = abi.encodePacked(address(target), inner);

vm.expectRevert(DelegateAnything.NotDelegateCall.selector);
delegateAnything.handleSequenceDelegateCall(bytes32(0), 0, 0, 0, 0, data);
}

function test_successfulDelegateCall() external {
uint256 expectedValue = 42;
bytes32 slot = STORAGE_SLOT;
bytes memory inner = abi.encodeWithSelector(Target.setStorageSlot.selector, slot, expectedValue);
bytes memory data = abi.encodePacked(address(target), inner);

// Call via delegatecall through wrapper
wrapper.delegateCall(bytes32(0), 0, 0, 0, 0, data);

// Validate storage slot on wrapper contract (since delegatecall executes in wrapper's context)
uint256 storedValue = uint256(vm.load(address(wrapper), slot));
assertEq(storedValue, expectedValue);
}

function test_failedDelegateCall() external {
bytes memory inner = abi.encodeWithSelector(Target.revertCall.selector);
bytes memory data = abi.encodePacked(address(target), inner);

vm.expectRevert(
abi.encodeWithSelector(
DelegateAnything.DelegateCallFailed.selector, abi.encodeWithSignature("Error(string)", "Revert message")
)
);
wrapper.delegateCall(bytes32(0), 0, 0, 0, 0, data);
}

}