Skip to content

Commit 28aed34

Browse files
AmxxernestognwnnsW3cairoetharr00
authored
Merge account abstraction work into master (#5274)
Co-authored-by: Ernesto García <ernestognw@gmail.com> Co-authored-by: Elias Rad <146735585+nnsW3@users.noreply.github.com> Co-authored-by: cairo <cairoeth@protonmail.com> Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
1 parent 2fa4d10 commit 28aed34

21 files changed

+2494
-95
lines changed

.changeset/hot-shrimps-wait.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`Packing`: Add variants for packing `bytes10` and `bytes22`

.changeset/small-seahorses-bathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts

.changeset/weak-roses-bathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts

.codecov.yml

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ coverage:
1313
ignore:
1414
- "test"
1515
- "contracts/mocks"
16+
- "contracts/vendor"

.github/workflows/checks.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,4 @@ jobs:
133133
with:
134134
check_hidden: true
135135
check_filenames: true
136-
skip: package-lock.json,*.pdf
136+
skip: package-lock.json,*.pdf,vendor

contracts/account/README.adoc

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
= Account
2+
3+
[.readme-notice]
4+
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account
5+
6+
This directory includes contracts to build accounts for ERC-4337.
7+
8+
== Utilities
9+
10+
{{ERC4337Utils}}
11+
12+
{{ERC7579Utils}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
6+
import {Math} from "../../utils/math/Math.sol";
7+
import {Packing} from "../../utils/Packing.sol";
8+
9+
/**
10+
* @dev Library with common ERC-4337 utility functions.
11+
*
12+
* See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337].
13+
*/
14+
library ERC4337Utils {
15+
using Packing for *;
16+
17+
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success.
18+
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;
19+
20+
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert.
21+
uint256 internal constant SIG_VALIDATION_FAILED = 1;
22+
23+
/// @dev Parses the validation data into its components. See {packValidationData}.
24+
function parseValidationData(
25+
uint256 validationData
26+
) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
27+
validAfter = uint48(bytes32(validationData).extract_32_6(0x00));
28+
validUntil = uint48(bytes32(validationData).extract_32_6(0x06));
29+
aggregator = address(bytes32(validationData).extract_32_20(0x0c));
30+
if (validUntil == 0) validUntil = type(uint48).max;
31+
}
32+
33+
/// @dev Packs the validation data into a single uint256. See {parseValidationData}.
34+
function packValidationData(
35+
address aggregator,
36+
uint48 validAfter,
37+
uint48 validUntil
38+
) internal pure returns (uint256) {
39+
return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator)));
40+
}
41+
42+
/// @dev Same as {packValidationData}, but with a boolean signature success flag.
43+
function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) {
44+
return
45+
packValidationData(
46+
address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))),
47+
validAfter,
48+
validUntil
49+
);
50+
}
51+
52+
/**
53+
* @dev Combines two validation data into a single one.
54+
*
55+
* The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while
56+
* the `validAfter` is the maximum and the `validUntil` is the minimum of both.
57+
*/
58+
function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) {
59+
(address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1);
60+
(address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2);
61+
62+
bool success = aggregator1 == address(0) && aggregator2 == address(0);
63+
uint48 validAfter = uint48(Math.max(validAfter1, validAfter2));
64+
uint48 validUntil = uint48(Math.min(validUntil1, validUntil2));
65+
return packValidationData(success, validAfter, validUntil);
66+
}
67+
68+
/// @dev Returns the aggregator of the `validationData` and whether it is out of time range.
69+
function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) {
70+
(address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData);
71+
return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
72+
}
73+
74+
/// @dev Computes the hash of a user operation with the current entrypoint and chainid.
75+
function hash(PackedUserOperation calldata self) internal view returns (bytes32) {
76+
return hash(self, address(this), block.chainid);
77+
}
78+
79+
/// @dev Sames as {hash}, but with a custom entrypoint and chainid.
80+
function hash(
81+
PackedUserOperation calldata self,
82+
address entrypoint,
83+
uint256 chainid
84+
) internal pure returns (bytes32) {
85+
bytes32 result = keccak256(
86+
abi.encode(
87+
keccak256(
88+
abi.encode(
89+
self.sender,
90+
self.nonce,
91+
keccak256(self.initCode),
92+
keccak256(self.callData),
93+
self.accountGasLimits,
94+
self.preVerificationGas,
95+
self.gasFees,
96+
keccak256(self.paymasterAndData)
97+
)
98+
),
99+
entrypoint,
100+
chainid
101+
)
102+
);
103+
return result;
104+
}
105+
106+
/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
107+
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
108+
return uint128(self.accountGasLimits.extract_32_16(0x00));
109+
}
110+
111+
/// @dev Returns `accountGasLimits` from the {PackedUserOperation}.
112+
function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
113+
return uint128(self.accountGasLimits.extract_32_16(0x10));
114+
}
115+
116+
/// @dev Returns the first section of `gasFees` from the {PackedUserOperation}.
117+
function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
118+
return uint128(self.gasFees.extract_32_16(0x00));
119+
}
120+
121+
/// @dev Returns the second section of `gasFees` from the {PackedUserOperation}.
122+
function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
123+
return uint128(self.gasFees.extract_32_16(0x10));
124+
}
125+
126+
/// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`).
127+
function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) {
128+
unchecked {
129+
// Following values are "per gas"
130+
uint256 maxPriorityFee = maxPriorityFeePerGas(self);
131+
uint256 maxFee = maxFeePerGas(self);
132+
return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee));
133+
}
134+
}
135+
136+
/// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
137+
function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
138+
return address(bytes20(self.paymasterAndData[0:20]));
139+
}
140+
141+
/// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
142+
function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
143+
return uint128(bytes16(self.paymasterAndData[20:36]));
144+
}
145+
146+
/// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
147+
function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
148+
return uint128(bytes16(self.paymasterAndData[36:52]));
149+
}
150+
}

0 commit comments

Comments
 (0)