Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fb71c43
feat: implement single signer fallback validation
Zer0dot Jul 31, 2024
1977bd7
chore: use custom error for fallback signer mismatch
Zer0dot Jul 31, 2024
76e0478
chore: modify create function visibility
Zer0dot Jul 31, 2024
587c9a9
forge install solady
Zer0dot Jul 31, 2024
1c0adfd
feat: use an appended bytecode address as an initial fallback signer
Zer0dot Jul 31, 2024
a1eb3a9
test: modify tests to use fallback signer rather than single signer v…
Zer0dot Aug 1, 2024
e6e69de
feat: setter for enabling or disabling fallback signer
Zer0dot Aug 1, 2024
10074f8
fix: fix rebase inconsistencies
Zer0dot Aug 7, 2024
0ab4399
fix: fix tests broken due to rebase
Zer0dot Aug 7, 2024
61ccf2f
[DRAFT] refactor/SMA: Inheritable Account Refactor (#133)
Zer0dot Aug 12, 2024
e8d320f
chore: document SMA new functions
Zer0dot Aug 14, 2024
0ec43dc
chore: reduce optimizer runs for SMA codesize
Zer0dot Aug 15, 2024
5f956cb
feat: add proper SMA support to factory
Zer0dot Aug 15, 2024
9e752bc
chore: update to match new file naming
Zer0dot Aug 15, 2024
7480321
chore: formatting
Zer0dot Aug 15, 2024
b01f8f3
chore: formatting
Zer0dot Aug 15, 2024
07a54c3
chore: remove obsolete comment
Zer0dot Aug 15, 2024
99d2b33
refactor: remove redundant factory check and correct return type for SMA
Zer0dot Aug 15, 2024
47c407b
chore: small comment cleanup
Zer0dot Aug 15, 2024
b2f9257
feat: add SMA deploy event
Zer0dot Aug 16, 2024
b999e5b
refactor: remove unused imports and extract variables to SMA
Zer0dot Aug 16, 2024
edce07b
refactor: use vm.envOr instead of envBool
Zer0dot Aug 19, 2024
6c7ca4f
chore: remove obsolete comment
Zer0dot Aug 19, 2024
9d43bde
chore: rename sma address getter
Zer0dot Aug 19, 2024
a8eb220
refactor: remove dependency on default validation id for specific der…
Zer0dot Aug 19, 2024
b7ab9e8
test: add codesize check for sma impl
Zer0dot Aug 19, 2024
12fc8ad
refactor: revert early rather than explicitly returning
Zer0dot Aug 19, 2024
57577f0
chore: formatting
Zer0dot Aug 19, 2024
4ab5b75
refactor: removed obsolete address comparison from sma creation
Zer0dot Aug 19, 2024
1d6b1ed
fix: use unchecked fallback signer getter for external getter
Zer0dot Aug 19, 2024
44a4211
chore: remove obsolete test
Zer0dot Aug 20, 2024
37ab830
chore: remove leftover event
Zer0dot Aug 20, 2024
dbbca2a
fix: add missing SMA function selector to global validation check, sl…
Zer0dot Aug 20, 2024
bc829fc
fix: remove obsolete check
Zer0dot Aug 20, 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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Factory owner capable only of managing stake
OWNER=
# EP 0.7 address
Expand All @@ -22,3 +21,6 @@ UNSTAKE_DELAY=
# Allowlist Module
ALLOWLIST_MODULE=
ALLOWLIST_MODULE_SALT=

# Whether to test the semi-modular or full modular account
SMA_TEST=false
10 changes: 8 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ jobs:
run: FOUNDRY_PROFILE=optimized-build forge build

- name: Run tests
run: FOUNDRY_PROFILE=optimized-test-deep forge test -vvv
run: FOUNDRY_PROFILE=optimized-test-deep SMA_TEST=false forge test -vvv

- name: Run SMA tests
run: FOUNDRY_PROFILE=optimized-test-deep SMA_TEST=true forge test -vvv

test-default:
name: Run Forge Tests (default)
Expand All @@ -88,4 +91,7 @@ jobs:
run: forge build

- name: Run tests
run: forge test -vvv
run: SMA_TEST=false forge test -vvv

- name: Run SMA tests
run: SMA_TEST=true forge test -vvv
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "lib/modular-account-libs"]
path = lib/modular-account-libs
url = https://github.com/erc6900/modular-account-libs
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ depth = 10
[profile.optimized-build]
via_ir = true
test = 'src'
optimizer_runs = 15000
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to revisit codesize in the future. It's unfortunate to reduce optimizer runs, but it's still more than enough.

optimizer_runs = 10000
out = 'out-optimized'

[profile.optimized-test]
Expand Down
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at a1f9be
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/
@openzeppelin/=lib/openzeppelin-contracts/
@modular-account-libs/=lib/modular-account-libs/src/
@modular-account-libs/=lib/modular-account-libs/src/
solady=lib/solady/src/
51 changes: 47 additions & 4 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {Script, console} from "forge-std/Script.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {AccountFactory} from "../src/account/AccountFactory.sol";

import {SemiModularAccount} from "../src/account/SemiModularAccount.sol";
import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol";
import {SingleSignerValidationModule} from "../src/modules/validation/SingleSignerValidationModule.sol";

Expand All @@ -16,10 +18,12 @@ contract DeployScript is Script {
address public owner = vm.envAddress("OWNER");

address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0));
address public semiModularAccountImpl = vm.envOr("SMA_IMPL", address(0));
address public factory = vm.envOr("FACTORY", address(0));
address public singleSignerValidationModule = vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE", address(0));

bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0)));
bytes32 public semiModularAccountImplSalt = bytes32(vm.envOr("SMA_IMPL_SALT", uint256(0)));
bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0)));
bytes32 public singleSignerValidationModuleSalt =
bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE_SALT", uint256(0)));
Expand All @@ -35,7 +39,8 @@ contract DeployScript is Script {

vm.startBroadcast();
_deployAccountImpl(accountImplSalt, accountImpl);
_deploySingleSignerValidationModule(singleSignerValidationModuleSalt, singleSignerValidationModule);
_deploySemiModularAccountImpl(semiModularAccountImplSalt, semiModularAccountImpl);
_deploySingleSignerValidation(singleSignerValidationModuleSalt, singleSignerValidationModule);
_deployAccountFactory(factorySalt, factory);
_addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount);
vm.stopBroadcast();
Expand Down Expand Up @@ -73,7 +78,39 @@ contract DeployScript is Script {
}
}

function _deploySingleSignerValidationModule(bytes32 salt, address expected) internal {
function _deploySemiModularAccountImpl(bytes32 salt, address expected) internal {
console.log(string.concat("Deploying SemiModularAccountImpl with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt,
keccak256(abi.encodePacked(type(SemiModularAccount).creationCode, abi.encode(entryPoint))),
CREATE2_FACTORY
);
if (addr != expected) {
console.log("Expected address mismatch");
console.log("Expected: ", expected);
console.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console.log("No code found at expected address, deploying...");
SemiModularAccount deployed = new SemiModularAccount{salt: salt}(entryPoint);

if (address(deployed) != expected) {
console.log("Deployed address mismatch");
console.log("Expected: ", expected);
console.log("Deployed: ", address(deployed));
revert();
}

console.log("Deployed SemiModularAccount at: ", address(deployed));
} else {
console.log("Code found at expected address, skipping deployment");
}
}

function _deploySingleSignerValidation(bytes32 salt, address expected) internal {
console.log(string.concat("Deploying SingleSignerValidationModule with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
Expand Down Expand Up @@ -111,7 +148,9 @@ contract DeployScript is Script {
keccak256(
abi.encodePacked(
type(AccountFactory).creationCode,
abi.encode(entryPoint, accountImpl, singleSignerValidationModule, owner)
abi.encode(
entryPoint, accountImpl, semiModularAccountImpl, singleSignerValidationModule, owner
)
)
),
CREATE2_FACTORY
Expand All @@ -126,7 +165,11 @@ contract DeployScript is Script {
if (addr.code.length == 0) {
console.log("No code found at expected address, deploying...");
AccountFactory deployed = new AccountFactory{salt: salt}(
entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidationModule, owner
entryPoint,
UpgradeableModularAccount(payable(accountImpl)),
SemiModularAccount(payable(semiModularAccountImpl)),
singleSignerValidationModule,
owner
);

if (address(deployed) != expected) {
Expand Down
38 changes: 38 additions & 0 deletions src/account/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,34 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {SemiModularAccount} from "../account/SemiModularAccount.sol";
import {UpgradeableModularAccount} from "../account/UpgradeableModularAccount.sol";
import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol";

import {LibClone} from "solady/utils/LibClone.sol";

contract AccountFactory is Ownable {
UpgradeableModularAccount public immutable ACCOUNT_IMPL;
SemiModularAccount public immutable SEMI_MODULAR_ACCOUNT_IMPL;
bytes32 private immutable _PROXY_BYTECODE_HASH;
IEntryPoint public immutable ENTRY_POINT;
address public immutable SINGLE_SIGNER_VALIDATION_MODULE;

event ModularAccountDeployed(address indexed account, address indexed owner, uint256 salt);
event SemiModularAccountDeployed(address indexed account, address indexed owner, uint256 salt);

constructor(
IEntryPoint _entryPoint,
UpgradeableModularAccount _accountImpl,
SemiModularAccount _semiModularImpl,
address _singleSignerValidationModule,
address owner
) Ownable(owner) {
ENTRY_POINT = _entryPoint;
_PROXY_BYTECODE_HASH =
keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), "")));
ACCOUNT_IMPL = _accountImpl;
SEMI_MODULAR_ACCOUNT_IMPL = _semiModularImpl;
SINGLE_SIGNER_VALIDATION_MODULE = _singleSignerValidationModule;
}

Expand Down Expand Up @@ -63,6 +70,23 @@ contract AccountFactory is Ownable {
return UpgradeableModularAccount(payable(addr));
}

function createSemiModularAccount(address owner, uint256 salt) external returns (SemiModularAccount) {
// both module address and entityId for fallback validations are hardcoded at the maximum value.
bytes32 fullSalt = getSalt(owner, salt, type(uint32).max);

bytes memory immutables = _getImmutableArgs(owner);

// LibClone short-circuits if it's already deployed.
(bool alreadyDeployed, address instance) =
LibClone.createDeterministicERC1967(address(SEMI_MODULAR_ACCOUNT_IMPL), immutables, fullSalt);

if (!alreadyDeployed) {
emit SemiModularAccountDeployed(instance, owner, salt);
}

return SemiModularAccount(payable(instance));
}

function addStake(uint32 unstakeDelay) external payable onlyOwner {
ENTRY_POINT.addStake{value: msg.value}(unstakeDelay);
}
Expand All @@ -82,7 +106,21 @@ contract AccountFactory is Ownable {
return Create2.computeAddress(getSalt(owner, salt, entityId), _PROXY_BYTECODE_HASH);
}

function getAddressSemiModular(address owner, uint256 salt) public view returns (address) {
bytes32 fullSalt = getSalt(owner, salt, type(uint32).max);
bytes memory immutables = _getImmutableArgs(owner);
return _getAddressSemiModular(immutables, fullSalt);
}

function getSalt(address owner, uint256 salt, uint32 entityId) public pure returns (bytes32) {
return keccak256(abi.encodePacked(owner, salt, entityId));
}

function _getAddressSemiModular(bytes memory immutables, bytes32 salt) internal view returns (address) {
return LibClone.predictDeterministicAddressERC1967(address(ACCOUNT_IMPL), immutables, salt, address(this));
}

function _getImmutableArgs(address owner) private pure returns (bytes memory) {
return abi.encodePacked(owner);
}
}
Loading