|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
pragma solidity ^0.8.20;
|
|
|
|
|
|
|
|
import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
|
|
|
|
import {Math} from "../../utils/math/Math.sol";
|
|
|
|
import {Packing} from "../../utils/Packing.sol";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Library with common ERC-4337 utility functions.
|
|
|
|
*
|
|
|
|
* See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337].
|
|
|
|
*/
|
|
|
|
library ERC4337Utils {
|
|
|
|
using Packing for *;
|
|
|
|
|
|
|
|
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success.
|
|
|
|
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;
|
|
|
|
|
|
|
|
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert.
|
|
|
|
uint256 internal constant SIG_VALIDATION_FAILED = 1;
|
|
|
|
|
|
|
|
/// @dev Parses the validation data into its components. See {packValidationData}.
|
|
|
|
function parseValidationData(
|
|
|
|
uint256 validationData
|
|
|
|
) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
|
|
|
|
validAfter = uint48(bytes32(validationData).extract_32_6(0));
|
|
|
|
validUntil = uint48(bytes32(validationData).extract_32_6(6));
|
|
|
|
aggregator = address(bytes32(validationData).extract_32_20(12));
|
|
|
|
if (validUntil == 0) validUntil = type(uint48).max;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Packs the validation data into a single uint256. See {parseValidationData}.
|
|
|
|
function packValidationData(
|
|
|
|
address aggregator,
|
|
|
|
uint48 validAfter,
|
|
|
|
uint48 validUntil
|
|
|
|
) internal pure returns (uint256) {
|
|
|
|
return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Same as {packValidationData}, but with a boolean signature success flag.
|
|
|
|
function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) {
|
|
|
|
return
|
|
|
|
packValidationData(
|
|
|
|
address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))),
|
|
|
|
validAfter,
|
|
|
|
validUntil
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Combines two validation data into a single one.
|
|
|
|
*
|
|
|
|
* The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while
|
|
|
|
* the `validAfter` is the maximum and the `validUntil` is the minimum of both.
|
|
|
|
*/
|
|
|
|
function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) {
|
|
|
|
(address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1);
|
|
|
|
(address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2);
|
|
|
|
|
|
|
|
bool success = aggregator1 == address(uint160(SIG_VALIDATION_SUCCESS)) &&
|
|
|
|
aggregator2 == address(uint160(SIG_VALIDATION_SUCCESS));
|
|
|
|
uint48 validAfter = uint48(Math.max(validAfter1, validAfter2));
|
|
|
|
uint48 validUntil = uint48(Math.min(validUntil1, validUntil2));
|
|
|
|
return packValidationData(success, validAfter, validUntil);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the aggregator of the `validationData` and whether it is out of time range.
|
|
|
|
function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) {
|
|
|
|
(address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData);
|
|
|
|
return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Computes the hash of a user operation for a given entrypoint and chainid.
|
|
|
|
function hash(
|
|
|
|
PackedUserOperation calldata self,
|
|
|
|
address entrypoint,
|
|
|
|
uint256 chainid
|
|
|
|
) internal pure returns (bytes32) {
|
|
|
|
bytes32 result = keccak256(
|
|
|
|
abi.encode(
|
|
|
|
keccak256(
|
|
|
|
abi.encode(
|
|
|
|
self.sender,
|
|
|
|
self.nonce,
|
|
|
|
keccak256(self.initCode),
|
|
|
|
keccak256(self.callData),
|
|
|
|
self.accountGasLimits,
|
|
|
|
self.preVerificationGas,
|
|
|
|
self.gasFees,
|
|
|
|
keccak256(self.paymasterAndData)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
entrypoint,
|
|
|
|
chainid
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
|
|
|
|
function factory(PackedUserOperation calldata self) internal pure returns (address) {
|
|
|
|
return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
|
|
|
|
function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
|
|
|
|
return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:];
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
|
|
|
|
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return uint128(self.accountGasLimits.extract_32_16(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns `callGasLimit` from the {PackedUserOperation}.
|
|
|
|
function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return uint128(self.accountGasLimits.extract_32_16(16));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the first section of `gasFees` from the {PackedUserOperation}.
|
|
|
|
function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return uint128(self.gasFees.extract_32_16(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the second section of `gasFees` from the {PackedUserOperation}.
|
|
|
|
function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return uint128(self.gasFees.extract_32_16(16));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`).
|
|
|
|
function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) {
|
|
|
|
unchecked {
|
|
|
|
// Following values are "per gas"
|
|
|
|
uint256 maxPriorityFee = maxPriorityFeePerGas(self);
|
|
|
|
uint256 maxFee = maxFeePerGas(self);
|
|
|
|
return Math.min(maxFee, maxPriorityFee + block.basefee);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
|
|
|
|
function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
|
|
|
|
return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20]));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
|
|
|
|
function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36]));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
|
|
|
|
function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
|
|
|
|
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}.
|
|
|
|
function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
|
|
|
|
return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:];
|
|
|
|
}
|
|
|
|
|
|
|
|
// slither-disable-next-line write-after-write
|
|
|
|
function _emptyCalldataBytes() private pure returns (bytes calldata result) {
|
|
|
|
assembly ("memory-safe") {
|
|
|
|
result.offset := 0
|
|
|
|
result.length := 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|