Skip to content

Commit

Permalink
Add Account Abstraction utils and interfaces (OpenZeppelin#5242)
Browse files Browse the repository at this point in the history
Co-authored-by: Hadrien Croubois <[email protected]>
  • Loading branch information
ernestognw and Amxx authored Oct 11, 2024
1 parent 518fd94 commit 2aa4828
Show file tree
Hide file tree
Showing 21 changed files with 1,458 additions and 541 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-shrimps-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Packing`: Add variants for packing `bytes10` and `bytes22`
5 changes: 5 additions & 0 deletions .changeset/small-seahorses-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts
5 changes: 5 additions & 0 deletions .changeset/weak-roses-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ coverage:
ignore:
- "test"
- "contracts/mocks"
- "contracts/vendor"
116 changes: 0 additions & 116 deletions contracts/abstraction/utils/ERC7579Utils.sol

This file was deleted.

12 changes: 12 additions & 0 deletions contracts/account/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
= Account

[.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account

This directory includes contracts to build accounts for ERC-4337.

== Utilities

{{ERC4337Utils}}

{{ERC7579Utils}}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@

pragma solidity ^0.8.20;

import {IEntryPoint, PackedUserOperation} from "../../interfaces/IERC4337.sol";
import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
import {Math} from "../../utils/math/Math.sol";
// import {Memory} from "../../utils/Memory.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 *;
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* return this value on success.
*/

/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success.
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;

/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* must return this value in case of signature failure, instead of revert.
*/
/// @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;

// Validation data
/// @dev Parses the validation data into its components. See {packValidationData}.
function parseValidationData(
uint256 validationData
) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
Expand All @@ -31,6 +30,7 @@ library ERC4337Utils {
if (validUntil == 0) validUntil = type(uint48).max;
}

/// @dev Packs the validation data into a single uint256. See {parseValidationData}.
function packValidationData(
address aggregator,
uint48 validAfter,
Expand All @@ -39,15 +39,22 @@ library ERC4337Utils {
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
uint256(
bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(
bytes20(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED)))
)
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);
Expand All @@ -58,6 +65,7 @@ library ERC4337Utils {
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) {
if (validationData == 0) {
return (address(0), false);
Expand All @@ -67,17 +75,17 @@ library ERC4337Utils {
}
}

// Packed user operation
/// @dev Computes the hash of a user operation with the current entrypoint and chainid.
function hash(PackedUserOperation calldata self) internal view returns (bytes32) {
return hash(self, address(this), block.chainid);
}

/// @dev Sames as {hash}, but with a custom entrypoint and chainid.
function hash(
PackedUserOperation calldata self,
address entrypoint,
uint256 chainid
) internal pure returns (bytes32) {
// Memory.FreePtr ptr = Memory.save();
bytes32 result = keccak256(
abi.encode(
keccak256(
Expand All @@ -96,26 +104,30 @@ library ERC4337Utils {
chainid
)
);
// Memory.load(ptr);
return result;
}

/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.accountGasLimits.extract_32_16(0x00));
}

/// @dev Returns `accountGasLimits` from the {PackedUserOperation}.
function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.accountGasLimits.extract_32_16(0x10));
}

/// @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(0x00));
}

/// @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(0x10));
}

/// @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"
Expand All @@ -125,74 +137,18 @@ library ERC4337Utils {
}
}

/// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
return 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 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 uint128(bytes16(self.paymasterAndData[36:52]));
}

struct UserOpInfo {
address sender;
uint256 nonce;
uint256 verificationGasLimit;
uint256 callGasLimit;
uint256 paymasterVerificationGasLimit;
uint256 paymasterPostOpGasLimit;
uint256 preVerificationGas;
address paymaster;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes32 userOpHash;
uint256 prefund;
uint256 preOpGas;
bytes context;
}

function load(UserOpInfo memory self, PackedUserOperation calldata source) internal view {
self.sender = source.sender;
self.nonce = source.nonce;
self.verificationGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x00));
self.callGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x10));
self.preVerificationGas = source.preVerificationGas;
self.maxPriorityFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x00));
self.maxFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x10));

if (source.paymasterAndData.length > 0) {
require(source.paymasterAndData.length >= 52, "AA93 invalid paymasterAndData");
self.paymaster = paymaster(source);
self.paymasterVerificationGasLimit = paymasterVerificationGasLimit(source);
self.paymasterPostOpGasLimit = paymasterPostOpGasLimit(source);
} else {
self.paymaster = address(0);
self.paymasterVerificationGasLimit = 0;
self.paymasterPostOpGasLimit = 0;
}
self.userOpHash = hash(source);
self.prefund = 0;
self.preOpGas = 0;
self.context = "";
}

function requiredPrefund(UserOpInfo memory self) internal pure returns (uint256) {
return
(self.verificationGasLimit +
self.callGasLimit +
self.paymasterVerificationGasLimit +
self.paymasterPostOpGasLimit +
self.preVerificationGas) * self.maxFeePerGas;
}

function gasPrice(UserOpInfo memory self) internal view returns (uint256) {
unchecked {
uint256 maxFee = self.maxFeePerGas;
uint256 maxPriorityFee = self.maxPriorityFeePerGas;
return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee));
}
}
}
Loading

0 comments on commit 2aa4828

Please sign in to comment.