Skip to content

Commit

Permalink
Start initial work on ERC4337 interface and helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Amxx committed Apr 4, 2024
1 parent 8a7a9c5 commit 67e66bf
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
109 changes: 109 additions & 0 deletions contracts/abstraction/UserOperationUtils.sol
Original file line number Diff line number Diff line change
@@ -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) {

Check failure on line 101 in contracts/abstraction/UserOperationUtils.sol

View workflow job for this annotation

GitHub Actions / lint

Private and internal functions must have leading underscore
assembly ("memory-safe") {
let ptr := mload(0x40)
let len := data.length
calldatacopy(ptr, data.offset, len)
ret := keccak256(ptr, len)
}
}
}
47 changes: 47 additions & 0 deletions contracts/interfaces/IERC4337.sol
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 67e66bf

Please sign in to comment.