Skip to content
Draft
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
2 changes: 1 addition & 1 deletion CHANGELOG_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Added support for ERC-4337 EntryPoint version 0.9.
- Gas snapshot updates reflecting v0.9 optimizations (reduced gas costs across all test scenarios)
- **Breaking Change:** UserOperation hash calculation has been changed in EntryPoint v0.9
- **Files:** `foundry.toml`, `remappings.txt`, `soldeer.lock`, `test/utils/EntryPointLib.sol`, `test/KernelUserOpTest.sol`, `test/KernelValidatorTest.sol`
- **EntryPoint Address:** `0x43370900c8de573dB349BEd8DD53b4Ebd3Cce709`
- **EntryPoint Address:** `0x433709009B8330FDa32311DF1C2AFA402eD8D009`
- **Commits:** 977ca07, aa91ef1, 110c7af, 3e72921
- **Note:** The module type ID was updated from 8 to 10 for `MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER` as part of this upgrade
- you can find the release docs in [here](https://docs.google.com/document/d/1RKkKZsP1eYkOoBEkzJ1vWRK_bcWXaewoGPzawMjsleM/edit?usp=drivesdk), please do note that this document is not in public yet
Expand Down
110 changes: 38 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Kernel v4

ERC-4337 / ERC-7702 modular smart account with pluggable validation, execution, and hook modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces.
ERC-4337 / ERC-7702 modular smart account with pluggable validation and execution modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces.

## Key Features

### Modular Architecture (ERC-7579)

Six pluggable module types that can be installed and uninstalled at runtime:
Six supported module types can be installed and uninstalled at runtime:

| Type | Role |
|------|------|
| ------ | ------ |
| **Validator** | Validates UserOps and signatures — owns a nonce key namespace |
| **Executor** | Calls `executeFromExecutor` to perform actions on behalf of the account |
| **Fallback** | Extends the account with new function selectors (call or delegatecall) |
| **Hook** | Pre/post execution checks on validators, executors, and fallback selectors |
| **Policy** | Part of a permission — enforces rules (e.g. spending limits, target allowlists) |
| **Signer** | Part of a permission — provides the signature verification (e.g. passkey, multisig) |
| **Signer** | Part of a permission — provides signature verification (e.g. passkey, multisig) |
| **Scoped Execution Hook** | Optional pre/post checks scoped to one validation, executor, or selector (type 11) |

### Permission System

Expand All @@ -35,29 +35,28 @@ Install modules atomically with the first UserOp — no separate setup transacti

The 32-byte ERC-4337 nonce encodes which validator to use, giving each validator/permission its own nonce namespace. See [Data Encoding > UserOp Nonce](#userop-nonce) for the full layout.

### Hook System
### Scoped Execution Hooks

Hooks provide pre/post execution checks. They bind to validators, executors, and fallback selectors independently:
An optional type-11 scoped execution hook runs before and after execution in one of three scopes: validation, executor, or selector. Hooked non-root validations are routed through `executeUserOp`; root validations bypass hooks. Executor hooks wrap `executeFromExecutor`, and selector hooks wrap fallback selector dispatch. Selector hooks apply only to calls that reach Kernel's fallback; native Kernel function dispatch bypasses them, and built-in token-receiver selectors cannot be installed as fallback targets.

- **Validator hook** — Runs around `executeUserOp` when a non-root validator with a hook is used
- **Executor hook** — Runs around `executeFromExecutor` for any installed executor
- **Fallback hook** — Runs around fallback selector dispatch
**Fallback access control:** a fallback selector with no scoped execution hook installed is callable only by the EntryPoint. Installing a selector-scoped execution hook makes the selector publicly callable, with the hook gating direct access via `preCheck`/`postCheck`.

Two sentinel values: `address(0)` = not installed, `address(1)` = installed with no hook.
`preCheck` and `postCheck` receive the same Kernel-generated `bytes32 id`. The ID layout is `[bytes1 scope][target][zero padding]`, where the target is a 21-byte ValidationId, 20-byte executor address, or 4-byte selector. `Utils.sol` exposes generation and decoding helpers for every scope.

### Signature Verification (ERC-1271 / ERC-7739)

Three signature modes for `isValidSignature`:

1. **Raw** — Direct hash signing (only on `Kernel7702` where the EOA is the signer)
2. **Chain-specific nested EIP-712** — Wraps the hash in a `TypedDataSign` struct bound to chain ID
3. **Replayable nested EIP-712** — Same wrapping but without chain ID, valid across chains

All modes support both validator-based and permission-based signature verification, selected by the first 21 bytes of the signature.
Structured signatures select root, validator, or permission validation through the leading `vType` and optional ID. Raw Kernel7702 signatures have no validation header.

### Standards

| Standard | Support |
|----------|---------|
| ---------- | --------- |
| [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) | Account abstraction via EntryPoint v0.9 |
| [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) | Modular smart account interfaces |
| [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702) | EOA code delegation (`Kernel7702`) |
Expand All @@ -79,7 +78,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier:
**vMode** (ValidationMode flags):

| Value | Meaning |
|-------|---------|
| ------- | --------- |
| `0x00` | Standard — chain-specific, no inline install |
| `0x08` | Enable — install modules inline, chain-specific enable signature |
| `0x0C` | Enable + replayable enable signature |
Expand All @@ -90,7 +89,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier:
**vType** (ValidationType):

| Value | Meaning | vId contains |
|-------|---------|-------------|
| ------- | --------- | ------------- |
| `0x00` | Root / Fallback | Ignored (uses stored root) |
| `0x01` | Validator | 20-byte validator address |
| `0x02` | Permission | 4-byte PermissionId (left-aligned, rest zero) |
Expand Down Expand Up @@ -143,44 +142,19 @@ If the enable-replayable flag (0x04) is set, the digest uses the chain-agnostic
After ERC-6492 unwrapping, the signature is parsed as:

```
| 1 byte | 1 byte | N bytes | remaining bytes |
| vMode | vType | vId | inner signature |
| 1 byte | N bytes | remaining bytes |
| vType | vId | inner signature |
```

Where N depends on vType:

| vType | N | vId content |
|-------|---|-------------|
| `0x00` (root) | 0 | Uses stored root, inner = `signature[2:]` |
| `0x01` (validator) | 20 | Validator address, inner = `signature[22:]` |
| `0x02` (permission) | 4 | PermissionId, inner = `signature[6:]` |

#### Standard Mode (no enable flag)

The inner signature is verified via `_verifySignature` against the installed validator or permission, same as UserOp standard mode.

#### Enable Mode for ERC-1271

Since `isValidSignature` is a `view` function, enable mode works differently than in UserOps — it **cannot** modify state (no module installation, no nonce increment). Instead it:

1. Verifies the install signature is valid (same digest as UserOp enable mode)
2. Checks the nonce is correct (view-only, no increment)
3. Uses **stateless** verification — finds the validator/permission modules inside the `packages` array and calls `IStatelessValidatorWithSender.validateSignatureWithDataWithSender` instead of the normal installed module
| ------- | --- | ------------- |
| `0x00` (root) | 0 | Uses stored root, inner = `signature[1:]` |
| `0x01` (validator) | 20 | Validator address, inner = `signature[21:]` |
| `0x02` (permission) | 4 | PermissionId, inner = `signature[5:]` |

The inner signature format is the same `EnableModeSignature`:

```
abi.encode(EnableModeSignature({
nonce: uint256,
packages: Install[],
enableSignature: bytes, // root validator's signature over the install digest
userOpSignature: bytes // verified statelessly against modules in packages
}))
```

For permission-based enable mode, `userOpSignature` is a `PermissionSignature` — one signature per policy/signer found in the packages with the matching PermissionId.

> **Note:** vType cannot be root (`0x00`) in enable mode — it must specify an explicit validator or permission.
The inner signature is verified via `_verifySignature` against the installed validator or permission. ERC-1271 has no validation-mode byte and does not support enable mode; validation modes remain part of ERC-4337 UserOperation nonces only.

#### Nested EIP-712 Wrapping

Expand Down Expand Up @@ -215,21 +189,15 @@ abi.encode(InstallModuleDataFormat({
#### internalData for Install

| Module Type | internalData format |
|-------------|---------------------|
| Validator (1) | `[bytes20 hookAddress][bytes4 selector₁][bytes4 selector₂]...` |
| Executor (2) | `[bytes20 hookAddress]` |
| Fallback (3) | `[bytes4 selector][bytes1 callType][bytes20 hookAddress]` |
| Hook (4) | Ignored (empty OK) |
| ------------- | --------------------- |
| Validator (1) | `[bytes4 selector₁][bytes4 selector₂]...` |
| Executor (2) | Empty (required) |
| Fallback (3) | Exactly `[bytes4 selector][bytes1 callType]` |
| Policy (5) | `[bytes4 permissionId]` |
| Signer (6) | `[bytes4 permissionId][bytes20 hookAddress][bytes4 selector₁]...` |

**hookAddress** sentinel values:
| Signer (6) | `[bytes4 permissionId][bytes4 selector₁]...` |
| Scoped Execution Hook (11) | `[bytes1 scope][target]` (validation: 21-byte ValidationId; executor: 20-byte address; selector: 4-byte selector) |

| Address | Meaning |
|---------|---------|
| `address(0)` | Not installed / entry-point-only (for fallback: only EntryPoint can call) |
| `address(1)` | Installed with no hook |
| Other | Hook contract address (must be installed as hook module first) |
Execution-hook scopes are `0x01` for validation, `0x02` for executor, and `0x03` for selector.

**callType** for fallback (type 3):

Expand All @@ -241,13 +209,13 @@ abi.encode(InstallModuleDataFormat({
#### internalData for Uninstall

| Module Type | internalData format |
|-------------|---------------------|
| ------------- | --------------------- |
| Validator (1) | Ignored |
| Executor (2) | Ignored |
| Fallback (3) | `[bytes4 selector]` (first 4 bytes used) |
| Hook (4) | Ignored |
| Executor (2) | Empty (required) |
| Fallback (3) | Exactly `[bytes4 selector]` |
| Policy (5) | `[bytes4 permissionId]` — must uninstall in LIFO order (last installed first) |
| Signer (6) | `[bytes4 permissionId]` — all policies must be uninstalled first |
| Signer (6) | `[bytes4 permissionId]` — all policies and its scoped execution hook must be removed first |
| Scoped Execution Hook (11) | `[bytes1 scope][target]` (same format as installation) |

### Batch Install via `Install[]`

Expand All @@ -268,23 +236,22 @@ Each `Install` struct:

```solidity
struct Install {
uint256 moduleType; // 1-6
uint256 moduleType; // 1, 2, 3, 5, 6, or 11
address module; // module contract address
bytes moduleData; // forwarded to onInstall
bytes internalData; // kernel config (same format as table above)
}
```

**Permission install order**: When installing a permission, all policies (type 5) for that PermissionId must come first, followed by exactly one signer (type 6) with the same PermissionId. The signer finalizes the permission. Multiple permissions can be installed in a single batch — just ensure each permission's policies+signer are grouped together.
**Permission install order**: policies (type 5) come first, followed by exactly one signer (type 6), then an optional scoped execution hook (type 11), all sharing the same PermissionId. The hook targets the permission's full 21-byte ValidationId and requires the signer-completed permission to exist. Validator, executor, and selector hooks are installed after their respective targets.

## Architecture

```
Kernel (abstract)
├── ModuleManager
│ ├── ValidationManager — Validator/permission lifecycle, enable-mode, nonce mgmt
│ ├── ExecutorManager — Executor install/uninstall with hook binding
│ ├── HookManager — Hook install/uninstall, pre/post check dispatch
│ ├── ExecutorManager — Executor install/uninstall state
│ └── SelectorManager — Fallback handler routing by function selector
├── ExecutionManager — ERC-7579 execution modes (single/batch/delegatecall)
└── ERC1271 — ERC-1271 / ERC-7739 signature verification
Expand All @@ -304,11 +271,10 @@ Supporting contracts:
All storage uses [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) namespaced slots to avoid collisions across modules and upgrades:

| Manager | Slot |
|---------|------|
| --------- | ------ |
| ValidationManager | `keccak256("kernel.v4.validation") - 1` |
| ModuleManager | `keccak256("kernel.v4.module") - 1` |
| ExecutorManager | `keccak256("kernel.v4.executor") - 1` |
| HookManager | `keccak256("kernel.v4.hook") - 1` |
| SelectorManager | `keccak256("kernel.v4.selector") - 1` |

## Project Structure
Expand Down Expand Up @@ -396,7 +362,7 @@ open coverage/index.html
## Dependencies

| Package | Version |
|---------|---------|
| --------- | --------- |
| [Solady](https://github.com/Vectorized/solady) | 0.1.26 |
| [account-abstraction](https://github.com/eth-infinitism/account-abstraction) | v0.9.0 |
| [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) | 5.4.0 |
Expand Down
Loading
Loading