Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.19;
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IPluginLoupe} from "../interfaces/IPluginLoupe.sol";
import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol";
import {
Expand All @@ -15,14 +15,12 @@ import {
} from "../libraries/AccountStorage.sol";
import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";

abstract contract BaseModularAccountLoupe is IPluginLoupe {
abstract contract AccountLoupe is IAccountLoupe {
using EnumerableSet for EnumerableSet.AddressSet;

error ManifestDiscrepancy(address plugin);

/// @notice Gets the validator and plugin configuration for a selector
/// @param selector The selector to get the configuration for
/// @return config The configuration for this selector
/// @inheritdoc IAccountLoupe
function getExecutionFunctionConfig(bytes4 selector)
external
view
Expand All @@ -47,9 +45,7 @@ abstract contract BaseModularAccountLoupe is IPluginLoupe {
config.runtimeValidationFunction = _storage.selectorData[selector].runtimeValidation;
}

/// @notice Gets the pre and post execution hooks for a selector
/// @param selector The selector to get the hooks for
/// @return execHooks The pre and post execution hooks for this selector
/// @inheritdoc IAccountLoupe
function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory execHooks) {
AccountStorage storage _storage = getAccountStorage();

Expand All @@ -69,10 +65,7 @@ abstract contract BaseModularAccountLoupe is IPluginLoupe {
}
}

/// @notice Gets the pre and post permitted call hooks applied for a plugin calling this selector
/// @param callingPlugin The plugin that is calling the selector
/// @param selector The selector the plugin is calling
/// @return execHooks The pre and post permitted call hooks for this selector
/// @inheritdoc IAccountLoupe
function getPermittedCallHooks(address callingPlugin, bytes4 selector)
external
view
Expand All @@ -99,32 +92,22 @@ abstract contract BaseModularAccountLoupe is IPluginLoupe {
}
}

/// @notice Gets the pre user op validation hooks associated with a selector
/// @param selector The selector to get the hooks for
/// @return preValidationHooks The pre user op validation hooks for this selector
function getPreUserOpValidationHooks(bytes4 selector)
/// @inheritdoc IAccountLoupe
function getPreValidationHooks(bytes4 selector)
external
view
returns (FunctionReference[] memory preValidationHooks)
returns (
FunctionReference[] memory preUserOpValidationHooks,
FunctionReference[] memory preRuntimeValidationHooks
)
{
preValidationHooks =
preUserOpValidationHooks =
toFunctionReferenceArray(getAccountStorage().selectorData[selector].preUserOpValidationHooks);
}

/// @notice Gets the pre runtime validation hooks associated with a selector
/// @param selector The selector to get the hooks for
/// @return preValidationHooks The pre runtime validation hooks for this selector
function getPreRuntimeValidationHooks(bytes4 selector)
external
view
returns (FunctionReference[] memory preValidationHooks)
{
preValidationHooks =
preRuntimeValidationHooks =
toFunctionReferenceArray(getAccountStorage().selectorData[selector].preRuntimeValidationHooks);
}

/// @notice Gets an array of all installed plugins
/// @return pluginAddresses The addresses of all installed plugins
/// @inheritdoc IAccountLoupe
function getInstalledPlugins() external view returns (address[] memory pluginAddresses) {
pluginAddresses = getAccountStorage().plugins.values();
}
Expand Down
1 change: 1 addition & 0 deletions src/account/AccountStorageInitializable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.19;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {AccountStorage, getAccountStorage} from "../libraries/AccountStorage.sol";

abstract contract AccountStorageInitializable {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {AccountExecutor} from "./AccountExecutor.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {
AccountStorage,
getAccountStorage,
Expand All @@ -17,6 +13,8 @@ import {
PermittedExternalCallData,
StoredInjectedHook
} from "../libraries/AccountStorage.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {
IPlugin,
ManifestExecutionHook,
Expand All @@ -27,14 +25,10 @@ import {
PluginManifest
} from "../interfaces/IPlugin.sol";

abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165 {
abstract contract PluginManagerInternals is IPluginManager {
using EnumerableSet for EnumerableSet.Bytes32Set;
using EnumerableSet for EnumerableSet.AddressSet;

// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff;
bytes4 internal constant _IERC165_INTERFACE_ID = 0x01ffc9a7;

error ArrayLengthMismatch();
error ExecuteFromPluginAlreadySet(bytes4 selector, address plugin);
error PermittedExecutionSelectorNotInstalled(bytes4 selector, address plugin);
Expand Down Expand Up @@ -382,9 +376,15 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165

// Update components according to the manifest.
// All conflicts should revert.

// Mark whether or not this plugin may spend native token amounts
if (manifest.canSpendNativeToken) {
_storage.pluginData[plugin].canSpendNativeToken = true;
}

length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length;) {
_setExecutionFunction(manifest.executionFunctions[i].selector, plugin);
_setExecutionFunction(manifest.executionFunctions[i], plugin);

unchecked {
++i;
Expand All @@ -402,7 +402,7 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165
}

// Add the permitted external calls to the account.
if (manifest.permitAnyExternalContract) {
if (manifest.permitAnyExternalAddress) {
_storage.pluginData[plugin].anyExternalExecPermitted = true;
} else {
// Only store the specific permitted external calls if "permit any" flag was not set.
Expand Down Expand Up @@ -615,7 +615,7 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165
revert PluginInstallCallbackFailed(plugin, revertReason);
}

emit PluginInstalled(plugin, manifestHash);
emit PluginInstalled(plugin, manifestHash, dependencies, injectedHooks);
}

function _uninstallPlugin(
Expand Down Expand Up @@ -773,7 +773,7 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165

// remove external call permissions

if (manifest.permitAnyExternalContract) {
if (manifest.permitAnyExternalAddress) {
// Only clear if it was set during install time
_storage.pluginData[plugin].anyExternalExecPermitted = false;
} else {
Expand Down Expand Up @@ -839,7 +839,7 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165

length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length;) {
_removeExecutionFunction(manifest.executionFunctions[i].selector);
_removeExecutionFunction(manifest.executionFunctions[i]);

unchecked {
++i;
Expand Down Expand Up @@ -893,7 +893,7 @@ abstract contract BaseModularAccount is IPluginManager, AccountExecutor, IERC165
onUninstallSuccess = false;
}

emit PluginUninstalled(plugin, manifestHash, onUninstallSuccess);
emit PluginUninstalled(plugin, onUninstallSuccess);
}

function _toSetValue(FunctionReference functionReference) internal pure returns (bytes32) {
Expand Down
43 changes: 28 additions & 15 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {UserOperation} from "@eth-infinitism/account-abstraction/interfaces/UserOperation.sol";
import {BaseAccount} from "@eth-infinitism/account-abstraction/core/BaseAccount.sol";
import {BaseModularAccount} from "./BaseModularAccount.sol";
import {BaseModularAccountLoupe} from "./BaseModularAccountLoupe.sol";
import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol";
import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol";
import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

import {AccountExecutor} from "./AccountExecutor.sol";
import {AccountLoupe} from "./AccountLoupe.sol";
import {AccountStorage, getAccountStorage, getPermittedCallKey} from "../libraries/AccountStorage.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {AccountStorageInitializable} from "./AccountStorageInitializable.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol";
import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {IStandardExecutor, Call} from "../interfaces/IStandardExecutor.sol";
import {PluginManagerInternals} from "./PluginManagerInternals.sol";
import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationDataHelpers.sol";

contract UpgradeableModularAccount is
IPluginManager,
BaseAccount,
BaseModularAccount,
BaseModularAccountLoupe,
UUPSUpgradeable,
AccountExecutor,
AccountLoupe,
AccountStorageInitializable,
BaseAccount,
IERC165,
IPluginExecutor,
IStandardExecutor,
IPluginExecutor
PluginManagerInternals,
UUPSUpgradeable
{
using EnumerableSet for EnumerableSet.Bytes32Set;

Expand All @@ -37,13 +40,18 @@ contract UpgradeableModularAccount is

IEntryPoint private immutable _ENTRY_POINT;

// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff;
bytes4 internal constant _IERC165_INTERFACE_ID = 0x01ffc9a7;

event ModularAccountInitialized(IEntryPoint indexed entryPoint);

error AlwaysDenyRule();
error AuthorizeUpgradeReverted(bytes revertReason);
error ExecFromPluginNotPermitted(address plugin, bytes4 selector);
error ExecFromPluginExternalNotPermitted(address plugin, address target, uint256 value, bytes data);
error InvalidConfiguration();
error NativeTokenSpendingNotPermitted(address plugin);
error PostExecHookReverted(address plugin, uint8 functionId, bytes revertReason);
error PreExecHookReverted(address plugin, uint8 functionId, bytes revertReason);
error PreRuntimeValidationHookFailed(address plugin, uint8 functionId, bytes revertReason);
Expand Down Expand Up @@ -213,6 +221,11 @@ contract UpgradeableModularAccount is
bytes4 selector = bytes4(data);
AccountStorage storage _storage = getAccountStorage();

// Make sure plugin is allowed to spend native token.
if (value > 0 && value > msg.value && !_storage.pluginData[msg.sender].canSpendNativeToken) {
revert NativeTokenSpendingNotPermitted(msg.sender);
}

// Check the caller plugin's permission to make this call

// Check the target contract permission.
Expand Down
56 changes: 56 additions & 0 deletions src/interfaces/IAccountLoupe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";

interface IAccountLoupe {
/// @notice Config for an execution function, given a selector
struct ExecutionFunctionConfig {
address plugin;
FunctionReference userOpValidationFunction;
FunctionReference runtimeValidationFunction;
}

/// @notice Pre and post hooks for a given selector
/// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty
struct ExecutionHooks {
FunctionReference preExecHook;
FunctionReference postExecHook;
}

/// @notice Gets the validation functions and plugin address for a selector
/// @dev If the selector is a native function, the plugin address will be the address of the account
/// @param selector The selector to get the configuration for
/// @return The configuration for this selector
function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory);

/// @notice Gets the pre and post execution hooks for a selector
/// @param selector The selector to get the hooks for
/// @return The pre and post execution hooks for this selector
function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory);

/// @notice Gets the pre and post permitted call hooks applied for a plugin calling this selector
/// @param callingPlugin The plugin that is calling the selector
/// @param selector The selector the plugin is calling
/// @return The pre and post permitted call hooks for this selector
function getPermittedCallHooks(address callingPlugin, bytes4 selector)
external
view
returns (ExecutionHooks[] memory);

/// @notice Gets the pre user op and runtime validation hooks associated with a selector
/// @param selector The selector to get the hooks for
/// @return preUserOpValidationHooks The pre user op validation hooks for this selector
/// @return preRuntimeValidationHooks The pre runtime validation hooks for this selector
function getPreValidationHooks(bytes4 selector)
external
view
returns (
FunctionReference[] memory preUserOpValidationHooks,
FunctionReference[] memory preRuntimeValidationHooks
);

/// @notice Gets an array of all installed plugins
/// @return The addresses of all installed plugins
function getInstalledPlugins() external view returns (address[] memory);
}
Loading