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

Implement ERC-4337 Account and ERC-7579 modules #5182

Closed
wants to merge 80 commits into from
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
24e6c8c
Account implementation and identities
ernestognw Sep 3, 2024
33774a6
Remove unnecessary change
ernestognw Sep 3, 2024
d2ef51d
Merge branch 'master' into account
ernestognw Sep 4, 2024
2d761cd
Remove unnecessary changes to Clones
ernestognw Sep 4, 2024
57021d6
Merge branch 'account-abstraction' into account
ernestognw Sep 18, 2024
481ffb0
Change Account implementations
ernestognw Sep 19, 2024
d816744
Lint
ernestognw Sep 19, 2024
aa4a7f7
Remove Memory utils
ernestognw Sep 19, 2024
074423d
Reorder
ernestognw Sep 19, 2024
558e16d
Reorder
ernestognw Sep 19, 2024
5803227
First docs
ernestognw Sep 19, 2024
54fc32b
More docs
ernestognw Sep 20, 2024
3b79b88
Fix readable signer
ernestognw Sep 20, 2024
ce2399b
Codespell
ernestognw Sep 20, 2024
f8ddac4
Add AccountEIP7702
ernestognw Sep 20, 2024
a3813d7
Fix compilation
ernestognw Sep 20, 2024
8a8de51
Add ERC7579
ernestognw Sep 20, 2024
0f5604a
Moar docs
ernestognw Sep 20, 2024
3fbd873
up
ernestognw Sep 21, 2024
e4d5b93
up
ernestognw Sep 21, 2024
1b70879
More docs
ernestognw Sep 21, 2024
d1738e0
Add ERC7579MultiSigner module
ernestognw Sep 21, 2024
ba8251c
up
ernestognw Sep 21, 2024
d8fff66
up
ernestognw Sep 21, 2024
b5dc98e
up
ernestognw Sep 22, 2024
79e11bb
up
ernestognw Sep 22, 2024
270ff94
up
ernestognw Sep 22, 2024
65f45ae
up
ernestognw Sep 23, 2024
5efd040
up
ernestognw Sep 23, 2024
41db378
up
ernestognw Sep 23, 2024
8b46711
up
ernestognw Sep 23, 2024
62d8164
up
ernestognw Sep 23, 2024
9f25076
up
ernestognw Sep 24, 2024
96a4800
up
ernestognw Sep 24, 2024
582accb
up
ernestognw Sep 25, 2024
dc7584f
codespell
ernestognw Sep 25, 2024
4844229
up
ernestognw Sep 25, 2024
7d3f069
Add notes
ernestognw Sep 25, 2024
2b14d66
Set test structure
ernestognw Sep 25, 2024
b80af5f
Fix codespell
ernestognw Sep 25, 2024
d4071d9
Reorder inheritance
ernestognw Sep 25, 2024
cfc8a9f
up
ernestognw Sep 25, 2024
eb8ee50
Update
ernestognw Sep 28, 2024
af35c87
Update
ernestognw Sep 28, 2024
0f11896
Update
ernestognw Sep 28, 2024
85171ef
Update
ernestognw Sep 28, 2024
4013a56
Update
ernestognw Sep 28, 2024
2d939e2
Fix stack too deep
ernestognw Sep 28, 2024
2d007e3
Update docs
ernestognw Sep 28, 2024
5ac6585
Update
ernestognw Sep 30, 2024
ed8678b
Make AccountERC7579Hooked abstract
ernestognw Oct 1, 2024
c3502c6
Revert clones test changes
ernestognw Oct 1, 2024
8c10ffa
Checkpoint MessageEnvelope tests
ernestognw Oct 1, 2024
90d6170
Fix inheritance order
ernestognw Oct 1, 2024
b63b53b
Exclude erc4337 entrypoint from transpilation
ernestognw Oct 1, 2024
a20b241
Remove console
ernestognw Oct 1, 2024
e2cae7d
Codespell
ernestognw Oct 1, 2024
ccd3e88
Exclude erc4337 entrypoint from transpilation
ernestognw Oct 1, 2024
185a280
Finish MessageEnvelopeUtils
ernestognw Oct 2, 2024
30eda8e
update transpiler
ernestognw Oct 2, 2024
278c5ad
WIP: Add ERC1271TypedSigner testrs
ernestognw Oct 2, 2024
66ff4ac
Lint
ernestognw Oct 2, 2024
6282757
Remove SHA256 wrapper
ernestognw Oct 2, 2024
46278ec
Make ERC1271TypedSigner tests work
ernestognw Oct 2, 2024
8212c68
Merge branch 'account-abstraction' into account
ernestognw Oct 2, 2024
46a3cfc
Fix ERC7579 Hook
ernestognw Oct 2, 2024
592339d
Apply suggestions
ernestognw Oct 2, 2024
b804925
Lint
ernestognw Oct 2, 2024
b03295a
Split ERC1155Holder into a lean version
ernestognw Oct 2, 2024
fd635dd
Add signer helpers
ernestognw Oct 2, 2024
872d148
Reorder Accounts inheritance
ernestognw Oct 2, 2024
253da58
Simplify and fix signers
ernestognw Oct 2, 2024
b93096a
WIP: AccountTests
ernestognw Oct 3, 2024
44586c3
Complete validateUserOp tests
ernestognw Oct 5, 2024
1e9efb0
Complete signer account tests
ernestognw Oct 7, 2024
e3e0fd0
Validators tests
ernestognw Oct 7, 2024
f6d6145
Lint
ernestognw Oct 7, 2024
6bd0a9f
Document validators
ernestognw Oct 7, 2024
99dbbe9
Test accounts fallback
ernestognw Oct 7, 2024
f791820
Add missing modules docs
ernestognw Oct 7, 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
80 changes: 80 additions & 0 deletions contracts/account/AccountBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {PackedUserOperation, IAccount, IEntryPoint, IAccountExecute} from "../interfaces/IERC4337.sol";
import {Address} from "../utils/Address.sol";
import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";

abstract contract AccountBase is IAccount, IAccountExecute, ERC1155Holder, ERC721Holder {
error AccountEntryPointRestricted();

modifier onlyEntryPointOrSelf() {
_checkEntryPointOrSelf();
_;
}

modifier onlyEntryPoint() {
_checkEntryPoint();
_;
}

function entryPoint() public view virtual returns (IEntryPoint) {
return IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032);
}

/// @dev Return the account nonce for the canonical sequence.
function getNonce() public view virtual returns (uint256) {
return getNonce(0);
}

/// @dev Return the account nonce for a given sequence (key).
function getNonce(uint192 key) public view virtual returns (uint256) {
return entryPoint().getNonce(address(this), key);
}

/// @inheritdoc IAccount
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) public virtual onlyEntryPoint returns (uint256) {
(, uint256 validationData) = _validateUserOp(userOp, userOpHash);
_payPrefund(missingAccountFunds);
return validationData;
}

/// @inheritdoc IAccountExecute
function executeUserOp(PackedUserOperation calldata userOp, bytes32 /*userOpHash*/) public virtual onlyEntryPoint {
Address.functionDelegateCall(address(this), userOp.callData[4:]);
}

function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual returns (address signer, uint256 validationData);

function _payPrefund(uint256 missingAccountFunds) internal virtual {
if (missingAccountFunds > 0) {
(bool success, ) = payable(msg.sender).call{value: missingAccountFunds}("");
success;
//ignore failure (its EntryPoint's job to verify, not account.)
}
}

function _checkEntryPoint() internal view virtual {
if (msg.sender != address(entryPoint())) {
revert AccountEntryPointRestricted();
}
}

function _checkEntryPointOrSelf() internal view virtual {
if (msg.sender != address(this) && msg.sender != address(entryPoint())) {
revert AccountEntryPointRestricted();
}
}

/// @dev Receive Ether.
receive() external payable virtual {}
}
227 changes: 227 additions & 0 deletions contracts/account/AccountERC7579.sol
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC1271} from "../interfaces/IERC1271.sol";
import {AccountBase} from "./AccountBase.sol";
import {PackedUserOperation} from "../interfaces/IERC4337.sol";
import {Address} from "../utils/Address.sol";
import {IERC7579AccountConfig, IERC7579Execution, IERC7579ModuleConfig} from "../interfaces/IERC7579Account.sol";
import {IERC7579Validator, IERC7579Module, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK} from "../interfaces/IERC7579Module.sol";
import {ERC7579Utils, Mode, CallType, ExecType} from "./utils/ERC7579Utils.sol";
import {ERC4337Utils} from "./utils/ERC4337Utils.sol";
import {EnumerableSet} from "../utils/structs/EnumerableSet.sol";

abstract contract AccountERC7579 is
AccountBase,
IERC7579ModuleConfig,
IERC7579Execution,
IERC7579AccountConfig,
IERC1271
{
using ERC7579Utils for *;
using EnumerableSet for *;

error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module);
error ERC7579UninstalledModule(uint256 moduleTypeId, address module);
error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module);
error ERC7579UnsupportedModuleType(uint256 moduleTypeId);

EnumerableSet.AddressSet private _validators;
EnumerableSet.AddressSet private _executors;
mapping(bytes4 => address) private _fallbacks;

modifier onlyModule(uint256 moduleTypeId) {
_checkModule(moduleTypeId, msg.sender);
_;
}

/// @inheritdoc IERC1271
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual override returns (bytes4) {
address module = address(bytes20(signature[0:20]));
_checkModule(MODULE_TYPE_VALIDATOR, module);
return IERC7579Validator(module).isValidSignatureWithSender(msg.sender, hash, signature);
}

/// @inheritdoc IERC7579AccountConfig
function accountId() public view virtual returns (string memory) {
//vendorname.accountname.semver
return "@openzeppelin/contracts.erc7579account.v0-beta";
}

/// @inheritdoc IERC7579AccountConfig
function supportsExecutionMode(bytes32 encodedMode) public view virtual returns (bool) {
return _supportsExecutionMode(encodedMode);
}

/// @inheritdoc IERC7579AccountConfig
function supportsModule(uint256 moduleTypeId) public view virtual returns (bool) {
return _supportsModule(moduleTypeId);
}

/// @inheritdoc IERC7579Execution
function execute(bytes32 mode, bytes calldata executionCalldata) public virtual onlyEntryPointOrSelf {
_execute(Mode.wrap(mode), executionCalldata);
}

/// @inheritdoc IERC7579Execution
function executeFromExecutor(
bytes32 mode,
bytes calldata executionCalldata
) public virtual onlyModule(MODULE_TYPE_EXECUTOR) returns (bytes[] memory) {
return _execute(Mode.wrap(mode), executionCalldata);
}

/// @inheritdoc IERC7579ModuleConfig
function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
) public view virtual returns (bool) {
return _isModuleInstalled(moduleTypeId, module, additionalContext);
}

/// @inheritdoc IERC7579ModuleConfig
function installModule(
uint256 moduleTypeId,
address module,
bytes calldata initData
) public virtual onlyEntryPointOrSelf {
_installModule(moduleTypeId, module, initData);
}

/// @inheritdoc IERC7579ModuleConfig
function uninstallModule(
uint256 moduleTypeId,
address module,
bytes calldata deInitData
) public virtual onlyEntryPointOrSelf {
_uninstallModule(moduleTypeId, module, deInitData);
}

function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override returns (address signer, uint256 validationData) {
PackedUserOperation memory userOpCopy = userOp;
address module = address(bytes20(userOp.signature[0:20]));
userOpCopy.signature = userOp.signature[20:];
return
isModuleInstalled(MODULE_TYPE_EXECUTOR, module, userOp.signature[0:0])
? (module, IERC7579Validator(module).validateUserOp(userOpCopy, userOpHash))
: (address(0), ERC4337Utils.SIG_VALIDATION_FAILED);
}

function _supportsExecutionMode(bytes32 encodedMode) internal pure virtual returns (bool) {
(CallType callType, , , ) = Mode.wrap(encodedMode).decodeMode();
return
callType == ERC7579Utils.CALLTYPE_SINGLE ||
callType == ERC7579Utils.CALLTYPE_BATCH ||
callType == ERC7579Utils.CALLTYPE_DELEGATECALL;
}

function _execute(
Mode mode,
bytes calldata executionCalldata
) internal virtual returns (bytes[] memory returnData) {
// TODO: ModeSelector? ModePayload?
(CallType callType, ExecType execType, , ) = mode.decodeMode();

if (callType == ERC7579Utils.CALLTYPE_SINGLE) return ERC7579Utils.execSingle(execType, executionCalldata);
if (callType == ERC7579Utils.CALLTYPE_BATCH) return ERC7579Utils.execBatch(execType, executionCalldata);
if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL)
return ERC7579Utils.execDelegateCall(execType, executionCalldata);
revert ERC7579Utils.ERC7579UnsupportedCallType(callType);
}

function _isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
) internal view virtual returns (bool) {
if (moduleTypeId == MODULE_TYPE_VALIDATOR) return _validators.contains(module);
if (moduleTypeId == MODULE_TYPE_EXECUTOR) return _executors.contains(module);
if (moduleTypeId == MODULE_TYPE_FALLBACK) return _fallbacks[bytes4(additionalContext[0:4])] != module;
return false;
}

function _installModule(uint256 moduleTypeId, address module, bytes calldata initData) internal virtual {
if (!_supportsModule(moduleTypeId)) revert ERC7579UnsupportedModuleType(moduleTypeId);
if (!IERC7579Module(module).isModuleType(moduleTypeId))
revert ERC7579MismatchedModuleTypeId(moduleTypeId, module);
if (
(moduleTypeId == MODULE_TYPE_VALIDATOR && !_validators.add(module)) ||
(moduleTypeId == MODULE_TYPE_EXECUTOR && !_executors.add(module)) ||
(moduleTypeId == MODULE_TYPE_FALLBACK && !_installFallback(module, bytes4(initData[0:4])))
) revert ERC7579AlreadyInstalledModule(moduleTypeId, module);

if (moduleTypeId == MODULE_TYPE_FALLBACK) initData = initData[4:];

IERC7579Module(module).onInstall(initData);
emit ModuleInstalled(moduleTypeId, module);
}

function _uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) internal virtual {
if (
(moduleTypeId == MODULE_TYPE_VALIDATOR && !_validators.remove(module)) ||
(moduleTypeId == MODULE_TYPE_EXECUTOR && !_executors.remove(module)) ||
(moduleTypeId == MODULE_TYPE_FALLBACK && !_uninstallFallback(module, bytes4(deInitData[0:4])))
) revert ERC7579UninstalledModule(moduleTypeId, module);

if (moduleTypeId == MODULE_TYPE_FALLBACK) deInitData = deInitData[4:];

IERC7579Module(module).onUninstall(deInitData);
emit ModuleUninstalled(moduleTypeId, module);
}

function _installFallback(address module, bytes4 selector) internal virtual returns (bool) {
if (_fallbacks[selector] != address(0)) return false;
_fallbacks[selector] = module;
return true;
}

function _uninstallFallback(address module, bytes4 selector) internal virtual returns (bool) {
address handler = _fallbacks[selector];
if (handler == address(0) || handler != module) return false;
delete _fallbacks[selector];
return true;
}

function _checkModule(uint256 moduleTypeId, address module) internal view virtual {
if (!_isModuleInstalled(moduleTypeId, module, msg.data)) {
revert ERC7579UninstalledModule(moduleTypeId, module);
}
}

function _supportsModule(uint256 moduleTypeId) internal view virtual returns (bool) {
return
moduleTypeId == MODULE_TYPE_VALIDATOR ||
moduleTypeId == MODULE_TYPE_EXECUTOR ||
moduleTypeId == MODULE_TYPE_FALLBACK;
}

function _fallbackHandler(bytes4 selector) internal view virtual returns (address) {
return _fallbacks[selector];
}

fallback() external payable virtual {
address handler = _fallbackHandler(msg.sig);
if (handler == address(0)) revert ERC7579UninstalledModule(MODULE_TYPE_FALLBACK, address(0));

// From https://eips.ethereum.org/EIPS/eip-7579#fallback[ERC-7579 specifications]:
// - MUST utilize ERC-2771 to add the original msg.sender to the calldata sent to the fallback handler
// - MUST use call to invoke the fallback handler
(bool success, bytes memory returndata) = handler.call{value: msg.value}(
abi.encodePacked(msg.data, msg.sender)
);
assembly ("memory-safe") {
switch success
case 0 {
revert(add(returndata, 0x20), mload(returndata))
}
default {
return(add(returndata, 0x20), mload(returndata))
}
}
}
}
19 changes: 19 additions & 0 deletions contracts/account/AccountSigner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {PackedUserOperation} from "../interfaces/IERC4337.sol";
import {ERC4337Utils} from "./utils/ERC4337Utils.sol";
import {EIP712ReadableSigner} from "./signers/EIP712ReadableSigner.sol";
import {AccountBase} from "./AccountBase.sol";
import {ShortStrings} from "../utils/ShortStrings.sol";

abstract contract AccountSigner is AccountBase, EIP712ReadableSigner {
function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override returns (address signer, uint256 validationData) {
if (!_isValidSignature(userOpHash, userOp.signature)) return (address(0), ERC4337Utils.SIG_VALIDATION_FAILED);
return (address(this), ERC4337Utils.SIG_VALIDATION_SUCCESS);
}
}
34 changes: 34 additions & 0 deletions contracts/account/AccountSignerERC7579.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC1271} from "../interfaces/IERC1271.sol";
import {PackedUserOperation} from "../interfaces/IERC4337.sol";
import {AccountERC7579} from "./AccountERC7579.sol";
import {AccountSigner, EIP712ReadableSigner} from "./AccountSigner.sol";
import {SignerECDSA} from "./signers/SignerECDSA.sol";
import {ERC4337Utils} from "./utils/ERC4337Utils.sol";
import {EIP712ReadableSigner} from "./signers/EIP712ReadableSigner.sol";

abstract contract AccountSignerERC7579 is AccountERC7579, AccountSigner, SignerECDSA {
function isValidSignature(
bytes32 hash,
bytes calldata signature
) public view override(AccountERC7579, EIP712ReadableSigner) returns (bytes4) {
bytes4 validModuleSignature = AccountERC7579.isValidSignature(hash, signature);
bytes4 validAccountSignature = EIP712ReadableSigner.isValidSignature(hash, signature);
bool isValid = validModuleSignature == IERC1271.isValidSignature.selector ||
validAccountSignature == IERC1271.isValidSignature.selector;
return isValid ? IERC1271.isValidSignature.selector : bytes4(0xffffffff);
}

function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override(AccountERC7579, AccountSigner) returns (address signer, uint256 validationData) {
(address accountSigner, uint256 accountValidationData) = AccountSigner._validateUserOp(userOp, userOpHash);
if (accountValidationData == ERC4337Utils.SIG_VALIDATION_SUCCESS) return (accountSigner, accountValidationData);
(address moduleSigner, uint256 moduleValidationData) = AccountERC7579._validateUserOp(userOp, userOpHash);
return (moduleSigner, moduleValidationData);
}
}
Loading
Loading