Skip to content
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
67 changes: 33 additions & 34 deletions script/universal/MultisigScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Script} from "lib/forge-std/src/Script.sol";
import {stdJson} from "lib/forge-std/src/StdJson.sol";
import {Vm} from "lib/forge-std/src/Vm.sol";

import {CBMulticall} from "../../src/utils/CBMulticall.sol";
import {ICBMulticall, Call3, Call3Value} from "../../src/utils/ICBMulticall.sol";

import {IGnosisSafe, Enum} from "./IGnosisSafe.sol";
import {Signatures} from "./Signatures.sol";
Expand Down Expand Up @@ -406,7 +406,7 @@ abstract contract MultisigScript is Script {
return scriptCalls[0];
}

CBMulticall.Call3[] memory rootCalls = new CBMulticall.Call3[](scriptCalls.length);
Call3[] memory rootCalls = new Call3[](scriptCalls.length);
uint256 rootCallsIndex;

Call[] memory currentGroup = new Call[](scriptCalls.length);
Expand Down Expand Up @@ -453,7 +453,7 @@ abstract contract MultisigScript is Script {
return Call({
operation: Enum.Operation.DelegateCall,
target: CB_MULTICALL,
data: abi.encodeCall(CBMulticall.aggregateDelegateCalls, (rootCalls)),
data: abi.encodeCall(ICBMulticall.aggregateDelegateCalls, (rootCalls)),
value: 0
});
}
Expand All @@ -469,7 +469,7 @@ abstract contract MultisigScript is Script {
/// @return rootCallsCount The number of root calls appended.
function _aggregateCalls(
Call3Type groupType,
CBMulticall.Call3[] memory rootCalls,
Call3[] memory rootCalls,
uint256 rootCallsIndex,
Call[] memory currentGroup,
uint256 currentGroupIndex
Expand All @@ -485,29 +485,29 @@ abstract contract MultisigScript is Script {
}
// Otherwise aggregate the calls into a single Multicall call.
else {
CBMulticall.Call3 memory rootCall;
Call3 memory rootCall;

if (groupType == Call3Type.CALL) {
CBMulticall.Call3[] memory call3s = new CBMulticall.Call3[](currentGroupIndex);
Call3[] memory call3s = new Call3[](currentGroupIndex);
for (uint256 j; j < currentGroupIndex; j++) {
call3s[j] = _toCall3(currentGroup[j]);
}

rootCall = CBMulticall.Call3({
rootCall = Call3({
target: CB_MULTICALL,
allowFailure: false,
callData: abi.encodeCall(CBMulticall.aggregate3, (call3s))
callData: abi.encodeCall(ICBMulticall.aggregate3, (call3s))
});
} else {
CBMulticall.Call3Value[] memory call3Values = new CBMulticall.Call3Value[](currentGroupIndex);
Call3Value[] memory call3Values = new Call3Value[](currentGroupIndex);
for (uint256 j; j < currentGroupIndex; j++) {
call3Values[j] = _toCall3Value(currentGroup[j]);
}

rootCall = CBMulticall.Call3({
rootCall = Call3({
target: CB_MULTICALL,
allowFailure: false,
callData: abi.encodeCall(CBMulticall.aggregate3Value, (call3Values))
callData: abi.encodeCall(ICBMulticall.aggregate3Value, (call3Values))
});
}

Expand Down Expand Up @@ -620,7 +620,7 @@ abstract contract MultisigScript is Script {

// Build the `execTransaction` calls chain for all the safe-to-safe approvals followed by the final script call.
Call[] memory execTransactionCalls = _buildExecTransactionCalls({safes: safes, callsChain: callsChain});
bytes memory txData = abi.encodeCall(CBMulticall.aggregate3, (_toCall3s(execTransactionCalls)));
bytes memory txData = abi.encodeCall(ICBMulticall.aggregate3, (_toCall3s(execTransactionCalls)));
console.logBytes(txData);

console.log("---\nSimulation link:");
Expand Down Expand Up @@ -895,53 +895,52 @@ abstract contract MultisigScript is Script {
return Call3Type.CALL_VALUE;
}

/// @notice Converts the given call to the format expected by the `CBMulticall.aggregate3` function.
/// @notice Converts the given call to the format expected by the `ICBMulticall.aggregate3` function.
///
/// @param call The call to convert to the format expected by the `CBMulticall.aggregate3` function.
/// @param call The call to convert to the format expected by the `ICBMulticall.aggregate3` function.
///
/// @return The call in the format expected by the `CBMulticall.aggregate3` function.
function _toCall3(Call memory call) internal pure returns (CBMulticall.Call3 memory) {
/// @return The call in the format expected by the `ICBMulticall.aggregate3` function.
function _toCall3(Call memory call) internal pure returns (Call3 memory) {
require(call.operation == Enum.Operation.Call, "MultisigScript::_toCall3: Operation must be Call");
require(call.value == 0, "MultisigScript::_toCall3: Value must be 0");

return CBMulticall.Call3({target: call.target, allowFailure: false, callData: call.data});
return Call3({target: call.target, allowFailure: false, callData: call.data});
}

/// @notice Converts the given call to the format expected by the `CBMulticall.aggregate3Value` function.
/// @notice Converts the given call to the format expected by the `ICBMulticall.aggregate3Value` function.
///
/// @param call The call to convert to the format expected by the `CBMulticall.aggregate3Value` function.
/// @param call The call to convert to the format expected by the `ICBMulticall.aggregate3Value` function.
///
/// @return The call in the format expected by the `CBMulticall.aggregate3Value` function.
function _toCall3Value(Call memory call) internal pure returns (CBMulticall.Call3Value memory) {
/// @return The call in the format expected by the `ICBMulticall.aggregate3Value` function.
function _toCall3Value(Call memory call) internal pure returns (Call3Value memory) {
require(call.operation == Enum.Operation.Call, "MultisigScript::_toCall3Value: Operation must be Call");
require(call.value > 0, "MultisigScript::_toCall3Value: Value must be greater than 0");

return
CBMulticall.Call3Value({target: call.target, allowFailure: false, value: call.value, callData: call.data});
return Call3Value({target: call.target, allowFailure: false, value: call.value, callData: call.data});
}

/// @notice Converts the given call to the format expected by the `CBMulticall.aggregateDelegateCalls` function.
/// @notice Converts the given call to the format expected by the `ICBMulticall.aggregateDelegateCalls` function.
///
/// @param call The call to convert to the format expected by the `CBMulticall.aggregateDelegateCalls` function.
/// @param call The call to convert to the format expected by the `ICBMulticall.aggregateDelegateCalls` function.
///
/// @return The call in the format expected by the `CBMulticall.aggregateDelegateCalls` function.
function _toDelegateCall3(Call memory call) internal pure returns (CBMulticall.Call3 memory) {
/// @return The call in the format expected by the `ICBMulticall.aggregateDelegateCalls` function.
function _toDelegateCall3(Call memory call) internal pure returns (Call3 memory) {
require(
call.operation == Enum.Operation.DelegateCall,
"MultisigScript::_toDelegateCall3: Operation must be DelegateCall"
);
require(call.value == 0, "MultisigScript::_toDelegateCall3: Value must be 0");

return CBMulticall.Call3({target: call.target, allowFailure: false, callData: call.data});
return Call3({target: call.target, allowFailure: false, callData: call.data});
}

/// @notice Converts the given calls to the format expected by the `aggregate3` function.
///
/// @param calls The calls to get the call3 values for.
///
/// @return The calls in the format expected by the `aggregate3` function.
function _toCall3s(Call[] memory calls) internal pure returns (CBMulticall.Call3[] memory) {
CBMulticall.Call3[] memory call3s = new CBMulticall.Call3[](calls.length);
function _toCall3s(Call[] memory calls) internal pure returns (Call3[] memory) {
Call3[] memory call3s = new Call3[](calls.length);
for (uint256 i; i < calls.length; i++) {
call3s[i] = _toCall3(calls[i]);
}
Expand All @@ -954,8 +953,8 @@ abstract contract MultisigScript is Script {
/// @param calls The calls to get the call3 values for.
///
/// @return The calls in the format expected by the `aggregate3` function.
function _toCall3Values(Call[] memory calls) internal pure returns (CBMulticall.Call3Value[] memory) {
CBMulticall.Call3Value[] memory call3Values = new CBMulticall.Call3Value[](calls.length);
function _toCall3Values(Call[] memory calls) internal pure returns (Call3Value[] memory) {
Call3Value[] memory call3Values = new Call3Value[](calls.length);
for (uint256 i; i < calls.length; i++) {
call3Values[i] = _toCall3Value(calls[i]);
}
Expand All @@ -968,8 +967,8 @@ abstract contract MultisigScript is Script {
/// @param calls The calls to get the call3 values for.
///
/// @return The calls in the format expected by the `aggregateDelegateCalls` function.
function _toDelegateCall3s(Call[] memory calls) internal pure returns (CBMulticall.Call3[] memory) {
CBMulticall.Call3[] memory delegateCall3s = new CBMulticall.Call3[](calls.length);
function _toDelegateCall3s(Call[] memory calls) internal pure returns (Call3[] memory) {
Call3[] memory delegateCall3s = new Call3[](calls.length);
for (uint256 i; i < calls.length; i++) {
delegateCall3s[i] = _toDelegateCall3(calls[i]);
}
Expand Down
16 changes: 8 additions & 8 deletions script/universal/MultisigScriptDeposit.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {CBMulticall} from "src/utils/CBMulticall.sol";
import {ICBMulticall, Call3Value} from "src/utils/ICBMulticall.sol";

import {MultisigScript} from "./MultisigScript.sol";
import {Enum} from "./IGnosisSafe.sol";
Expand Down Expand Up @@ -34,9 +34,9 @@ interface IOptimismPortal2 {
/// return vm.envAddress("OWNER_SAFE");
/// }
///
/// function _buildL2Calls() internal view override returns (CBMulticall.Call3Value[] memory) {
/// CBMulticall.Call3Value[] memory calls = new CBMulticall.Call3Value[](1);
/// calls[0] = CBMulticall.Call3Value({
/// function _buildL2Calls() internal view override returns (Call3Value[] memory) {
/// Call3Value[] memory calls = new Call3Value[](1);
/// calls[0] = Call3Value({
/// target: L2_CONTRACT,
/// allowFailure: false,
/// callData: abi.encodeCall(IL2Contract.someFunction, (arg1, arg2)),
Expand Down Expand Up @@ -112,7 +112,7 @@ abstract contract MultisigScriptDeposit is MultisigScript {
/// The `value` field in each Call3Value struct specifies ETH to send with that
/// specific L2 call. The total ETH will be bridged via the deposit transaction.
/// @return calls Array of calls to execute on L2 via CBMulticall
function _buildL2Calls() internal view virtual returns (CBMulticall.Call3Value[] memory);
function _buildL2Calls() internal view virtual returns (Call3Value[] memory);

//////////////////////////////////////////////////////////////////////////////////////
/// Overridden Functions ///
Expand All @@ -131,13 +131,13 @@ abstract contract MultisigScriptDeposit is MultisigScript {
/// function on L2 automatically distributes the ETH to each call according to its
/// specified `value` field - no additional developer action is required.
function _buildCalls() internal view virtual override returns (Call[] memory) {
CBMulticall.Call3Value[] memory l2Calls = _buildL2Calls();
Call3Value[] memory l2Calls = _buildL2Calls();
require(l2Calls.length > 0, "MultisigScriptDeposit: no L2 calls");
uint256 totalValue = _sumL2CallValues(l2Calls);

// Encode L2 calls as a multicall
// Note: We use aggregate3Value to support per-call ETH distribution on L2
bytes memory l2Data = abi.encodeCall(CBMulticall.aggregate3Value, (l2Calls));
bytes memory l2Data = abi.encodeCall(ICBMulticall.aggregate3Value, (l2Calls));

// Wrap in depositTransaction call to OptimismPortal
Call[] memory l1Calls = new Call[](1);
Expand Down Expand Up @@ -167,7 +167,7 @@ abstract contract MultisigScriptDeposit is MultisigScript {
/// @notice Sums the ETH values from an array of L2 calls
/// @param l2Calls The array of L2 calls to sum values from
/// @return total The total ETH value across all calls
function _sumL2CallValues(CBMulticall.Call3Value[] memory l2Calls) internal pure returns (uint256 total) {
function _sumL2CallValues(Call3Value[] memory l2Calls) internal pure returns (uint256 total) {
for (uint256 i; i < l2Calls.length; i++) {
total += l2Calls[i].value;
}
Expand Down
27 changes: 3 additions & 24 deletions src/utils/CBMulticall.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {ICBMulticall, Call, Call3, Call3Value, Result} from "./ICBMulticall.sol";

/// @title CBMulticall
///
/// @notice Aggregate results from multiple function calls
Expand All @@ -10,30 +12,7 @@ pragma solidity 0.8.15;
/// permissioned calls that require a value set. When routing a call through a multisig, for example, it's
/// beneficial to be able to DELEGATECALL from the multisig to this contract, thus maintaining the multisig as the
/// `msg.sender` while still allowing the calls to utilize ETH already held by the multisig.
contract CBMulticall {
struct Call {
address target;
bytes callData;
}

struct Call3 {
address target;
bool allowFailure;
bytes callData;
}

struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}

struct Result {
bool success;
bytes returnData;
}

contract CBMulticall is ICBMulticall {
address private immutable THIS_CB_MULTICALL;

error MustDelegateCall();
Expand Down
44 changes: 44 additions & 0 deletions src/utils/ICBMulticall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

struct Call {
address target;
bytes callData;
}

struct Call3 {
address target;
bool allowFailure;
bytes callData;
}

struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}

struct Result {
bool success;
bytes returnData;
}

/// @title ICBMulticall
/// @notice Interface for the CBMulticall contract used in multisig scripts.
interface ICBMulticall {
/// @notice Aggregate calls, ensuring each returns success if required
/// @param calls An array of Call3 structs
/// @return returnData An array of Result structs
function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);

/// @notice Aggregate calls, ensuring each returns success if required
/// @param calls An array of Call3 structs
/// @return returnData An array of Result structs
function aggregateDelegateCalls(Call3[] calldata calls) external payable returns (Result[] memory returnData);

/// @notice Aggregate calls with a msg value
/// @param calls An array of Call3Value structs
/// @return returnData An array of Result structs
function aggregate3Value(Call3Value[] calldata calls) external payable returns (Result[] memory returnData);
}
Loading