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

Feat/sma 809 potential account factories #73

Merged
merged 48 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
91d778b
rename getCounterfactual + add util Boostrap.sol + stakemanager to st…
livingrockrises May 14, 2024
0c31a97
fix script
livingrockrises May 14, 2024
a7870d2
restructure
livingrockrises May 14, 2024
a8f4816
add meta factory
livingrockrises May 15, 2024
48ce072
preserve old factory
livingrockrises May 15, 2024
27ee508
add tests validate user operation from entry point
Aboudjem May 16, 2024
3d8988a
add accesscontrol+unauthorized access tests
Aboudjem May 16, 2024
c16674a
refactor: add InvalidSignature error to EventsAndErrors.sol
Aboudjem May 16, 2024
72c1f48
add tests cases + fix wrong asserts
Aboudjem May 16, 2024
0219d86
chore: Update deployment account funding amount in test
Aboudjem May 16, 2024
5787c54
Remove unnecessary code in TestERC4337Account_ValidateUserOp.t.sol
Aboudjem May 16, 2024
16395f1
add test for validation of user operation nonce
Aboudjem May 16, 2024
1b86b42
Merge branch 'dev' of https://github.com/bcnmy/erc7579-modular-smart-…
Aboudjem May 16, 2024
fcebab3
Merge branch 'dev' of https://github.com/bcnmy/erc7579-modular-smart-…
Aboudjem May 16, 2024
fa87c90
Merge branch 'dev' of https://github.com/bcnmy/erc7579-modular-smart-…
Aboudjem May 16, 2024
b1232a3
Merge branch 'dev' into feat/SMA-809-potential-account-factory
Aboudjem May 16, 2024
a3144cd
update factory, add more factories and foundry test cases
livingrockrises May 19, 2024
25e167a
Merge branch 'dev' into feat/SMA-809-potential-account-factory
livingrockrises May 19, 2024
92ebf11
refactor meta factory
livingrockrises May 20, 2024
c3c132e
refactor + natspecs + logs
livingrockrises May 21, 2024
796f2f2
update factories + refactor + working foundry tests + update hardhat …
livingrockrises May 21, 2024
e3f8ea8
refactor
livingrockrises May 21, 2024
556e045
Merge branch 'dev' into feat/SMA-809-potential-account-factory
livingrockrises May 21, 2024
693e8df
resolve conflicts with helper contract
livingrockrises May 21, 2024
12067c6
refactor: update imports in TestHelper.t.sol, remove unused and reorder
Aboudjem May 21, 2024
3ee489c
refactor: update Stakeable contract with natspecs
Aboudjem May 21, 2024
52e0a3d
refactor: update IStakeable interface with natspecs
Aboudjem May 21, 2024
b018f8c
refactor: rename to test_DeployAccount_CreateAccount function + natspecs
Aboudjem May 21, 2024
5ffee46
refactor: update TestAccountFactory_Deployments contract with natspecs
Aboudjem May 21, 2024
4ce97ad
typo fix + update Bootstrap functions to comply with styleguide
Aboudjem May 21, 2024
81f9aa5
refactor: Update natspecs and refactor names
Aboudjem May 21, 2024
4cda43a
refactor: Update K1ValidatorFactory to use correct function name for …
Aboudjem May 21, 2024
9ab7b48
refactor: Update ArbitrumSmartAccountUpgradeTest.t.sol to use updated…
Aboudjem May 21, 2024
c93a009
refactor: Update func names, remove unused and reorder
Aboudjem May 21, 2024
b0f5f69
refactor: Update BootstrapUtil.sol to comply with styleguide
Aboudjem May 21, 2024
cbd9e18
lint fix
Aboudjem May 21, 2024
b48cd26
refactor: Update Stakeable contract with constructor parameter name c…
Aboudjem May 21, 2024
9568092
lint fix
Aboudjem May 21, 2024
065887b
refactor: Update K1ValidatorFactory to use correct function name for …
Aboudjem May 21, 2024
af17db6
fix typo FactoryNotWhotelisted to FactoryNotWhitelisted in
Aboudjem May 21, 2024
296c3fd
gas optimization, remove memory var + named returns
Aboudjem May 21, 2024
f2eff9c
refactor: Update DEFAULT_ARBITRUM_RPC_URL in ArbitrumSettings contract
Aboudjem May 22, 2024
3bc5eae
Update Stakeable Natspec
Aboudjem May 22, 2024
aaa2574
refactor: Update IStakeable.sol with improved natspec and consistency
Aboudjem May 22, 2024
bfe499b
Merge pull request #79 from bcnmy/chore/account-factory-refactoring
livingrockrises May 22, 2024
5f2d353
fix whitelist factory test
livingrockrises May 24, 2024
00216fa
refactor + act on PR
livingrockrises May 24, 2024
0efdb79
lint fixes
livingrockrises May 26, 2024
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
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"imports-on-top": "error",
"ordering": "error",
"visibility-modifier-order": "error",
"code-complexity": ["error", 8],
"code-complexity": ["error", 9],
"function-max-lines": ["error", 80],
"max-line-length": ["error", 150],
"no-empty-blocks": "off",
Expand Down
11 changes: 4 additions & 7 deletions contracts/Nexus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,12 @@ contract Nexus is INexus, EIP712, BaseAccount, ExecutionHelper, ModuleManager, U
else revert UnsupportedModuleType(moduleTypeId);
}

/// @notice Initializes the smart account with a validator.
/// @param firstValidator The first validator to install upon initialization.
/// @param initData Initialization data for setting up the validator.
/// @dev This function sets the foundation for the smart account's operational logic and security.
/// @notice Implementation details may be adjusted based on factory requirements.
function initialize(address firstValidator, bytes calldata initData) external payable virtual {
function initializeAccount(bytes calldata initData) external payable virtual {
// checks if already initialized and reverts before setting the state to initialized
_initModuleManager();
_installValidator(firstValidator, initData);
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
(bool success, ) = bootstrap.delegatecall(bootstrapCall);
if (!success) revert NexusInitializationFailed();
}

/// @notice Validates a signature according to ERC-1271 standards.
Expand Down
52 changes: 52 additions & 0 deletions contracts/common/Stakeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// ──────────────────────────────────────────────────────────────────────────────
// _ __ _ __
// / | / /__ | |/ /_ _______
// / |/ / _ \| / / / / ___/
// / /| / __/ / /_/ (__ )
// /_/ |_/\___/_/|_\__,_/____/
//
// ──────────────────────────────────────────────────────────────────────────────
// Nexus: A suite of contracts for Modular Smart Account compliant with ERC-7579 and ERC-4337, developed by Biconomy.
// Learn more at https://biconomy.io. For security issues, contact: [email protected]

import { Ownable } from "solady/src/auth/Ownable.sol";
import { IEntryPoint } from "account-abstraction/contracts/interfaces/IEntryPoint.sol";

import { IStakeable } from "../interfaces/common/IStakeable.sol";

/// @title Stakeable Entity
/// @notice Provides functionality to stake, unlock, and withdraw Ether on an EntryPoint.
contract Stakeable is Ownable, IStakeable {
constructor(address newOwner) {
_setOwner(newOwner);
}

/// @notice Stakes a certain amount of Ether on an EntryPoint.
/// @dev The contract should have enough Ether to cover the stake.
/// @param epAddress The address of the EntryPoint where the stake is added.
/// @param unstakeDelaySec The delay in seconds before the stake can be unlocked.
function addStake(address epAddress, uint32 unstakeDelaySec) external payable onlyOwner {
require(epAddress != address(0), "Invalid EP address");
IEntryPoint(epAddress).addStake{ value: msg.value }(unstakeDelaySec);
}

/// @notice Unlocks the stake on an EntryPoint.
/// @dev This starts the unstaking delay after which funds can be withdrawn.
/// @param epAddress The address of the EntryPoint from which the stake is to be unlocked.
function unlockStake(address epAddress) external onlyOwner {
require(epAddress != address(0), "Invalid EP address");
IEntryPoint(epAddress).unlockStake();
}

/// @notice Withdraws the stake from an EntryPoint to a specified address.
/// @dev This can only be done after the unstaking delay has passed since the unlock.
/// @param epAddress The address of the EntryPoint where the stake is withdrawn from.
/// @param withdrawAddress The address to receive the withdrawn stake.
function withdrawStake(address epAddress, address payable withdrawAddress) external onlyOwner {
require(epAddress != address(0), "Invalid EP address");
IEntryPoint(epAddress).withdrawStake(withdrawAddress);
}
}
82 changes: 82 additions & 0 deletions contracts/factory/BiconomyMetaFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// ──────────────────────────────────────────────────────────────────────────────
// _ __ _ __
// / | / /__ | |/ /_ _______
// / |/ / _ \| / / / / ___/
// / /| / __/ / /_/ (__ )
// /_/ |_/\___/_/|_\__,_/____/
//
// ──────────────────────────────────────────────────────────────────────────────
// Nexus: A suite of contracts for Modular Smart Account compliant with ERC-7579 and ERC-4337, developed by Biconomy.
// Learn more at https://biconomy.io. For security issues, contact: [email protected]

import { Stakeable } from "../common/Stakeable.sol";

// can stake
// can whitelist factories
// deployAccount with chosen factory and required data for that facotry

/// @title Nexus - BiconomyMetaFactory
/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a factory pattern.
/// @dev Utilizes the `Stakeable` for staking requirements
/// This contract serves as a 'Meta' factory to generate new Nexus instances using specific chosen and approved factories.
/// @author @livingrockrises | Biconomy | [email protected]
contract BiconomyMetaFactory is Stakeable {
/// @dev Stores the factory addresses that are whitelisted.
mapping(address => bool) public factoryWhitelist;

/// @dev Throws when the factory is not whitelisted.
error FactoryNotWhitelisted();

constructor(address owner) Stakeable(owner) {}

/// @notice Adds an address to the factory whitelist.
/// @param factory The address to be whitelisted.
function addFactoryToWhitelist(address factory) external onlyOwner {
factoryWhitelist[factory] = true;
}

/// @notice Removes an address from the factory whitelist.
/// @param factory The address to be removed from the whitelist.
function removeFactoryFromWhitelist(address factory) external onlyOwner {
factoryWhitelist[factory] = false;
}

// Note: deploy using only one of the whitelisted factories
livingrockrises marked this conversation as resolved.
Show resolved Hide resolved
// these factories could possibly enshrine specific module/s
// factory should know how to decode this factoryData

/// @notice Deploys a new Nexus with a specific factory and initialization data.
/// @dev factoryData is the encoded data for the method to be called on the Factory
/// @dev factoryData is posted on the factory using factory.call(factoryData)
/// instead of calling a specific method always to allow more freedom.
/// factory should know how to decode this factoryData
/// @notice These factories could possibly enshrine specific module/s to avoid arbitary execution and prevent griefing.
/// @notice Another benefit of this pattern is that the factory can be upgraded without changing this contract.
/// @param factory The address of the factory to be used for deployment.
/// @param factoryData The encoded data for the method to be called on the Factory.
function deployWithFactory(address factory, bytes calldata factoryData) external payable returns (address payable createdAccount) {
if (!factoryWhitelist[address(factory)]) {
revert FactoryNotWhitelisted();
}
(bool success, bytes memory returnData) = factory.call(factoryData);

// if needed to make success check add this here
// Check if the call was successful
require(success, "Call to deployWithFactory failed");

// If needed to return created address mload returnData
// Decode the returned address
assembly {
createdAccount := mload(add(returnData, 0x20))
}
}

/// @notice Checks if an address is whitelisted.
/// @param factory The address to check.
function isWhitelisted(address factory) public view returns (bool) {
return factoryWhitelist[factory];
}
}
97 changes: 97 additions & 0 deletions contracts/factory/K1ValidatorFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// ──────────────────────────────────────────────────────────────────────────────
// _ __ _ __
// / | / /__ | |/ /_ _______
// / |/ / _ \| / / / / ___/
// / /| / __/ / /_/ (__ )
// /_/ |_/\___/_/|_\__,_/____/
//
// ──────────────────────────────────────────────────────────────────────────────
// Nexus: A suite of contracts for Modular Smart Account 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/src/utils/LibClone.sol";
import { INexus } from "../interfaces/INexus.sol";
import { BootstrapUtil } from "../utils/BootstrapUtil.sol";
import { Bootstrap, BootstrapConfig } from "../utils/Bootstrap.sol";
import { Stakeable } from "../common/Stakeable.sol";

/// @title Nexus - K1ValidatorFactory for Nexus account
contract K1ValidatorFactory is BootstrapUtil, Stakeable {
/// @notice Stores the implementation contract address used to create new Nexus instances.
/// @dev This address is set once upon deployment and cannot be changed afterwards.
address public immutable ACCOUNT_IMPLEMENTATION;

/// @notice Stores the K1 Validator module address
/// @dev This address is set once upon deployment and cannot be changed afterwards.
address public immutable K1_VALIDATOR;

/// @notice Stores the K1 Validator module address
/// @dev This address is set once upon deployment and cannot be changed afterwards.
Bootstrap public immutable BOOTSTRAPPER;

/// @notice Emitted when a new Smart Account is created, capturing the account details and associated module configurations.
event AccountCreated(address indexed account, address indexed owner, uint256 indexed index);

/// @notice Constructor to set the immutable variables.
/// @param implementation The address of the Nexus implementation to be used for all deployments.
/// @param k1Validator The address of the K1 Validator module to be used for all deployments.
/// @param bootstrapper The address of the Boostrapper module to be used for all deployments.
constructor(address factoryOwner, address implementation, address k1Validator, Bootstrap bootstrapper) Stakeable(factoryOwner) {
ACCOUNT_IMPLEMENTATION = implementation;
K1_VALIDATOR = k1Validator;
BOOTSTRAPPER = bootstrapper;
}

/// @notice Creates a new Nexus with a specific validator and initialization data.
/// @param eoaOwner The address of the EOA owner of the Nexus.
/// @param index The index of the Nexus.
/// @return The address of the newly created Nexus.
/// @dev Deploys a new Nexus using a deterministic address based on the input parameters.
function createAccount(address eoaOwner, uint256 index) external payable returns (address payable) {
(index);
bytes32 actualSalt;
assembly {
let ptr := mload(0x40)
let calldataLength := sub(calldatasize(), 0x04)
mstore(0x40, add(ptr, calldataLength))
calldatacopy(ptr, 0x04, calldataLength)
actualSalt := keccak256(ptr, calldataLength)
}
// Review: if salt should include K1 Validator address as well
// actualSalt = keccak256(abi.encodePacked(actualSalt, K1_VALIDATOR));

(bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt);
BootstrapConfig memory validator = makeBootstrapConfigSingle(K1_VALIDATOR, abi.encodePacked(eoaOwner));
bytes memory initData = BOOTSTRAPPER.getInitNexusWithSingleValidatorCalldata(validator);

if (!alreadyDeployed) {
INexus(account).initializeAccount(initData);
emit AccountCreated(account, eoaOwner, index);
}
return payable(account);
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @param eoaOwner The address of the EOA owner of the Nexus.
/// @param index The index of the Nexus.
/// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used.
/// @dev This function allows for address calculation without deploying the Nexus.
function computeAccountAddress(address eoaOwner, uint256 index) external view returns (address payable expectedAddress) {
(eoaOwner, index);
bytes32 actualSalt;

assembly {
let ptr := mload(0x40)
let calldataLength := sub(calldatasize(), 0x04)
mstore(0x40, add(ptr, calldataLength))
calldatacopy(ptr, 0x04, calldataLength)
actualSalt := keccak256(ptr, calldataLength)
}

// Review: if salt should include K1 Validator address as well
expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this)));
}
}
130 changes: 130 additions & 0 deletions contracts/factory/ModuleWhitelistFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// ──────────────────────────────────────────────────────────────────────────────
// _ __ _ __
// / | / /__ | |/ /_ _______
// / |/ / _ \| / / / / ___/
// / /| / __/ / /_/ (__ )
// /_/ |_/\___/_/|_\__,_/____/
//
// ──────────────────────────────────────────────────────────────────────────────
// Nexus: A suite of contracts for Modular Smart Account 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/src/utils/LibClone.sol";
import { Stakeable } from "../common/Stakeable.sol";
import { INexus } from "../interfaces/INexus.sol";
import { BootstrapConfig } from "../utils/Bootstrap.sol";
import { BytesLib } from "../lib/BytesLib.sol";

/// @title Nexus - ModuleWhitelistFactory for Nexus account
contract ModuleWhitelistFactory is Stakeable {
/// @notice Stores the implementation contract address used to create new Nexus instances.
/// @dev This address is set once upon deployment and cannot be changed afterwards.
address public immutable ACCOUNT_IMPLEMENTATION;

/// @notice Stores the module addresses that are whitelisted.
mapping(address => bool) public moduleWhitelist;

/// @notice Emitted when a new Smart Account is created, capturing initData and salt used to deploy the account.
event AccountCreated(address indexed account, bytes indexed initData, bytes32 indexed salt);

/// @notice Thorwn when the module is not whitelisted
error ModuleNotWhitelisted(address module);

/// @notice Constructor to set the smart account implementation address.
/// @param implementation The address of the Nexus implementation to be used for all deployments.
constructor(address factoryOwner, address implementation) Stakeable(factoryOwner) {
ACCOUNT_IMPLEMENTATION = implementation;
}

/// @notice Adds an address to the module whitelist.
/// @param module The address to be whitelisted.
function addModuleToWhitelist(address module) external onlyOwner {
moduleWhitelist[module] = true;
}

/// @notice Removes an address from the module whitelist.
/// @param module The address to be removed from the whitelist.
function removeModuleFromWhitelist(address module) external onlyOwner {
moduleWhitelist[module] = false;
}

function createAccount(bytes calldata initData, bytes32 salt) external payable returns (address payable) {
// Decode the initData to extract the call target and call data
(, bytes memory callData) = abi.decode(initData, (address, bytes));

// Extract the inner data by removing the first 4 bytes (the function selector)
bytes memory innerData = BytesLib.slice(callData, 4, callData.length - 4);

// Decode the call data to extract the parameters passed to initNexus
// Review if we should verify calldata[0:4] against the function selector of initNexus
(
BootstrapConfig[] memory validators,
BootstrapConfig[] memory executors,
BootstrapConfig memory hook,
BootstrapConfig[] memory fallbacks
) = abi.decode(innerData, (BootstrapConfig[], BootstrapConfig[], BootstrapConfig, BootstrapConfig[]));

for (uint256 i = 0; i < validators.length; i++) {
if (!isWhitelisted(validators[i].module)) {
revert ModuleNotWhitelisted(validators[i].module);
}
}

for (uint256 i = 0; i < executors.length; i++) {
if (!isWhitelisted(executors[i].module)) {
revert ModuleNotWhitelisted(executors[i].module);
}
}

if (!isWhitelisted(hook.module)) {
revert ModuleNotWhitelisted(hook.module);
}

for (uint256 i = 0; i < fallbacks.length; i++) {
if (!isWhitelisted(fallbacks[i].module)) {
revert ModuleNotWhitelisted(fallbacks[i].module);
}
}

bytes32 actualSalt;
assembly {
let ptr := mload(0x40)
let calldataLength := sub(calldatasize(), 0x04)
mstore(0x40, add(ptr, calldataLength))
calldatacopy(ptr, 0x04, calldataLength)
actualSalt := keccak256(ptr, calldataLength)
}

(bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt);

if (!alreadyDeployed) {
INexus(account).initializeAccount(initData);
emit AccountCreated(account, initData, salt);
}
return payable(account);
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @dev This function allows for address calculation without deploying the Nexus.
function computeAccountAddress(bytes calldata initData, bytes32 salt) external view returns (address payable expectedAddress) {
(initData, salt);
bytes32 actualSalt;
assembly {
let ptr := mload(0x40)
let calldataLength := sub(calldatasize(), 0x04)
mstore(0x40, add(ptr, calldataLength))
calldatacopy(ptr, 0x04, calldataLength)
actualSalt := keccak256(ptr, calldataLength)
}
expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this)));
}

/// @notice Checks if an address is whitelisted.
/// @param module The address to check.
function isWhitelisted(address module) public view returns (bool) {
return moduleWhitelist[module];
}
}
Loading
Loading