-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
876e140
Add ERC-7579 modules documentation
ernestognw a9b6b25
Revise
ernestognw f5d4e77
u9p
ernestognw 8c31ad9
Merge branch 'master' into chore/docs-erc7579
ernestognw da7ed55
Fix docs examples
ernestognw 7519741
up
ernestognw 315e8ff
up
ernestognw ed8cf29
add 7579 and 7702 refs in accounts further notes
ernestognw 936fc52
up
ernestognw 4021923
Merge branch 'master' into chore/docs-erc7579
ernestognw 9f815d9
Fix compilation
ernestognw 2940a4f
Update contracts/mocks/docs/account/MyAccountERC7579.sol
ernestognw 1b29bf6
Update docs/modules/ROOT/pages/account-modules.adoc
ernestognw f394bf3
Apply review
ernestognw 6de4389
Update docs/modules/ROOT/pages/account-modules.adoc
ernestognw 1d536a4
Apply suggestions from code review
ernestognw ab77541
Update docs/modules/ROOT/pages/account-modules.adoc
ernestognw 8980e0c
improve docs
ernestognw acd14be
review
ernestognw 53c0913
nit
ernestognw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// 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 MyAccountERC7579 is Initializable, AccountERC7579 { | ||
function initializeAccount(address validator, bytes calldata validatorData) public initializer { | ||
// Install a validator module to handle signature verification | ||
_installModule(MODULE_TYPE_VALIDATOR, validator, validatorData); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
contracts/mocks/docs/account/modules/MyERC7579DelayedSocialRecovery.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// contracts/MyERC7579DelayedSocialRecovery.sol | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {ERC7579Executor} from "../../../../account/modules/ERC7579Executor.sol"; | ||
import {ERC7579Validator} from "../../../../account/modules/ERC7579Validator.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)"); | ||
|
||
function isModuleType(uint256 moduleTypeId) public pure override(ERC7579Executor, ERC7579Validator) returns (bool) { | ||
return ERC7579Executor.isModuleType(moduleTypeId) || ERC7579Executor.isModuleType(moduleTypeId); | ||
} | ||
|
||
// Data encoding: [uint16(executorArgsLength), executorArgs, uint16(multisigArgsLength), multisigArgs] | ||
function onInstall(bytes calldata data) public override(ERC7579DelayedExecutor, ERC7579Multisig) { | ||
uint16 executorArgsLength = uint16(bytes2(data[0:2])); // First 2 bytes are the length | ||
bytes calldata executorArgs = data[2:2 + executorArgsLength]; // Next bytes are the args | ||
uint16 multisigArgsLength = uint16(bytes2(data[2 + executorArgsLength:4 + executorArgsLength])); // Next 2 bytes are the length | ||
bytes calldata multisigArgs = data[4 + executorArgsLength:4 + executorArgsLength + multisigArgsLength]; // Next bytes are the args | ||
|
||
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 view override { | ||
uint16 executionCalldataLength = uint16(bytes2(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 | ||
require(_rawERC7579Validation(account, _getExecuteTypeHash(account, salt, mode, executionCalldata), signature)); | ||
} | ||
|
||
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))); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// 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"; | ||
|
||
// Basic validator module | ||
abstract contract MyERC7579RecoveryValidator is ERC7579Validator {} | ||
|
||
// Basic executor module | ||
abstract contract MyERC7579RecoveryExecutor is ERC7579Executor {} | ||
|
||
// Basic fallback handler | ||
abstract contract MyERC7579RecoveryFallback is IERC7579Module {} | ||
|
||
// Basic hook | ||
abstract contract MyERC7579RecoveryHook is IERC7579Hook {} |
44 changes: 44 additions & 0 deletions
44
contracts/mocks/docs/account/modules/MyERC7579SocialRecovery.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// 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 {ERC7579Validator} from "../../../../account/modules/ERC7579Validator.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)"); | ||
|
||
function isModuleType(uint256 moduleTypeId) public pure override(ERC7579Executor, ERC7579Validator) returns (bool) { | ||
return ERC7579Executor.isModuleType(moduleTypeId) || ERC7579Executor.isModuleType(moduleTypeId); | ||
} | ||
|
||
// Data encoding: [uint16(executionCalldataLength), executionCalldata, signature] | ||
function _validateExecution( | ||
address account, | ||
bytes32 salt, | ||
bytes32 mode, | ||
bytes calldata data | ||
) internal override returns (bytes calldata) { | ||
uint16 executionCalldataLength = uint16(bytes2(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 | ||
require(_rawERC7579Validation(account, _getExecuteTypeHash(account, salt, mode, executionCalldata), signature)); | ||
return executionCalldata; | ||
} | ||
|
||
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)) | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
= Account Modules | ||
|
||
Smart accounts built with https://eips.ethereum.org/EIPS/eip-7579[ERC-7579] provide a standardized way to extend account functionality through modules (i.e. smart contract instances). This architecture allows accounts to support various features that are compatible with a wide variety of account implementations. See https://erc7579.com/modules[compatible modules]. | ||
|
||
== 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. | ||
|
||
=== Accounts | ||
|
||
OpenZeppelin offers an implementation of an xref:api:account.adoc#AccountERC7579[`AccountERC7579`] contract that allows installing modules compliant with this standard. There's also an xref:api:account.adoc#AccountERC7579Hooked[`AccountERC7579Hooked`] variant that supports installation of hooks. 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:account.adoc#AccountERC7579Hooked[`AccountERC7579Hooked`] only supports a single hook. 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 as smart contracts called _modules_. 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 can implement multiple types simultaneously, which means you could combine an executor module with hooks to enforce behaviors on an account, such as maintaining ERC-20 approvals or preventing the removal of certain permissions. | ||
|
||
See https://erc7579.com/modules[popular module implementations]. | ||
|
||
==== Building Custom Modules | ||
|
||
The library provides _standard composable modules_ as building blocks with an internal API for developers. By combining these components, you can create a rich set of variants without including unnecessary features. | ||
|
||
A good starting point is the xref:api:account.adoc#ERC7579Executor[`ERC7579Executor`] or xref:api:account.adoc#ERC7579Validator[`ERC7579Validator`], which include an opinionated base layer easily combined with other abstract modules. Hooks and fallback handlers are more straightforward to implement directly 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. The https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/account/utils/draft-ERC7579Utils.sol[`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; | ||
---- | ||
|
||
===== 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 allows an account to be recovered when access is lost by relying on trusted parties ("guardians") who verify the user's identity and help restore access. | ||
|
||
Social recovery is not a single solution but a design space with multiple configuration options: | ||
|
||
* Delay configuration | ||
* Expiration settings | ||
* Different guardian types | ||
* Cancellation windows | ||
* Confirmation requirements | ||
|
||
To support _different guardian types_, we can leverage ERC-7913 as discussed in the xref:multisig.adoc#beyond_standard_signature_verification[multisig] section. For ERC-7579 modules, this is implemented through the xref:api:account.adoc#ERC7579Multisig[`ERC7579Multisig`] validator. | ||
|
||
Combined with an xref:api:account.adoc#ERC7579Executor[`ERC7579Executor`], it provides a basic foundation that can be extended with more sophisticated features: | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/modules/MyERC7579SocialRecovery.sol[] | ||
---- | ||
|
||
For enhanced security, you can extend this foundation with scheduling, delays, and cancellations using xref:api:account.adoc#ERC7579DelayedExecutor[`ERC7579DelayedExecutor`]. This allows guardians to schedule recovery operations with a time delay, providing a security window to detect and cancel suspicious recovery attempts before they execute: | ||
|
||
[source,solidity] | ||
---- | ||
include::api:example$account/modules/MyERC7579DelayedSocialRecovery.sol[] | ||
---- | ||
|
||
NOTE: The delayed executor's signature validation doesn't require a nonce since operations are uniquely identified by their xref:api:account.adoc#ERC7579DelayedExecutor-hashOperation-address-bytes32-bytes32-bytes-[operation id] and cannot be scheduled twice. | ||
|
||
These implementations demonstrate how to build progressively more secure social recovery mechanisms, from basic multi-signature recovery to time-delayed recovery with cancellation capabilities. | ||
|
||
For additional functionality, developers can use: | ||
|
||
* xref:api:account.adoc#ERC7579MultisigWeighted[`ERC7579MultisigWeighted`] to assign different weights to signers | ||
* xref:api:account.adoc#ERC7579MultisigConfirmation[`ERC7579MultisigConfirmation`] to implement a confirmation system that verifies signatures when adding signers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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'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
Uh oh!
There was an error while loading. Please reload this page.
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 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 comment
The 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