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(contracts-rfq): Multicall target abstraction [SLT-134] #3078

Merged
merged 7 commits into from
Sep 10, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Interface for a contract that can be called multiple times by the same caller. Inspired by MulticallV3:
/// https://github.com/mds1/multicall/blob/master/src/Multicall3.sol
interface IMulticallTarget {
struct Result {
bool success;
bytes returnData;
}

function multicallNoResults(bytes[] calldata data, bool ignoreReverts) external;
function multicallWithResults(
bytes[] calldata data,
bool ignoreReverts
)
external
returns (Result[] memory results);
}
76 changes: 76 additions & 0 deletions packages/contracts-rfq/contracts/utils/MulticallTarget.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

// solhint-disable avoid-low-level-calls
/// @notice Template for a contract that supports batched calls (preserving the msg.sender).
/// Only calls with zero msg.value could be batched.
abstract contract MulticallTarget is IMulticallTarget {
error MulticallTarget__UndeterminedRevert();

/// @notice Perform a batched call to this contract, preserving the msg.sender.
/// The return data from each call is discarded.
/// @dev The method is non-payable, so only calls with `msg.value == 0` could be batched.
/// It's possible to ignore the reverts from the calls by setting the `ignoreReverts` flag.
/// Otherwise, the whole batch call will be reverted with the original revert reason.
/// @param data List of abi-encoded calldata for the calls to perform.
/// @param ignoreReverts Whether to ignore the revert errors from the calls.
function multicallNoResults(bytes[] calldata data, bool ignoreReverts) external {
for (uint256 i = 0; i < data.length; ++i) {
// We perform a delegate call to ourself to preserve the msg.sender. This is identical to `msg.sender`
// calling the functions directly one by one, therefore doesn't add any security risks.
// Note: msg.value is also preserved when doing a delegate call, but this function is not payable,
// so it's always 0 and not a security risk.
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success && !ignoreReverts) {
_bubbleRevert(result);
}
}
}
Comment on lines +19 to +30

Check notice

Code scanning / Slither

Calls inside a loop Low

Comment on lines +19 to +30

Check warning

Code scanning / Slither

Low-level calls Warning


/// @notice Perform a batched call to this contract, preserving the msg.sender.
/// The return data from each call is preserved.
/// @dev The method is non-payable, so only calls with `msg.value == 0` could be batched.
/// It's possible to ignore the reverts from the calls by setting the `ignoreReverts` flag.
/// Otherwise, the whole batch call will be reverted with the original revert reason.
/// @param data List of abi-encoded calldata for the calls to perform.
/// @param ignoreReverts Whether to ignore the revert errors from the calls.
/// @return results List of results from the calls: `(success, returnData)`.
function multicallWithResults(
bytes[] calldata data,
bool ignoreReverts
)
external
returns (Result[] memory results)
{
results = new Result[](data.length);
for (uint256 i = 0; i < data.length; ++i) {
// We perform a delegate call to ourself to preserve the msg.sender. This is identical to `msg.sender`
// calling the functions directly one by one, therefore doesn't add any security risks.
// Note: msg.value is also preserved when doing a delegate call, but this function is not payable,
// so it's always 0 and not a security risk.
(results[i].success, results[i].returnData) = address(this).delegatecall(data[i]);
if (!results[i].success && !ignoreReverts) {
_bubbleRevert(results[i].returnData);
}
}
}
Comment on lines +40 to +58

Check notice

Code scanning / Slither

Calls inside a loop Low

Comment on lines +40 to +58

/// @dev Bubbles the revert message from the underlying call.
/// Note: preserves the same custom error or revert string, if one was used.
/// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/utils/Address.sol#L143-L158
function _bubbleRevert(bytes memory returnData) internal pure {
// Look for revert reason and bubble it up if present
if (returnData.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returnData)
revert(add(32, returnData), returndata_size)
}
} else {
revert MulticallTarget__UndeterminedRevert();
}
}
Comment on lines +63 to +75

Check warning

Code scanning / Slither

Assembly usage Warning

}
Loading
Loading