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

[1/n] Introduce EntityId in account and plugins #93

Merged
merged 11 commits into from
Jul 17, 2024
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
Loading