-
Notifications
You must be signed in to change notification settings - Fork 32
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
Changes from all commits
cf3d1a5
5ebe456
0d6f72f
548bc52
5aaccf0
a99293d
63f82fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
} |
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 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
MulticallTarget.multicallWithResults(bytes[],bool) has external calls inside a loop: (results[i].success,results[i].returnData) = address(this).delegatecall(data[i])
Comment on lines
+40
to
+58
Check warning Code scanning / Slither Low-level calls Warning |
||
|
||
/// @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 |
||
} |
Check notice
Code scanning / Slither
Calls inside a loop Low