123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- // SPDX-License-Identifier: MIT
- // OpenZeppelin Contracts (last updated v5.3.0) (account/utils/draft-ERC7579Utils.sol)
- pragma solidity ^0.8.20;
- import {Execution} from "../../interfaces/draft-IERC7579.sol";
- import {Packing} from "../../utils/Packing.sol";
- import {Address} from "../../utils/Address.sol";
- type Mode is bytes32;
- type CallType is bytes1;
- type ExecType is bytes1;
- type ModeSelector is bytes4;
- type ModePayload is bytes22;
- /**
- * @dev Library with common ERC-7579 utility functions.
- *
- * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579].
- */
- // slither-disable-next-line unused-state
- library ERC7579Utils {
- using Packing for *;
- /// @dev A single `call` execution.
- CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00);
- /// @dev A batch of `call` executions.
- CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01);
- /// @dev A `delegatecall` execution.
- CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
- /// @dev Default execution type that reverts on failure.
- ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
- /// @dev Execution type that does not revert on failure.
- ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01);
- /**
- * @dev Emits when an {EXECTYPE_TRY} execution fails.
- * @param batchExecutionIndex The index of the failed call in the execution batch.
- * @param returndata The returned data from the failed call.
- */
- event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata);
- /// @dev The provided {CallType} is not supported.
- error ERC7579UnsupportedCallType(CallType callType);
- /// @dev The provided {ExecType} is not supported.
- error ERC7579UnsupportedExecType(ExecType execType);
- /// @dev The provided module doesn't match the provided module type.
- error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module);
- /// @dev The module is not installed.
- error ERC7579UninstalledModule(uint256 moduleTypeId, address module);
- /// @dev The module is already installed.
- error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module);
- /// @dev The module type is not supported.
- error ERC7579UnsupportedModuleType(uint256 moduleTypeId);
- /// @dev Input calldata not properly formatted and possibly malicious.
- error ERC7579DecodingError();
- /// @dev Executes a single call.
- function execSingle(
- bytes calldata executionCalldata,
- ExecType execType
- ) internal returns (bytes[] memory returnData) {
- (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata);
- returnData = new bytes[](1);
- returnData[0] = _call(0, execType, target, value, callData);
- }
- /// @dev Executes a batch of calls.
- function execBatch(
- bytes calldata executionCalldata,
- ExecType execType
- ) internal returns (bytes[] memory returnData) {
- Execution[] calldata executionBatch = decodeBatch(executionCalldata);
- returnData = new bytes[](executionBatch.length);
- for (uint256 i = 0; i < executionBatch.length; ++i) {
- returnData[i] = _call(
- i,
- execType,
- executionBatch[i].target,
- executionBatch[i].value,
- executionBatch[i].callData
- );
- }
- }
- /// @dev Executes a delegate call.
- function execDelegateCall(
- bytes calldata executionCalldata,
- ExecType execType
- ) internal returns (bytes[] memory returnData) {
- (address target, bytes calldata callData) = decodeDelegate(executionCalldata);
- returnData = new bytes[](1);
- returnData[0] = _delegatecall(0, execType, target, callData);
- }
- /// @dev Encodes the mode with the provided parameters. See {decodeMode}.
- function encodeMode(
- CallType callType,
- ExecType execType,
- ModeSelector selector,
- ModePayload payload
- ) internal pure returns (Mode mode) {
- return
- Mode.wrap(
- CallType
- .unwrap(callType)
- .pack_1_1(ExecType.unwrap(execType))
- .pack_2_4(bytes4(0))
- .pack_6_4(ModeSelector.unwrap(selector))
- .pack_10_22(ModePayload.unwrap(payload))
- );
- }
- /// @dev Decodes the mode into its parameters. See {encodeMode}.
- function decodeMode(
- Mode mode
- ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) {
- return (
- CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)),
- ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)),
- ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)),
- ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10))
- );
- }
- /// @dev Encodes a single call execution. See {decodeSingle}.
- function encodeSingle(
- address target,
- uint256 value,
- bytes calldata callData
- ) internal pure returns (bytes memory executionCalldata) {
- return abi.encodePacked(target, value, callData);
- }
- /// @dev Decodes a single call execution. See {encodeSingle}.
- function decodeSingle(
- bytes calldata executionCalldata
- ) internal pure returns (address target, uint256 value, bytes calldata callData) {
- target = address(bytes20(executionCalldata[0:20]));
- value = uint256(bytes32(executionCalldata[20:52]));
- callData = executionCalldata[52:];
- }
- /// @dev Encodes a delegate call execution. See {decodeDelegate}.
- function encodeDelegate(
- address target,
- bytes calldata callData
- ) internal pure returns (bytes memory executionCalldata) {
- return abi.encodePacked(target, callData);
- }
- /// @dev Decodes a delegate call execution. See {encodeDelegate}.
- function decodeDelegate(
- bytes calldata executionCalldata
- ) internal pure returns (address target, bytes calldata callData) {
- target = address(bytes20(executionCalldata[0:20]));
- callData = executionCalldata[20:];
- }
- /// @dev Encodes a batch of executions. See {decodeBatch}.
- function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) {
- return abi.encode(executionBatch);
- }
- /// @dev Decodes a batch of executions. See {encodeBatch}.
- ///
- /// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted.
- function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) {
- unchecked {
- uint256 bufferLength = executionCalldata.length;
- // Check executionCalldata is not empty.
- if (bufferLength < 32) revert ERC7579DecodingError();
- // Get the offset of the array (pointer to the array length).
- uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0:32]));
- // The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the
- // buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk.
- if (arrayLengthOffset > bufferLength - 32) revert ERC7579DecodingError();
- // Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow.
- uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 32]));
- // Check that the buffer is long enough to store the array elements as "offset pointer":
- // - each element of the array is an "offset pointer" to the data.
- // - each "offset pointer" (to an array element) takes 32 bytes.
- // - validity of the calldata at that location is checked when the array element is accessed, so we only
- // need to check that the buffer is large enough to hold the pointers.
- //
- // Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk.
- // Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow.
- if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 32 < arrayLength * 32)
- revert ERC7579DecodingError();
- assembly ("memory-safe") {
- executionBatch.offset := add(add(executionCalldata.offset, arrayLengthOffset), 32)
- executionBatch.length := arrayLength
- }
- }
- }
- /// @dev Executes a `call` to the target with the provided {ExecType}.
- function _call(
- uint256 index,
- ExecType execType,
- address target,
- uint256 value,
- bytes calldata data
- ) private returns (bytes memory) {
- (bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).call{value: value}(
- data
- );
- return _validateExecutionMode(index, execType, success, returndata);
- }
- /// @dev Executes a `delegatecall` to the target with the provided {ExecType}.
- function _delegatecall(
- uint256 index,
- ExecType execType,
- address target,
- bytes calldata data
- ) private returns (bytes memory) {
- (bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).delegatecall(data);
- return _validateExecutionMode(index, execType, success, returndata);
- }
- /// @dev Validates the execution mode and returns the returndata.
- function _validateExecutionMode(
- uint256 index,
- ExecType execType,
- bool success,
- bytes memory returndata
- ) private returns (bytes memory) {
- if (execType == ERC7579Utils.EXECTYPE_DEFAULT) {
- Address.verifyCallResult(success, returndata);
- } else if (execType == ERC7579Utils.EXECTYPE_TRY) {
- if (!success) emit ERC7579TryExecuteFail(index, returndata);
- } else {
- revert ERC7579UnsupportedExecType(execType);
- }
- return returndata;
- }
- }
- // Operators
- using {eqCallType as ==} for CallType global;
- using {eqExecType as ==} for ExecType global;
- using {eqModeSelector as ==} for ModeSelector global;
- using {eqModePayload as ==} for ModePayload global;
- /// @dev Compares two `CallType` values for equality.
- function eqCallType(CallType a, CallType b) pure returns (bool) {
- return CallType.unwrap(a) == CallType.unwrap(b);
- }
- /// @dev Compares two `ExecType` values for equality.
- function eqExecType(ExecType a, ExecType b) pure returns (bool) {
- return ExecType.unwrap(a) == ExecType.unwrap(b);
- }
- /// @dev Compares two `ModeSelector` values for equality.
- function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) {
- return ModeSelector.unwrap(a) == ModeSelector.unwrap(b);
- }
- /// @dev Compares two `ModePayload` values for equality.
- function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) {
- return ModePayload.unwrap(a) == ModePayload.unwrap(b);
- }
|