Skip to content

Commit

Permalink
Merge pull request #96 from liquity/multicall
Browse files Browse the repository at this point in the history
feat: replace Multicall with our own MultiDelegateCall
  • Loading branch information
danielattilasimon authored Dec 6, 2024
2 parents cafde08 + ce58d2c commit 8709efa
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 44 deletions.
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 @@ -1438,7 +1438,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);
}
}

0 comments on commit 8709efa

Please sign in to comment.