Skip to content
Merged
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
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ via-ir = true
fuzz = { runs = 10_000 }
verbosity = 4

[profile.via-ir]
via_ir = true

[fuzz]
runs = 1000
max_test_rejects = 1_000_000
Expand Down
25 changes: 24 additions & 1 deletion src/StartaleSmartAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ contract StartaleSmartAccount is IStartaleSmartAccount, BaseAccount, ExecutionHe
/// @dev Features Module Enable Mode.
/// This Module Enable Mode flow is intended for the module acting as the validator
/// for the user operation that triggers the Module Enable Flow. Otherwise, a call to
/// `Nexus.installModule` should be included in `userOp.callData`.
/// `IERC7579Account.installModule` should be included in `userOp.callData`.
function validateUserOp(
PackedUserOperation calldata op,
bytes32 userOpHash,
Expand Down Expand Up @@ -192,6 +192,8 @@ contract StartaleSmartAccount is IStartaleSmartAccount, BaseAccount, ExecutionHe
/// - 4 for Hook
/// - 8 for 1271 Prevalidation Hook
/// - 9 for 4337 Prevalidation Hook
/// @notice
/// If the module is malicious, it can prevent itself from being uninstalled by spending all gas in the onUninstall() method.
/// @param module The address of the module to uninstall.
/// @param deInitData De-initialization data for the module.
/// @dev Ensures that the operation is authorized and valid before proceeding with the uninstallation.
Expand All @@ -204,6 +206,7 @@ contract StartaleSmartAccount is IStartaleSmartAccount, BaseAccount, ExecutionHe

if (moduleTypeId == MODULE_TYPE_VALIDATOR) {
_uninstallValidator(module, deInitData);
_checkInitializedValidators();
} else if (moduleTypeId == MODULE_TYPE_EXECUTOR) {
_uninstallExecutor(module, deInitData);
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
Expand Down Expand Up @@ -437,6 +440,26 @@ contract StartaleSmartAccount is IStartaleSmartAccount, BaseAccount, ExecutionHe
}
}

// checks if there's at least one validator initialized
function _checkInitializedValidators() internal view {
if (!_amIERC7702() && !IValidator(_DEFAULT_VALIDATOR).isInitialized(address(this))) {
unchecked {
SentinelListLib.SentinelList storage validators = _getAccountStorage().validators;
address next = validators.entries[SENTINEL];
while (next != ZERO_ADDRESS && next != SENTINEL) {
if (IValidator(next).isInitialized(address(this))) {
break;
}
next = validators.getNext(next);
}
if (next == SENTINEL) {
//went through all validators and none was initialized
revert CanNotRemoveLastValidator();
}
}
}
}

/// @dev EIP712 domain name and version.
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = 'Startale';
Expand Down
22 changes: 0 additions & 22 deletions src/core/ExecutionHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,6 @@ abstract contract ExecutionHelper is IExecutionHelper {
}
}

/// @notice Executes a call to a target address with specified value and data.
/// same as _execute, but callData can be in memory
function _executeMemory(
address target,
uint256 value,
bytes memory callData
) internal virtual returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
if iszero(call(gas(), target, value, add(callData, 0x20), mload(callData), codesize(), 0x00)) {
// Bubble up the revert if the call reverts.
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
mstore(result, returndatasize()) // Store the length.
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize()) // Copy the returndata.
mstore(0x40, add(o, returndatasize())) // Allocate the memory.
}
}

/// @notice Executes a call to a target address with specified value and data.
/// Same as _execute but without return data for gas optimization.
function _executeNoReturndata(address target, uint256 value, bytes calldata callData) internal virtual {
Expand Down
28 changes: 8 additions & 20 deletions src/core/ModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@
using ExcessivelySafeCall for address;
using ECDSA for bytes32;

/// @dev The slot in the transient storage to store the hooking flag.
// keccak256(abi.encode(uint256(keccak256(bytes("startale.modulemanager.hooking"))) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant HOOKING_FLAG_TRANSIENT_STORAGE_SLOT =
0x1085a7be7203eb252e321995729a7b29dd605712ca7b7b85cd33107663f41400;

/// @dev The default validator address.
/// @notice To explicitly initialize the default validator, StartaleSmartAccount.execute(_DEFAULT_VALIDATOR.onInstall(...)) should be called.
address internal immutable _DEFAULT_VALIDATOR;
Expand All @@ -66,7 +61,7 @@
}

/// @notice Ensures the message sender is a registered executor module.
modifier onlyExecutorModule() virtual {

Check warning on line 64 in src/core/ModuleManager.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Function order is incorrect, modifier definition can not go after constructor (line 55)
require(_getAccountStorage().executors.contains(msg.sender), InvalidModule(msg.sender));
_;
}
Expand All @@ -75,16 +70,9 @@
/// @dev sender, msg.data and msg.value is passed to the hook to implement custom flows.
modifier withHook() {
address hook = _getHook();
bool hooking;
assembly {
hooking := tload(HOOKING_FLAG_TRANSIENT_STORAGE_SLOT)
}
if (hook == address(0) || hooking) {
if (hook == address(0)) {
_;
} else {
assembly {
tstore(HOOKING_FLAG_TRANSIENT_STORAGE_SLOT, 1)
}
bytes memory hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data);
_;
IHook(hook).postCheck(hookData);
Expand Down Expand Up @@ -137,7 +125,7 @@
/// @param selector The function selector to query.
/// @return calltype The type of call that the handler manages.
/// @return handler The address of the fallback handler.
function getFallbackHandlerBySelector(bytes4 selector) external view returns (CallType, address) {

Check warning on line 128 in src/core/ModuleManager.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

first return value does not have a name

Check warning on line 128 in src/core/ModuleManager.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

second return value does not have a name
FallbackHandler memory handler = _getAccountStorage().fallbacks[selector];
return (handler.calltype, handler.handler);
}
Expand Down Expand Up @@ -191,7 +179,7 @@
/// @dev This function goes through hook checks via withHook modifier.
/// @dev No need to check that the module is already installed, as this check is done
/// when trying to sstore the module in an appropriate SentinelList
function _installModule(uint256 moduleTypeId, address module, bytes calldata initData) internal withHook {
function _installModule(uint256 moduleTypeId, address module, bytes calldata initData) internal {
if (!_areSentinelListsInitialized()) {
_initSentinelLists();
}
Expand All @@ -218,7 +206,7 @@
/// @dev Installs a new validator module after checking if it matches the required module type.
/// @param validator The address of the validator module to be installed.
/// @param data Initialization data to configure the validator upon installation.
function _installValidator(address validator, bytes calldata data) internal virtual {
function _installValidator(address validator, bytes calldata data) internal virtual withHook {
if (!IValidator(validator).isModuleType(MODULE_TYPE_VALIDATOR)) revert MismatchModuleTypeId();
if (validator == _DEFAULT_VALIDATOR) {
revert DefaultValidatorAlreadyInstalled();
Expand All @@ -227,7 +215,7 @@
IValidator(validator).onInstall(data);
}

/// @dev Uninstalls a validator module /!\ ensuring the account retains at least one validator.
/// @dev Uninstalls a validator module
/// @param validator The address of the validator to be uninstalled.
/// @param data De-initialization data to configure the validator upon uninstallation.
function _uninstallValidator(address validator, bytes calldata data) internal virtual {
Expand All @@ -246,7 +234,7 @@
/// @dev Installs a new executor module after checking if it matches the required module type.
/// @param executor The address of the executor module to be installed.
/// @param data Initialization data to configure the executor upon installation.
function _installExecutor(address executor, bytes calldata data) internal virtual {
function _installExecutor(address executor, bytes calldata data) internal virtual withHook {
if (!IExecutor(executor).isModuleType(MODULE_TYPE_EXECUTOR)) revert MismatchModuleTypeId();
_getAccountStorage().executors.push(executor);
IExecutor(executor).onInstall(data);
Expand All @@ -266,7 +254,7 @@
/// @dev Installs a hook module, ensuring no other hooks are installed before proceeding.
/// @param hook The address of the hook to be installed.
/// @param data Initialization data to configure the hook upon installation.
function _installHook(address hook, bytes calldata data) internal virtual {
function _installHook(address hook, bytes calldata data) internal virtual withHook {
if (!IHook(hook).isModuleType(MODULE_TYPE_HOOK)) revert MismatchModuleTypeId();
address currentHook = _getHook();
require(currentHook == address(0), HookAlreadyInstalled(currentHook));
Expand Down Expand Up @@ -297,7 +285,7 @@
/// @dev Installs a fallback handler for a given selector with initialization data.
/// @param handler The address of the fallback handler to install.
/// @param params The initialization parameters including the selector and call type.
function _installFallbackHandler(address handler, bytes calldata params) internal virtual {
function _installFallbackHandler(address handler, bytes calldata params) internal virtual withHook {
if (!IFallback(handler).isModuleType(MODULE_TYPE_FALLBACK)) revert MismatchModuleTypeId();
// Extract the function selector from the provided parameters.
bytes4 selector = bytes4(params[0:4]);
Expand Down Expand Up @@ -350,7 +338,7 @@
uint256 preValidationHookType,
address preValidationHook,
bytes calldata data
) internal virtual {
) internal virtual withHook {
if (!IModule(preValidationHook).isModuleType(preValidationHookType)) revert MismatchModuleTypeId();
address currentPreValidationHook = _getPreValidationHook(preValidationHookType);
if (currentPreValidationHook != address(0)) revert PrevalidationHookAlreadyInstalled(currentPreValidationHook);
Expand All @@ -363,9 +351,9 @@
/// @param hookType The type of the pre-validation hook.
/// @param data De-initialization data to configure the hook upon uninstallation.
function _uninstallPreValidationHook(
address preValidationHook,

Check warning on line 354 in src/core/ModuleManager.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "preValidationHook" is unused
uint256 hookType,
bytes calldata data

Check warning on line 356 in src/core/ModuleManager.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "data" is unused
) internal virtual {
// Review
// Check if the hook is installed according to supplied type
Expand Down
20 changes: 10 additions & 10 deletions src/factory/EOAOnboardingFactory.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {BootstrapLib} from '../lib/BootstrapLib.sol';

Check warning on line 4 in src/factory/EOAOnboardingFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "BootstrapLib" is unused

import {ProxyLib} from '../lib/ProxyLib.sol';
import {Bootstrap, BootstrapConfig} from '../utils/Bootstrap.sol';

Check warning on line 7 in src/factory/EOAOnboardingFactory.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "BootstrapConfig" is unused
import {Stakeable} from '../utils/Stakeable.sol';

/// @title EOAOnboardingFactory for Startale Smart Account
Expand All @@ -13,7 +13,7 @@
/// Special thanks to the Biconomy team for https://github.com/bcnmy/nexus/ on which this factory implementation is highly based on.
/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady
contract EOAOnboardingFactory is Stakeable {
/// @notice Stores the implementation contract address used to create new Nexus instances.
/// @notice Stores the implementation contract address used to create new account instances.
/// @dev This address is set once upon deployment and cannot be changed afterwards.
address public immutable ACCOUNT_IMPLEMENTATION;

Expand All @@ -32,7 +32,7 @@
error ZeroAddressNotAllowed();

/// @notice Constructor to set the immutable variables.
/// @param implementation The address of the Nexus implementation to be used for all deployments.
/// @param implementation The address of the smart account implementation to be used for all deployments.
/// @param factoryOwner The address of the factory owner.
/// @param ecdsaValidator The address of the K1 Validator module to be used for all deployments.
/// @param bootstrapper The address of the Bootstrapper module to be used for all deployments.
Expand Down Expand Up @@ -62,9 +62,10 @@
// Compute the salt for deterministic deployment
bytes32 salt = keccak256(abi.encodePacked(eoaOwner, index));

// Create the validator configuration using the NexusBootstrap library
BootstrapConfig memory validator = BootstrapLib.createSingleConfig(ECDSA_VALIDATOR, abi.encodePacked(eoaOwner));
bytes memory initData = BOOTSTRAPPER.getInitWithSingleValidatorCalldata(validator);
bytes memory initData = abi.encode(
address(BOOTSTRAPPER),
abi.encodeCall(BOOTSTRAPPER.initWithSingleValidator, (ECDSA_VALIDATOR, abi.encodePacked(eoaOwner)))
);

// Deploy the Smart account using the ProxyLib
(bool alreadyDeployed, address payable account) = ProxyLib.deployProxy(ACCOUNT_IMPLEMENTATION, salt, initData);
Expand All @@ -85,11 +86,10 @@
// Compute the salt for deterministic deployment
bytes32 salt = keccak256(abi.encodePacked(eoaOwner, index));

// Create the validator configuration using the NexusBootstrap library
BootstrapConfig memory validator = BootstrapLib.createSingleConfig(ECDSA_VALIDATOR, abi.encodePacked(eoaOwner));

// Get the initialization data for the Nexus account
bytes memory initData = BOOTSTRAPPER.getInitWithSingleValidatorCalldata(validator);
bytes memory initData = abi.encode(
address(BOOTSTRAPPER),
abi.encodeCall(BOOTSTRAPPER.initWithSingleValidator, (ECDSA_VALIDATOR, abi.encodePacked(eoaOwner)))
);

// Compute the predicted address using the ProxyLib
return ProxyLib.predictProxyAddress(ACCOUNT_IMPLEMENTATION, salt, initData);
Expand Down
18 changes: 17 additions & 1 deletion src/lib/BootstrapLib.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {BootstrapConfig} from '../utils/Bootstrap.sol';
import {BootstrapConfig, BootstrapPreValidationHookConfig} from '../utils/Bootstrap.sol';

/// @title Bootstrap Configuration Library
/// @notice Provides utility functions to create and manage BootstrapConfig structures.
Expand All @@ -28,6 +28,22 @@ library BootstrapLib {
config[0].data = data;
}

/// @notice Creates an array with a single BootstrapPreValidationHookConfig structure.
/// @param hookType The type of the pre-validation hook.
/// @param module The address of the module.
/// @param data The initialization data for the module.
/// @return config An array containing a single BootstrapPreValidationHookConfig structure.
function createArrayPreValidationHookConfig(
uint256 hookType,
address module,
bytes memory data
) internal pure returns (BootstrapPreValidationHookConfig[] memory config) {
config = new BootstrapPreValidationHookConfig[](1);
config[0].hookType = hookType;
config[0].module = module;
config[0].data = data;
}

/// @notice Creates an array of BootstrapConfig structures.
/// @param modules An array of module addresses.
/// @param datas An array of initialization data for each module.
Expand Down
Loading
Loading