Skip to content
Merged
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ libs = ["lib", "dependencies"]
fs_permissions = [{ access = "read", path = "out-optimized" }]
allow_paths = ["*", "/"]
optimizer = true
optimizer_runs = 20_000
optimizer_runs = 2_000
via_ir = true

[fmt]
Expand Down
28 changes: 28 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import { Script } from "forge-std/Script.sol";

import { MSAFactory } from "src/MSAFactory.sol";
import { EOAKeyValidator } from "src/modules/EOAKeyValidator.sol";
import { SessionKeyValidator } from "src/modules/SessionKeyValidator.sol";
import { WebAuthnValidator } from "src/modules/WebAuthnValidator.sol";
import { ModularSmartAccount } from "src/ModularSmartAccount.sol";

contract Deploy is Script {
function run() public {
// TODO: use correct owner address.
address owner = msg.sender;

address[] memory defaultModules = new address[](3);
defaultModules[0] = address(new TransparentUpgradeableProxy(address(new EOAKeyValidator()), owner, ""));
defaultModules[1] = address(new TransparentUpgradeableProxy(address(new SessionKeyValidator()), owner, ""));
defaultModules[2] = address(new TransparentUpgradeableProxy(address(new WebAuthnValidator()), owner, ""));

address accountImpl = address(new ModularSmartAccount());
address beacon = address(new UpgradeableBeacon(accountImpl, owner));
address factory = address(new TransparentUpgradeableProxy(address(new MSAFactory(beacon)), owner, ""));
}
}
42 changes: 42 additions & 0 deletions src/MSAFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";

import { IMSA } from "./interfaces/IMSA.sol";

/// @title MSAFactory
/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
/// @dev This contract is used to deploy SSO accounts as beacon proxies.
contract MSAFactory {
/// @dev The address of the beacon contract used for the accounts' beacon proxies.
address public immutable beacon;

/// @notice A mapping from unique account IDs to their corresponding deployed account addresses.
/// TODO: add versioning for upgradeability
mapping(bytes32 accountId => address deployedAccount) public accountRegistry;

/// TODO: have this contract be a module registry too?
// address[] public moduleRegistry;

/// @notice Emitted when a new account is successfully created.
/// @param accountAddress The address of the newly created account.
/// @param accountId A unique identifier for the account.
event AccountCreated(address indexed accountAddress, bytes32 accountId);

error AccountAlreadyExists(bytes32 accountId);

constructor(address _beacon) {
beacon = _beacon;
}

function deployAccount(bytes32 accountId, bytes calldata initData) external returns (address account) {
require(accountRegistry[accountId] == address(0), AccountAlreadyExists(accountId));

accountRegistry[accountId] = address(account);
account = address(new BeaconProxy{ salt: accountId }(beacon, initData));

emit AccountCreated(account, accountId);
}
}
117 changes: 34 additions & 83 deletions src/ModularSmartAccount.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { ERC1271 } from "solady/accounts/ERC1271.sol";

import { ExecutionLib } from "./libraries/ExecutionLib.sol";
import { ExecutionHelper } from "./core/ExecutionHelper.sol";
import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
import { IERC7579Account, Execution } from "./interfaces/IERC7579Account.sol";
import { IMSA } from "./interfaces/IMSA.sol";
import { ModuleManager } from "./core/ModuleManager.sol";
// import { HookManager } from "./core/HookManager.sol";
import { ERC1271Handler } from "./core/ERC1271Handler.sol";
import { RegistryAdapter } from "./core/RegistryAdapter.sol";
import { ECDSA } from "solady/utils/ECDSA.sol";
import { Initializable } from "./libraries/Initializable.sol";
// import { ERC7779Adapter } from "./core/ERC7779Adapter.sol";
// import { PreValidationHookManager } from "./core/PreValidationHookManager.sol";

import {
IModule,
Expand All @@ -37,8 +35,6 @@ import {
CALLTYPE_DELEGATECALL,
ModeLib
} from "./libraries/ModeLib.sol";
import { AccountBase } from "./core/AccountBase.sol";
import { console } from "forge-std/console.sol";

/**
* @author zeroknots.eth | rhinestone.wtf
Expand All @@ -47,19 +43,13 @@ import { console } from "forge-std/console.sol";
* This account implements ExecType: DEFAULT and TRY.
* Hook support is implemented
*/
contract ModularSmartAccount is
IMSA,
ExecutionHelper,
ModuleManager,
// HookManager,
// PreValidationHookManager,
RegistryAdapter
{
// ERC7779Adapter

contract ModularSmartAccount is IMSA, ExecutionHelper, ERC1271Handler, RegistryAdapter, Initializable {
using ExecutionLib for bytes;
using ModeLib for ModeCode;
using ECDSA for bytes32;

constructor() {
_disableInitializers();
}

/**
* @inheritdoc IERC7579Account
Expand Down Expand Up @@ -200,7 +190,6 @@ contract ModularSmartAccount is
external
payable
onlyEntryPointOrSelf
// withHook
withRegistry(module, moduleTypeId)
{
if (!IModule(module).isModuleType(moduleTypeId)) revert MismatchModuleTypeId(moduleTypeId);
Expand All @@ -211,17 +200,7 @@ contract ModularSmartAccount is
_installExecutor(module, initData);
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
_installFallbackHandler(module, initData);
}
// TODO
// else if (moduleTypeId == MODULE_TYPE_HOOK) {
// _installHook(module, initData);
// } else if (
// moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271
// || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337
// ) {
// _installPreValidationHook(module, moduleTypeId, initData);
// }
else {
} else {
revert UnsupportedModuleType(moduleTypeId);
}
emit ModuleInstalled(moduleTypeId, module);
Expand All @@ -238,25 +217,14 @@ contract ModularSmartAccount is
external
payable
onlyEntryPointOrSelf
// withHook
{
if (moduleTypeId == MODULE_TYPE_VALIDATOR) {
_uninstallValidator(module, deInitData);
} else if (moduleTypeId == MODULE_TYPE_EXECUTOR) {
_uninstallExecutor(module, deInitData);
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
_uninstallFallbackHandler(module, deInitData);
}
// TODO
// else if (moduleTypeId == MODULE_TYPE_HOOK) {
// _uninstallHook(module, deInitData);
// } else if (
// moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271
// || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337
// ) {
// _uninstallPreValidationHook(module, moduleTypeId, deInitData);
// }
else {
} else {
revert UnsupportedModuleType(moduleTypeId);
}
emit ModuleUninstalled(moduleTypeId, module);
Expand Down Expand Up @@ -289,30 +257,21 @@ contract ModularSmartAccount is
if (!_isValidatorInstalled(validator)) {
return VALIDATION_FAILED;
} else {
// TODO
// (userOpHash, userOp.signature) = _withPreValidationHook(userOpHash, userOp, missingAccountFunds);
// bubble up the return value of the validator module
validSignature = IValidator(validator).validateUserOp(userOp, userOpHash);
}
}

/**
* @dev ERC-1271 isValidSignature
* This function is intended to be used to validate a smart account signature
* and may forward the call to a validator module
*
* @param hash The hash of the data that is signed
* @param data The data that is signed
*/
function isValidSignature(bytes32 hash, bytes calldata data) external view virtual override returns (bytes4) {
address validator = address(bytes20(data[:20]));
if (!_isValidatorInstalled(validator)) {
revert InvalidModule(validator);
}
// TODO
// bytes memory signature_;
// (hash, signature_) = _withPreValidationHook(hash, data[20:]);
return IValidator(validator).isValidSignatureWithSender(msg.sender, hash, data[20:]);
function isValidSignature(
bytes32 hash,
bytes calldata data
)
public
view
override(ERC1271, IERC7579Account)
returns (bytes4)
{
return super.isValidSignature(hash, data);
}

/**
Expand All @@ -334,17 +293,7 @@ contract ModularSmartAccount is
return _isExecutorInstalled(module);
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
return _isFallbackHandlerInstalled(abi.decode(additionalContext, (bytes4)), module);
}
// TODO
// else if (moduleTypeId == MODULE_TYPE_HOOK) {
// return _isHookInstalled(module);
// } else if (
// moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271
// || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337
// ) {
// return _isPreValidationHookInstalled(module, moduleTypeId);
// }
else {
} else {
return false;
}
}
Expand Down Expand Up @@ -394,15 +343,17 @@ contract ModularSmartAccount is
* @dev Initializes the account. Function might be called directly, or by a Factory
* @param data. encoded data that can be used during the initialization phase
*/
function initializeAccount(address entryPoint, address validator, bytes calldata data) public payable virtual {
// protect this function to only be callable when used with the proxy factory or when
// account calls itself
if (msg.sender != address(this)) {
Initializable.checkInitializable();
function initializeAccount(
address[] calldata validators,
bytes[] calldata data
)
external
payable
virtual
initializer
{
for (uint256 i = 0; i < validators.length; i++) {
_installValidator(address(validators[i]), data[i]);
}

ENTRY_POINT = entryPoint;

_installValidator(address(validator), data);
}
}
7 changes: 1 addition & 6 deletions src/core/AccountBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ pragma solidity ^0.8.21;
contract AccountBase {
error AccountAccessUnauthorized();

// TODO: custom slot for this?
address public ENTRY_POINT;

/////////////////////////////////////////////////////
// Access Control
////////////////////////////////////////////////////
address public constant ENTRY_POINT = 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108;

modifier onlyEntryPointOrSelf() virtual {
if (!(msg.sender == ENTRY_POINT || msg.sender == address(this))) {
Expand Down
65 changes: 65 additions & 0 deletions src/core/ERC1271Handler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { ERC1271 } from "solady/accounts/ERC1271.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { ModuleManager } from "./ModuleManager.sol";
import { IValidator } from "../interfaces/IERC7579Module.sol";

/// @title ERC1271Handler
/// @author Matter Labs
/// @notice Contract which provides ERC1271 signature validation
/// @notice Uses ERC7739 for signature replay protection
abstract contract ERC1271Handler is ERC1271, ModuleManager {
/// @notice Returns the domain name and version for the EIP-712 signature.
/// @return name string - The name of the domain
/// @return version string - The version of the domain
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
return ("zksync-sso-1271", "1.0.0");
}

/// @notice Indicates whether or not the contract may cache the domain name and version.
/// @return bool - Whether the domain name and version may change.
function _domainNameAndVersionMayChange() internal pure override returns (bool) {
return true;
}

// @notice Returns whether the signature provided is valid for the provided hash.
// @dev Does not run validation hooks. Is used internally after ERC7739 unwrapping.
// @param hash bytes32 - Hash of the data that is signed
// @param signature bytes calldata - K1 owner signature OR validator address concatenated to signature
// @return bool - Whether the signature is valid
function _erc1271IsValidSignatureNowCalldata(
bytes32 hash,
bytes calldata data
)
internal
view
virtual
override
returns (bool)
{
address validator = address(bytes20(data[:20]));
if (!_isValidatorInstalled(validator)) {
revert InvalidModule(validator);
}
return IValidator(validator).isValidSignatureWithSender(msg.sender, hash, data[20:])
== IERC1271.isValidSignature.selector;
}

/// @notice This function is not used anywhere in the contract, but is required to be implemented.
function _erc1271Signer() internal pure override returns (address) {
revert();
}

/// @dev Returns whether the `msg.sender` is considered safe, such
/// that we don't need to use the nested EIP-712 workflow.
/// @return bool - currently, always returns false
function _erc1271CallerIsSafe() internal pure override returns (bool) {
return false;
}

function domainSeparator() external view returns (bytes32) {
return _domainSeparator();
}
}
Loading