Skip to content

Commit

Permalink
feat: use LinkedListSetLib and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jaypaik committed Mar 10, 2024
1 parent 6e25bcb commit d60557c
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 178 deletions.
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
[submodule "lib/modular-account"]
path = lib/modular-account
url = https://github.com/alchemyplatform/modular-account
branch = v1.0.x
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@openzeppelin/=lib/openzeppelin-contracts/
account-abstraction/=lib/account-abstraction/contracts/
modular-account/=lib/modular-account/src/
224 changes: 82 additions & 142 deletions src/MultiOwnerLightAccount.sol

Large diffs are not rendered by default.

92 changes: 57 additions & 35 deletions src/MultiOwnerLightAccountFactory.sol
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";

import {MultiOwnerLightAccount} from "./MultiOwnerLightAccount.sol";

/**
* @title A factory contract for MultiOwnerLightAccount
* @dev A UserOperations "initCode" holds the address of the factory, and a method call (to createAccount, in this sample factory).
* The factory's createAccount returns the target account address even if it is already installed.
* This way, the entryPoint.getSenderAddress() can be called either before or after the account is created.
*/
/// @title A factory contract for MultiOwnerLightAccount.
/// @dev A UserOperations "initCode" holds the address of the factory, and a method call (`createAccount` or
/// `createAccountSingle`). The factory returns the target account address even if it is already deployed. This way,
/// `entryPoint.getSenderAddress()` can be called either before or after the account is created.
contract MultiOwnerLightAccountFactory {
uint256 internal constant _MAX_OWNERS_ON_CREATION = 100;
MultiOwnerLightAccount public immutable accountImplementation;

constructor(IEntryPoint _entryPoint) {
accountImplementation = new MultiOwnerLightAccount(_entryPoint);
error InvalidOwners();
error OwnersLimitExceeded();

constructor(IEntryPoint entryPoint) {
accountImplementation = new MultiOwnerLightAccount(entryPoint);
}

/**
* @notice Create an account, and return its address.
* Returns the address even if the account is already deployed.
* @dev During UserOperation execution, this method is called only if the account is not deployed.
* This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation.
* @param owners The owners of the account to be created
* @param salt A salt, which can be changed to create multiple accounts with the same owner
* @return ret The address of either the newly deployed account or an existing account with this owner and salt
*/
/// @notice Create an account, and return its address. Returns the address even if the account is already deployed.
/// @dev During UserOperation execution, this method is called only if the account is not deployed. This method
/// returns an existing account address so that `entryPoint.getSenderAddress()` would work even after account
/// creation.
/// @param owners The owners of the account to be created.
/// @param salt A salt, which can be changed to create multiple accounts with the same owners.
/// @return ret The address of either the newly deployed account or an existing account with these owners and salt.
function createAccount(address[] calldata owners, uint256 salt) public returns (MultiOwnerLightAccount ret) {
address addr = getAddress(owners, salt);
uint256 codeSize = addr.code.length;
Expand All @@ -45,16 +44,12 @@ contract MultiOwnerLightAccountFactory {
);
}

/**
* @notice Create an account, and return its address.
* Returns the address even if the account is already deployed.
* @dev During UserOperation execution, this method is called only if the account is not deployed.
* This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation.
* @param owner The owner of the account to be created
* @param salt A salt, which can be changed to create multiple accounts with the same owner
* @return ret The address of either the newly deployed account or an existing account with this owner and salt
*/
function createAccount(address owner, uint256 salt) public returns (MultiOwnerLightAccount ret) {
/// @notice Create an account, and return its address. Returns the address even if the account is already deployed.
/// @dev This method uses less calldata than `createAccount` when creating accounts with a single initial owner.
/// @param owner The owner of the account to be created.
/// @param salt A salt, which can be changed to create multiple accounts with the same owner.
/// @return ret The address of either the newly deployed account or an existing account with this owner and salt.
function createAccountSingle(address owner, uint256 salt) public returns (MultiOwnerLightAccount ret) {
address[] memory owners = new address[](1);
owners[0] = owner;
address addr = getAddress(owners, salt);
Expand All @@ -71,13 +66,20 @@ contract MultiOwnerLightAccountFactory {
);
}

/**
* @notice Calculate the counterfactual address of this account as it would be returned by createAccount()
* @param owners The owners of the account to be created
* @param salt A salt, which can be changed to create multiple accounts with the same owner
* @return The address of the account that would be created with createAccount()
*/
/// @notice Calculate the counterfactual address of this account as it would be returned by `createAccount`.
/// @param owners The owners of the account to be created.
/// @param salt A salt, which can be changed to create multiple accounts with the same owners.
/// @return The address of the account that would be created with `createAccount`.
function getAddress(address[] memory owners, uint256 salt) public view returns (address) {
// This protects against counterfactuals being generated against an exceptionally large number of owners
// that may exceed the block gas limit when actually creating the account.
if (owners.length > _MAX_OWNERS_ON_CREATION) {
revert OwnersLimitExceeded();
}
if (!_isValidOwnersArray(owners)) {
revert InvalidOwners();
}

return Create2.computeAddress(
bytes32(salt),
keccak256(
Expand All @@ -90,4 +92,24 @@ contract MultiOwnerLightAccountFactory {
)
);
}

/// @dev `owners` must be in strictly ascending order and not include the 0 address.
/// @param owners Array of owner addresses.
/// @return Whether the owners array is valid.
function _isValidOwnersArray(address[] memory owners) internal pure returns (bool) {
if (owners.length == 0) {
return false;
}

address prevOwner;
uint256 length = owners.length;
for (uint256 i = 0; i < length; ++i) {
if (owners[i] <= prevOwner) {
return false;
}
prevOwner = owners[i];
}

return true;
}
}
2 changes: 1 addition & 1 deletion test/LightAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ contract LightAccountTest is Test {
bytes32(uint256(uint160(0x0000000071727De22E5E9d8BAf0edAc6f37da032)))
)
),
0xbb16617edd0a177192b9a37ddb37f7783ae99a0cd21d53607df759ded54e024c
0x3bc154d32c096215e957ca99af52e83275464261e8cbe90d8da1df052c89947a
);
}

Expand Down
Loading

0 comments on commit d60557c

Please sign in to comment.