-
Notifications
You must be signed in to change notification settings - Fork 24
Add ERC-7579 modules docs and EIP-7702 note in accounts docs #157
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 1 commit
876e140
a9b6b25
f5d4e77
8c31ad9
da7ed55
7519741
315e8ff
ed8cf29
936fc52
4021923
9f815d9
2940a4f
1b29bf6
f394bf3
6de4389
1d536a4
ab77541
8980e0c
acd14be
53c0913
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,14 @@ | ||
// contracts/MyAccountERC7579.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; | ||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; | ||
import {MODULE_TYPE_VALIDATOR} from "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; | ||
import {AccountERC7579} from "../../../account/extensions/AccountERC7579.sol"; | ||
|
||
contract MyAccount is Initializable, AccountERC7579 { | ||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function initializeAccount(address validator, bytes calldata validatorData) public initializer { | ||
_installModule(MODULE_TYPE_VALIDATOR, validator, validatorData); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// contracts/MyERC7579DelayedSocialRecovery.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {Calldata} from "@openzeppelin/contracts/utils/Calldata.sol"; | ||
import {ERC7579DelayedExecutor} from "../../../../account/modules/ERC7579DelayedExecutor.sol"; | ||
import {ERC7579Multisig} from "../../../../account/modules/ERC7579Multisig.sol"; | ||
|
||
abstract contract MyERC7579DelayedSocialRecovery is EIP712, ERC7579DelayedExecutor, ERC7579Multisig { | ||
bytes32 private constant RECOVER_TYPEHASH = | ||
keccak256("Recover(address account,bytes32 salt,bytes32 mode,bytes executionCalldata)"); | ||
|
||
// Data encoding: [uint16(executorArgsLength), executorArgs, uint16(multisigArgsLength), multisigArgs] | ||
function onInstall(bytes calldata data) public override(ERC7579DelayedExecutor, ERC7579Multisig) { | ||
uint16 executorArgsLength = uint16(uint256(bytes32(data[0:2]))); // First 2 bytes are the length | ||
bytes calldata executorArgs = data[2:2 + executorArgsLength]; // Next bytes are the args | ||
uint16 multisigArgsLength = uint16(uint256(bytes32(data[2 + executorArgsLength:]))); // Next 2 bytes are the length | ||
bytes calldata multisigArgs = data[2 + executorArgsLength + 2:2 + executorArgsLength + 2 + multisigArgsLength]; // Next bytes are the args | ||
arr00 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
ERC7579DelayedExecutor.onInstall(executorArgs); | ||
ERC7579Multisig.onInstall(multisigArgs); | ||
} | ||
|
||
function onUninstall(bytes calldata) public override(ERC7579DelayedExecutor, ERC7579Multisig) { | ||
ERC7579DelayedExecutor.onUninstall(Calldata.emptyBytes()); | ||
ERC7579Multisig.onUninstall(Calldata.emptyBytes()); | ||
} | ||
|
||
// Data encoding: [uint16(executionCalldataLength), executionCalldata, signature] | ||
function _validateSchedule( | ||
address account, | ||
bytes32 salt, | ||
bytes32 mode, | ||
bytes calldata data | ||
) internal override returns (bool) { | ||
uint16 executionCalldataLength = uint16(uint256(bytes32(data[0:2]))); // First 2 bytes are the length | ||
bytes calldata executionCalldata = data[2:2 + executionCalldataLength]; // Next bytes are the calldata | ||
bytes calldata signature = data[2 + executionCalldataLength:]; // Remaining bytes are the signature | ||
return | ||
_validateMultisignature(account, _getExecuteTypeHash(account, salt, mode, executionCalldata), signature) || | ||
super._validateSchedule(account, salt, mode, data); | ||
} | ||
|
||
function _getExecuteTypeHash( | ||
address account, | ||
bytes32 salt, | ||
bytes32 mode, | ||
bytes calldata executionCalldata | ||
) internal view returns (bytes32) { | ||
return _hashTypedDataV4(keccak256(abi.encode(RECOVER_TYPEHASH, account, salt, mode, executionCalldata))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// contracts/MyERC7579Modules.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {IERC7579Module, IERC7579Hook} from "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; | ||
import {ERC7579Executor} from "../../../../account/modules/ERC7579Executor.sol"; | ||
import {ERC7579Validator} from "../../../../account/modules/ERC7579Validator.sol"; | ||
|
||
abstract contract MyERC7579RecoveryValidator is ERC7579Validator {} | ||
|
||
abstract contract MyERC7579RecoveryExecutor is ERC7579Executor {} | ||
|
||
abstract contract MyERC7579RecoveryFallback is IERC7579Module {} | ||
|
||
abstract contract MyERC7579RecoveryHook is IERC7579Hook {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// contracts/MyERC7579SocialRecovery.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; | ||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {ERC7579Executor} from "../../../../account/modules/ERC7579Executor.sol"; | ||
import {ERC7579Multisig} from "../../../../account/modules/ERC7579Multisig.sol"; | ||
|
||
abstract contract MyERC7579SocialRecovery is EIP712, ERC7579Executor, ERC7579Multisig, Nonces { | ||
bytes32 private constant RECOVER_TYPEHASH = | ||
keccak256("Recover(address account,bytes32 salt,uint256 nonce,bytes32 mode,bytes executionCalldata)"); | ||
|
||
// Data encoding: [uint16(executionCalldataLength), executionCalldata, signature] | ||
function _validateExecution( | ||
address account, | ||
bytes32 salt, | ||
bytes32 mode, | ||
bytes calldata data | ||
) internal override returns (bool valid, bytes calldata executionCalldata) { | ||
uint16 executionCalldataLength = uint16(uint256(bytes32(data[0:2]))); // First 2 bytes are the length | ||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bytes calldata actualExecutionCalldata = data[2:2 + executionCalldataLength]; // Next bytes are the calldata | ||
bytes calldata signature = data[2 + executionCalldataLength:]; // Remaining bytes are the signature | ||
return ( | ||
_validateMultisignature( | ||
account, | ||
_getExecuteTypeHash(account, salt, mode, actualExecutionCalldata), | ||
signature | ||
), | ||
actualExecutionCalldata | ||
); | ||
} | ||
|
||
function _getExecuteTypeHash( | ||
address account, | ||
bytes32 salt, | ||
bytes32 mode, | ||
bytes calldata executionCalldata | ||
) internal returns (bytes32) { | ||
return | ||
_hashTypedDataV4( | ||
keccak256(abi.encode(RECOVER_TYPEHASH, account, salt, _useNonce(account), mode, executionCalldata)) | ||
); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure where and exactly how, but I would like to briefly mention the benefits of implicitly using ERC-7913 building blocks on the Social Recovery example, since I think, and this is very opinionated, that it is a valuable feature that makes a compelling reason enough to choose this over other implementations, on top of the great composability and extensibility provided. Wdyt? @ernestognw There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think standards should be sold. I mean that a good standard solves an issue cleanly, and I think this is the case. The recommendation should be "just use ERC-7913" because it's simply the best way to approach support for arbitrary authorization types. Instead, maybe users would benefit from understanding how to bring ERC-7913 to their use cases. They should be guided about when to use each variant (SingerERC7913, MultisignerERC7913, ERC7579Multisig, ERC7913Utils, ERC7913RSAVerifier, ERC7913ZKVerifier, ERC7913P256Verifier and beyond) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I get what you say. Is it that the reasons of why using an ERC7579Multisig aren't explained? That'd be true. I just updated with a bit more context in 8980e0c |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
= Account Modules | ||
|
||
Smart accounts built with https://eips.ethereum.org/EIPS/eip-7579[ERC-7579] support provide a standardized way to extend account functionality through modules. This architecture allows accounts to support various features that are compatible with a wide variety of account implementations. See https://erc7579.com/accounts[compatible accounts]. | ||
|
||
== ERC-7579 | ||
|
||
ERC-7579 defines a standardized interface for modular smart accounts. This standard enables accounts to install, uninstall, and interact with modules that extend their capabilities in a composable manner with different account implementations as long as they are compatible with ERC-7579 modules. | ||
|
||
=== Accounts | ||
|
||
OpenZeppelin offers an implementation of an xref:api:accounts.adoc#AccountERC7579[`AccountERC7579`] contract that allows installing modules compliant with this standard. Also, there's an xref:api:accounts.adoc#AccountERC7579Hooked[`AccountERC7579Hooked`] variant that allows installing a hook. Like xref:accounts.adoc#handling_initialization[most accounts, an instance should define an initializer function] where the first module that controls the account will be set. | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/MyAccountERC7579.sol[] | ||
---- | ||
|
||
NOTE: For simplicity, the xref:api:accounts.adoc#AccountERC7579Hooked[`AccountERC7579Hooked`] only supports a single hook. However, a common workaround is to install a https://github.com/rhinestonewtf/core-modules/blob/7afffccb44d73dbaca2481e7b92bce0621ea6449/src/HookMultiPlexer/HookMultiPlexer.sol[single hook with a multiplexer pattern] to extend the functionality to multiple hooks. | ||
|
||
=== Modules | ||
|
||
Functionality is added to accounts through encapsulated functionality deployed at a smart contract called _module_. The standard defines four primary module types: | ||
|
||
* *Validator modules (type 1)*: Handle signature verification and user operation validation | ||
* *Executor modules (type 2)*: Execute operations on behalf of the account | ||
* *Fallback modules (type 3)*: Handle fallback calls for specific function selectors | ||
* *Hook modules (type 4)*: Execute logic before and after operations | ||
|
||
Modules are not mutually exclusive, which means that you could, for example, combine an executor module with hooks, so that it shares both types. These could be useful if developers want to enforce behaviors on an account, like keeping ERC20 approvals or avoid removing certain permissions. | ||
|
||
See https://erc7579.com/modules[popular module implementations]. | ||
|
||
==== Setting up a module | ||
|
||
The library provides with _standard composable modules_ to build your own. These are thought as building blocks that expose an internal API for developers to build functionalities. Composing them would produce a rich set of variants for developers without forcing unnecessary features they don't want to. | ||
|
||
To get started, you may like to start off from xref:api:account.adoc#ERC7579Executor[ERC7579Executor] or xref:api:account.adoc#ERC7579Validator[ERC7579Validator], which include an opinionated base layer which can easily be combined with other abstract modules. In the case of hooks or fallback handlers, they're more straight forward to implement out from interfaces. | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/modules/MyERC7579Modules.sol[] | ||
---- | ||
|
||
TIP: Explore these abstract ERC-7579 modules in the xref:api:account.adoc#modules[API Reference]. | ||
|
||
==== Execution Modes | ||
|
||
ERC-7579 supports various execution modes, which are encoded as a `bytes32` value using specific bit patterns. The `ERC7579Utils` library provides utility functions to work with these modes: | ||
|
||
[source,solidity] | ||
---- | ||
// Parts of an execution mode | ||
type Mode is bytes32; | ||
type CallType is bytes1; | ||
type ExecType is bytes1; | ||
type ModeSelector is bytes4; | ||
type ModePayload is bytes22; | ||
---- | ||
|
||
TIP: Use the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/account/utils/draft-ERC7579Utils.sol[ERC7579Utils] library to encode and decode ERC-7579 modes and their components. | ||
|
||
===== Call Types | ||
|
||
Call types determine the kind of execution: | ||
|
||
[%header,cols="1,1,3"] | ||
|=== | ||
|Type |Value |Description | ||
|`CALLTYPE_SINGLE` |`0x00` |A single `call` execution | ||
|`CALLTYPE_BATCH` |`0x01` |A batch of `call` executions | ||
|`CALLTYPE_DELEGATECALL` |`0xFF` |A `delegatecall` execution | ||
|=== | ||
|
||
===== Execution Types | ||
|
||
Execution types determine how failures are handled: | ||
|
||
[%header,cols="1,1,3"] | ||
|=== | ||
|Type |Value |Description | ||
|`EXECTYPE_DEFAULT` |`0x00` |Reverts on failure | ||
|`EXECTYPE_TRY` |`0x01` |Does not revert on failure, emits an event instead | ||
|=== | ||
|
||
==== Execution Data Format | ||
|
||
The execution data format varies depending on the call type: | ||
|
||
* For single calls: `abi.encodePacked(target, value, callData)` | ||
* For batched calls: `abi.encode(Execution[])` where `Execution` is a struct containing `target`, `value`, and `callData` | ||
* For delegate calls: `abi.encodePacked(target, callData)` | ||
|
||
== Examples | ||
|
||
=== Social Recovery | ||
|
||
Social Recovery is a method to regain access to an account by relying on trusted parties or "guardians" who verify the user's identity and help restore access, typically without needing a centralized authority. | ||
|
||
Although commonly cited as a single solution, social recovery is a whole design space. It includes multiple nuanced design decisions like _delay configuration_, _expiration_, _support of different guardian types_, _cancellation windows_, and even _confirmations_ to make sure guardians are capable to execute a recovery procedure. These functionalities are difficult to agreed upon in a standard way given the tradeoffs between configurations (e.g. should guardians have different weights?). | ||
|
||
In practice, a simple approach to social recovery is to coordinate multiple signatures from the guardians who can execute a recovery. Fortunately, the library includes an xref:api.accounts.adoc#ERC7579Executor[`ERC7579Executor`] that you can configure by implementing the xref:api.accounts.adoc#ERC7579Executor-_validateExecution[`_validateExecution`] function. In combination with xref:api.accounts.adoc#ERC7579Multisig[ERC7579Multisig], it's possible to create a simple _social_ executor that supports generic keys following https://eips.ethereum.org/EIPS/eip-7913[ERC-7913]: | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/modules/MyERC7579SocialRecovery.sol[] | ||
---- | ||
|
||
This approach, although lightweight, this approach lacks of more configuration options like delays or expirations. For these purposes, developers can use the xref:api.accounts.adoc#ERC7579DelayedExecutor[`ERC7579DelayedExecutor`] contract instead, that includes support for scheduling execution requests with an execution delay and cancellations: | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/modules/MyERC7579DelayedSocialRecovery.sol[] | ||
---- | ||
|
||
Developers can also use xref:api.accounts.adoc#ERC7579MultisigWeighted[ERC7579MultisigWeighted] to assign flexible weights to each signers. Also, they could implement a confirmation system that verifies a signature when signers are added to an executor using xref:api.accounts.adoc#ERC7579MultisigConfirmation[ERC7579MultisigConfirmation] |
Uh oh!
There was an error while loading. Please reload this page.