Skip to content

Commit

Permalink
feat: SMA 3 - Appended Single Signer Address (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zer0dot authored Aug 20, 2024
1 parent 8d573c5 commit 98507f8
Show file tree
Hide file tree
Showing 26 changed files with 536 additions and 167 deletions.
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
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

0 comments on commit 98507f8

Please sign in to comment.