// SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; // solhint-disable avoid-low-level-calls // solhint-disable no-inline-assembly // Audit on 5-Jan-2021 by Keno and BoringCrypto // WARNING!!! // Combining BoringBatchable with msg.value can cause double spending issues // https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong/ import "../interfaces/IERC20.sol"; contract BaseBoringBatchable { /// @dev Helper function to extract a useful revert message from a failed call. /// If the returned data is malformed or not correctly abi encoded then this call can fail itself. function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) { // If the _res length is less than 68, then the transaction failed silently (without a revert message) if (_returnData.length < 68) return "Transaction reverted silently"; assembly { // Slice the sighash. _returnData := add(_returnData, 0x04) } return abi.decode(_returnData, (string)); // All that remains is the revert string } /// @notice Allows batched call to self (this contract). /// @param calls An array of inputs for each call. /// @param revertOnFail If True then reverts after a failed call and stops doing further calls. // F1: External is ok here because this is the batch function, adding it to a batch makes no sense // F2: Calls in the batch may be payable, delegatecall operates in the same context, so each call in the batch has access to msg.value // C3: The length of the loop is fully under user control, so can't be exploited // C7: Delegatecall is only used on the same contract, so it's safe function batch(bytes[] calldata calls, bool revertOnFail) external payable { for (uint256 i = 0; i < calls.length; i++) { (bool success, bytes memory result) = address(this).delegatecall(calls[i]); if (!success && revertOnFail) { revert(_getRevertMsg(result)); } } } } contract BoringBatchable is BaseBoringBatchable { /// @notice Call wrapper that performs `ERC20.permit` on `token`. /// Lookup `IERC20.permit`. // F6: Parameters can be used front-run the permit and the user's permit will fail (due to nonce or other revert) // if part of a batch this could be used to grief once as the second call would not need the permit function permitToken( IERC20 token, address from, address to, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public { token.permit(from, to, amount, deadline, v, r, s); } }