Skip to content
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: replace Multicall with our own MultiDelegateCall #96

Merged
merged 1 commit into from
Dec 6, 2024
Merged
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
4 changes: 2 additions & 2 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import {UserProxyFactory} from "./UserProxyFactory.sol";

import {add, max} from "./utils/Math.sol";
import {_requireNoDuplicates, _requireNoNegatives} from "./utils/UniqueArray.sol";
import {Multicall} from "./utils/Multicall.sol";
import {MultiDelegateCall} from "./utils/MultiDelegateCall.sol";
import {WAD, PermitParams} from "./utils/Types.sol";
import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol";
import {Ownable} from "./utils/Ownable.sol";

/// @title Governance: Modular Initiative based Governance
contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IGovernance {
contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Ownable, IGovernance {
using SafeERC20 for IERC20;

uint256 constant MIN_GAS_TO_HOOK = 350_000;
Expand Down
9 changes: 9 additions & 0 deletions src/interfaces/IMultiDelegateCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IMultiDelegateCall {
/// @notice Call multiple functions of the contract while preserving `msg.sender`
/// @param inputs Function calls to perform, encoded using `abi.encodeCall()` or equivalent
/// @return returnValues Raw data returned by each call
function multiDelegateCall(bytes[] calldata inputs) external returns (bytes[] memory returnValues);
}
13 changes: 0 additions & 13 deletions src/interfaces/IMulticall.sol

This file was deleted.

27 changes: 27 additions & 0 deletions src/utils/MultiDelegateCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IMultiDelegateCall} from "../interfaces/IMultiDelegateCall.sol";

contract MultiDelegateCall is IMultiDelegateCall {
/// @inheritdoc IMultiDelegateCall
function multiDelegateCall(bytes[] calldata inputs) external returns (bytes[] memory returnValues) {
returnValues = new bytes[](inputs.length);

for (uint256 i; i < inputs.length; ++i) {
(bool success, bytes memory returnData) = address(this).delegatecall(inputs[i]);

if (!success) {
// Bubble up the revert
assembly {
revert(
add(32, returnData), // offset (skip first 32 bytes, where the size of the array is stored)
mload(returnData) // size
)
}
}

returnValues[i] = returnData;
}
}
}
28 changes: 0 additions & 28 deletions src/utils/Multicall.sol

This file was deleted.

2 changes: 1 addition & 1 deletion test/Governance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ abstract contract GovernanceTest is Test {
);
data[6] = abi.encodeWithSignature("resetAllocations(address[],bool)", initiatives, true);
data[7] = abi.encodeWithSignature("withdrawLQTY(uint88)", lqtyAmount);
bytes[] memory response = governance.multicall(data);
bytes[] memory response = governance.multiDelegateCall(data);

(uint88 allocatedLQTY,) = abi.decode(response[3], (uint88, uint120));
assertEq(allocatedLQTY, lqtyAmount);
Expand Down
85 changes: 85 additions & 0 deletions test/MultiDelegateCall.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {stdError} from "forge-std/StdError.sol";
import {MultiDelegateCall} from "../src/utils/MultiDelegateCall.sol";

contract Target is MultiDelegateCall {
error CustomError(string);

function id(bytes calldata x) external pure returns (bytes calldata) {
return x;
}

function revertWithMessage(string calldata message) external pure {
revert(message);
}

function revertWithCustomError(string calldata message) external pure {
revert CustomError(message);
}

function panicWithArithmeticError() external pure returns (int256) {
return -type(int256).min;
}
}

contract MultiDelegateCallTest is Test {
function test_CallsAllInputsAndAggregatesResults() external {
Target target = new Target();

bytes[] memory inputValues = new bytes[](3);
inputValues[0] = abi.encode("asd", 123);
inputValues[1] = abi.encode("fgh", 456);
inputValues[2] = abi.encode("jkl", 789);

bytes[] memory inputs = new bytes[](3);
inputs[0] = abi.encodeCall(target.id, (inputValues[0]));
inputs[1] = abi.encodeCall(target.id, (inputValues[1]));
inputs[2] = abi.encodeCall(target.id, (inputValues[2]));

bytes[] memory returnValues = target.multiDelegateCall(inputs);
assertEq(returnValues.length, inputs.length, "returnValues.length != inputs.length");

assertEq(abi.decode(returnValues[0], (bytes)), inputValues[0], "returnValues[0]");
assertEq(abi.decode(returnValues[1], (bytes)), inputValues[1], "returnValues[1]");
assertEq(abi.decode(returnValues[2], (bytes)), inputValues[2], "returnValues[2]");
}

function test_StopsAtFirstRevertAndBubblesItUp() external {
Target target = new Target();

bytes[] memory inputs = new bytes[](3);
inputs[0] = abi.encodeCall(target.id, ("asd"));
inputs[1] = abi.encodeCall(target.revertWithMessage, ("fgh"));
inputs[2] = abi.encodeCall(target.revertWithMessage, ("jkl"));

vm.expectRevert(bytes("fgh"));
target.multiDelegateCall(inputs);
}

function test_CanBubbleCustomError() external {
Target target = new Target();

bytes[] memory inputs = new bytes[](3);
inputs[0] = abi.encodeCall(target.id, ("asd"));
inputs[1] = abi.encodeCall(target.revertWithCustomError, ("fgh"));
inputs[2] = abi.encodeCall(target.revertWithMessage, ("jkl"));

vm.expectRevert(abi.encodeWithSelector(Target.CustomError.selector, "fgh"));
target.multiDelegateCall(inputs);
}

function test_CanBubblePanic() external {
Target target = new Target();

bytes[] memory inputs = new bytes[](3);
inputs[0] = abi.encodeCall(target.id, ("asd"));
inputs[1] = abi.encodeCall(target.panicWithArithmeticError, ());
inputs[2] = abi.encodeCall(target.revertWithMessage, ("jkl"));

vm.expectRevert(stdError.arithmeticError);
target.multiDelegateCall(inputs);
}
}
Loading