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

ERC-4337 & ERC-7579 compliant modular smart accounts #4991

Draft
wants to merge 75 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
5e82076
Add P256 implementation and testing
Amxx Feb 7, 2024
da0f27e
enable optimizations by default
Amxx Feb 7, 2024
aa59c67
test recovering address
Amxx Feb 7, 2024
9512947
improved testing
Amxx Feb 7, 2024
a60bf48
spelling
Amxx Feb 7, 2024
9185026
fix lint
Amxx Feb 7, 2024
025e360
expose imports tick
Amxx Feb 7, 2024
803e735
fix lint
Amxx Feb 7, 2024
57fcecd
fix lint
Amxx Feb 7, 2024
4dae298
add changeset
Amxx Feb 7, 2024
6cf039d
improve doc
Amxx Feb 7, 2024
c094fa1
add envvar to force allowUnlimitedContractSize
Amxx Feb 7, 2024
20a03df
fix lint
Amxx Feb 7, 2024
15f1a6b
fix stack too deep error in coverage
Amxx Feb 7, 2024
e2040e4
reoder arguments to match ecrecover and EIP-7212
Amxx Feb 13, 2024
695b732
reduce diff
Amxx Mar 13, 2024
41aaf71
Merge branch 'master' into feature/P256
Amxx Mar 13, 2024
f36f183
Start initial work on ERC4337 interface and helpers
Amxx Apr 4, 2024
a1532d0
Packing library
Amxx Apr 4, 2024
00e0eea
reorder UserOperationUtils.sol
Amxx Apr 5, 2024
8fa363e
4337 account wip
Amxx Apr 8, 2024
9309f71
wip
Amxx Apr 10, 2024
1616a96
wip
Amxx Apr 11, 2024
aef2168
refactor
Amxx Apr 11, 2024
04a6fb0
refactor
Amxx Apr 12, 2024
0f3c3fa
entrypoint deploys account
Amxx Apr 12, 2024
3bf4557
Update contracts/utils/cryptography/P256.sol
Amxx Apr 24, 2024
3cbf426
Merge branch 'master' into feature/P256
Amxx Apr 25, 2024
bba7fa3
update pseudocode reference
Amxx Apr 25, 2024
2812ed8
Update contracts/utils/cryptography/P256.sol
Amxx Apr 25, 2024
e0ef63b
refactor neutral element in jAdd
Amxx Apr 26, 2024
a13ad48
refactor entrypoint
Amxx Apr 26, 2024
342256c
erc4337 js helper
Amxx Apr 26, 2024
30c2d12
update 4337 helper
Amxx Apr 29, 2024
b1f3b60
Merge remote-tracking branch 'origin' into erc4337/interfaces-and-hel…
Amxx Apr 30, 2024
2f07188
Working on abstract account primitives
Amxx Apr 30, 2024
a129a45
improve tests
Amxx Apr 30, 2024
57b4fb2
use getBytes
Amxx Apr 30, 2024
c641f0a
update Account
Amxx Apr 30, 2024
3124b88
Merge branch 'feature/P256' into erc4337/interfaces-and-helpers
Amxx Apr 30, 2024
618b563
Add ECDSA and P256 variants of SimpleAccount
Amxx Apr 30, 2024
14474fc
move AccountECDSA and AccountP256 to a "modules" subfolder
Amxx Apr 30, 2024
7babdf1
Merge branch 'master' into erc4337/interfaces-and-helpers
Amxx Apr 30, 2024
6a5c91b
fix comments
Amxx May 1, 2024
1487fdb
inline documentation
Amxx May 2, 2024
6373a08
Inline documentation
Amxx May 2, 2024
4512481
add AccountMultisig module
Amxx May 3, 2024
ee47efc
update
Amxx May 3, 2024
003c232
refactor signature processing
Amxx May 4, 2024
318c372
add AccountAllSignatures.sol that support ECDSA & P256 identities in …
Amxx May 13, 2024
03dfa71
up
Amxx May 15, 2024
82d6bde
Add Account7702
Amxx May 20, 2024
612cadc
AccountCommon
Amxx May 21, 2024
b81817a
rename
Amxx May 21, 2024
fbf4ca5
Merge branch 'erc4337/EIP-7702-account' into erc4337/interfaces-and-h…
Amxx May 21, 2024
a84c65e
Merge branch 'master' into erc4337/interfaces-and-helpers
Amxx Jun 12, 2024
d4bdf5a
Merge branch 'master' into erc4337/interfaces-and-helpers
Amxx Jun 13, 2024
3696b7d
Add clone variant with instance parameters stored in 'immutable storage'
Amxx Jul 10, 2024
6121a83
add RSA and P256 immutable identity contracts
Amxx Jul 11, 2024
c023a18
Merge branch 'feature/AA/immutable-identity-contracts' into erc4337/i…
Amxx Jul 11, 2024
5745f60
erc1271 recovery
Amxx Jul 11, 2024
add9961
identify / multisig with ERC1271
Amxx Jul 11, 2024
2004e2a
move signature to calldata
Amxx Jul 12, 2024
fbaa7a1
fix stack too deep
Amxx Jul 12, 2024
e170ceb
use abi.encodePacked instead of mcopy
Amxx Jul 14, 2024
5a4b300
simplify
Amxx Jul 19, 2024
de032e3
starting rewrite account common as ERC7579
Amxx Jul 19, 2024
45abf84
fuzzing test of ERC7579Utils
Amxx Jul 19, 2024
e7fec84
move module support (by types) to extensions
Amxx Jul 19, 2024
625db1b
up
Amxx Jul 19, 2024
6fb68a1
up
Amxx Jul 19, 2024
fb90b92
up
Amxx Jul 19, 2024
22ddfb7
test batch
Amxx Jul 19, 2024
d0fdca7
use calldata
Amxx Jul 22, 2024
89e9504
avoid assembly
Amxx Jul 22, 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
2 changes: 2 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ jobs:
run: bash scripts/upgradeable/transpile.sh
- name: Run tests
run: npm run test
env:
UNLIMITED: true
- name: Check linearisation of the inheritance graph
run: npm run test:inheritance
- name: Check storage layout
Expand Down
145 changes: 145 additions & 0 deletions contracts/abstraction/account/Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {PackedUserOperation, IAccount, IAccountExecute, IEntryPoint} from "../../interfaces/IERC4337.sol";
import {ERC4337Utils} from "./../utils/ERC4337Utils.sol";
import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol";
import {Address} from "../../utils/Address.sol";

abstract contract Account is IAccount, IAccountExecute {
error AccountEntryPointRestricted();

/****************************************************************************************************************
* Modifiers *
****************************************************************************************************************/

modifier onlyEntryPointOrSelf() {
if (msg.sender != address(this) && msg.sender != address(entryPoint())) {
revert AccountEntryPointRestricted();
}
_;
}

modifier onlyEntryPoint() {
if (msg.sender != address(entryPoint())) {
revert AccountEntryPointRestricted();
}
_;
}

/****************************************************************************************************************
* Hooks *
****************************************************************************************************************/

/**
* @dev Return the entryPoint used by this account.
*
* Subclass should return the current entryPoint used by this account.
*/
function entryPoint() public view virtual returns (IEntryPoint);

/**
* @dev Return weither an address (identity) is authorized to operate on this account. Depending on how the
* account is configured, this can be interpreted as either the owner of the account (if operating using a single
* owner -- default) or as an authorized signer if operating using as a multisig account.
*
* Subclass must implement this using their own access control mechanism.
*/
function _isAuthorized(address) internal view virtual returns (bool);

/**
* @dev Recover the signer for a given signature and user operation hash. This function does not need to verify
* that the recovered signer is authorized.
*
* Subclass must implement this using their own choice of cryptography.
*/
function _recoverSigner(bytes32 userOpHash, bytes calldata signature) internal view virtual returns (address);

/****************************************************************************************************************
* Public interface *
****************************************************************************************************************/

/**
* @dev Return the account nonce for the canonical sequence.
*/
function getNonce() public view virtual returns (uint256) {
return entryPoint().getNonce(address(this), 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 validationData) {
(bool valid, , uint48 validAfter, uint48 validUntil) = _processSignature(userOpHash, userOp.signature);
_validateNonce(userOp.nonce);
_payPrefund(missingAccountFunds);
return ERC4337Utils.packValidationData(valid, validAfter, validUntil);
}

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

/****************************************************************************************************************
* Internal mechanisms *
****************************************************************************************************************/

/**
* @dev Process the signature is valid for this message.
* @param userOpHash - Hash of the request that must be signed (includes the entrypoint and chain id)
* @param signature - The user's signature
* @return valid - Signature is valid
* @return signer - Address of the signer that produced the signature
* @return validAfter - first timestamp this operation is valid
* @return validUntil - last timestamp this operation is valid. 0 for "indefinite"
*/
function _processSignature(
bytes32 userOpHash,
bytes calldata signature
) internal view virtual returns (bool valid, address signer, uint48 validAfter, uint48 validUntil) {
address recovered = _recoverSigner(userOpHash, signature);
return (recovered != address(0) && _isAuthorized(recovered), recovered, 0, 0);
}

/**
* @dev Validate the nonce of the UserOperation.
* This method may validate the nonce requirement of this account.
* e.g.
* To limit the nonce to use sequenced UserOps only (no "out of order" UserOps):
* `require(nonce < type(uint64).max)`
*
* The actual nonce uniqueness is managed by the EntryPoint, and thus no other
* action is needed by the account itself.
*
* @param nonce to validate
*/
function _validateNonce(uint256 nonce) internal view virtual {}

/**
* @dev Sends to the entrypoint (msg.sender) the missing funds for this transaction.
* SubClass MAY override this method for better funds management
* (e.g. send to the entryPoint more than the minimum required, so that in future transactions
* it will not be required to send again).
* @param missingAccountFunds - The minimum value this method should send the entrypoint.
* This value MAY be zero, in case there is enough deposit,
* or the userOp has a paymaster.
*/
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.)
}
}
}
233 changes: 233 additions & 0 deletions contracts/abstraction/account/ERC7579Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Account} from "./Account.sol";
import {Address} from "../../utils/Address.sol";
import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol";
import {ERC721Holder} from "../../token/ERC721/utils/ERC721Holder.sol";
import {IEntryPoint} from "../../interfaces/IERC4337.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
import {IERC7579Execution, IERC7579AccountConfig, IERC7579ModuleConfig} from "../../interfaces/IERC7579Account.sol";
import {IERC7579Module, MODULE_TYPE_EXECUTOR} from "../../interfaces/IERC7579Module.sol";
import {ERC7579Utils, Execution, Mode, CallType, ExecType} from "../utils/ERC7579Utils.sol";

abstract contract ERC7579Account is
IERC165, // required by erc-7579
IERC1271, // required by erc-7579
IERC7579Execution, // required by erc-7579
IERC7579AccountConfig, // required by erc-7579
IERC7579ModuleConfig, // required by erc-7579
Account,
ERC165,
ERC721Holder,
ERC1155Holder
{
using ERC7579Utils for *;

IEntryPoint private immutable _entryPoint;

event ERC7579TryExecuteUnsuccessful(uint256 batchExecutionindex, bytes result);
error ERC7579UnsupportedCallType(CallType callType);
error ERC7579UnsupportedExecType(ExecType execType);
error MismatchModuleTypeId(uint256 moduleTypeId, address module);
error UnsupportedModuleType(uint256 moduleTypeId);
error ModuleRestricted(uint256 moduleTypeId, address caller);
error ModuleAlreadyInstalled(uint256 moduleTypeId, address module);
error ModuleNotInstalled(uint256 moduleTypeId, address module);

modifier onlyModule(uint256 moduleTypeId) {
if (!isModuleInstalled(moduleTypeId, msg.sender, msg.data)) {
revert ModuleRestricted(moduleTypeId, msg.sender);
}
_;
}

constructor(IEntryPoint entryPoint_) {
_entryPoint = entryPoint_;
}

receive() external payable {}

function entryPoint() public view virtual override returns (IEntryPoint) {
return _entryPoint;
}

/// @inheritdoc IERC165
function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, ERC165, ERC1155Holder) returns (bool) {
// TODO: more?
return super.supportsInterface(interfaceId);
}

/// @inheritdoc IERC1271
function isValidSignature(bytes32 hash, bytes calldata signature) public view returns (bytes4 magicValue) {
(bool valid, , uint48 validAfter, uint48 validUntil) = _processSignature(hash, signature);
return
(valid && validAfter < block.timestamp && (validUntil == 0 || validUntil > block.timestamp))
? IERC1271.isValidSignature.selector
: bytes4(0);
}

/****************************************************************************************************************
* ERC-7579 Execution *
****************************************************************************************************************/

/// @inheritdoc IERC7579Execution
function execute(bytes32 mode, bytes calldata executionCalldata) public virtual onlyEntryPoint {
_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);
}

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) {
(address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle();
returnData = new bytes[](1);
returnData[0] = _execute(0, execType, target, value, callData);
} else if (callType == ERC7579Utils.CALLTYPE_BATCH) {
Execution[] calldata executionBatch = executionCalldata.decodeBatch();
returnData = new bytes[](executionBatch.length);
for (uint256 i = 0; i < executionBatch.length; ++i) {
returnData[i] = _execute(
i,
execType,
executionBatch[i].target,
executionBatch[i].value,
executionBatch[i].callData
);
}
} else if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL) {
(address target, bytes calldata callData) = executionCalldata.decodeDelegate();
returnData = new bytes[](1);
returnData[0] = _executeDelegate(0, execType, target, callData);
} else {
revert ERC7579UnsupportedCallType(callType);
}
}

function _execute(
uint256 index,
ExecType execType,
address target,
uint256 value,
bytes memory data
) private returns (bytes memory) {
if (execType == ERC7579Utils.EXECTYPE_DEFAULT) {
(bool success, bytes memory returndata) = target.call{value: value}(data);
Address.verifyCallResult(success, returndata);
return returndata;
} else if (execType == ERC7579Utils.EXECTYPE_TRY) {
(bool success, bytes memory returndata) = target.call{value: value}(data);
if (!success) emit ERC7579TryExecuteUnsuccessful(index, returndata);
return returndata;
} else {
revert ERC7579UnsupportedExecType(execType);
}
}

function _executeDelegate(
uint256 index,
ExecType execType,
address target,
bytes memory data
) private returns (bytes memory) {
if (execType == ERC7579Utils.EXECTYPE_DEFAULT) {
(bool success, bytes memory returndata) = target.delegatecall(data);
Address.verifyCallResult(success, returndata);
return returndata;
} else if (execType == ERC7579Utils.EXECTYPE_TRY) {
(bool success, bytes memory returndata) = target.delegatecall(data);
if (!success) emit ERC7579TryExecuteUnsuccessful(index, returndata);
return returndata;
} else {
revert ERC7579UnsupportedExecType(execType);
}
}

/****************************************************************************************************************
* ERC-7579 Account and Modules *
****************************************************************************************************************/

/// @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) {
(CallType callType, , , ) = Mode.wrap(encodedMode).decodeMode();
return
callType == ERC7579Utils.CALLTYPE_SINGLE ||
callType == ERC7579Utils.CALLTYPE_BATCH ||
callType == ERC7579Utils.CALLTYPE_DELEGATECALL;
}

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

/// @inheritdoc IERC7579ModuleConfig
function installModule(
uint256 moduleTypeId,
address module,
bytes calldata initData
) public virtual onlyEntryPointOrSelf {
if (!IERC7579Module(module).isModuleType(moduleTypeId)) revert MismatchModuleTypeId(moduleTypeId, module);
_installModule(moduleTypeId, module, initData);
/// TODO: silent unreachable and re-enable this event
// emit ModuleInstalled(moduleTypeId, module);
}

/// @inheritdoc IERC7579ModuleConfig
function uninstallModule(
uint256 moduleTypeId,
address module,
bytes calldata deInitData
) public virtual onlyEntryPointOrSelf {
_uninstallModule(moduleTypeId, module, deInitData);
/// TODO: silent unreachable and re-enable this event
// emit ModuleUninstalled(moduleTypeId, module);
}

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

/****************************************************************************************************************
* Hooks *
****************************************************************************************************************/

function _installModule(uint256 moduleTypeId, address /*module*/, bytes calldata /*initData*/) internal virtual {
revert UnsupportedModuleType(moduleTypeId);
}

function _uninstallModule(
uint256 moduleTypeId,
address /*module*/,
bytes calldata /*deInitData*/
) internal virtual {
revert UnsupportedModuleType(moduleTypeId);
}
}
Loading
Loading