Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Account Abstraction utils and interfaces #5242

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6d4cbbf
Add ERC4337 and ERC7579 utilities
ernestognw Oct 8, 2024
6dc7749
Lint
ernestognw Oct 8, 2024
efa9e2b
Lint
ernestognw Oct 8, 2024
211770b
Remove linting for vendored contracts and change to explicit imports
ernestognw Oct 8, 2024
1a25df6
Codespell
ernestognw Oct 8, 2024
fae755e
Merge branch 'master' into aa/utils-interfaces
ernestognw Oct 8, 2024
353f278
Complete tests
ernestognw Oct 9, 2024
33b0c1b
Fix transpilation
ernestognw Oct 9, 2024
a013306
Exclude vendored contracts from slither detection
ernestognw Oct 9, 2024
90fffbf
Fix transpilation (?)
ernestognw Oct 9, 2024
913e522
Filter constants un ERC7579Utils
ernestognw Oct 9, 2024
01e19a1
Make StakeManager's deposit's private
ernestognw Oct 9, 2024
8030e42
up
ernestognw Oct 9, 2024
81b0caa
up
ernestognw Oct 9, 2024
d83ed9e
up
ernestognw Oct 9, 2024
d6767b9
up
ernestognw Oct 9, 2024
8aecee3
Add doc
ernestognw Oct 9, 2024
c6b5aac
Fix coverage
ernestognw Oct 9, 2024
d1bc68d
draft-ERC433Utils -> draft-ERC4337Utils
ernestognw Oct 9, 2024
4dab8d0
Remove mixed contracts
ernestognw Oct 9, 2024
7934421
reset vendored contract to the match the source
Amxx Oct 9, 2024
75d470e
Merge branch 'account-abstraction' into aa/utils-interfaces
Amxx Oct 9, 2024
a194289
remove contracts/abstraction, that was replaced by contracts/account
Amxx Oct 9, 2024
8be86a7
remove duplicated
Amxx Oct 9, 2024
77f9821
refactor mock
Amxx Oct 9, 2024
a183548
don't include vendored erc4337-entrypoint in the nopm package.
Amxx Oct 9, 2024
176f151
change imports to aliased in vendor/erc4337-entrypoint for upgradeabl…
Amxx Oct 9, 2024
21e950b
fix
Amxx Oct 9, 2024
6a8cfde
fix
Amxx Oct 9, 2024
71f8ec8
codespell
Amxx Oct 9, 2024
7ac541a
exlude vendor contract from namespace transformation
Amxx Oct 10, 2024
8341a4f
test
Amxx Oct 10, 2024
8bac09c
Fix transpilation and testing of upgradeable contracts
Amxx Oct 10, 2024
c2e9839
update
Amxx Oct 10, 2024
09a5691
Applying review
ernestognw Oct 10, 2024
6216b5c
Update contracts/interfaces/draft-IERC4337.sol
ernestognw Oct 10, 2024
afbb6e4
Remove IEntryPointSimulation
ernestognw Oct 10, 2024
2a50933
Fix conflicts
ernestognw Oct 10, 2024
5bb9757
/// comments
Amxx Oct 10, 2024
cd3e388
minor refactor
Amxx Oct 10, 2024
bdf55bb
update
Amxx Oct 10, 2024
b72e3b1
Update .changeset/small-seahorses-bathe.md
ernestognw Oct 10, 2024
0a2fb48
Update .codecov.yml
ernestognw Oct 10, 2024
2073799
rename global comparators
Amxx Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ERC7579 modular accounts
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
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"
- "vendor"
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
115 changes: 0 additions & 115 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,33 @@

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)

/**
* @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)
/**
* @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 +38,9 @@ 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,6 +49,9 @@ 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(
Expand All @@ -48,6 +61,12 @@ library ERC4337Utils {
);
}

/**
* @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,26 +77,33 @@ 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);
} else {
(address agregator, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData);
return (agregator, block.timestamp > validUntil || block.timestamp < validAfter);
(address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData);
return (aggregator_, block.timestamp > validUntil || block.timestamp < validAfter);
}
}

// 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 +122,41 @@ 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 +166,24 @@ 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
Loading