From f36f183be3df9b1be3bfe8fc76831e5bec97b6a5 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 4 Apr 2024 18:16:40 +0200 Subject: [PATCH] Start initial work on ERC4337 interface and helpers --- contracts/abstraction/UserOperationUtils.sol | 109 +++++++++++++++++++ contracts/interfaces/IERC4337.sol | 47 ++++++++ 2 files changed, 156 insertions(+) create mode 100644 contracts/abstraction/UserOperationUtils.sol create mode 100644 contracts/interfaces/IERC4337.sol diff --git a/contracts/abstraction/UserOperationUtils.sol b/contracts/abstraction/UserOperationUtils.sol new file mode 100644 index 00000000000..0d2228f3308 --- /dev/null +++ b/contracts/abstraction/UserOperationUtils.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../interfaces/IERC4337.sol"; +import {Math} from "../utils/math/Math.sol"; + +// TODO: move that to a dedicated file in `contracts/utils/math` ? +library Unpack { + function split(bytes32 packed) internal pure returns (uint256 high128, uint256 low128) { + return (uint128(bytes16(packed)), uint128(uint256(packed))); + } + + function high(bytes32 packed) internal pure returns (uint256) { + return uint256(packed) >> 128; + } + + function low(bytes32 packed) internal pure returns (uint256) { + return uint128(uint256(packed)); + } +} + +library UserOperationUtils { + using Unpack for bytes32; + + uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20; + uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36; + uint256 public constant PAYMASTER_DATA_OFFSET = 52; + + // Need to fuzz this against `userOp.sender` + function getSender(PackedUserOperation calldata userOp) internal pure returns (address) { + address data; + assembly { + data := calldataload(userOp) + } + return address(uint160(data)); + } + + function getMaxPriorityFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return userOp.gasFees.high(); + } + + function getMaxFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return userOp.gasFees.low(); + } + + function getGasPrice(PackedUserOperation calldata userOp) internal view returns (uint256) { + unchecked { + (uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = userOp.gasFees.split(); + return + maxFeePerGas == maxPriorityFeePerGas + ? maxFeePerGas + : Math.min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); + } + } + + function getVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return userOp.accountGasLimits.high(); + } + + function getCallGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return userOp.accountGasLimits.low(); + } + + function getPaymasterVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])); + } + + function getPostOpGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])); + } + + function getPaymasterStaticFields( + bytes calldata paymasterAndData + ) internal pure returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) { + return ( + address(bytes20(paymasterAndData[:PAYMASTER_VALIDATION_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])) + ); + } + + function encode(PackedUserOperation calldata userOp) internal pure returns (bytes memory ret) { + return + abi.encode( + userOp.sender, + userOp.nonce, + calldataKeccak(userOp.initCode), + calldataKeccak(userOp.callData), + userOp.accountGasLimits, + userOp.preVerificationGas, + userOp.gasFees, + calldataKeccak(userOp.paymasterAndData) + ); + } + + function hash(PackedUserOperation calldata userOp) internal pure returns (bytes32) { + return keccak256(encode(userOp)); + } + + function calldataKeccak(bytes calldata data) private pure returns (bytes32 ret) { + assembly ("memory-safe") { + let ptr := mload(0x40) + let len := data.length + calldatacopy(ptr, data.offset, len) + ret := keccak256(ptr, len) + } + } +} diff --git a/contracts/interfaces/IERC4337.sol b/contracts/interfaces/IERC4337.sol new file mode 100644 index 00000000000..51146667ccc --- /dev/null +++ b/contracts/interfaces/IERC4337.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/* +struct UserOperation { + address sender; // The account making the operation + uint256 nonce; // Anti-replay parameter (see “Semi-abstracted Nonce Support” ) + address factory; // account factory, only for new accounts + bytes factoryData; // data for account factory (only if account factory exists) + bytes callData; // The data to pass to the sender during the main execution call + uint256 callGasLimit; // The amount of gas to allocate the main execution call + uint256 verificationGasLimit; // The amount of gas to allocate for the verification step + uint256 preVerificationGas; // Extra gas to pay the bunder + uint256 maxFeePerGas; // Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) + uint256 maxPriorityFeePerGas; // Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) + address paymaster; // Address of paymaster contract, (or empty, if account pays for itself) + uint256 paymasterVerificationGasLimit; // The amount of gas to allocate for the paymaster validation code + uint256 paymasterPostOpGasLimit; // The amount of gas to allocate for the paymaster post-operation code + bytes paymasterData; // Data for paymaster (only if paymaster exists) + bytes signature; // Data passed into the account to verify authorization +} +*/ + +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // concatenation of factory address and factoryData (or empty) + bytes callData; + bytes32 accountGasLimits; // concatenation of verificationGas (16 bytes) and callGas (16 bytes) + uint256 preVerificationGas; + bytes32 gasFees; // concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + bytes paymasterAndData; // concatenation of paymaster fields (or empty) + bytes signature; +} + +interface IAccount { + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} + +interface IAccountExecute { + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +}