Contract Name:
MultiECDSAValidatorNew
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "solady/utils/ECDSA.sol";
import "src/utils/KernelHelper.sol";
import "src/interfaces/IAddressBook.sol";
import "src/interfaces/IValidator.sol";
import "src/common/Types.sol";
contract MultiECDSAValidatorNew is IKernelValidator {
event OwnerAdded(address indexed kernel, address indexed owner);
event OwnerRemoved(address indexed kernel, address indexed owner);
mapping(address owner => mapping(address kernel => bool) hello)
public isOwner;
function disable(bytes calldata _data) external payable override {
address[] memory owners = abi.decode(_data, (address[]));
for (uint256 i = 0; i < owners.length; i++) {
isOwner[owners[i]][msg.sender] = false;
emit OwnerRemoved(msg.sender, owners[i]);
}
}
function enable(bytes calldata _data) external payable override {
address addressBook = address(bytes20(_data));
address[] memory owners = IAddressBook(addressBook).getOwners();
for (uint256 i = 0; i < owners.length; i++) {
isOwner[owners[i]][msg.sender] = true;
emit OwnerAdded(msg.sender, owners[i]);
}
}
function validateUserOp(
UserOperation calldata _userOp,
bytes32 _userOpHash,
uint256
) external payable override returns (ValidationData validationData) {
address signer = ECDSA.recover(_userOpHash, _userOp.signature);
if (isOwner[signer][msg.sender]) {
return ValidationData.wrap(0);
}
bytes32 hash = ECDSA.toEthSignedMessageHash(_userOpHash);
signer = ECDSA.recover(hash, _userOp.signature);
if (!isOwner[signer][msg.sender]) {
return SIG_VALIDATION_FAILED;
}
return ValidationData.wrap(0);
}
function validateSignature(
bytes32 hash,
bytes calldata signature
) public view override returns (ValidationData) {
bytes32 wrappedHash = keccak256(abi.encodePacked(hash, msg.sender));
address signer = ECDSA.recover(wrappedHash, signature);
if (isOwner[signer][msg.sender]) {
return ValidationData.wrap(0);
}
bytes32 ethHash = ECDSA.toEthSignedMessageHash(wrappedHash);
signer = ECDSA.recover(ethHash, signature);
if (!isOwner[signer][msg.sender]) {
return SIG_VALIDATION_FAILED;
}
return ValidationData.wrap(0);
}
function validCaller(
address _caller,
bytes calldata
) external view override returns (bool) {
return isOwner[_caller][msg.sender];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
library ECDSA {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The signature is invalid.
error InvalidSignature();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The number which `s` must not exceed in order for
/// the signature to be non-malleable.
bytes32 private constant _MALLEABILITY_THRESHOLD =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RECOVERY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: as of Solady version 0.0.68, these functions will
// revert upon recovery failure for more safety by default.
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
function recover(bytes32 hash, bytes memory signature) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Copy `r` and `s`.
mstore(0x40, mload(add(signature, 0x20))) // `r`.
let s := mload(add(signature, 0x40))
mstore(0x60, s)
// Store the `hash` in the scratch space.
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, mload(add(signature, 0x60))))
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
and(
// If the signature is exactly 65 bytes in length.
eq(mload(signature), 65),
// If `s` in lower half order, such that the signature is not malleable.
lt(s, add(_MALLEABILITY_THRESHOLD, 1))
), // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
result := mload(0x00)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
// Store the function selector of `InvalidSignature()`.
mstore(0x00, 0x8baa579f)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the zero slot.
mstore(0x60, 0)
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
function recoverCalldata(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Directly copy `r` and `s` from the calldata.
calldatacopy(0x40, signature.offset, 0x40)
// Store the `hash` in the scratch space.
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40))))
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
and(
// If the signature is exactly 65 bytes in length.
eq(signature.length, 65),
// If `s` in lower half order, such that the signature is not malleable.
lt(mload(0x60), add(_MALLEABILITY_THRESHOLD, 1))
), // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
result := mload(0x00)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
// Store the function selector of `InvalidSignature()`.
mstore(0x00, 0x8baa579f)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the zero slot.
mstore(0x60, 0)
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the EIP-2098 short form signature defined by `r` and `vs`.
///
/// This function only accepts EIP-2098 short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
///
/// To be honest, I do not recommend using EIP-2098 signatures
/// for simplicity, performance, and security reasons. Most if not
/// all clients support traditional non EIP-2098 signatures by default.
/// As such, this method is intentionally not fully inlined.
/// It is merely included for completeness.
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) {
uint8 v;
bytes32 s;
/// @solidity memory-safe-assembly
assembly {
s := shr(1, shl(1, vs))
v := add(shr(255, vs), 27)
}
result = recover(hash, v, r, s);
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the signature defined by `v`, `r`, `s`.
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
// If `s` in lower half order, such that the signature is not malleable.
lt(s, add(_MALLEABILITY_THRESHOLD, 1)), // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
result := mload(0x00)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
// Store the function selector of `InvalidSignature()`.
mstore(0x00, 0x8baa579f)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the zero slot.
mstore(0x60, 0)
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* TRY-RECOVER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// WARNING!
// These functions will NOT revert upon recovery failure.
// Instead, they will return the zero address upon recovery failure.
// It is critical that the returned address is NEVER compared against
// a zero address (e.g. an uninitialized address variable).
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
function tryRecover(bytes32 hash, bytes memory signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
if iszero(xor(mload(signature), 65)) {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Copy `r` and `s`.
mstore(0x40, mload(add(signature, 0x20))) // `r`.
let s := mload(add(signature, 0x40))
mstore(0x60, s)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(s, _MALLEABILITY_THRESHOLD)) {
// Store the `hash` in the scratch space.
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, mload(add(signature, 0x60))))
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x40, // Start of output.
0x20 // Size of output.
)
)
// Restore the zero slot.
mstore(0x60, 0)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
}
// Restore the free memory pointer.
mstore(0x40, m)
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
function tryRecoverCalldata(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
if iszero(xor(signature.length, 65)) {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Directly copy `r` and `s` from the calldata.
calldatacopy(0x40, signature.offset, 0x40)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(mload(0x60), _MALLEABILITY_THRESHOLD)) {
// Store the `hash` in the scratch space.
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40))))
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x40, // Start of output.
0x20 // Size of output.
)
)
// Restore the zero slot.
mstore(0x60, 0)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
}
// Restore the free memory pointer.
mstore(0x40, m)
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the EIP-2098 short form signature defined by `r` and `vs`.
///
/// This function only accepts EIP-2098 short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
///
/// To be honest, I do not recommend using EIP-2098 signatures
/// for simplicity, performance, and security reasons. Most if not
/// all clients support traditional non EIP-2098 signatures by default.
/// As such, this method is intentionally not fully inlined.
/// It is merely included for completeness.
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (address result)
{
uint8 v;
bytes32 s;
/// @solidity memory-safe-assembly
assembly {
s := shr(1, shl(1, vs))
v := add(shr(255, vs), 27)
}
result = tryRecover(hash, v, r, s);
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the signature defined by `v`, `r`, `s`.
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(s, _MALLEABILITY_THRESHOLD)) {
// Store the `hash`, `v`, `r`, `s` in the scratch space.
mstore(0x00, hash)
mstore(0x20, and(v, 0xff))
mstore(0x40, r)
mstore(0x60, s)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x40, // Start of output.
0x20 // Size of output.
)
)
// Restore the zero slot.
mstore(0x60, 0)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(xor(0x60, returndatasize()))
}
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
// Store into scratch space for keccak256.
mstore(0x20, hash)
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32")
// 0x40 - 0x04 = 0x3c
result := keccak256(0x04, 0x3c)
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
assembly {
// The length of "\x19Ethereum Signed Message:\n" is 26 bytes (i.e. 0x1a).
// If we reserve 2 words, we'll have 64 - 26 = 38 bytes to store the
// ASCII decimal representation of the length of `s` up to about 2 ** 126.
// Instead of allocating, we temporarily copy the 64 bytes before the
// start of `s` data to some variables.
let m := mload(sub(s, 0x20))
// The length of `s` is in bytes.
let sLength := mload(s)
let ptr := add(s, 0x20)
let w := not(0)
// `end` marks the end of the memory which we will compute the keccak256 of.
let end := add(ptr, sLength)
// Convert the length of the bytes to ASCII decimal representation
// and store it into the memory.
for { let temp := sLength } 1 {} {
ptr := add(ptr, w) // `sub(ptr, 1)`.
mstore8(ptr, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
// Copy the header over to the memory.
mstore(sub(ptr, 0x20), "\x00\x00\x00\x00\x00\x00\x19Ethereum Signed Message:\n")
// Compute the keccak256 of the memory.
result := keccak256(sub(ptr, 0x1a), sub(end, sub(ptr, 0x1a)))
// Restore the previous memory.
mstore(s, sLength)
mstore(sub(s, 0x20), m)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {SIG_VALIDATION_FAILED_UINT} from "src/common/Constants.sol";
import {ValidationData} from "src/common/Types.sol";
function _intersectValidationData(ValidationData a, ValidationData b) pure returns (ValidationData validationData) {
assembly {
// xor(a,b) == shows only matching bits
// and(xor(a,b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) == filters out the validAfter and validUntil bits
// if the result is not zero, then aggregator part is not matching
switch iszero(and(xor(a, b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff))
case 1 {
// validAfter
let a_vd := and(0xffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff, a)
let b_vd := and(0xffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff, b)
validationData := xor(a_vd, mul(xor(a_vd, b_vd), gt(b_vd, a_vd)))
// validUntil
a_vd := and(0x000000000000ffffffffffff0000000000000000000000000000000000000000, a)
b_vd := and(0x000000000000ffffffffffff0000000000000000000000000000000000000000, b)
let until := xor(a_vd, mul(xor(a_vd, b_vd), lt(b_vd, a_vd)))
if iszero(until) { until := 0x000000000000ffffffffffff0000000000000000000000000000000000000000 }
validationData := or(validationData, until)
}
default { validationData := SIG_VALIDATION_FAILED_UINT }
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IAddressBook {
function getOwners() external view returns (address[] memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol";
import "src/common/Types.sol";
interface IKernelValidator {
function enable(bytes calldata _data) external payable;
function disable(bytes calldata _data) external payable;
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingFunds)
external
payable
returns (ValidationData);
function validateSignature(bytes32 hash, bytes calldata signature) external view returns (ValidationData);
function validCaller(address caller, bytes calldata data) external view returns (bool);
}
// 3 modes
// 1. default mode, use preset validator for the kernel
// 2. enable mode, enable a new validator for given action and use it for current userOp
// 3. sudo mode, use default plugin for current userOp
pragma solidity ^0.8.9;
import "src/common/Constants.sol";
type ValidAfter is uint48;
type ValidUntil is uint48;
type ValidationData is uint256;
ValidationData constant SIG_VALIDATION_FAILED = ValidationData.wrap(SIG_VALIDATION_FAILED_UINT);
function packValidationData(ValidAfter validAfter, ValidUntil validUntil) pure returns (ValidationData) {
return ValidationData.wrap(
uint256(ValidAfter.unwrap(validAfter)) << 208 | uint256(ValidUntil.unwrap(validUntil)) << 160
);
}
function parseValidationData(ValidationData validationData)
pure
returns (ValidAfter validAfter, ValidUntil validUntil, address result)
{
assembly {
result := validationData
validUntil := and(shr(160, validationData), 0xffffffffffff)
switch iszero(validUntil)
case 1 { validUntil := 0xffffffffffff }
validAfter := shr(208, validationData)
}
}
pragma solidity ^0.8.0;
// constants for kernel metadata
string constant KERNEL_NAME = "Kernel";
string constant KERNEL_VERSION = "0.2.1";
// ERC4337 constants
uint256 constant SIG_VALIDATION_FAILED_UINT = 1;
// STRUCT_HASH
bytes32 constant VALIDATOR_APPROVED_STRUCT_HASH = 0x3ce406685c1b3551d706d85a68afdaa49ac4e07b451ad9b8ff8b58c3ee964176;
// Storage slots
bytes32 constant KERNEL_STORAGE_SLOT = 0x439ffe7df606b78489639bc0b827913bd09e1246fa6802968a5b3694c53e0dd8;
bytes32 constant KERNEL_STORAGE_SLOT_1 = 0x439ffe7df606b78489639bc0b827913bd09e1246fa6802968a5b3694c53e0dd9;
bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;
/* solhint-disable no-inline-assembly */
import {calldataKeccak} from "../core/Helpers.sol";
/**
* User Operation struct
* @param sender the sender account of this request.
* @param nonce unique value the sender uses to verify it is not a replay.
* @param initCode if set, the account contract will be created by this constructor/
* @param callData the method call to execute on this account.
* @param callGasLimit the gas limit passed to the callData method call.
* @param verificationGasLimit gas used for validateUserOp and validatePaymasterUserOp.
* @param preVerificationGas gas not calculated by the handleOps method, but added to the gas paid. Covers batch overhead.
* @param maxFeePerGas same as EIP-1559 gas parameter.
* @param maxPriorityFeePerGas same as EIP-1559 gas parameter.
* @param paymasterAndData if set, this field holds the paymaster address and paymaster-specific data. the paymaster will pay for the transaction instead of the sender.
* @param signature sender-verified signature over the entire request, the EntryPoint address and the chain ID.
*/
struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}
/**
* Utility functions helpful when working with UserOperation structs.
*/
library UserOperationLib {
function getSender(UserOperation calldata userOp) internal pure returns (address) {
address data;
//read sender from userOp, which is first userOp member (saves 800 gas...)
assembly {data := calldataload(userOp)}
return address(uint160(data));
}
//relayer/block builder might submit the TX with higher priorityFee, but the user should not
// pay above what he signed for.
function gasPrice(UserOperation calldata userOp) internal view returns (uint256) {
unchecked {
uint256 maxFeePerGas = userOp.maxFeePerGas;
uint256 maxPriorityFeePerGas = userOp.maxPriorityFeePerGas;
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) {
address sender = getSender(userOp);
uint256 nonce = userOp.nonce;
bytes32 hashInitCode = calldataKeccak(userOp.initCode);
bytes32 hashCallData = calldataKeccak(userOp.callData);
uint256 callGasLimit = userOp.callGasLimit;
uint256 verificationGasLimit = userOp.verificationGasLimit;
uint256 preVerificationGas = userOp.preVerificationGas;
uint256 maxFeePerGas = userOp.maxFeePerGas;
uint256 maxPriorityFeePerGas = userOp.maxPriorityFeePerGas;
bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData);
return abi.encode(
sender, nonce,
hashInitCode, hashCallData,
callGasLimit, verificationGasLimit, preVerificationGas,
maxFeePerGas, maxPriorityFeePerGas,
hashPaymasterAndData
);
}
function hash(UserOperation calldata userOp) internal pure returns (bytes32) {
return keccak256(pack(userOp));
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;
/* solhint-disable no-inline-assembly */
/**
* returned data from validateUserOp.
* validateUserOp returns a uint256, with is created by `_packedValidationData` and parsed by `_parseValidationData`
* @param aggregator - address(0) - the account validated the signature by itself.
* address(1) - the account failed to validate the signature.
* otherwise - this is an address of a signature aggregator that must be used to validate the signature.
* @param validAfter - this UserOp is valid only after this timestamp.
* @param validaUntil - this UserOp is valid only up to this timestamp.
*/
struct ValidationData {
address aggregator;
uint48 validAfter;
uint48 validUntil;
}
//extract sigFailed, validAfter, validUntil.
// also convert zero validUntil to type(uint48).max
function _parseValidationData(uint validationData) pure returns (ValidationData memory data) {
address aggregator = address(uint160(validationData));
uint48 validUntil = uint48(validationData >> 160);
if (validUntil == 0) {
validUntil = type(uint48).max;
}
uint48 validAfter = uint48(validationData >> (48 + 160));
return ValidationData(aggregator, validAfter, validUntil);
}
// intersect account and paymaster ranges.
function _intersectTimeRange(uint256 validationData, uint256 paymasterValidationData) pure returns (ValidationData memory) {
ValidationData memory accountValidationData = _parseValidationData(validationData);
ValidationData memory pmValidationData = _parseValidationData(paymasterValidationData);
address aggregator = accountValidationData.aggregator;
if (aggregator == address(0)) {
aggregator = pmValidationData.aggregator;
}
uint48 validAfter = accountValidationData.validAfter;
uint48 validUntil = accountValidationData.validUntil;
uint48 pmValidAfter = pmValidationData.validAfter;
uint48 pmValidUntil = pmValidationData.validUntil;
if (validAfter < pmValidAfter) validAfter = pmValidAfter;
if (validUntil > pmValidUntil) validUntil = pmValidUntil;
return ValidationData(aggregator, validAfter, validUntil);
}
/**
* helper to pack the return value for validateUserOp
* @param data - the ValidationData to pack
*/
function _packValidationData(ValidationData memory data) pure returns (uint256) {
return uint160(data.aggregator) | (uint256(data.validUntil) << 160) | (uint256(data.validAfter) << (160 + 48));
}
/**
* helper to pack the return value for validateUserOp, when not using an aggregator
* @param sigFailed - true for signature failure, false for success
* @param validUntil last timestamp this UserOperation is valid (or zero for infinite)
* @param validAfter first timestamp this UserOperation is valid
*/
function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) pure returns (uint256) {
return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48));
}
/**
* keccak function over calldata.
* @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it.
*/
function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) {
assembly {
let mem := mload(0x40)
let len := data.length
calldatacopy(mem, data.offset, len)
ret := keccak256(mem, len)
}
}