Skip to content
Closed
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
7 changes: 6 additions & 1 deletion src/account/AccountLoupe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AccountStorage,
getAccountStorage,
SelectorData,
toFunctionReference,
toFunctionReferenceArray,
toExecutionHook
} from "./AccountStorage.sol";
Expand Down Expand Up @@ -38,7 +39,7 @@ abstract contract AccountLoupe is IAccountLoupe {
config.plugin = _storage.selectorData[selector].plugin;
}

config.validationFunction = _storage.selectorData[selector].validation;
config.defaultValidationFunction = toFunctionReference(_storage.selectorData[selector].validations.at(0));
Copy link
Collaborator

@howydev howydev May 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(small nit) maybe primaryValidationFunction?

}

/// @inheritdoc IAccountLoupe
Expand All @@ -55,6 +56,10 @@ abstract contract AccountLoupe is IAccountLoupe {
}
}

function getValidationFunctions(bytes4 selector) external view returns (FunctionReference[] memory) {
return toFunctionReferenceArray(getAccountStorage().selectorData[selector].validations);
}

/// @inheritdoc IAccountLoupe
function getPreValidationHooks(bytes4 selector)
external
Expand Down
2 changes: 1 addition & 1 deletion src/account/AccountStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct SelectorData {
// but it packs alongside `plugin` while still leaving some other space in the slot for future packing.
uint48 denyExecutionCount;
// User operation validation and runtime validation share a function reference.
FunctionReference validation;
EnumerableSet.Bytes32Set validations;
// The pre validation hooks for this function selector.
EnumerableSet.Bytes32Set preValidationHooks;
// The execution hooks for this function selector.
Expand Down
10 changes: 6 additions & 4 deletions src/account/PluginManagerInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ abstract contract PluginManagerInternals is IPluginManager {
{
SelectorData storage _selectorData = getAccountStorage().selectorData[selector];

if (_selectorData.validation.notEmpty()) {
// Fail on duplicate definitions - otherwise dependencies could shadow non-depdency
// validation functions, leading to partial uninstalls.
if (!_selectorData.validations.add(toSetValue(validationFunction))) {
revert ValidationFunctionAlreadySet(selector, validationFunction);
}

_selectorData.validation = validationFunction;
}

function _removeValidationFunction(bytes4 selector, FunctionReference validationFunction)
Expand All @@ -95,7 +95,9 @@ abstract contract PluginManagerInternals is IPluginManager {
{
SelectorData storage _selectorData = getAccountStorage().selectorData[selector];

_selectorData.validation = FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE;
// May ignore return value, as the manifest hash is validated to ensure that the validation function
// exists.
_selectorData.validations.remove(toSetValue(validationFunction));
}

function _addExecHooks(
Expand Down
72 changes: 64 additions & 8 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
getPermittedCallKey,
SelectorData,
toFunctionReference,
toExecutionHook
toExecutionHook,
toSetValue
} from "./AccountStorage.sol";
import {AccountStorageInitializable} from "./AccountStorageInitializable.sol";
import {PluginManagerInternals} from "./PluginManagerInternals.sol";
Expand Down Expand Up @@ -71,7 +72,7 @@ contract UpgradeableModularAccount is
// Wraps execution of a native function with runtime validation and hooks
// Used for upgradeTo, upgradeToAndCall, execute, executeBatch, installPlugin, uninstallPlugin
modifier wrapNativeFunction() {
_doRuntimeValidationIfNotFromEP();
_doRuntimeValidationIfNotFromEPorSelf();

PostExecToRun[] memory postExecHooks = _doPreExecHooks(msg.sig, msg.data);

Expand Down Expand Up @@ -123,7 +124,7 @@ contract UpgradeableModularAccount is
revert UnrecognizedFunction(msg.sig);
}

_doRuntimeValidationIfNotFromEP();
_doRuntimeValidationIfNotFromEPorSelf();

PostExecToRun[] memory postExecHooks;
// Cache post-exec hooks in memory
Expand Down Expand Up @@ -171,6 +172,33 @@ contract UpgradeableModularAccount is
}
}

function executeWithValidation(FunctionReference validation, bytes calldata data)
external
returns (bytes memory)
{
// If the call is from the entry point or the account itself, we skip runtime validation because
// User Op validation must have occurred.
if (msg.sender != address(_ENTRY_POINT) && msg.sender != address(this)) {
bytes4 selector = bytes4(data); // If extending, the call will fail in the sub-call anyways.

if (!getAccountStorage().selectorData[selector].validations.contains(toSetValue(validation))) {
revert RuntimeValidationFunctionMissing(selector);
}

_doRuntimeValidation(validation);
}

(bool success, bytes memory returnData) = address(this).call(data);

if (!success) {
assembly ("memory-safe") {
revert(add(returnData, 32), mload(returnData))
}
}

return returnData;
}

/// @inheritdoc IPluginExecutor
function executeFromPlugin(bytes calldata data) external payable override returns (bytes memory) {
bytes4 selector = bytes4(data[:4]);
Expand Down Expand Up @@ -333,9 +361,28 @@ contract UpgradeableModularAccount is
revert AlwaysDenyRule();
}

FunctionReference userOpValidationFunction = getAccountStorage().selectorData[selector].validation;
// Unless otherwise specified, use the validator at validations[0] as the "default validation".
// This is typically the first validation function added, but ordering is not guaranteed by the
// OZ EnumerableSet. todo: some manual control over this.

if (selector == UpgradeableModularAccount.executeWithValidation.selector) {
(FunctionReference userOpValidationFunction, bytes memory actualData) =
abi.decode(userOp.callData[4:], (FunctionReference, bytes));
bytes4 actualSelector = bytes4(actualData);

if (!_storage.selectorData[actualSelector].validations.contains(toSetValue(userOpValidationFunction)))
{
revert UserOpValidationFunctionMissing(selector);
}

validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash);
validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash);
(userOpValidationFunction);
} else {
FunctionReference userOpValidationFunction =
toFunctionReference(getAccountStorage().selectorData[selector].validations.at(0));

validationData = _doUserOpValidation(selector, userOpValidationFunction, userOp, userOpHash);
}
}

// To support gas estimation, we don't fail early when the failure is caused by a signature failure
Expand Down Expand Up @@ -387,16 +434,25 @@ contract UpgradeableModularAccount is
}
}

function _doRuntimeValidationIfNotFromEP() internal {
function _doRuntimeValidationIfNotFromEPorSelf() internal {
AccountStorage storage _storage = getAccountStorage();

if (_storage.selectorData[msg.sig].denyExecutionCount > 0) {
revert AlwaysDenyRule();
}

if (msg.sender == address(_ENTRY_POINT)) return;
if (msg.sender == address(_ENTRY_POINT) || msg.sender == address(this)) return;

// Unless otherwise specified, use the validator at validations[0] as the "default validation".
// This is typically the first validation function added, but ordering is not guaranteed by the
// OZ EnumerableSet. todo: some manual control over this.
FunctionReference runtimeValidationFunction =
toFunctionReference(_storage.selectorData[msg.sig].validations.at(0));

_doRuntimeValidation(runtimeValidationFunction);
}

FunctionReference runtimeValidationFunction = _storage.selectorData[msg.sig].validation;
function _doRuntimeValidation(FunctionReference runtimeValidationFunction) internal {
// run all preRuntimeValidation hooks
EnumerableSet.Bytes32Set storage preRuntimeValidationHooks =
getAccountStorage().selectorData[msg.sig].preValidationHooks;
Expand Down
7 changes: 6 additions & 1 deletion src/interfaces/IAccountLoupe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface IAccountLoupe {
/// @notice Config for an execution function, given a selector.
struct ExecutionFunctionConfig {
address plugin;
FunctionReference validationFunction;
FunctionReference defaultValidationFunction;
}

/// @notice Get the validation functions and plugin address for a selector.
Expand All @@ -29,6 +29,11 @@ interface IAccountLoupe {
/// @return The pre and post execution hooks for this selector.
function getExecutionHooks(bytes4 selector) external view returns (ExecutionHook[] memory);

// todo: natspec
function getValidationFunctions(bytes4 selector) external view returns (FunctionReference[] memory);

// todo: should we support a validation checking function (bytes4, FunctionReference) -> bool?

/// @notice Get the pre user op and runtime validation hooks associated with a selector.
/// @param selector The selector to get the hooks for.
/// @return preValidationHooks The pre validation hooks for this selector.
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/IStandardExecutor.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.25;

import {FunctionReference} from "./IPluginManager.sol";

struct Call {
// The target address for the account to call.
address target;
Expand All @@ -25,4 +27,9 @@ interface IStandardExecutor {
/// @param calls The array of calls.
/// @return An array containing the return data from the calls.
function executeBatch(Call[] calldata calls) external payable returns (bytes[] memory);

// todo: natspec
function executeWithValidation(FunctionReference validation, bytes calldata data)
external
returns (bytes memory);
}
2 changes: 1 addition & 1 deletion src/plugins/owner/SingleOwnerPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
{
if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) {
// Validate that the sender is the owner of the account or self.
if (sender != _owners[msg.sender] && sender != msg.sender) {
if (sender != _owners[msg.sender]) {
revert NotAuthorized();
}
return;
Expand Down
4 changes: 2 additions & 2 deletions test/account/AccountLoupe.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ contract AccountLoupeTest is AccountTestBase {

assertEq(config.plugin, address(account1));
assertEq(
FunctionReference.unwrap(config.validationFunction),
FunctionReference.unwrap(config.defaultValidationFunction),
FunctionReference.unwrap(expectedValidations[i])
);
}
Expand All @@ -93,7 +93,7 @@ contract AccountLoupeTest is AccountTestBase {

assertEq(config.plugin, expectedPluginAddress[i]);
assertEq(
FunctionReference.unwrap(config.validationFunction),
FunctionReference.unwrap(config.defaultValidationFunction),
FunctionReference.unwrap(expectedValidations[i])
);
}
Expand Down
Loading