Skip to content

Commit

Permalink
feat: Validation revamp - Introduce validation composability and allo…
Browse files Browse the repository at this point in the history
…w multiple validation installation on account (#93)
  • Loading branch information
fangting-alchemy authored Jul 17, 2024
1 parent 1685e91 commit d2e3c3a
Show file tree
Hide file tree
Showing 48 changed files with 912 additions and 1,002 deletions.
2 changes: 1 addition & 1 deletion 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 two plugins (`SingleOwnerPlugin` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates.
The implementation includes an upgradable modular account with two plugins (`SingleSignerValidation` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates.

## Important Callouts

Expand Down
10 changes: 5 additions & 5 deletions src/account/AccountLoupe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";

import {IAccountLoupe, ExecutionHook} from "../interfaces/IAccountLoupe.sol";
import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol";
import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol";
import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol";
import {getAccountStorage, toExecutionHook, toSelector} from "./AccountStorage.sol";

Expand All @@ -29,7 +29,7 @@ abstract contract AccountLoupe is IAccountLoupe {
}

/// @inheritdoc IAccountLoupe
function getSelectors(FunctionReference validationFunction) external view returns (bytes4[] memory) {
function getSelectors(PluginEntity validationFunction) external view returns (bytes4[] memory) {
uint256 length = getAccountStorage().validationData[validationFunction].selectors.length();

bytes4[] memory selectors = new bytes4[](length);
Expand Down Expand Up @@ -61,7 +61,7 @@ abstract contract AccountLoupe is IAccountLoupe {
}

/// @inheritdoc IAccountLoupe
function getPermissionHooks(FunctionReference validationFunction)
function getPermissionHooks(PluginEntity validationFunction)
external
view
override
Expand All @@ -79,11 +79,11 @@ abstract contract AccountLoupe is IAccountLoupe {
}

/// @inheritdoc IAccountLoupe
function getPreValidationHooks(FunctionReference validationFunction)
function getPreValidationHooks(PluginEntity validationFunction)
external
view
override
returns (FunctionReference[] memory preValidationHooks)
returns (PluginEntity[] memory preValidationHooks)
{
preValidationHooks = getAccountStorage().validationData[validationFunction].preValidationHooks;
}
Expand Down
43 changes: 20 additions & 23 deletions src/account/AccountStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";

import {ExecutionHook} from "../interfaces/IAccountLoupe.sol";
import {FunctionReference} from "../interfaces/IPluginManager.sol";
import {PluginEntity} from "../interfaces/IPluginManager.sol";

// bytes = keccak256("ERC6900.UpgradeableModularAccount.Storage")
bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x9f09680beaa4e5c9f38841db2460c401499164f368baef687948c315d9073e40;
Expand All @@ -31,7 +31,7 @@ struct ValidationData {
// Whether or not this validation is a signature validator.
bool isSignatureValidation;
// The pre validation hooks for this function selector.
FunctionReference[] preValidationHooks;
PluginEntity[] preValidationHooks;
// Permission hooks for this validation function.
EnumerableSet.Bytes32Set permissionHooks;
// The set of selectors that may be validated by this validation function.
Expand All @@ -46,7 +46,7 @@ struct AccountStorage {
EnumerableMap.AddressToUintMap pluginManifestHashes;
// Execution functions and their associated functions
mapping(bytes4 => SelectorData) selectorData;
mapping(FunctionReference validationFunction => ValidationData) validationData;
mapping(PluginEntity validationFunction => ValidationData) validationData;
// For ERC165 introspection
mapping(bytes4 => uint256) supportedIfaces;
}
Expand All @@ -59,32 +59,32 @@ function getAccountStorage() pure returns (AccountStorage storage _storage) {

using EnumerableSet for EnumerableSet.Bytes32Set;

function toSetValue(FunctionReference functionReference) pure returns (bytes32) {
return bytes32(FunctionReference.unwrap(functionReference));
function toSetValue(PluginEntity functionReference) pure returns (bytes32) {
return bytes32(PluginEntity.unwrap(functionReference));
}

function toFunctionReference(bytes32 setValue) pure returns (FunctionReference) {
return FunctionReference.wrap(bytes21(setValue));
function toPluginEntity(bytes32 setValue) pure returns (PluginEntity) {
return PluginEntity.wrap(bytes24(setValue));
}

// ExecutionHook layout:
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Function Reference
// 0x__________________________________________AA____________________ is pre hook
// 0x____________________________________________BB__________________ is post hook
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF______________________ Hook Plugin Entity
// 0x________________________________________________AA____________________ is pre hook
// 0x__________________________________________________BB__________________ is post hook

function toSetValue(ExecutionHook memory executionHook) pure returns (bytes32) {
return bytes32(FunctionReference.unwrap(executionHook.hookFunction))
| bytes32(executionHook.isPreHook ? uint256(1) << 80 : 0)
| bytes32(executionHook.isPostHook ? uint256(1) << 72 : 0);
return bytes32(PluginEntity.unwrap(executionHook.hookFunction))
| bytes32(executionHook.isPreHook ? uint256(1) << 56 : 0)
| bytes32(executionHook.isPostHook ? uint256(1) << 48 : 0);
}

function toExecutionHook(bytes32 setValue)
pure
returns (FunctionReference hookFunction, bool isPreHook, bool isPostHook)
returns (PluginEntity hookFunction, bool isPreHook, bool isPostHook)
{
hookFunction = FunctionReference.wrap(bytes21(setValue));
isPreHook = (uint256(setValue) >> 80) & 0xFF == 1;
isPostHook = (uint256(setValue) >> 72) & 0xFF == 1;
hookFunction = PluginEntity.wrap(bytes24(setValue));
isPreHook = (uint256(setValue) >> 56) & 0xFF == 1;
isPostHook = (uint256(setValue) >> 48) & 0xFF == 1;
}

function toSetValue(bytes4 selector) pure returns (bytes32) {
Expand All @@ -96,15 +96,12 @@ function toSelector(bytes32 setValue) pure returns (bytes4) {
}

/// @dev Helper function to get all elements of a set into memory.
function toFunctionReferenceArray(EnumerableSet.Bytes32Set storage set)
view
returns (FunctionReference[] memory)
{
function toPluginEntityArray(EnumerableSet.Bytes32Set storage set) view returns (PluginEntity[] memory) {
uint256 length = set.length();
FunctionReference[] memory result = new FunctionReference[](length);
PluginEntity[] memory result = new PluginEntity[](length);
for (uint256 i = 0; i < length; ++i) {
bytes32 key = set.at(i);
result[i] = FunctionReference.wrap(bytes21(key));
result[i] = PluginEntity.wrap(bytes24(key));
}
return result;
}
38 changes: 19 additions & 19 deletions src/account/PluginManager2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ pragma solidity ^0.8.25;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IPlugin} from "../interfaces/IPlugin.sol";
import {FunctionReference, ValidationConfig} from "../interfaces/IPluginManager.sol";
import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol";
import {PluginEntity, ValidationConfig} from "../interfaces/IPluginManager.sol";
import {PluginEntityLib} from "../helpers/PluginEntityLib.sol";
import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol";
import {ValidationData, getAccountStorage, toSetValue, toFunctionReference} from "./AccountStorage.sol";
import {ValidationData, getAccountStorage, toSetValue, toPluginEntity} from "./AccountStorage.sol";
import {ExecutionHook} from "../interfaces/IAccountLoupe.sol";

// Temporary additional functions for a user-controlled install flow for validation functions.
Expand All @@ -18,10 +18,10 @@ abstract contract PluginManager2 {
// Index marking the start of the data for the validation function.
uint8 internal constant _RESERVED_VALIDATION_DATA_INDEX = 255;

error PreValidationAlreadySet(FunctionReference validationFunction, FunctionReference preValidationFunction);
error ValidationAlreadySet(bytes4 selector, FunctionReference validationFunction);
error ValidationNotSet(bytes4 selector, FunctionReference validationFunction);
error PermissionAlreadySet(FunctionReference validationFunction, ExecutionHook hook);
error PreValidationAlreadySet(PluginEntity validationFunction, PluginEntity preValidationFunction);
error ValidationAlreadySet(bytes4 selector, PluginEntity validationFunction);
error ValidationNotSet(bytes4 selector, PluginEntity validationFunction);
error PermissionAlreadySet(PluginEntity validationFunction, ExecutionHook hook);
error PreValidationHookLimitExceeded();

function _installValidation(
Expand All @@ -35,16 +35,16 @@ abstract contract PluginManager2 {
getAccountStorage().validationData[validationConfig.functionReference()];

if (preValidationHooks.length > 0) {
(FunctionReference[] memory preValidationFunctions, bytes[] memory initDatas) =
abi.decode(preValidationHooks, (FunctionReference[], bytes[]));
(PluginEntity[] memory preValidationFunctions, bytes[] memory initDatas) =
abi.decode(preValidationHooks, (PluginEntity[], bytes[]));

for (uint256 i = 0; i < preValidationFunctions.length; ++i) {
FunctionReference preValidationFunction = preValidationFunctions[i];
PluginEntity preValidationFunction = preValidationFunctions[i];

_validationData.preValidationHooks.push(preValidationFunction);

if (initDatas[i].length > 0) {
(address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction);
(address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction);
IPlugin(preValidationPlugin).onInstall(initDatas[i]);
}
}
Expand All @@ -67,7 +67,7 @@ abstract contract PluginManager2 {
}

if (initDatas[i].length > 0) {
(address executionPlugin,) = FunctionReferenceLib.unpack(permissionFunction.hookFunction);
(address executionPlugin,) = PluginEntityLib.unpack(permissionFunction.hookFunction);
IPlugin(executionPlugin).onInstall(initDatas[i]);
}
}
Expand All @@ -90,7 +90,7 @@ abstract contract PluginManager2 {
}

function _uninstallValidation(
FunctionReference validationFunction,
PluginEntity validationFunction,
bytes calldata uninstallData,
bytes calldata preValidationHookUninstallData,
bytes calldata permissionHookUninstallData
Expand All @@ -104,11 +104,11 @@ abstract contract PluginManager2 {
bytes[] memory preValidationHookUninstallDatas = abi.decode(preValidationHookUninstallData, (bytes[]));

// Clear pre validation hooks
FunctionReference[] storage preValidationHooks = _validationData.preValidationHooks;
PluginEntity[] storage preValidationHooks = _validationData.preValidationHooks;
for (uint256 i = 0; i < preValidationHooks.length; ++i) {
FunctionReference preValidationFunction = preValidationHooks[i];
PluginEntity preValidationFunction = preValidationHooks[i];
if (preValidationHookUninstallDatas[0].length > 0) {
(address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction);
(address preValidationPlugin,) = PluginEntityLib.unpack(preValidationFunction);
IPlugin(preValidationPlugin).onUninstall(preValidationHookUninstallDatas[0]);
}
}
Expand All @@ -122,9 +122,9 @@ abstract contract PluginManager2 {
EnumerableSet.Bytes32Set storage permissionHooks = _validationData.permissionHooks;
uint256 i = 0;
while (permissionHooks.length() > 0) {
FunctionReference permissionHook = toFunctionReference(permissionHooks.at(0));
PluginEntity permissionHook = toPluginEntity(permissionHooks.at(0));
permissionHooks.remove(toSetValue(permissionHook));
(address permissionHookPlugin,) = FunctionReferenceLib.unpack(permissionHook);
(address permissionHookPlugin,) = PluginEntityLib.unpack(permissionHook);
IPlugin(permissionHookPlugin).onUninstall(permissionHookUninstallDatas[i++]);
}
}
Expand All @@ -137,7 +137,7 @@ abstract contract PluginManager2 {
}

if (uninstallData.length > 0) {
(address plugin,) = FunctionReferenceLib.unpack(validationFunction);
(address plugin,) = PluginEntityLib.unpack(validationFunction);
IPlugin(plugin).onUninstall(uninstallData);
}
}
Expand Down
22 changes: 11 additions & 11 deletions src/account/PluginManagerInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@ import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165C
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";

import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol";
import {PluginEntityLib} from "../helpers/PluginEntityLib.sol";
import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol";
import {ExecutionHook} from "../interfaces/IAccountLoupe.sol";
import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol";
import {PluginEntity, IPluginManager} from "../interfaces/IPluginManager.sol";
import {KnownSelectors} from "../helpers/KnownSelectors.sol";
import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol";

abstract contract PluginManagerInternals is IPluginManager {
using EnumerableSet for EnumerableSet.Bytes32Set;
using EnumerableMap for EnumerableMap.AddressToUintMap;
using FunctionReferenceLib for FunctionReference;
using PluginEntityLib for PluginEntity;

error ArrayLengthMismatch();
error Erc4337FunctionNotAllowed(bytes4 selector);
error ExecutionFunctionAlreadySet(bytes4 selector);
error InvalidPluginManifest();
error IPluginFunctionNotAllowed(bytes4 selector);
error NativeFunctionNotAllowed(bytes4 selector);
error NullFunctionReference();
error NullPluginEntity();
error NullPlugin();
error PluginAlreadyInstalled(address plugin);
error PluginInstallCallbackFailed(address plugin, bytes revertReason);
error PluginInterfaceNotSupported(address plugin);
error PluginNotInstalled(address plugin);
error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction);
error ValidationFunctionAlreadySet(bytes4 selector, PluginEntity validationFunction);

// Storage update operations

Expand Down Expand Up @@ -78,7 +78,7 @@ abstract contract PluginManagerInternals is IPluginManager {
function _addValidationFunction(address plugin, ManifestValidation memory mv) internal {
AccountStorage storage _storage = getAccountStorage();

FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId);
PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId);

if (mv.isDefault) {
_storage.validationData[validationFunction].isGlobal = true;
Expand All @@ -99,7 +99,7 @@ abstract contract PluginManagerInternals is IPluginManager {
function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal {
AccountStorage storage _storage = getAccountStorage();

FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId);
PluginEntity validationFunction = PluginEntityLib.pack(plugin, mv.entityId);

_storage.validationData[validationFunction].isGlobal = false;
_storage.validationData[validationFunction].isSignatureValidation = false;
Expand All @@ -113,7 +113,7 @@ abstract contract PluginManagerInternals is IPluginManager {

function _addExecHooks(
EnumerableSet.Bytes32Set storage hooks,
FunctionReference hookFunction,
PluginEntity hookFunction,
bool isPreExecHook,
bool isPostExecHook
) internal {
Expand All @@ -126,7 +126,7 @@ abstract contract PluginManagerInternals is IPluginManager {

function _removeExecHooks(
EnumerableSet.Bytes32Set storage hooks,
FunctionReference hookFunction,
PluginEntity hookFunction,
bool isPreExecHook,
bool isPostExecHook
) internal {
Expand Down Expand Up @@ -183,7 +183,7 @@ abstract contract PluginManagerInternals is IPluginManager {
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks;
FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId);
PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId);
_addExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook);
}

Expand Down Expand Up @@ -223,7 +223,7 @@ abstract contract PluginManagerInternals is IPluginManager {
uint256 length = manifest.executionHooks.length;
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId);
PluginEntity hookFunction = PluginEntityLib.pack(plugin, mh.entityId);
EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks;
_removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook);
}
Expand Down
Loading

0 comments on commit d2e3c3a

Please sign in to comment.