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

Release/nexus v1.2.0 ep v0.7 #242

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3c08abd
feat: add eip7702 support
highskore Dec 10, 2024
5689c78
chore: fix function ordering
highskore Dec 10, 2024
6a9f7a0
fix(hardhat): fix hardhat test
highskore Dec 10, 2024
c3b4fc6
chore(K1ValidatoFactory): remove unused error
highskore Dec 10, 2024
691cdff
chore(MockValidator): revert removing require
highskore Dec 13, 2024
bb7f70e
refactor(validateUserOp): check happy path first
highskore Dec 13, 2024
d0cc50e
refactor(_checkUserOpSignature): check happy path first
highskore Dec 13, 2024
dd96bd7
fix tests dep in script
Dec 18, 2024
3fc0861
Merge pull request #230 from bcnmy/fix/scripts-dep
filmakarov Dec 18, 2024
036e348
Merge pull request #227 from highskore/feat/eip7702-support
filmakarov Dec 18, 2024
fb07c7e
Fix test case for delegate call
Man-Jain Dec 18, 2024
be790e8
Merge pull request #229 from Man-Jain/issue-228
filmakarov Dec 19, 2024
8cd5169
feat: add prevalidation hook support
highskore Jan 7, 2025
ff8f36f
chore: fix linter issues
highskore Jan 7, 2025
731daf9
fix: init userOp to op
highskore Jan 8, 2025
ab75991
fix: use 2771 for 4337 prevalidation msg.sender
highskore Jan 10, 2025
859bf3b
test(pre-validation/multiplexer): add k1 validator tests
highskore Jan 10, 2025
683ee6e
fix: make MockResourceLock 4337 storage compliant
highskore Jan 10, 2025
84f9aa0
fix: remove account from 1271 interface
highskore Jan 11, 2025
a078cd7
Merge pull request #232 from highskore/feat/pre-validation-hooks
filmakarov Jan 14, 2025
451ed01
secure batch decode + test fix
Feb 3, 2025
cdd799b
module enable mode for uninitialized 7702 acct
Feb 3, 2025
76b5ab4
7702 enable mode
Feb 3, 2025
1e4eab1
refactor
Feb 3, 2025
f611a15
Merge pull request #236 from bcnmy/feat/7702-compatible-moduleEnableMode
filmakarov Feb 3, 2025
71218d6
Merge pull request #235 from bcnmy/fix/decodeBatch-security
filmakarov Feb 5, 2025
8c0bbfe
reduce size
Feb 20, 2025
87b5df4
builds within size
Feb 20, 2025
9e7efbc
fix verision
Feb 20, 2025
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: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"reason-string": "error",
"avoid-low-level-calls": "off",
"no-inline-assembly": "off",
"no-complex-fallback": "off"
"no-complex-fallback": "off",
"gas-custom-errors": "off"
},
"plugins": ["prettier"]
}
129 changes: 107 additions & 22 deletions contracts/Nexus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,33 @@ import { IERC7484 } from "./interfaces/IERC7484.sol";
import { ModuleManager } from "./base/ModuleManager.sol";
import { ExecutionHelper } from "./base/ExecutionHelper.sol";
import { IValidator } from "./interfaces/modules/IValidator.sol";
import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK, MODULE_TYPE_MULTI, SUPPORTS_ERC7739 } from "./types/Constants.sol";
import { ModeLib, ExecutionMode, ExecType, CallType, CALLTYPE_BATCH, CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "./lib/ModeLib.sol";
import {
MODULE_TYPE_VALIDATOR,
MODULE_TYPE_EXECUTOR,
MODULE_TYPE_FALLBACK,
MODULE_TYPE_HOOK,
MODULE_TYPE_MULTI,
MODULE_TYPE_PREVALIDATION_HOOK_ERC1271,
MODULE_TYPE_PREVALIDATION_HOOK_ERC4337,
SUPPORTS_ERC7739,
VALIDATION_SUCCESS,
VALIDATION_FAILED
} from "./types/Constants.sol";
import {
ModeLib,
ExecutionMode,
ExecType,
CallType,
CALLTYPE_BATCH,
CALLTYPE_SINGLE,
CALLTYPE_DELEGATECALL,
EXECTYPE_DEFAULT,
EXECTYPE_TRY
} from "./lib/ModeLib.sol";
import { NonceLib } from "./lib/NonceLib.sol";
import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol";
import { Initializable } from "./lib/Initializable.sol";
import { EmergencyUninstall } from "./types/DataTypes.sol";

/// @title Nexus - Smart Account
/// @notice This contract integrates various functionalities to handle modular smart accounts compliant with ERC-7579 and ERC-4337 standards.
Expand Down Expand Up @@ -78,16 +101,36 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
PackedUserOperation calldata op,
bytes32 userOpHash,
uint256 missingAccountFunds
) external virtual payPrefund(missingAccountFunds) onlyEntryPoint returns (uint256 validationData) {
)
external
virtual
payPrefund(missingAccountFunds)
onlyEntryPoint
returns (uint256 validationData)
{
address validator = op.nonce.getValidator();
if (op.nonce.isModuleEnableMode()) {
PackedUserOperation memory userOp = op;
userOp.signature = _enableMode(userOpHash, op.signature);
require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator));
(userOpHash, userOp.signature) = _withPreValidationHook(userOpHash, userOp, missingAccountFunds);
validationData = IValidator(validator).validateUserOp(userOp, userOpHash);
} else {
require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator));
validationData = IValidator(validator).validateUserOp(op, userOpHash);
if (_isValidatorInstalled(validator)) {
PackedUserOperation memory userOp = op;
// If the validator is installed, forward the validation task to the validator
(userOpHash, userOp.signature) = _withPreValidationHook(userOpHash, op, missingAccountFunds);
validationData = IValidator(validator).validateUserOp(userOp, userOpHash);
} else {
// If the account is not initialized, check the signature against the account
if (!_isAlreadyInitialized()) {
// Check the userOp signature if the validator is not installed (used for EIP7702)
validationData = _checkSelfSignature(op.signature, userOpHash) ? VALIDATION_SUCCESS : VALIDATION_FAILED;
} else {
// If the account is initialized, revert as the validator is not installed
revert ValidatorNotInstalled(validator);
}
}
}
}

Expand Down Expand Up @@ -117,7 +160,14 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
function executeFromExecutor(
ExecutionMode mode,
bytes calldata executionCalldata
) external payable onlyExecutorModule withHook withRegistry(msg.sender, MODULE_TYPE_EXECUTOR) returns (bytes[] memory returnData) {
)
external
payable
onlyExecutorModule
withHook
withRegistry(msg.sender, MODULE_TYPE_EXECUTOR)
returns (bytes[] memory returnData)
{
(CallType callType, ExecType execType) = mode.decodeBasic();
// check if calltype is batch or single or delegate call
if (callType == CALLTYPE_SINGLE) {
Expand All @@ -140,7 +190,9 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
(bool success, bytes memory innerCallRet) = address(this).delegatecall(callData);
if (success) {
emit Executed(userOp, innerCallRet);
} else revert ExecutionFailed();
} else {
revert ExecutionFailed();
}
}

/// @notice Installs a new module to the smart account.
Expand All @@ -149,11 +201,18 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// - 2 for Executor
/// - 3 for Fallback
/// - 4 for Hook
/// - 8 for 1271 Prevalidation Hook
/// - 9 for 4337 Prevalidation Hook
/// @param module The address of the module to install.
/// @param initData Initialization data for the module.
/// @dev This function can only be called by the EntryPoint or the account itself for security reasons.
/// @dev This function goes through hook checks via withHook modifier through internal function _installModule.
function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable onlyEntryPointOrSelf {
// protection for EIP7702 accounts which were not initialized
// and try to install a validator or executor during the first userOp not via initializeAccount()
if ((moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR) && !_isAlreadyInitialized()) {
_initModuleManager();
}
_installModule(moduleTypeId, module, initData);
emit ModuleInstalled(moduleTypeId, module);
}
Expand All @@ -164,6 +223,8 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// - 2 for Executor
/// - 3 for Fallback
/// - 4 for Hook
/// - 8 for 1271 Prevalidation Hook
/// - 9 for 4337 Prevalidation Hook
/// @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 @@ -177,13 +238,27 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
_uninstallExecutor(module, deInitData);
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
_uninstallFallbackHandler(module, deInitData);
} else if (moduleTypeId == MODULE_TYPE_HOOK) {
_uninstallHook(module, deInitData);
} else if (
moduleTypeId == MODULE_TYPE_HOOK || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337
) {
_uninstallHook(module, moduleTypeId, deInitData);
}
}

function emergencyUninstallHook(address hook, bytes calldata deInitData) external payable onlyEntryPoint {
require(_isModuleInstalled(MODULE_TYPE_HOOK, hook, deInitData), ModuleNotInstalled(MODULE_TYPE_HOOK, hook));
function emergencyUninstallHook(EmergencyUninstall calldata data, bytes calldata signature) external payable {
// Validate the signature
_checkEmergencyUninstallSignature(data, signature);
// Parse uninstall data
(uint256 hookType, address hook, bytes calldata deInitData) = (data.hookType, data.hook, data.deInitData);

// Validate the hook is of a supported type and is installed
require(
hookType == MODULE_TYPE_HOOK || hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337,
UnsupportedModuleType(hookType)
);
require(_isModuleInstalled(hookType, hook, deInitData), ModuleNotInstalled(hookType, hook));

// Get the account storage
AccountStorage storage accountStorage = _getAccountStorage();
uint256 hookTimelock = accountStorage.emergencyUninstallTimelock[hook];

Expand All @@ -198,18 +273,28 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
} else if (block.timestamp >= hookTimelock + _EMERGENCY_TIMELOCK) {
// if the timelock expired, clear it and uninstall the hook
accountStorage.emergencyUninstallTimelock[hook] = 0;
_uninstallHook(hook, deInitData);
emit ModuleUninstalled(MODULE_TYPE_HOOK, hook);
_uninstallHook(hook, hookType, deInitData);
emit ModuleUninstalled(hookType, hook);
} else {
// if the timelock is initiated but not expired, revert
revert EmergencyTimeLockNotExpired();
}
}

/// @notice Initializes the smart account with the specified initialization data.
/// @param initData The initialization data for the smart account.
/// @dev This function can only be called by the account itself or the proxy factory.
/// When a 7702 account is created, the first userOp should contain self-call to initialize the account.
function initializeAccount(bytes calldata initData) external payable virtual {
// Protect this function to only be callable when used with the proxy factory or when
// account calls itself
if (msg.sender != address(this)) {
Initializable.requireInitializable();
}

_initModuleManager();
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
(bool success, ) = bootstrap.delegatecall(bootstrapCall);
(bool success,) = bootstrap.delegatecall(bootstrapCall);

require(success, NexusInitializationFailed());
require(_hasValidators(), NoValidatorInstalled());
Expand All @@ -234,11 +319,12 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
}
}
// else proceed with normal signature verification

// First 20 bytes of data will be validator address and rest of the bytes is complete signature.
address validator = address(bytes20(signature[0:20]));
require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator));
try IValidator(validator).isValidSignatureWithSender(msg.sender, hash, signature[20:]) returns (bytes4 res) {
bytes memory signature_;
(hash, signature_) = _withPreValidationHook(hash, signature[20:]);
try IValidator(validator).isValidSignatureWithSender(msg.sender, hash, signature_) returns (bytes4 res) {
return res;
} catch {
return bytes4(0xffffffff);
Expand Down Expand Up @@ -278,9 +364,8 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
(CallType callType, ExecType execType) = mode.decodeBasic();

// Return true if both the call type and execution type are supported.
return
(callType == CALLTYPE_SINGLE || callType == CALLTYPE_BATCH || callType == CALLTYPE_DELEGATECALL) &&
(execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY);
return (callType == CALLTYPE_SINGLE || callType == CALLTYPE_BATCH || callType == CALLTYPE_DELEGATECALL)
&& (execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY);
}

/// @notice Determines whether a module is installed on the smart account.
Expand Down Expand Up @@ -340,7 +425,7 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// thus the account will proceed with normal signature verification
/// and return 0xffffffff as a result.
function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
bytes4 result;
bytes4 result;
unchecked {
SentinelListLib.SentinelList storage validators = _getAccountStorage().validators;
address next = validators.entries[SENTINEL];
Expand All @@ -358,11 +443,11 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// @dev Ensures that only authorized callers can upgrade the smart contract implementation.
/// This is part of the UUPS (Universal Upgradeable Proxy Standard) pattern.
/// @param newImplementation The address of the new implementation to upgrade to.
function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) onlyEntryPointOrSelf {}
function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) onlyEntryPointOrSelf { }

/// @dev EIP712 domain name and version.
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "Nexus";
version = "1.0.1";
version = "1.2.0";
}
}
2 changes: 1 addition & 1 deletion contracts/base/BaseAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { IBaseAccount } from "../interfaces/base/IBaseAccount.sol";
/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady
contract BaseAccount is IBaseAccount {
/// @notice Identifier for this implementation on the network
string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.0.0";
string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.2.0";

/// @notice The canonical address for the ERC4337 EntryPoint contract, version 0.7.
/// This address is consistent across all supported networks.
Expand Down
Loading
Loading