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 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
bfb4271
changes to sc re default module
Feb 27, 2025
c170055
tests
Feb 27, 2025
3b944d1
default module works
Feb 27, 2025
f5e0e6e
fix foumdry tests
Feb 27, 2025
d189e7f
fix hh tests
Feb 27, 2025
7c8fc67
enable mode + prev hook
Feb 27, 2025
76be984
Merge pull request #245 from bcnmy/feat/epv7/default-validator
filmakarov Feb 27, 2025
ac751b4
events in bootstrapper
Feb 27, 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"]
}
200 changes: 151 additions & 49 deletions contracts/Nexus.sol

Large diffs are not rendered by default.

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
252 changes: 225 additions & 27 deletions contracts/base/ModuleManager.sol

Large diffs are not rendered by default.

54 changes: 23 additions & 31 deletions contracts/factory/K1ValidatorFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ pragma solidity ^0.8.27;
// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy.
// Learn more at https://biconomy.io. For security issues, contact: [email protected]

import { LibClone } from "solady/utils/LibClone.sol";
import { INexus } from "../interfaces/INexus.sol";
import { BootstrapLib } from "../lib/BootstrapLib.sol";
import { NexusBootstrap, BootstrapConfig } from "../utils/NexusBootstrap.sol";
import { Stakeable } from "../common/Stakeable.sol";
import { IERC7484 } from "../interfaces/IERC7484.sol";
import { ProxyLib } from "../lib/ProxyLib.sol";

/// @title K1ValidatorFactory for Nexus Account
/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a K1 validator.
Expand Down Expand Up @@ -47,21 +46,12 @@ contract K1ValidatorFactory is Stakeable {
/// @notice Error thrown when a zero address is provided for the implementation, K1 validator, or bootstrapper.
error ZeroAddressNotAllowed();

/// @notice Error thrown when an inner call fails.
error InnerCallFailed();

/// @notice Constructor to set the immutable variables.
/// @param implementation The address of the Nexus implementation to be used for all deployments.
/// @param factoryOwner The address of the factory owner.
/// @param k1Validator 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.
constructor(
address implementation,
address factoryOwner,
address k1Validator,
NexusBootstrap bootstrapper,
IERC7484 registry
) Stakeable(factoryOwner) {
constructor(address implementation, address factoryOwner, address k1Validator, NexusBootstrap bootstrapper, IERC7484 registry) Stakeable(factoryOwner) {
require(
!(implementation == address(0) || k1Validator == address(0) || address(bootstrapper) == address(0) || factoryOwner == address(0)),
ZeroAddressNotAllowed()
Expand All @@ -78,28 +68,20 @@ contract K1ValidatorFactory is Stakeable {
/// @param attesters The list of attesters for the Nexus.
/// @param threshold The threshold for the Nexus.
/// @return The address of the newly created Nexus.
function createAccount(
address eoaOwner,
uint256 index,
address[] calldata attesters,
uint8 threshold
) external payable returns (address payable) {
// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold));

// Deploy the Nexus contract using the computed salt
(bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt);
function createAccount(address eoaOwner, uint256 index, address[] calldata attesters, uint8 threshold) external payable returns (address payable) {
// Compute the salt for deterministic deployment
bytes32 salt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold));

// Create the validator configuration using the NexusBootstrap library
BootstrapConfig memory validator = BootstrapLib.createSingleConfig(K1_VALIDATOR, abi.encodePacked(eoaOwner));
bytes memory initData = BOOTSTRAPPER.getInitNexusWithSingleValidatorCalldata(validator, REGISTRY, attesters, threshold);

// Initialize the account if it was not already deployed
// Deploy the Nexus account using the ProxyLib
(bool alreadyDeployed, address payable account) = ProxyLib.deployProxy(ACCOUNT_IMPLEMENTATION, salt, initData);
if (!alreadyDeployed) {
INexus(account).initializeAccount(initData);
emit AccountCreated(account, eoaOwner, index);
}
return payable(account);
return account;
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
Expand All @@ -113,11 +95,21 @@ contract K1ValidatorFactory is Stakeable {
uint256 index,
address[] calldata attesters,
uint8 threshold
) external view returns (address payable expectedAddress) {
// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold));
)
external
view
returns (address payable expectedAddress)
{
// Compute the salt for deterministic deployment
bytes32 salt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold));

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

// Get the initialization data for the Nexus account
bytes memory initData = BOOTSTRAPPER.getInitNexusWithSingleValidatorCalldata(validator, REGISTRY, attesters, threshold);

// Predict the deterministic address using the LibClone library
expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this)));
// Compute the predicted address using the ProxyLib
return ProxyLib.predictProxyAddress(ACCOUNT_IMPLEMENTATION, salt, initData);
}
}
20 changes: 7 additions & 13 deletions contracts/factory/NexusAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pragma solidity ^0.8.27;
// ──────────────────────────────────────────────────────────────────────────────
// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy.
// Learn more at https://biconomy.io. To report security issues, please contact us at: [email protected]
import { LibClone } from "solady/utils/LibClone.sol";
import { INexus } from "../interfaces/INexus.sol";

import { Stakeable } from "../common/Stakeable.sol";
import { INexusFactory } from "../interfaces/factory/INexusFactory.sol";
import { ProxyLib } from "../lib/ProxyLib.sol";

/// @title Nexus Account Factory
/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a factory pattern.
Expand Down Expand Up @@ -42,26 +42,20 @@ contract NexusAccountFactory is Stakeable, INexusFactory {
/// @param salt Unique salt for the Smart Account creation.
/// @return The address of the newly created Nexus account.
function createAccount(bytes calldata initData, bytes32 salt) external payable override returns (address payable) {
// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt));

// Deploy the account using the deterministic address
(bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt);

// Deploy the Nexus account using the ProxyLib
(bool alreadyDeployed, address payable account) = ProxyLib.deployProxy(ACCOUNT_IMPLEMENTATION, salt, initData);
if (!alreadyDeployed) {
INexus(account).initializeAccount(initData);
emit AccountCreated(account, initData, salt);
}
return payable(account);
return account;
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @param initData - Initialization data to be called on the new Smart Account.
/// @param salt - Unique salt for the Smart Account creation.
/// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used.
function computeAccountAddress(bytes calldata initData, bytes32 salt) external view override returns (address payable expectedAddress) {
// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt));
expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this)));
// Return the expected address of the Nexus account using the provided initialization data and salt
return ProxyLib.predictProxyAddress(ACCOUNT_IMPLEMENTATION, salt, initData);
}
}
31 changes: 7 additions & 24 deletions contracts/factory/RegistryFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ pragma solidity ^0.8.27;
// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy.
// Learn more at https://biconomy.io. To report security issues, please contact us at: [email protected]

import { LibClone } from "solady/utils/LibClone.sol";
import { LibSort } from "solady/utils/LibSort.sol";
import { BytesLib } from "../lib/BytesLib.sol";
import { INexus } from "../interfaces/INexus.sol";
import { BootstrapConfig } from "../utils/NexusBootstrap.sol";
import { Stakeable } from "../common/Stakeable.sol";
import { IERC7484 } from "../interfaces/IERC7484.sol";
import { INexusFactory } from "../interfaces/factory/INexusFactory.sol";
import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK } from "../types/Constants.sol";
import { ProxyLib } from "../lib/ProxyLib.sol";

/// @title RegistryFactory
/// @notice Factory for creating Nexus accounts with whitelisted modules. Ensures compliance with ERC-7579 and ERC-4337 standards.
Expand Down Expand Up @@ -113,15 +112,8 @@ contract RegistryFactory is Stakeable, INexusFactory {
// Ensure that the initData is structured for the expected NexusBootstrap.initNexus or similar method.
// This step is crucial for ensuring the proper initialization of the Nexus smart account.
bytes memory innerData = BytesLib.slice(callData, 4, callData.length - 4);
(
BootstrapConfig[] memory validators,
BootstrapConfig[] memory executors,
BootstrapConfig memory hook,
BootstrapConfig[] memory fallbacks,
,
,

) = abi.decode(innerData, (BootstrapConfig[], BootstrapConfig[], BootstrapConfig, BootstrapConfig[], address, address[], uint8));
(BootstrapConfig[] memory validators, BootstrapConfig[] memory executors, BootstrapConfig memory hook, BootstrapConfig[] memory fallbacks,,,) =
abi.decode(innerData, (BootstrapConfig[], BootstrapConfig[], BootstrapConfig, BootstrapConfig[], address, address[], uint8));

// Ensure that all specified modules are whitelisted and allowed for the account.
for (uint256 i = 0; i < validators.length; i++) {
Expand All @@ -138,29 +130,20 @@ contract RegistryFactory is Stakeable, INexusFactory {
require(_isModuleAllowed(fallbacks[i].module, MODULE_TYPE_FALLBACK), ModuleNotWhitelisted(fallbacks[i].module));
}

// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt));

// Deploy the account using the deterministic address
(bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt);

// Deploy the Nexus account using the ProxyLib
(bool alreadyDeployed, address payable account) = ProxyLib.deployProxy(ACCOUNT_IMPLEMENTATION, salt, initData);
if (!alreadyDeployed) {
// Initialize the Nexus account using the provided initialization data
INexus(account).initializeAccount(initData);
emit AccountCreated(account, initData, salt);
}

return payable(account);
return account;
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @param initData - Initialization data to be called on the new Smart Account.
/// @param salt - Unique salt for the Smart Account creation.
/// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used.
function computeAccountAddress(bytes calldata initData, bytes32 salt) external view override returns (address payable expectedAddress) {
// Compute the actual salt for deterministic deployment
bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt));
expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this)));
return ProxyLib.predictProxyAddress(ACCOUNT_IMPLEMENTATION, salt, initData);
}

function getAttesters() public view returns (address[] memory) {
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/INexusEventsAndErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ interface INexusEventsAndErrors {

/// @notice Error thrown when attempted to emergency-uninstall a hook
error EmergencyTimeLockNotExpired();

/// @notice Error thrown when attempted to upgrade an ERC7702 account via UUPS proxy upgrade mechanism
error ERC7702AccountCannotBeUpgradedThisWay();

/// @notice Error thrown when the provided initData is invalid.
error InvalidInitData();
}
12 changes: 12 additions & 0 deletions contracts/interfaces/base/IModuleManagerEventsAndErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ interface IModuleManagerEventsAndErrors {
/// @dev Thrown when there is an attempt to install a hook while another is already installed.
error HookAlreadyInstalled(address currentHook);

/// @dev Thrown when there is an attempt to install a PreValidationHook while another is already installed.
error PrevalidationHookAlreadyInstalled(address currentPreValidationHook);

/// @dev Thrown when there is an attempt to install a fallback handler for a selector already having one.
error FallbackAlreadyInstalledForSelector(bytes4 selector);

Expand All @@ -84,6 +87,12 @@ interface IModuleManagerEventsAndErrors {
/// @dev Thrown when unable to validate Module Enable Mode signature
error EnableModeSigError();

/// @dev Thrown when unable to validate Emergency Uninstall signature
error EmergencyUninstallSigError();

/// @notice Error thrown when an invalid nonce is used
error InvalidNonce();

/// Error thrown when account installs/uninstalls module with mismatched input `moduleTypeId`
error MismatchModuleTypeId(uint256 moduleTypeId);

Expand All @@ -96,4 +105,7 @@ interface IModuleManagerEventsAndErrors {
/// @notice Error thrown when an execution with an unsupported CallType was made.
/// @param callType The unsupported call type.
error UnsupportedCallType(CallType callType);

/// @notice Error thrown when the default validator is already installed.
error DefaultValidatorAlreadyInstalled();
}
29 changes: 21 additions & 8 deletions contracts/interfaces/base/IStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pragma solidity ^0.8.27;
// Learn more at https://biconomy.io. To report security issues, please contact us at: [email protected]

import { SentinelListLib } from "sentinellist/SentinelList.sol";

import { IPreValidationHookERC1271, IPreValidationHookERC4337 } from "../modules/IPreValidationHook.sol";
import { IHook } from "../modules/IHook.sol";
import { CallType } from "../../lib/ModeLib.sol";

Expand All @@ -31,16 +31,29 @@ import { CallType } from "../../lib/ModeLib.sol";
interface IStorage {
/// @notice Struct storing validators and executors using Sentinel lists, and fallback handlers via mapping.
struct AccountStorage {
SentinelListLib.SentinelList validators; ///< List of validators, initialized upon contract deployment.
SentinelListLib.SentinelList executors; ///< List of executors, similarly initialized.
mapping(bytes4 => FallbackHandler) fallbacks; ///< Mapping of selectors to their respective fallback handlers.
IHook hook; ///< Current hook module associated with this account.
mapping(address hook => uint256) emergencyUninstallTimelock; ///< Mapping of hooks to requested timelocks.
///< List of validators, initialized upon contract deployment.
SentinelListLib.SentinelList validators;
///< List of executors, similarly initialized.
SentinelListLib.SentinelList executors;
///< Mapping of selectors to their respective fallback handlers.
mapping(bytes4 => FallbackHandler) fallbacks;
///< Current hook module associated with this account.
IHook hook;
///< Mapping of hooks to requested timelocks.
mapping(address hook => uint256) emergencyUninstallTimelock;
///< PreValidation hook for validateUserOp
IPreValidationHookERC4337 preValidationHookERC4337;
///< PreValidation hook for isValidSignature
IPreValidationHookERC1271 preValidationHookERC1271;
///< Mapping of used nonces for replay protection.
mapping(uint256 => bool) nonces;
}

/// @notice Defines a fallback handler with an associated handler address and a call type.
struct FallbackHandler {
address handler; ///< The address of the fallback function handler.
CallType calltype; ///< The type of call this handler supports (e.g., static or call).
///< The address of the fallback function handler.
address handler;
///< The type of call this handler supports (e.g., static or call).
CallType calltype;
}
}
38 changes: 38 additions & 0 deletions contracts/interfaces/modules/IPreValidationHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
import { IModule } from "./IModule.sol";

/// @title Nexus - IPreValidationHookERC1271 Interface
/// @notice Defines the interface for ERC-1271 pre-validation hooks
interface IPreValidationHookERC1271 is IModule {
/// @notice Performs pre-validation checks for isValidSignature
/// @dev This method is called before the validation of a signature on a validator within isValidSignature
/// @param sender The original sender of the request
/// @param hash The hash of signed data
/// @param data The signature data to validate
/// @return hookHash The hash after applying the pre-validation hook
/// @return hookSignature The signature after applying the pre-validation hook
function preValidationHookERC1271(address sender, bytes32 hash, bytes calldata data) external view returns (bytes32 hookHash, bytes memory hookSignature);
}

/// @title Nexus - IPreValidationHookERC4337 Interface
/// @notice Defines the interface for ERC-4337 pre-validation hooks
interface IPreValidationHookERC4337 is IModule {
/// @notice Performs pre-validation checks for user operations
/// @dev This method is called before the validation of a user operation
/// @param userOp The user operation to be validated
/// @param missingAccountFunds The amount of funds missing in the account
/// @param userOpHash The hash of the user operation data
/// @return hookHash The hash after applying the pre-validation hook
/// @return hookSignature The signature after applying the pre-validation hook
function preValidationHookERC4337(
PackedUserOperation calldata userOp,
uint256 missingAccountFunds,
bytes32 userOpHash
)
external
view
returns (bytes32 hookHash, bytes memory hookSignature);
}
Loading
Loading