-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #96 from liquity/multicall
feat: replace Multicall with our own MultiDelegateCall
- Loading branch information
Showing
7 changed files
with
124 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |