Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ ENTRYPOINT=
# Create2 expected addresses of the contracts.
# When running for the first time, the error message will contain the expected addresses.
ACCOUNT_IMPL=
SINGLE_SIGNER_VALIDATION=
SINGLE_SIGNER_VALIDATION_MODULE=
FACTORY=

# Optional, defaults to bytes32(0)
ACCOUNT_IMPL_SALT=
FACTORY_SALT=
SINGLE_SIGNER_VALIDATION_SALT=
SINGLE_SIGNER_VALIDATION_MODULE_SALT=

# Optional, defaults to 0.1 ether and 1 day, respectively
STAKE_AMOUNT=
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation.

The implementation includes an upgradable modular account with three modules (`SingleSignerValidation`, `TokenReceiverModule`, and `AllowlistModule`). It is compliant with ERC-6900 with the latest updates.
The implementation includes an upgradable modular account with three modules (`SingleSignerValidationModule`, `TokenReceiverModule`, and `AllowlistModule`). It is compliant with ERC-6900 with the latest updates.

## Important Callouts

Expand Down Expand Up @@ -31,9 +31,10 @@ FOUNDRY_PROFILE=optimized-test forge test -vvv

## Integration testing

The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidation`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply.
The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidationModule`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply.

To run this script, provide appropriate values in a `.env` file based on the `.env.example` template, then run:

```bash
forge script script/Deploy.s.sol <wallet options> -r <rpc_url> --broadcast
```
Expand Down
5 changes: 2 additions & 3 deletions deployments/arb-sepolia.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ Chain ID: 421614
| v0.8.0-alpha.1 | `0xC64Cb5192a1440Fea12CE03D000EAeB247B2369B` | [explorer](https://sepolia.arbiscan.io/address/0xC64Cb5192a1440Fea12CE03D000EAeB247B2369B) | `0` |
| v0.8.0-alpha.0 | `0x0809BF385117a43A322A4E31d459c0EcaA3B1A08` | [explorer](https://sepolia.arbiscan.io/address/0x0809BF385117a43A322A4E31d459c0EcaA3B1A08) | `0` |

## SingleSignerValidation
## SingleSignerValidationModule

| Version | Address | Explorer | Salt |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- |
| v0.8.0-alpha.1 | `0xEa3a0b544d517f6Ed3Dc2186C74D869c702C376e` | [explorer](https://sepolia.arbiscan.io/address/0xEa3a0b544d517f6Ed3Dc2186C74D869c702C376e) | `0` |
| v0.8.0-alpha.0 | `0x9DA8c098A483E257dd96022831DF308cB24fCBE6` | [explorer](https://sepolia.arbiscan.io/address/0x9DA8c098A483E257dd96022831DF308cB24fCBE6) | `0` |


## AllowlistModule

| Version | Address | Explorer | Salt |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- |
| v0.8.0-alpha.1 | `0x5B13F222A841A42C59324FFF0A229FfeA1CAcC3c` | [explorer](https://sepolia.arbiscan.io/address/0x5B13F222A841A42C59324FFF0A229FfeA1CAcC3c) | `0` |
| v0.8.0-alpha.1 | `0x5B13F222A841A42C59324FFF0A229FfeA1CAcC3c` | [explorer](https://sepolia.arbiscan.io/address/0x5B13F222A841A42C59324FFF0A229FfeA1CAcC3c) | `0` |
23 changes: 12 additions & 11 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {AccountFactory} from "../src/account/AccountFactory.sol";
import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol";
import {SingleSignerValidation} from "../src/modules/validation/SingleSignerValidation.sol";
import {SingleSignerValidationModule} from "../src/modules/validation/SingleSignerValidationModule.sol";

contract DeployScript is Script {
IEntryPoint public entryPoint = IEntryPoint(payable(vm.envAddress("ENTRYPOINT")));
Expand All @@ -17,11 +17,12 @@ contract DeployScript is Script {

address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0));
address public factory = vm.envOr("FACTORY", address(0));
address public singleSignerValidation = vm.envOr("SINGLE_SIGNER_VALIDATION", address(0));
address public singleSignerValidationModule = vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE", address(0));

bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0)));
bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0)));
bytes32 public singleSignerValidationSalt = bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_SALT", uint256(0)));
bytes32 public singleSignerValidationModuleSalt =
bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE_SALT", uint256(0)));

uint256 public requiredStakeAmount = vm.envOr("STAKE_AMOUNT", uint256(0.1 ether));
uint256 public requiredUnstakeDelay = vm.envOr("UNSTAKE_DELAY", uint256(1 days));
Expand All @@ -34,7 +35,7 @@ contract DeployScript is Script {

vm.startBroadcast();
_deployAccountImpl(accountImplSalt, accountImpl);
_deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation);
_deploySingleSignerValidationModule(singleSignerValidationModuleSalt, singleSignerValidationModule);
_deployAccountFactory(factorySalt, factory);
_addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount);
vm.stopBroadcast();
Expand Down Expand Up @@ -72,11 +73,11 @@ contract DeployScript is Script {
}
}

function _deploySingleSignerValidation(bytes32 salt, address expected) internal {
console.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt)));
function _deploySingleSignerValidationModule(bytes32 salt, address expected) internal {
console.log(string.concat("Deploying SingleSignerValidationModule with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), CREATE2_FACTORY
salt, keccak256(abi.encodePacked(type(SingleSignerValidationModule).creationCode)), CREATE2_FACTORY
);
if (addr != expected) {
console.log("Expected address mismatch");
Expand All @@ -87,7 +88,7 @@ contract DeployScript is Script {

if (addr.code.length == 0) {
console.log("No code found at expected address, deploying...");
SingleSignerValidation deployed = new SingleSignerValidation{salt: salt}();
SingleSignerValidationModule deployed = new SingleSignerValidationModule{salt: salt}();

if (address(deployed) != expected) {
console.log("Deployed address mismatch");
Expand All @@ -96,7 +97,7 @@ contract DeployScript is Script {
revert();
}

console.log("Deployed SingleSignerValidation at: ", address(deployed));
console.log("Deployed SingleSignerValidationModule at: ", address(deployed));
} else {
console.log("Code found at expected address, skipping deployment");
}
Expand All @@ -110,7 +111,7 @@ contract DeployScript is Script {
keccak256(
abi.encodePacked(
type(AccountFactory).creationCode,
abi.encode(entryPoint, accountImpl, singleSignerValidation, owner)
abi.encode(entryPoint, accountImpl, singleSignerValidationModule, owner)
)
),
CREATE2_FACTORY
Expand All @@ -125,7 +126,7 @@ contract DeployScript is Script {
if (addr.code.length == 0) {
console.log("No code found at expected address, deploying...");
AccountFactory deployed = new AccountFactory{salt: salt}(
entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidation, owner
entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidationModule, owner
);

if (address(deployed) != expected) {
Expand Down
8 changes: 4 additions & 4 deletions src/account/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ contract AccountFactory is Ownable {
UpgradeableModularAccount public immutable ACCOUNT_IMPL;
bytes32 private immutable _PROXY_BYTECODE_HASH;
IEntryPoint public immutable ENTRY_POINT;
address public immutable SINGLE_SIGNER_VALIDATION;
address public immutable SINGLE_SIGNER_VALIDATION_MODULE;

constructor(
IEntryPoint _entryPoint,
UpgradeableModularAccount _accountImpl,
address _singleSignerValidation,
address _singleSignerValidationModule,
address owner
) Ownable(owner) {
ENTRY_POINT = _entryPoint;
_PROXY_BYTECODE_HASH =
keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), "")));
ACCOUNT_IMPL = _accountImpl;
SINGLE_SIGNER_VALIDATION = _singleSignerValidation;
SINGLE_SIGNER_VALIDATION_MODULE = _singleSignerValidationModule;
}

/**
Expand All @@ -50,7 +50,7 @@ contract AccountFactory is Ownable {
new ERC1967Proxy{salt: combinedSalt}(address(ACCOUNT_IMPL), "");
// point proxy to actual implementation and init plugins
UpgradeableModularAccount(payable(addr)).initializeWithValidation(
ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION, entityId, true, true),
ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION_MODULE, entityId, true, true),
new bytes4[](0),
pluginInstallData,
new bytes[](0)
Expand Down
2 changes: 1 addition & 1 deletion src/account/ModuleManagerInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {HookConfigLib} from "../helpers/HookConfigLib.sol";
import {KnownSelectors} from "../helpers/KnownSelectors.sol";
import {ModuleEntityLib} from "../helpers/ModuleEntityLib.sol";
import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol";
import {ExecutionManifest, ManifestExecutionHook} from "../interfaces/IExecution.sol";
import {ExecutionManifest, ManifestExecutionHook} from "../interfaces/IExecutionModule.sol";
import {IModule} from "../interfaces/IModule.sol";
import {HookConfig, IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol";
import {
Expand Down
23 changes: 12 additions & 11 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import {_coalescePreValidation, _coalesceValidation} from "../helpers/Validation

import {DIRECT_CALL_VALIDATION_ENTITYID, RESERVED_VALIDATION_DATA_INDEX} from "../helpers/Constants.sol";

import {ExecutionManifest} from "../interfaces/IExecution.sol";
import {IExecutionHook} from "../interfaces/IExecutionHook.sol";
import {IExecutionHookModule} from "../interfaces/IExecutionHookModule.sol";
import {ExecutionManifest} from "../interfaces/IExecutionModule.sol";
import {IModuleManager, ModuleEntity, ValidationConfig} from "../interfaces/IModuleManager.sol";
import {Call, IStandardExecutor} from "../interfaces/IStandardExecutor.sol";
import {IValidation} from "../interfaces/IValidation.sol";
import {IValidationHook} from "../interfaces/IValidationHook.sol";

import {IValidationHookModule} from "../interfaces/IValidationHookModule.sol";
import {IValidationModule} from "../interfaces/IValidationModule.sol";
import {AccountExecutor} from "./AccountExecutor.sol";
import {AccountLoupe} from "./AccountLoupe.sol";
import {AccountStorage, getAccountStorage, toHookConfig, toSetValue} from "./AccountStorage.sol";
Expand Down Expand Up @@ -310,7 +311,7 @@ contract UpgradeableModularAccount is
}

if (
IValidation(module).validateSignature(address(this), entityId, msg.sender, hash, signature[24:])
IValidationModule(module).validateSignature(address(this), entityId, msg.sender, hash, signature[24:])
== _1271_MAGIC_VALUE
) {
return _1271_MAGIC_VALUE;
Expand Down Expand Up @@ -397,7 +398,7 @@ contract UpgradeableModularAccount is

(address module, uint32 entityId) = preUserOpValidationHooks[i].unpack();
uint256 currentValidationRes =
IValidationHook(module).preUserOpValidationHook(entityId, userOp, userOpHash);
IValidationHookModule(module).preUserOpValidationHook(entityId, userOp, userOpHash);

if (uint160(currentValidationRes) > 1) {
// If the aggregator is not 0 or 1, it is an unexpected value
Expand All @@ -415,7 +416,7 @@ contract UpgradeableModularAccount is
userOp.signature = signatureSegment.getBody();

(address module, uint32 entityId) = userOpValidationFunction.unpack();
uint256 currentValidationRes = IValidation(module).validateUserOp(entityId, userOp, userOpHash);
uint256 currentValidationRes = IValidationModule(module).validateUserOp(entityId, userOp, userOpHash);

if (preUserOpValidationHooks.length != 0) {
// If we have other validation data we need to coalesce with
Expand Down Expand Up @@ -470,7 +471,7 @@ contract UpgradeableModularAccount is

(address module, uint32 entityId) = runtimeValidationFunction.unpack();

try IValidation(module).validateRuntime(
try IValidationModule(module).validateRuntime(
address(this), entityId, msg.sender, msg.value, callData, authSegment.getBody()
)
// forgefmt: disable-start
Expand Down Expand Up @@ -521,7 +522,7 @@ contract UpgradeableModularAccount is
returns (bytes memory preExecHookReturnData)
{
(address module, uint32 entityId) = preExecHook.unpack();
try IExecutionHook(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns (
try IExecutionHookModule(module).preExecutionHook(entityId, msg.sender, msg.value, data) returns (
bytes memory returnData
) {
preExecHookReturnData = returnData;
Expand All @@ -547,7 +548,7 @@ contract UpgradeableModularAccount is

(address module, uint32 entityId) = postHookToRun.postExecHook.unpack();
// solhint-disable-next-line no-empty-blocks
try IExecutionHook(module).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {}
try IExecutionHookModule(module).postExecutionHook(entityId, postHookToRun.preExecHookReturnData) {}
catch (bytes memory revertReason) {
revert PostExecHookReverted(module, entityId, revertReason);
}
Expand All @@ -560,7 +561,7 @@ contract UpgradeableModularAccount is
bytes memory currentAuthData
) internal {
(address hookModule, uint32 hookEntityId) = validationHook.unpack();
try IValidationHook(hookModule).preRuntimeValidationHook(
try IValidationHookModule(hookModule).preRuntimeValidationHook(
hookEntityId, msg.sender, msg.value, callData, currentAuthData
)
// forgefmt: disable-start
Expand Down
23 changes: 13 additions & 10 deletions src/helpers/KnownSelectors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol";

import {IExecution} from "../interfaces/IExecution.sol";
import {IExecutionHook} from "../interfaces/IExecutionHook.sol";
import {IExecutionHookModule} from "../interfaces/IExecutionHookModule.sol";
import {IExecutionModule} from "../interfaces/IExecutionModule.sol";
import {IModule} from "../interfaces/IModule.sol";
import {IModuleManager} from "../interfaces/IModuleManager.sol";
import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol";
import {IValidation} from "../interfaces/IValidation.sol";
import {IValidationHook} from "../interfaces/IValidationHook.sol";

import {IValidationHookModule} from "../interfaces/IValidationHookModule.sol";
import {IValidationModule} from "../interfaces/IValidationModule.sol";

/// @dev Library to help to check if a selector is a know function selector of the modular account or ERC-4337
/// contract.
Expand Down Expand Up @@ -49,11 +50,13 @@ library KnownSelectors {

function isIModuleFunction(bytes4 selector) internal pure returns (bool) {
return selector == IModule.onInstall.selector || selector == IModule.onUninstall.selector
|| selector == IExecution.executionManifest.selector || selector == IModule.moduleMetadata.selector
|| selector == IExecutionHook.preExecutionHook.selector
|| selector == IExecutionHook.postExecutionHook.selector || selector == IValidation.validateUserOp.selector
|| selector == IValidation.validateRuntime.selector || selector == IValidation.validateSignature.selector
|| selector == IValidationHook.preUserOpValidationHook.selector
|| selector == IValidationHook.preRuntimeValidationHook.selector;
|| selector == IExecutionModule.executionManifest.selector || selector == IModule.moduleMetadata.selector
|| selector == IExecutionHookModule.preExecutionHook.selector
|| selector == IExecutionHookModule.postExecutionHook.selector
|| selector == IValidationModule.validateUserOp.selector
|| selector == IValidationModule.validateRuntime.selector
|| selector == IValidationModule.validateSignature.selector
|| selector == IValidationHookModule.preUserOpValidationHook.selector
|| selector == IValidationHookModule.preRuntimeValidationHook.selector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.25;

import {IModule} from "./IModule.sol";

interface IExecutionHook is IModule {
interface IExecutionHookModule is IModule {
/// @notice Run the pre execution hook specified by the `entityId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param entityId An identifier that routes the call to different internal implementations, should there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct ExecutionManifest {
bytes4[] interfaceIds;
}

interface IExecution is IModule {
interface IExecutionModule is IModule {
/// @notice Describe the contents and intended configuration of the module.
/// @dev This manifest MUST stay constant over time.
/// @return A manifest describing the contents and intended configuration of the module.
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IModuleManager.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.25;

import {ExecutionManifest} from "./IExecution.sol";
import {ExecutionManifest} from "./IExecutionModule.sol";

type ModuleEntity is bytes24;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface

import {IModule} from "./IModule.sol";

interface IValidationHook is IModule {
interface IValidationHookModule is IModule {
/// @notice Run the pre user operation validation hook specified by the `entityId`.
/// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1.
/// @param entityId An identifier that routes the call to different internal implementations, should there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface

import {IModule} from "./IModule.sol";

interface IValidation is IModule {
interface IValidationModule is IModule {
/// @notice Run the user operation validationFunction specified by the `entityId`.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
Expand Down
Loading