-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add Account framework docs and guides #5660
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
Changes from 135 commits
f5544fd
40dfd20
0bcc521
7e75026
5fb074c
e5df541
5ad9788
7090f67
c4af1d7
aa8f29b
415c00d
f60aa3a
087a844
79629b7
f47cab7
8f58197
ecede7f
68bd96a
d5cb119
6a0ae8a
a49a157
a634278
8b6501a
39a1026
71a6b25
35d4a12
a95705b
90509bd
10f40d7
36fb044
f534243
c0e5e45
7e10f80
c6ed868
b87c8e2
f6d07c2
cfa2392
9a8e63f
3a90091
bdec803
73c12c7
1c97739
6b1bbd8
7764515
19fe4c5
11c42c3
be68753
f0a1155
c42a7fd
6e576ca
593e879
c3f39a1
db76c3b
8eebff0
65fa7de
abac3bd
7d120b9
02eccc1
c39d5f5
54f632a
58c794e
6a60523
80edba8
29c48d9
fae0a67
c6b299e
e412bd9
f7f64ee
2c33b49
b0b70eb
b695659
5c4fb88
8f32638
b12ca61
45fa35f
bec8059
0d7f9d0
2376a45
1e35eab
c275c03
5975d79
9356599
86c2cb8
6898230
4e2bc70
2a1f503
326c466
52ac908
6725618
0cce4d5
5c45ff0
80d8bd5
30f3bfa
eab64f8
e3dcc2b
3ac675d
b20b017
804ceea
d0f5961
98108d9
906a805
1bfa65c
1a9c074
4135626
e1ffe05
7070e39
709493e
91aeb92
a1ca133
6491896
7cf7b85
ce284c4
1567931
fd5ac83
ca2284d
e636dcd
fd60f74
de857b4
5729b5a
5099acb
cdd1f24
f2f74f7
dc1927d
14c79f9
d4c461a
1180ed0
fe88580
645102d
fa79cb0
773d687
4f98c5a
26d971a
e6fda11
07a2ca0
9ce669f
613307d
5b216ed
47a1504
1eeafd9
86c2fa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // contracts/MyAccountERC7702.sol | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {Account} from "../../../account/Account.sol"; | ||
| import {ERC721Holder} from "../../../token/ERC721/utils/ERC721Holder.sol"; | ||
| import {ERC1155Holder} from "../../../token/ERC1155/utils/ERC1155Holder.sol"; | ||
| import {ERC7821} from "../../../account/extensions/draft-ERC7821.sol"; | ||
| import {SignerERC7702} from "../../../utils/cryptography/signers/SignerERC7702.sol"; | ||
|
|
||
| contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder { | ||
| /// @dev Allows the entry point as an authorized executor. | ||
| function _erc7821AuthorizedExecutor( | ||
| address caller, | ||
| bytes32 mode, | ||
| bytes calldata executionData | ||
| ) internal view virtual override returns (bool) { | ||
| return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // contracts/MyFactoryAccount.sol | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pragma solidity ^0.8.20; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {Clones} from "../../../proxy/Clones.sol"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {Address} from "../../../utils/Address.sol"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @dev A factory contract to create accounts on demand. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contract MyFactoryAccount { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Clones for address; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Address for address; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| address private immutable _impl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(address impl_) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require(impl_.code.length > 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _impl = impl_; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// @dev Predict the address of the account | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function predictAddress(bytes32 salt, bytes calldata callData) public view returns (address) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (_impl.predictDeterministicAddress(_saltedCallData(salt, callData), address(this))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// @dev Create clone accounts on demand | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function cloneAndInitialize(bytes32 salt, bytes calldata callData) public returns (address) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| address predicted = predictAddress(salt, callData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (predicted.code.length == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _impl.cloneDeterministic(_saltedCallData(salt, callData)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| predicted.functionCall(callData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return predicted; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function _saltedCallData(bytes32 salt, bytes calldata callData) internal pure returns (bytes32) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Scope salt to the callData to avoid front-running the salt with a different callData | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return keccak256(abi.encodePacked(salt, callData)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// @dev Predict the address of the account | |
| function predictAddress(bytes32 salt, bytes calldata callData) public view returns (address) { | |
| return (_impl.predictDeterministicAddress(_saltedCallData(salt, callData), address(this))); | |
| } | |
| /// @dev Create clone accounts on demand | |
| function cloneAndInitialize(bytes32 salt, bytes calldata callData) public returns (address) { | |
| address predicted = predictAddress(salt, callData); | |
| if (predicted.code.length == 0) { | |
| _impl.cloneDeterministic(_saltedCallData(salt, callData)); | |
| predicted.functionCall(callData); | |
| } | |
| return predicted; | |
| } | |
| function _saltedCallData(bytes32 salt, bytes calldata callData) internal pure returns (bytes32) { | |
| // Scope salt to the callData to avoid front-running the salt with a different callData | |
| return keccak256(abi.encodePacked(salt, callData)); | |
| } | |
| /// @dev Predict the address of the account | |
| function predictAddress(bytes calldata callData) public view returns (address) { | |
| return _impl.predictDeterministicAddress(keccak256(callData), address(this)); | |
| } | |
| /// @dev Create clone accounts on demand | |
| function cloneAndInitialize(bytes calldata callData) public returns (address) { | |
| address predicted = predictAddress(callData); | |
| if (predicted.code.length == 0) { | |
| _impl.cloneDeterministic(keccak256(callData)); | |
| predicted.functionCall(callData); | |
| } | |
| return predicted; | |
| } |
If someone what to use a salt, they will "just" have to call cloneAndInitialize with
abi.encodePacked(abi.encodeCall(Account.intialize, (<...args...>)), salt);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question is, given that both versions have similar features, do we want:
- a more simpler version, easy to read, but where some features are "hidden" (and optional)
- a more complex version, harder to read, but where the extra feature are "in your face"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong preference, I think the factory is illustrative and the documentation already suggests some key considerations like "ensure the account address is deterministically tied to the initial owners". So all good with the current suggestion.
Overall I agree that an automated factory generation in Wizard must do the job better than this, and we may want to go for that option instead of a canonical factory in OZ contracts.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -151,7 +151,7 @@ abstract contract MultiSignerERC7913 is AbstractSigner { | |||||
| * | ||||||
| * Requirements: | ||||||
| * | ||||||
| * * The {signers}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not. | ||||||
| * * The {getSignerCount}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not. | ||||||
|
||||||
| * * The {getSignerCount}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not. | |
| * * The {getSignerCount} must be greater or equal than to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes the line too large, and not sure how to keep a single bullet point in 2 rows
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| = Account Abstraction | ||
|
|
||
| Unlike Externally Owned Accounts (EOAs), smart contracts may contain arbitrary verification logic based on authentication mechanisms different to Ethereum's native xref:api:utils.adoc#ECDSA[ECDSA] and have execution advantages such as batching or gas sponsorship. To leverage these properties of smart contracts, the community has widely adopted https://eips.ethereum.org/EIPS/eip-4337[ERC-4337], a standard to process user operations through an alternative mempool. | ||
|
|
||
| The library provides multiple contracts for Account Abstraction following this standard as it enables more flexible and user-friendly interactions with applications. Account Abstraction use cases include wallets in novel contexts (e.g. embedded wallets), more granular configuration of accounts, and recovery mechanisms. | ||
|
|
||
| == ERC-4337 Overview | ||
|
|
||
| The ERC-4337 is a detailed specification of how to implement the necessary logic to handle operations without making changes to the protocol level (i.e. the rules of the blockchain itself). This specification defines the following components: | ||
|
|
||
| === UserOperation | ||
|
|
||
| A `UserOperation` is a higher-layer pseudo-transaction object that represents the intent of the account. This shares some similarities with regular EVM transactions like the concept of `gasFees` or `callData` but includes fields that enable new capabilities. | ||
|
|
||
| ```solidity | ||
| 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; | ||
| } | ||
| ``` | ||
|
|
||
| This process of bundling user operations involves several costs that the bundler must cover, including base transaction fees, calldata serialization, entrypoint execution, and paymaster context costs. To compensate for these expenses, bundlers use the `preVerificationGas` and `gasFees` fields to charge users appropriately. | ||
|
|
||
| NOTE: Estimating `preVerificationGas` is not standardized as it varies based on network conditions such as gas prices and the size of the operation bundle. | ||
|
|
||
| TIP: Use xref:api:account.adoc#ERC4337Utils[`ERC4337Utils`] to manipulate the `UserOperation` struct and other ERC-4337 related values. | ||
|
|
||
| === Entrypoint | ||
|
|
||
| Each `UserOperation` is executed through a contract known as the https://etherscan.io/address/0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108#code[`EntryPoint`]. This contract is a singleton deployed across multiple networks at the same address although other custom implementations may be used. | ||
|
|
||
| The Entrypoint contracts is considered a trusted entity by the account. | ||
|
|
||
| === Bundlers | ||
|
|
||
| The bundler is a piece of _offchain_ infrastructure that is in charge of processing an alternative mempool of user operations. Bundlers themselves call the Entrypoint contract's `handleOps` function with an array of UserOperations that are executed and included in a block. | ||
|
|
||
| During the process, the bundler pays for the gas of executing the transaction and gets refunded during the execution phase of the Entrypoint contract. | ||
|
|
||
| ```solidity | ||
| /// @dev Process `userOps` and `beneficiary` receives all | ||
| /// the gas fees collected during the bundle execution. | ||
| function handleOps( | ||
| PackedUserOperation[] calldata ops, | ||
| address payable beneficiary | ||
| ) external { ... } | ||
| ``` | ||
|
|
||
| === Account Contract | ||
|
|
||
| The Account Contract is a smart contract that implements the logic required to validate a `UserOperation` in the context of ERC-4337. Any smart contract account should conform with the `IAccount` interface to validate operations. | ||
|
|
||
| ```solidity | ||
| interface IAccount { | ||
| function validateUserOp(PackedUserOperation calldata, bytes32, uint256) external returns (uint256 validationData); | ||
| } | ||
| ``` | ||
|
|
||
| Similarly, an Account should have a way to execute these operations by either handling arbitrary calldata on its `fallback` or implementing the `IAccountExecute` interface: | ||
|
|
||
| ```solidity | ||
| interface IAccountExecute { | ||
| function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; | ||
| } | ||
| ``` | ||
|
|
||
| NOTE: The `IAccountExecute` interface is optional. Developers might want to use xref:api:account.adoc#ERC7821[`ERC-7821`] for a minimal batched execution interface or rely on ERC-7579 or any other execution logic. | ||
|
|
||
| To build your own account, see xref:accounts.adoc[accounts]. | ||
|
|
||
| === Factory Contract | ||
|
|
||
| The smart contract accounts are created by a Factory contract defined by the Account developer. This factory receives arbitrary bytes as `initData` and returns an `address` where the logic of the account is deployed. | ||
|
|
||
| To build your own factory, see xref:accounts.adoc#accounts_factory[account factories]. | ||
|
|
||
| === Paymaster Contract | ||
|
|
||
| A Paymaster is an optional entity that can sponsor gas fees for Accounts, or allow them to pay for those fees in ERC-20 instead of native currency. This abstracts gas away of the user experience in the same way that computational costs of cloud servers are abstracted away from end-users. | ||
|
|
||
| To build your own paymaster, see https://docs.openzeppelin.com/community-contracts/0.0.1/paymasters[paymasters]. | ||
|
|
||
| == Further notes | ||
|
|
||
| === ERC-7562 Validation Rules | ||
|
|
||
| To process a bundle of `UserOperations`, bundlers call xref:api:account.adoc#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-[`validateUserOp`] on each operation sender to check whether the operation can be executed. However, the bundler has no guarantee that the state of the blockchain will remain the same after the validation phase. To overcome this problem, https://eips.ethereum.org/EIPS/eip-7562[ERC-7562] proposes a set of limitations to EVM code so that bundlers (or node operators) are protected from unexpected state changes. | ||
|
|
||
| These rules outline the requirements for operations to be processed by the canonical mempool. | ||
|
|
||
| Accounts can access its own storage during the validation phase, they might easily violate ERC-7562 storage access rules in undirect ways. For example, most accounts access their public keys from storage when validating a signature, limiting the ability of having accounts that validate operations for other accounts (e.g. via ERC-1271) | ||
|
|
||
| TIP: Although any Account that breaks such rules may still be processed by a private bundler, developers should keep in mind the centralization tradeoffs of relying on private infrastructure instead of _permissionless_ execution. |
Uh oh!
There was an error while loading. Please reload this page.