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

Barz v2 #2

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
artifacts
cache
coverage*
**/typechain/
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "solidity",
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
}
],
"plugins": ["./node_modules/prettier-plugin-solidity/src/index.js"]
}
2 changes: 1 addition & 1 deletion contracts/Barz.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {LibDiamond} from "./libraries/LibDiamond.sol";
import {IBarz} from "./interfaces/IBarz.sol";
2 changes: 1 addition & 1 deletion contracts/BarzFactory.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {Barz} from "./Barz.sol";
import {IBarzFactory} from "./interfaces/IBarzFactory.sol";
2 changes: 1 addition & 1 deletion contracts/aa-4337/core/BaseAccount.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-empty-blocks */
2 changes: 1 addition & 1 deletion contracts/aa-4337/core/EntryPoint.sol
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
** Only one instance required on each chain.
**/
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
2 changes: 1 addition & 1 deletion contracts/aa-4337/core/Helpers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/* solhint-disable no-inline-assembly */

2 changes: 1 addition & 1 deletion contracts/aa-4337/core/NonceManager.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import "../interfaces/IEntryPoint.sol";

2 changes: 1 addition & 1 deletion contracts/aa-4337/core/SenderCreator.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/**
* helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,
2 changes: 1 addition & 1 deletion contracts/aa-4337/core/StakeManager.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import "../interfaces/IStakeManager.sol";

9 changes: 6 additions & 3 deletions contracts/facets/AccountFacet.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {UserOperation} from "../aa-4337/interfaces/UserOperation.sol";
import {IEntryPoint} from "../aa-4337/interfaces/IEntryPoint.sol";
import {BaseAccount} from "../aa-4337/core/BaseAccount.sol";
import {LibFacetGuard} from "../libraries/LibFacetGuard.sol";
import {LibAppStorage, BarzStorage} from "../libraries/LibAppStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";
import {LibLoupe} from "../libraries/LibLoupe.sol";
@@ -185,8 +186,10 @@ contract AccountFacet is IAccountFacet, BarzStorage, BaseAccount {
(bool success, bytes memory result) = facet.delegatecall(validateCall);
if (!success) revert AccountFacet__CallNotSuccessful();
validationData = uint256(bytes32(result));
if (validationData == 0) emit VerificationSuccess(_userOpHash);
else emit VerificationFailure(_userOpHash);
if (validationData == 0) {
LibFacetGuard.allowFacetValidation();
emit VerificationSuccess(_userOpHash);
} else emit VerificationFailure(_userOpHash);
}

/**
413 changes: 413 additions & 0 deletions contracts/facets/AccountFacetV2.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/facets/AccountRecoveryFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {LibAppStorage} from "../libraries/LibAppStorage.sol";
19 changes: 15 additions & 4 deletions contracts/facets/GuardianFacet.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {LibDiamond} from "../libraries/LibDiamond.sol";
import {LibFacetStorage, GuardianStorage, StorageConfig} from "../libraries/LibFacetStorage.sol";
import {LibFacetGuard} from "../libraries/LibFacetGuard.sol";
import {LibGuardian} from "../libraries/LibGuardian.sol";
import {ISecurityManager} from "../infrastructure/interfaces/ISecurityManager.sol";
import {IGuardianFacet} from "./interfaces/IGuardianFacet.sol";
@@ -34,6 +35,8 @@ contract GuardianFacet is IGuardianFacet {
*/
function addGuardians(address[] calldata _guardians) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

for (uint256 i; i < _guardians.length; ) {
addGuardian(_guardians[i]);
unchecked {
@@ -50,6 +53,8 @@ contract GuardianFacet is IGuardianFacet {
*/
function addGuardian(address _guardian) public override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

GuardianStorage storage gs = LibFacetStorage.guardianStorage();
if (_guardian == address(this)) {
revert GuardianFacet__GuardianCannotBeSelf();
@@ -91,6 +96,8 @@ contract GuardianFacet is IGuardianFacet {
*/
function removeGuardians(address[] calldata _guardians) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

for (uint256 i; i < _guardians.length; ) {
removeGuardian(_guardians[i]);
unchecked {
@@ -107,9 +114,9 @@ contract GuardianFacet is IGuardianFacet {
*/
function removeGuardian(address _guardian) public override {
LibDiamond.enforceIsSelf();
if (!isGuardian(_guardian)) {
revert GuardianFacet__NonExistentGuardian();
}
LibFacetGuard.enforceFacetValidation();

if (!isGuardian(_guardian)) revert GuardianFacet__NonExistentGuardian();
GuardianStorage storage gs = LibFacetStorage.guardianStorage();
bytes32 id = keccak256(abi.encodePacked(_guardian, "REMOVE"));
if (
@@ -218,6 +225,8 @@ contract GuardianFacet is IGuardianFacet {
*/
function cancelGuardianAddition(address _guardian) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

bytes32 id = keccak256(abi.encodePacked(_guardian, "ADD"));
GuardianStorage storage gs = LibFacetStorage.guardianStorage();
if (gs.pending[id] == 0) {
@@ -235,6 +244,8 @@ contract GuardianFacet is IGuardianFacet {
*/
function cancelGuardianRemoval(address _guardian) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

bytes32 id = keccak256(abi.encodePacked(_guardian, "REMOVE"));
GuardianStorage storage gs = LibFacetStorage.guardianStorage();
if (gs.pending[id] == 0) {
2 changes: 1 addition & 1 deletion contracts/facets/LockFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {LibAppStorage, Lock} from "../libraries/LibAppStorage.sol";
2 changes: 1 addition & 1 deletion contracts/facets/Modifiers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {LibGuardian} from "../libraries/LibGuardian.sol";
import {BarzStorage} from "../libraries/LibAppStorage.sol";
2 changes: 1 addition & 1 deletion contracts/facets/ReentrancyGuard.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {LibReentrancyGuardStorage, ReentrancyGuardStorage} from "../libraries/LibReentrancyGuardStorage.sol";

9 changes: 8 additions & 1 deletion contracts/facets/RestrictionsFacet.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {ReentrancyGuard} from "./ReentrancyGuard.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";
import {LibFacetGuard} from "../libraries/LibFacetGuard.sol";
import {LibAppStorage} from "../libraries/LibAppStorage.sol";
import {LibFacetStorage, RestrictionsStorage} from "../libraries/LibFacetStorage.sol";
import {IRestriction} from "../restrictions/IRestriction.sol";
@@ -34,6 +35,7 @@ contract RestrictionsFacet is IRestrictionsFacet, ReentrancyGuard {
address[] calldata _restrictions
) public override returns (uint256 initSuccess) {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();
LibAppStorage.enforceRestrictionsInitialize();

if (_restrictions.length == 0) {
@@ -63,6 +65,7 @@ contract RestrictionsFacet is IRestrictionsFacet, ReentrancyGuard {
returns (uint256 uninitSuccess)
{
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();
LibAppStorage.setRestrictionsUninitialized();
RestrictionsStorage storage restrictionsStorage = LibFacetStorage
.restrictionsStorage();
@@ -93,6 +96,8 @@ contract RestrictionsFacet is IRestrictionsFacet, ReentrancyGuard {
*/
function addRestriction(address _restriction) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

if (LibDiamond.restrictionsFacet() == address(0)) {
revert RestrictionsFacet__ZeroAddressRestrictionsFacet();
}
@@ -117,6 +122,8 @@ contract RestrictionsFacet is IRestrictionsFacet, ReentrancyGuard {
*/
function removeRestriction(address _restriction) external override {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

RestrictionsStorage storage restrictionsStorage = LibFacetStorage
.restrictionsStorage();

6 changes: 5 additions & 1 deletion contracts/facets/SignatureMigrationFacet.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {LibAppStorage} from "../libraries/LibAppStorage.sol";
import {LibFacetStorage, SignatureMigrationStorage, SignatureMigrationConfig, SignatureMigrationApprovalConfig, ApprovalConfig} from "../libraries/LibFacetStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";
import {LibFacetGuard} from "../libraries/LibFacetGuard.sol";
import {LibGuardian} from "../libraries/LibGuardian.sol";
import {LibLoupe} from "../libraries/LibLoupe.sol";
import {Modifiers} from "./Modifiers.sol";
@@ -77,6 +78,8 @@ contract SignatureMigrationFacet is ISignatureMigrationFacet, Modifiers {
{
// Only self contract can call this function
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

// Should revert if guardian exist
if (0 != LibGuardian.guardianCount()) {
revert SignatureMigrationFacet__InvalidRouteWithGuardian();
@@ -338,6 +341,7 @@ contract SignatureMigrationFacet is ISignatureMigrationFacet, Modifiers {
function finalizeSignatureMigration() external override {
// NOTE: Only owner can call this function
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

SignatureMigrationStorage storage ms = LibFacetStorage
.migrationStorage();
2 changes: 1 addition & 1 deletion contracts/facets/TokenReceiverFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
74 changes: 74 additions & 0 deletions contracts/facets/V2MigrationFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {LibMigratorStorage} from "../libraries/LibMigratorStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";
import {IERC1271} from "../interfaces/ERC/IERC1271.sol";
import {IVerificationFacet} from "./interfaces/IVerificationFacet.sol";
import {IDiamondCut} from "./base/interfaces/IDiamondCut.sol";
import {IDiamondLoupe} from "./base/interfaces/IDiamondLoupe.sol";
import {IFacetRegistry} from "../infrastructure/interfaces/IFacetRegistry.sol";
import {BarzStorage} from "../libraries/LibAppStorage.sol";
import {IMMSAFacet} from "./mmsa/interfaces/IMMSAFacet.sol";

contract V2MigrationFacet is BarzStorage {
IDiamondLoupe public immutable defaultFallbackHandler;
address public immutable r1FacetV2;
address public immutable self;

event V2MigrationComplete();

error V2MigrationFacet__AlreadyV2();
error V2MigrationFacet__Disallowed();

constructor(address _defaultFallbackHandlerV2, address _r1FacetV2) {
LibMigratorStorage.migratorStorage().version = 2;

defaultFallbackHandler = IDiamondLoupe(_defaultFallbackHandlerV2);
r1FacetV2 = _r1FacetV2;
self = address(this);
}

function migrateToV2() external {
LibDiamond.enforceIsSelf();

if (
!IFacetRegistry(s.facetRegistry).isFacetFunctionSelectorRegistered(
self,
0x474e4af5
)
) {
// Keccak("BarzV2Migration")
revert V2MigrationFacet__Disallowed();
}

if (LibMigratorStorage.migratorStorage().version != 0) {
revert V2MigrationFacet__AlreadyV2();
}

LibMigratorStorage.migratorStorage().version = 2;

LibDiamond
.diamondStorage()
.defaultFallbackHandler = defaultFallbackHandler;

IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
bytes4[] memory verificationFunctionSelectors = new bytes4[](3);
verificationFunctionSelectors[0] = IERC1271.isValidSignature.selector;
verificationFunctionSelectors[1] = IVerificationFacet
.validateOwnerSignature
.selector;
verificationFunctionSelectors[2] = IVerificationFacet.owner.selector;

cut[0] = IDiamondCut.FacetCut({
facetAddress: r1FacetV2,
action: IDiamondCut.FacetCutAction.Replace,
functionSelectors: verificationFunctionSelectors
});

IDiamondCut(address(this)).diamondCut(cut, address(0), "");
IMMSAFacet(address(this)).initMMSA();

emit V2MigrationComplete();
}
}
7 changes: 6 additions & 1 deletion contracts/facets/base/DiamondCutFacet.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {LibDiamond} from "../../libraries/LibDiamond.sol";
import {LibFacetGuard} from "../../libraries/LibFacetGuard.sol";
import {LibGuardian} from "../../libraries/LibGuardian.sol";
import {ISecurityManager} from "../../infrastructure/interfaces/ISecurityManager.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
@@ -36,6 +37,8 @@ contract DiamondCutFacet is Modifiers, IDiamondCut {
bool _flag
) external override onlyWhenUnlocked {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

LibDiamond.diamondStorage().supportedInterfaces[_interfaceId] = _flag;
emit SupportsInterfaceUpdated(_interfaceId, _flag);
}
@@ -52,6 +55,7 @@ contract DiamondCutFacet is Modifiers, IDiamondCut {
bytes calldata
) external override onlyWhenUnlocked {
LibDiamond.enforceIsSelf();
LibFacetGuard.enforceFacetValidation();

_checkFacetCutValidity(_diamondCut);
// require approval from guardian if guardian exists
@@ -62,6 +66,7 @@ contract DiamondCutFacet is Modifiers, IDiamondCut {
unchecked {
++LibFacetStorage.diamondCutStorage().nonce;
}

LibDiamond.diamondCut(_diamondCut, address(0), "");
}

2 changes: 1 addition & 1 deletion contracts/facets/base/DiamondLoupeFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import {IERC165} from "../../interfaces/ERC/IERC165.sol";
2 changes: 1 addition & 1 deletion contracts/facets/base/interfaces/IDiamondCut.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/**
* @title DiamondCut Facet Interface
2 changes: 1 addition & 1 deletion contracts/facets/base/interfaces/IDiamondLoupe.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
2 changes: 1 addition & 1 deletion contracts/facets/base/interfaces/IStorageLoupe.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

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

2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IAccountFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {IEntryPoint} from "../../aa-4337/interfaces/IEntryPoint.sol";

62 changes: 62 additions & 0 deletions contracts/facets/interfaces/IAccountFacetV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {IEntryPoint} from "../../aa-4337/interfaces/IEntryPoint.sol";

/**
* @title Account Facet Interface V2
* @dev Interface of module contract that provides the account features and init/unitialization of signer
* compatible with EIP-1271 & EIP-4337
* @author David Yongjun Kim (@Powerstream3604)
*/
interface IAccountFacetV2 {
event AccountInitialized(
IEntryPoint indexed entryPoint,
bytes indexed ownerPublicKey
);
// NOTE: Added Below Event
event VerificationSuccess(bytes32);
event VerificationFailure(bytes32);
event ValidatorSystemAdded(bytes4 key, address system);
event ValidatorSystemRemoved(bytes4 key);

error AccountFacetV2__NotFromEntryPoint();
error AccountFacetV2__InitializationFailure();
error AccountFacetV2__RestrictionsFailure();
error AccountFacetV2__NonExistentVerificationFacet();
error AccountFacetV2__CallNotSuccessful();
error AccountFacetV2__InvalidArrayLength();
error AccountFacetV2__NonexistentValidatorSystem();
error AccountFacetV2__ValidatorSystemAlreadyExists();
error AccountFacetV2__NonExistentValidatorSystem();

function initialize(
address verificationFacet,
address anEntryPoint,
address facetRegistry,
address _defaultFallBack,
bytes calldata _ownerPublicKey
) external returns (uint256);

function executeSingle(
address dest,
uint256 value,
bytes calldata func
) external;

function executeBatch(
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) external;

function addValidatorSystem(bytes2 systemKey, address system) external;

function removeValidatorSystem(bytes2 systemKey) external;

function nonce(uint192 key) external view returns (uint256);

function getValidatorSystem(
bytes2 _systemKey
) external view returns (address);
}
2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IAccountRecoveryFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {RecoveryConfig} from "../../libraries/LibFacetStorage.sol";

2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IGuardianFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/**
* @title Guardian Facet Interface
2 changes: 1 addition & 1 deletion contracts/facets/interfaces/ILockFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {Lock} from "../../libraries/LibAppStorage.sol";

2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IMultiSigFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/**
* @title Multi-sig facet Interface
2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IRestrictionsFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

/**
* @title Restrictions Facet Interface
2 changes: 1 addition & 1 deletion contracts/facets/interfaces/ISignatureMigrationFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {SignatureMigrationConfig} from "../../libraries/LibFacetStorage.sol";

2 changes: 1 addition & 1 deletion contracts/facets/interfaces/IVerificationFacet.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;
pragma solidity 0.8.26;

import {UserOperation} from "../../aa-4337/interfaces/UserOperation.sol";

439 changes: 439 additions & 0 deletions contracts/facets/mmsa/MMSAFacet.sol

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions contracts/facets/mmsa/interfaces/IHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

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

/// @title Hook Management Interface
/// @notice Provides methods for pre-checks and post-checks of transactions to ensure conditions and state consistency.
/// @dev Defines two critical lifecycle hooks in the transaction process: `preCheck` and `postCheck`.
/// These methods facilitate validating conditions prior to execution and verifying state changes afterwards, respectively.
interface IHook is IModule {
/// @notice Performs checks before a transaction is executed, potentially modifying the transaction context.
/// @dev This method is called before the execution of a transaction to validate and possibly adjust execution context.
/// @param msgSender The original sender of the transaction.
/// @param msgValue The amount of wei sent with the call.
/// @param msgData The calldata of the transaction.
/// @return hookData Data that may be used or modified throughout the transaction lifecycle, passed to `postCheck`.
function preCheck(
address msgSender,
uint256 msgValue,
bytes calldata msgData
) external returns (bytes memory hookData);

/// @notice Performs checks after a transaction is executed to ensure state consistency and log results.
/// @dev This method is called after the execution of a transaction to verify and react to the execution outcome.
/// @param hookData Data returned from `preCheck`, containing execution context or modifications.
function postCheck(bytes calldata hookData) external;
}
81 changes: 81 additions & 0 deletions contracts/facets/mmsa/interfaces/IMMSAFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {ExecMode, ValidationId} from "../utils/Types.sol";

interface IMMSAFacet {
error MMSAFacet__NotFromEntryPoint();
error MMSAFacet__NotfromEntryPointOrSelf();
error MMSAFacet__ModuleAlreadyInstalled();
error MMSAFacet__InvalidModuleType();
error MMSAFacet__InvalidModule(uint256 moduleType, address module);
error MMSAFacet__ModuleNotInstalled(uint256 moduleType, address module);
error MMSAFacet__InvalidFallbackHandler(bytes4);
error MMSAFacet__InvalidCallType();
error MMSAFacet__InvalidExecutor();
error MMSAFacet__InvalidValidator(address validator);
error MMSAFacet__UnsupportedCallType();

event ModuleInstalled(uint256 moduleTypeId, address module);
event ModuleUninstalled(uint256 moduleTypeId, address module);

function initMMSA() external;

function installModule(
uint256 moduleTypeId,
address module,
bytes calldata initData
) external payable;

function uninstallModule(
uint256 moduleTypeId,
address module,
bytes calldata _uninitData
) external;

function installValidations(
ValidationId[] calldata validationIds,
bytes[] calldata validationData
) external;

function execute(
ExecMode mode,
bytes calldata executionCalldata
) external payable;

function executeFromExecutor(
ExecMode mode,
bytes calldata executionCalldata
) external payable returns (bytes[] memory returnData);

function accountId() external pure returns (string memory);

function supportsExecutionMode(ExecMode mode) external pure returns (bool);

function supportsModule(uint256 moduleTypeId) external pure returns (bool);

function getModulesPaginated(
uint256 moduleTypeId,
address start,
uint256 pageSize
) external view returns (address[] memory, address);

function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
) external view returns (bool);

function mmsaFallback(
bytes calldata fallbackData
) external payable returns (bytes memory);

function mmsaStaticFallback(
bytes calldata fallbackData
) external view returns (bytes memory);

function mmsaIsValidSignature(
bytes32 msgHash,
bytes calldata signature
) external view returns (bytes4);
}
26 changes: 26 additions & 0 deletions contracts/facets/mmsa/interfaces/IModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

interface IModule {
/// @notice Installs the module with necessary initialization data.
/// @dev Reverts if the module is already initialized.
/// @param data Arbitrary data required for initializing the module during `onInstall`.
function onInstall(bytes calldata data) external;

/// @notice Uninstalls the module and allows for cleanup via arbitrary data.
/// @dev Reverts if any issues occur that prevent clean uninstallation.
/// @param data Arbitrary data required for deinitializing the module during `onUninstall`.
function onUninstall(bytes calldata data) external;

/// @notice Determines if the module matches a specific module type.
/// @dev Should return true if the module corresponds to the type ID, false otherwise.
/// @param moduleTypeId Numeric ID of the module type as per ERC-7579 specifications.
/// @return True if the module is of the specified type, false otherwise.
function isModuleType(uint256 moduleTypeId) external view returns (bool);

/// @notice Checks if the module has been initialized for a specific smart account.
/// @dev Returns true if initialized, false otherwise.
/// @param smartAccount Address of the smart account to check for initialization status.
/// @return True if the module is initialized for the given smart account, false otherwise.
function isInitialized(address smartAccount) external view returns (bool);
}
18 changes: 18 additions & 0 deletions contracts/facets/mmsa/interfaces/IPolicy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";
import {IModule} from "./IModule.sol";

interface IPolicy is IModule {
function checkUserOpPolicy(
bytes32 id,
UserOperation calldata userOp
) external payable returns (uint256);
function checkSignaturePolicy(
bytes32 id,
address sender,
bytes32 hash,
bytes calldata sig
) external view returns (uint256);
}
19 changes: 19 additions & 0 deletions contracts/facets/mmsa/interfaces/ISigner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";
import {IModule} from "./IModule.sol";

interface ISigner is IModule {
function checkUserOpSignature(
bytes32 id,
UserOperation calldata userOp,
bytes32 userOpHash
) external payable returns (uint256);
function checkSignature(
bytes32 id,
address sender,
bytes32 hash,
bytes calldata sig
) external view returns (bytes4);
}
30 changes: 30 additions & 0 deletions contracts/facets/mmsa/interfaces/IValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";
import {IModule} from "./IModule.sol";

interface IValidator is IModule {
/// @notice Validates a user operation as per ERC-4337 standard requirements.
/// @dev Should ensure that the signature and nonce are verified correctly before the transaction is allowed to proceed.
/// The function returns a status code indicating validation success or failure.
/// @param userOp The user operation containing transaction details to be validated.
/// @param userOpHash The hash of the user operation data, used for verifying the signature.
/// @return status The result of the validation process, typically indicating success or the type of failure.
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash
) external returns (uint256);

/// @notice Verifies a signature against a hash, using the sender's address as a contextual check.
/// @dev Used to confirm the validity of a signature against the specific conditions set by the sender.
/// @param sender The address from which the operation was initiated, adding an additional layer of validation against the signature.
/// @param hash The hash of the data signed.
/// @param data The signature data to validate.
/// @return magicValue A bytes4 value that corresponds to the ERC-1271 standard, indicating the validity of the signature.
function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata data
) external view returns (bytes4);
}
46 changes: 46 additions & 0 deletions contracts/facets/mmsa/utils/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {CallType, ExecType, ValidationType, PassFlag} from "./Types.sol";

// Module type identifier for validators
uint256 constant VALIDATOR_MODULE_TYPE = 1;

// Module type identifier for executors
uint256 constant EXECUTOR_MODULE_TYPE = 2;

// Module type identifier for fallback handlers
uint256 constant FALLBACK_MODULE_TYPE = 3;

// Module type identifier for hooks
uint256 constant HOOK_MODULE_TYPE = 4;

// Module type for policies
uint256 constant POLICY_MODULE_TYPE = 5;

// Module type for signers
uint256 constant SIGNER_MODULE_TYPE = 6;

// --- ERC7579 calltypes ---
CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00);

CallType constant CALLTYPE_BATCH = CallType.wrap(0x01);

CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE);

CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xff);

// --- ERC7579 exectypes ---
ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);

ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01);

// --- permission skip flags ---
PassFlag constant SKIP_USEROP = PassFlag.wrap(0x0001);
PassFlag constant SKIP_SIGNATURE = PassFlag.wrap(0x0002);

// --- ERC7579 exectypes ---
ValidationType constant VALIDATOR_VALIDATION_TYPE = ValidationType.wrap(0x01);
ValidationType constant PERMISSION_VALIDATION_TYPE = ValidationType.wrap(0x02);

uint256 constant VALIDATION_FAILURE = 1;
144 changes: 144 additions & 0 deletions contracts/facets/mmsa/utils/Executor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {Execution} from "./Types.sol";

contract Executor {
event TryExecFailure(uint256 index, bytes result);

error TargetIsSelf();

function _decodeBatch(
bytes calldata _calldata
) internal pure returns (Execution[] calldata executionBatch) {
/*
* Batch Call Calldata Layout
* Offset (in bytes) | Length (in bytes) | Contents
* 0x0 | 0x4 | bytes4 function selector
* 0x4 | - |
abi.encode(IERC7579Execution.Execution[])
*/
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
let dataPointer := add(
_calldata.offset,
calldataload(_calldata.offset)
)

// Extract the ERC7579 Executions
executionBatch.offset := add(dataPointer, 32)
executionBatch.length := calldataload(dataPointer)
}
}

function _decodeSingle(
bytes calldata _calldata
)
internal
pure
returns (address target, uint256 value, bytes calldata callData)
{
target = address(bytes20(_calldata[0:20]));
value = uint256(bytes32(_calldata[20:52]));
callData = _calldata[52:];
}

function _execute(
Execution[] calldata _executions
) internal returns (bytes[] memory results) {
results = new bytes[](_executions.length);

for (uint256 i = 0; i < _executions.length; ) {
Execution calldata execution = _executions[i];
results[i] = _execute(
execution.target,
execution.value,
execution.callData
);
}
}

function _tryExecute(
Execution[] calldata _executions
) internal returns (bytes[] memory results) {
results = new bytes[](_executions.length);

for (uint256 i; i < _executions.length; i++) {
Execution calldata execution = _executions[i];
bool success;
(success, results[i]) = _tryExecute(
execution.target,
execution.value,
execution.callData
);
if (!success) {
emit TryExecFailure(i, results[i]);
}
}
}

function _execute(
address _target,
uint256 _value,
bytes calldata _calldata
) internal returns (bytes memory result) {
_checkTarget(_target);
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
calldatacopy(result, _calldata.offset, _calldata.length)

if iszero(
call(
gas(),
_target,
_value,
result,
_calldata.length,
codesize(),
0x00
)
) {
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}

mstore(result, returndatasize())
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize())
mstore(0x40, add(o, returndatasize()))
}
}

function _tryExecute(
address _target,
uint256 _value,
bytes calldata _calldata
) internal returns (bool success, bytes memory result) {
_checkTarget(_target);
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
calldatacopy(result, _calldata.offset, _calldata.length)
success := call(
gas(),
_target,
_value,
result,
_calldata.length,
codesize(),
0x00
)
mstore(result, returndatasize())
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize())
mstore(0x40, add(o, returndatasize()))
}
}

function _checkTarget(address _target) internal view {
if (_target == address(this)) {
revert TargetIsSelf();
}
}
}
206 changes: 206 additions & 0 deletions contracts/facets/mmsa/utils/ModuleManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {LibSentinelList} from "../../../libraries/LibSentinelList.sol";
import {LibMMSAStorage, MMSAStorage, FallbackHandler} from "../../../libraries/LibMMSAStorage.sol";
import {CallType} from "./Types.sol";
import {IValidator} from "../interfaces/IValidator.sol";
import {IHook} from "../interfaces/IHook.sol";
import {IModule} from "../interfaces/IModule.sol";
import {VALIDATOR_MODULE_TYPE, EXECUTOR_MODULE_TYPE, FALLBACK_MODULE_TYPE, HOOK_MODULE_TYPE} from "./Constants.sol";

contract ModuleManager {
using LibSentinelList for LibSentinelList.SentinelList;

event UninstallCallFailed(address module, bytes data);

error ModuleManager__AlreadyInitialized();
error ModuleManager__ValidatorAlreadyInstalled();
error ModuleManager__InvalidValidatorAddress();
error ModuleManager__NonExistentValidator();
error ModuleManager__InvalidExecutorAddress();
error ModuleManager__FallbackHandlerAlreadyInstalled();
error ModuleManager__InvalidSelector();
error ModuleManager__HookAlreadyInstalled();
error ModuleManager__InvalidHandler();

function _initialize() internal virtual {
if (LibMMSAStorage.mmsaStorage().isInitialized) {
revert ModuleManager__AlreadyInitialized();
}
LibMMSAStorage.mmsaStorage().isInitialized = true;
_initModuleManager();
}

function _initModuleManager() internal virtual {
// account module storage
MMSAStorage storage ams = LibMMSAStorage.mmsaStorage();
ams.executors.init();
ams.validators.init();
}

function _installValidator(
address _validator,
bytes calldata _data
) internal virtual {
if (_validator.code.length == 0) {
revert ModuleManager__InvalidValidatorAddress();
}
LibMMSAStorage.mmsaStorage().validators.push(_validator);

IValidator(_validator).onInstall(_data);
}

function _uninstallValidator(
address _validator,
bytes calldata _data
) internal virtual {
(address prev, bytes memory disableModuleData) = abi.decode(
_data,
(address, bytes)
);
LibMMSAStorage.mmsaStorage().validators.pop(prev, _validator);

try IValidator(_validator).onUninstall(disableModuleData) {} catch {
emit UninstallCallFailed(_validator, _data);
}
}

function _installExecutor(
address _executor,
bytes calldata _data
) internal virtual {
if (_executor.code.length == 0) {
revert ModuleManager__InvalidExecutorAddress();
}
LibMMSAStorage.mmsaStorage().executors.push(_executor);

IModule(_executor).onInstall(_data);
}

function _uninstallExecutor(
address _executor,
bytes calldata _data
) internal virtual {
(address prev, bytes memory disableModuleData) = abi.decode(
_data,
(address, bytes)
);
LibMMSAStorage.mmsaStorage().executors.pop(prev, _executor);

try IModule(_executor).onUninstall(disableModuleData) {} catch {
emit UninstallCallFailed(_executor, _data);
}
}

function _installHook(
address _hook,
bytes calldata _data
) internal virtual {
// TODO Implement this function. Exploring Hook options
if (address(LibMMSAStorage.mmsaStorage().hook) == address(0)) {
revert ModuleManager__HookAlreadyInstalled();
}
LibMMSAStorage.mmsaStorage().hook = IHook(_hook);
IHook(_hook).onInstall(_data);
}

function _uninstallHook(
address _hook,
bytes calldata _data
) internal virtual {
// TODO Implement this function. Exploring Hook options
LibMMSAStorage.mmsaStorage().hook = IHook(address(0));

try IHook(_hook).onUninstall(_data) {} catch {
emit UninstallCallFailed(_hook, _data);
}
}

function _installFallbackHandler(
address _handler,
bytes calldata _params
) internal virtual {
bytes4 selector = bytes4(_params[0:4]);

CallType calltype = CallType.wrap(bytes1(_params[4]));

// Revert if the selector is either `onInstall(bytes)` (0x6d61fe70) or `onUninstall(bytes)` (0x8a91b0e3).
// These selectors are explicitly forbidden to prevent security vulnerabilities.
// Allowing these selectors would enable unauthorized users to uninstall and reinstall critical modules.
if (selector == bytes4(0x6d61fe70) || selector == bytes4(0x8a91b0e3)) {
revert ModuleManager__InvalidSelector();
}
if (_handler == address(this)) {
revert ModuleManager__InvalidHandler();
}
if (_isFallbackHandlerInstalled(selector)) {
revert ModuleManager__FallbackHandlerAlreadyInstalled();
}

LibMMSAStorage.mmsaStorage().fallbacks[selector] = FallbackHandler(
_handler,
calltype
);

IModule(_handler).onInstall(_params[4:]);
}

function _uninstallFallbackHandler(
address _fallbackHandler,
bytes calldata _data
) internal virtual {
LibMMSAStorage.mmsaStorage().fallbacks[
bytes4(_data[0:4])
] = FallbackHandler(address(0), CallType.wrap(0x00));
IModule(_fallbackHandler).onUninstall(_data[4:]);
}

function _isFallbackHandlerInstalled(
bytes4 _selector
) internal view returns (bool) {
return
LibMMSAStorage.mmsaStorage().fallbacks[_selector].handler !=
address(0);
}

function _isValidatorInstalled(
address _validator
) internal view virtual returns (bool) {
return LibMMSAStorage.mmsaStorage().validators.contains(_validator);
}

function _isExecutorInstalled(
address _executor
) internal view virtual returns (bool) {
return LibMMSAStorage.mmsaStorage().executors.contains(_executor);
}

function _isHookInstalled(address _hook) internal view returns (bool) {
// TODO Implement this function. Exploring Hook options
return address(LibMMSAStorage.mmsaStorage().hook) == _hook;
}

function _isModuleInstalled(
uint256 _moduleTypeId,
address _module,
bytes calldata _additionalContext
) internal view returns (bool) {
if (_moduleTypeId == VALIDATOR_MODULE_TYPE) {
return _isValidatorInstalled(_module);
} else if (_moduleTypeId == EXECUTOR_MODULE_TYPE) {
return _isExecutorInstalled(_module);
} else if (_moduleTypeId == FALLBACK_MODULE_TYPE) {
return
(_additionalContext.length < 4)
? false
: (LibMMSAStorage
.mmsaStorage()
.fallbacks[bytes4(_additionalContext[0:4])]
.handler == _module);
} else if (_moduleTypeId == HOOK_MODULE_TYPE) {
return _isHookInstalled(_module);
}
return false;
}
}
209 changes: 209 additions & 0 deletions contracts/facets/mmsa/utils/Types.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {IPolicy} from "../interfaces/IPolicy.sol";
import {VALIDATION_FAILURE} from "./Constants.sol";

struct Execution {
address target;
uint256 value;
bytes callData;
}

type ExecMode is bytes32;

type CallType is bytes1;

type ExecType is bytes1;

type ExecModeSelector is bytes4;

type ExecModePayload is bytes22;

using {eqModeSelector as ==} for ExecModeSelector global;
using {eqCallType as ==} for CallType global;
using {notEqCallType as !=} for CallType global;
using {eqExecType as ==} for ExecType global;

function eqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) == CallType.unwrap(b);
}

function notEqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) != CallType.unwrap(b);
}

function eqExecType(ExecType a, ExecType b) pure returns (bool) {
return ExecType.unwrap(a) == ExecType.unwrap(b);
}

function eqModeSelector(
ExecModeSelector a,
ExecModeSelector b
) pure returns (bool) {
return ExecModeSelector.unwrap(a) == ExecModeSelector.unwrap(b);
}

type ValidationMode is bytes1;

type ValidationId is bytes21;

type ValidationType is bytes1;

type PermissionId is bytes4;

type PolicyData is bytes22;

type PassFlag is bytes2;

type ValidAfter is uint48;

type ValidUntil is uint48;

using {vModeEqual as ==} for ValidationMode global;
using {vTypeEqual as ==} for ValidationType global;
using {vIdentifierEqual as ==} for ValidationId global;
using {vModeNotEqual as !=} for ValidationMode global;
using {vTypeNotEqual as !=} for ValidationType global;
using {vIdentifierNotEqual as !=} for ValidationId global;

// nonce = uint192(key) + nonce
// key = mode + (vtype + validationDataWithoutType) + 2bytes parallelNonceKey
// key = 0x00 + 0x00 + 0x000 .. 00 + 0x0000
// key = 0x00 + 0x01 + 0x1234...ff + 0x0000
// key = 0x00 + 0x02 + ( ) + 0x000

function vModeEqual(ValidationMode a, ValidationMode b) pure returns (bool) {
return ValidationMode.unwrap(a) == ValidationMode.unwrap(b);
}

function vModeNotEqual(ValidationMode a, ValidationMode b) pure returns (bool) {
return ValidationMode.unwrap(a) != ValidationMode.unwrap(b);
}

function vTypeEqual(ValidationType a, ValidationType b) pure returns (bool) {
return ValidationType.unwrap(a) == ValidationType.unwrap(b);
}

function vTypeNotEqual(ValidationType a, ValidationType b) pure returns (bool) {
return ValidationType.unwrap(a) != ValidationType.unwrap(b);
}

function vIdentifierEqual(ValidationId a, ValidationId b) pure returns (bool) {
return ValidationId.unwrap(a) == ValidationId.unwrap(b);
}

function vIdentifierNotEqual(
ValidationId a,
ValidationId b
) pure returns (bool) {
return ValidationId.unwrap(a) != ValidationId.unwrap(b);
}

function _mergeValidationData(
uint256 a,
uint256 b
) pure returns (uint256 validationData) {
assembly {
// xor(a,b) == shows only matching bits
// and(xor(a,b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) == filters out the validAfter and validUntil bits
// if the result is not zero, then aggregator part is not matching
// validCase :
// a == 0 || b == 0 || xor(a,b) == 0
// invalidCase :
// a mul b != 0 && xor(a,b) != 0
let sum := shl(96, add(a, b))
switch or(
iszero(
and(
xor(a, b),
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
)
),
or(eq(sum, shl(96, a)), eq(sum, shl(96, b)))
)
case 1 {
validationData := and(
or(a, b),
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
)
// validAfter
let a_vd := and(
0xffffffffffff0000000000000000000000000000000000000000000000000000,
a
)
let b_vd := and(
0xffffffffffff0000000000000000000000000000000000000000000000000000,
b
)
validationData := or(
validationData,
xor(a_vd, mul(xor(a_vd, b_vd), gt(b_vd, a_vd)))
)
// validUntil
a_vd := and(
0x000000000000ffffffffffff0000000000000000000000000000000000000000,
a
)
if iszero(a_vd) {
a_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
b_vd := and(
0x000000000000ffffffffffff0000000000000000000000000000000000000000,
b
)
if iszero(b_vd) {
b_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
let until := xor(a_vd, mul(xor(a_vd, b_vd), lt(b_vd, a_vd)))
if iszero(until) {
until := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
validationData := or(validationData, until)
}
default {
validationData := VALIDATION_FAILURE
}
}
}

function decodePolicyData(
PolicyData _policyData
) pure returns (PassFlag passFlag, IPolicy policy) {
assembly {
passFlag := _policyData
policy := shr(80, _policyData)
}
}

function getValidationType(
ValidationId _validationId
) pure returns (ValidationType validationType) {
assembly {
validationType := _validationId
}
}

function getValidator(
ValidationId _validationId
) pure returns (address validator) {
assembly {
validator := shr(88, _validationId)
}
}

function getPermissionId(
ValidationId _validationId
) pure returns (PermissionId permissionId) {
assembly {
permissionId := shl(8, _validationId)
}
}

function getValidationData(
uint256 _validationData
) pure returns (address data) {
assembly {
data := _validationData
}
}
371 changes: 371 additions & 0 deletions contracts/facets/mmsa/utils/ValidationManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";
import {ExecMode, CallType, ExecType, Execution, ValidationMode, ValidationId, ValidationType, PermissionId, PolicyData, PassFlag, ValidAfter, ValidUntil} from "./Types.sol";
import {VALIDATOR_VALIDATION_TYPE, PERMISSION_VALIDATION_TYPE, VALIDATOR_MODULE_TYPE, POLICY_MODULE_TYPE, SIGNER_MODULE_TYPE, SKIP_USEROP, SKIP_SIGNATURE, VALIDATION_FAILURE} from "./Constants.sol";
import {ModuleManager} from "./ModuleManager.sol";
import {IMMSAFacet} from "../interfaces/IMMSAFacet.sol";
import {IValidator} from "../interfaces/IValidator.sol";
import {IPolicy} from "../interfaces/IPolicy.sol";
import {ISigner} from "../interfaces/ISigner.sol";
import {LibMMSAStorage, MMSAStorage, PermissionConfig} from "../../../libraries/LibMMSAStorage.sol";
import {LibSentinelList} from "../../../libraries/LibSentinelList.sol";

/**
* @title Validation Manager
* @author David Yongjun Kim (@PowerStream3604)
* @dev Barz implemented permission referencing ZeroDev Permission System. Thanks to ZeroDev team.
* We made updates/optimizations to better fit with the need and security model of Barz.
*/

contract ValidationManager {
using LibSentinelList for LibSentinelList.SentinelList;

event PermissionUninstallCallFailed(address module, bytes data);

error ValidationManager__InvalidValidationType();
error ValidationManager__InvalidValidatorAddress();
error ValidationManager__InvalidPolicyDataLength();
error ValidationManager__PolicyFailed(address policy);
error ValidationManager__PermissionNotAllowedForPermission();

function _installValidation(
ValidationId _validationId,
bytes calldata _validatorData
) internal {
ValidationType validationType = getValidationType(_validationId);

if (validationType == VALIDATOR_VALIDATION_TYPE) {
address validator = getValidator(_validationId);

if (validator.code.length == 0) {
revert ValidationManager__InvalidValidatorAddress();
}
LibMMSAStorage.mmsaStorage().validators.push(validator);

IValidator(validator).onInstall(_validatorData);
emit IMMSAFacet.ModuleInstalled(VALIDATOR_MODULE_TYPE, validator);
} else if (validationType == PERMISSION_VALIDATION_TYPE) {
PermissionId permission = getPermissionId(_validationId);
_installPermission(permission, _validatorData);
} else {
revert ValidationManager__InvalidValidationType();
}
}

function _installPermission(
PermissionId _permission,
bytes calldata _permissionData
) internal {
MMSAStorage storage mmsaStorage = LibMMSAStorage.mmsaStorage();
bytes[] calldata permissionInstallationData;
assembly {
permissionInstallationData.offset := add(
add(_permissionData.offset, 32),
calldataload(_permissionData.offset)
)
permissionInstallationData.length := calldataload(
sub(permissionInstallationData.offset, 32)
)
}
if (
permissionInstallationData.length > 254 ||
permissionInstallationData.length == 0
) {
revert ValidationManager__InvalidPolicyDataLength();
}

if (mmsaStorage.permissionConfig[_permission].policyData.length > 0) {
delete mmsaStorage.permissionConfig[_permission].policyData;
}
uint256 signerIndex = permissionInstallationData.length - 1;

for (uint256 i = 0; i < signerIndex; ++i) {
mmsaStorage.permissionConfig[_permission].policyData.push(
PolicyData.wrap(bytes22(permissionInstallationData[i][0:22]))
);
IPolicy(address(bytes20(permissionInstallationData[i][2:22])))
.onInstall(
abi.encodePacked(
bytes32(PermissionId.unwrap(_permission)),
permissionInstallationData[i][22:]
)
);

emit IMMSAFacet.ModuleInstalled(
POLICY_MODULE_TYPE,
address(bytes20(permissionInstallationData[i][2:22]))
);
}

ISigner signer = ISigner(
address(bytes20(permissionInstallationData[signerIndex][2:22]))
);
mmsaStorage.permissionConfig[_permission].signer = signer;
mmsaStorage.permissionConfig[_permission].permissionFlag = PassFlag
.wrap(bytes2(permissionInstallationData[signerIndex][0:22]));
signer.onInstall(
abi.encodePacked(
bytes32(PermissionId.unwrap(_permission)),
permissionInstallationData[signerIndex][22:]
)
);
emit IMMSAFacet.ModuleInstalled(SIGNER_MODULE_TYPE, address(signer));
}

function _uninstallPermission(
PermissionId _permission,
bytes calldata _permissionData
) internal {
bytes[] calldata permissionUninstallData;
assembly {
permissionUninstallData.offset := add(
add(_permissionData.offset, 32),
calldataload(_permissionData.offset)
)
permissionUninstallData.length := calldataload(
sub(permissionUninstallData.offset, 32)
)
}
PermissionConfig storage permissionConfig = LibMMSAStorage
.mmsaStorage()
.permissionConfig[_permission];

if (
permissionUninstallData.length !=
permissionConfig.policyData.length + 1
) {
revert ValidationManager__InvalidPolicyDataLength();
}
PolicyData[] storage policyData = permissionConfig.policyData;
for (uint256 i = 0; i < policyData.length; ++i) {
(, IPolicy policy) = decodePolicyData(policyData[i]);

try
policy.onUninstall(
abi.encodePacked(
bytes32(PermissionId.unwrap(_permission)),
permissionUninstallData[i]
)
)
{} catch {
emit PermissionUninstallCallFailed(
address(policy),
permissionUninstallData[i]
);
}

emit IMMSAFacet.ModuleUninstalled(
POLICY_MODULE_TYPE,
address(policy)
);
}
delete LibMMSAStorage.mmsaStorage().permissionConfig[_permission];

try
permissionConfig.signer.onUninstall(
abi.encodePacked(
PermissionId.unwrap(_permission),
permissionUninstallData[permissionUninstallData.length - 1]
)
)
{} catch {
emit PermissionUninstallCallFailed(
address(permissionConfig.signer),
permissionUninstallData[permissionUninstallData.length - 1]
);
}
emit IMMSAFacet.ModuleUninstalled(
SIGNER_MODULE_TYPE,
address(permissionConfig.signer)
);
}

function _validate(
ValidationId _validation,
UserOperation calldata _userOp,
bytes32 _userOpHash
) internal returns (uint256 validationData) {
ValidationType validationType = getValidationType(_validation);

if (validationType == VALIDATOR_VALIDATION_TYPE) {
address validator = getValidator(_validation);

if (!LibMMSAStorage.mmsaStorage().validators.contains(validator)) {
return VALIDATION_FAILURE;
}
validationData = IValidator(validator).validateUserOp(
_userOp,
_userOpHash
);
} else {
PermissionId permissionId = getPermissionId(_validation);

if (
PassFlag.unwrap(
LibMMSAStorage
.mmsaStorage()
.permissionConfig[permissionId]
.permissionFlag
) &
PassFlag.unwrap(SKIP_USEROP) !=
0
) {
revert ValidationManager__PermissionNotAllowedForPermission();
}

(
uint256 policyValidationData,
ISigner signer
) = _validateUserOpPolicy(permissionId, _userOp, _userOp.signature);
validationData = _mergeValidationData(
uint256(0),
policyValidationData
);
validationData = _mergeValidationData(
validationData,
signer.checkUserOpSignature(
bytes32(PermissionId.unwrap(permissionId)),
_userOp,
_userOpHash
)
);
}
}

function _validateUserOpPolicy(
PermissionId _permission,
UserOperation memory _userOp,
bytes calldata _userOpSig
) internal returns (uint256 validationData, ISigner signer) {
PermissionConfig storage permissionStorage = LibMMSAStorage
.mmsaStorage()
.permissionConfig[_permission];
PolicyData[] storage policyData = permissionStorage.policyData;
for (uint256 i = 0; i < policyData.length; i++) {
(PassFlag passFlag, IPolicy policy) = decodePolicyData(
policyData[i]
);
if (PassFlag.unwrap(passFlag) & PassFlag.unwrap(SKIP_USEROP) == 0) {
validationData = policy.checkUserOpPolicy(
bytes32(PermissionId.unwrap(_permission)),
_userOp
);
address result = getValidationData(validationData);
if (result != address(0)) {
revert ValidationManager__PolicyFailed(address(policy));
}
}
}
return (validationData, permissionStorage.signer);
}

function _mergeValidationData(
uint256 a,
uint256 b
) internal pure returns (uint256 validationData) {
assembly {
// xor(a,b) == shows only matching bits
// and(xor(a,b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) == filters out the validAfter and validUntil bits
// if the result is not zero, then aggregator part is not matching
// validCase :
// a == 0 || b == 0 || xor(a,b) == 0
// invalidCase :
// a mul b != 0 && xor(a,b) != 0
let sum := shl(96, add(a, b))
switch or(
iszero(
and(
xor(a, b),
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
)
),
or(eq(sum, shl(96, a)), eq(sum, shl(96, b)))
)
case 1 {
validationData := and(
or(a, b),
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
)
// validAfter
let a_vd := and(
0xffffffffffff0000000000000000000000000000000000000000000000000000,
a
)
let b_vd := and(
0xffffffffffff0000000000000000000000000000000000000000000000000000,
b
)
validationData := or(
validationData,
xor(a_vd, mul(xor(a_vd, b_vd), gt(b_vd, a_vd)))
)
// validUntil
a_vd := and(
0x000000000000ffffffffffff0000000000000000000000000000000000000000,
a
)
if iszero(a_vd) {
a_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
b_vd := and(
0x000000000000ffffffffffff0000000000000000000000000000000000000000,
b
)
if iszero(b_vd) {
b_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
let until := xor(a_vd, mul(xor(a_vd, b_vd), lt(b_vd, a_vd)))
if iszero(until) {
until := 0x000000000000ffffffffffff0000000000000000000000000000000000000000
}
validationData := or(validationData, until)
}
default {
validationData := VALIDATION_FAILURE
}
}
}

function decodePolicyData(
PolicyData _policyData
) internal pure returns (PassFlag passFlag, IPolicy policy) {
assembly {
passFlag := _policyData
policy := shr(80, _policyData)
}
}

function getValidationType(
ValidationId _validationId
) internal pure returns (ValidationType validationType) {
assembly {
validationType := _validationId
}
}

function getValidator(
ValidationId _validationId
) internal pure returns (address validator) {
assembly {
validator := shr(88, _validationId)
}
}

function getPermissionId(
ValidationId _validationId
) internal pure returns (PermissionId permissionId) {
assembly {
permissionId := shl(8, _validationId)
}
}

function getValidationData(
uint256 _validationData
) internal pure returns (address data) {
assembly {
data := _validationData
}
}
}
967 changes: 967 additions & 0 deletions contracts/facets/msca/MSCAFacet.sol

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions contracts/facets/msca/interfaces/IAccountLoupe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {FunctionReference} from "./IModuleManager.sol";

/// @title Account Loupe Interface
interface IAccountLoupe {
/// @notice Config for an execution function, given a selector.
struct ExecutionFunctionConfig {
address module;
FunctionReference userOpValidationFunction;
FunctionReference runtimeValidationFunction;
}

/// @notice Pre and post hooks for a given selector.
/// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty.
struct ExecutionHooks {
FunctionReference preExecHook;
FunctionReference postExecHook;
}

/// @notice Get the validation functions and module address for a selector.
/// @dev If the selector is a native function, the module address will be the address of the account.
/// @param selector The selector to get the configuration for.
/// @return The configuration for this selector.
function getExecutionFunctionConfig(
bytes4 selector
) external view returns (ExecutionFunctionConfig memory);

/// @notice Get the pre and post execution hooks for a selector.
/// @param selector The selector to get the hooks for.
/// @return The pre and post execution hooks for this selector.
function getExecutionHooks(
bytes4 selector
) external view returns (ExecutionHooks[] memory);

/// @notice Get the pre user op and runtime validation hooks associated with a selector.
/// @param selector The selector to get the hooks for.
/// @return preUserOpValidationHooks The pre user op validation hooks for this selector.
/// @return preRuntimeValidationHooks The pre runtime validation hooks for this selector.
function getPreValidationHooks(
bytes4 selector
)
external
view
returns (
FunctionReference[] memory preUserOpValidationHooks,
FunctionReference[] memory preRuntimeValidationHooks
);

/// @notice Get an array of all installed modules.
/// @return The addresses of all installed modules.
function getInstalledModules() external view returns (address[] memory);
}
16 changes: 16 additions & 0 deletions contracts/facets/msca/interfaces/IAccountView.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {IEntryPoint} from "../../../aa-4337/interfaces/IEntryPoint.sol";

/// @title Account View Interface
interface IAccountView {
/// @notice Get the entry point for this account.
/// @return entryPoint The entry point for this account.
function entryPoint() external view returns (IEntryPoint);

/// @notice Get the account nonce.
/// @dev Uses key 0.
/// @return nonce The next account nonce.
function getNonce() external view returns (uint256);
}
83 changes: 83 additions & 0 deletions contracts/facets/msca/interfaces/IMSCAFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {FunctionReference, IModuleManager} from "./IModuleManager.sol";
import {Call, IStandardExecutor} from "./IStandardExecutor.sol";
import {IAccountLoupe} from "./IAccountLoupe.sol";

interface IMSCAFacet {
/// @dev Note that MSCAFacet also includes functions in IAccountLoupe.sol, IStandardExecutor.sol, IModuleExecutor.sol and ModuleManager

event MSCAInitialized(address entryPoint);

error InvalidFallbackData();
/// @dev Struct to hold optional configuration data for uninstalling a module. This should be encoded and
/// passed to the `config` parameter of `uninstallModule`.
struct UninstallModuleConfig {
// ABI-encoding of a `ModuleManifest` to specify the original manifest
// used to install the module now being uninstalled, in cases where the
// module manifest has changed. If empty, uses the default behavior of
// calling the module to get its current manifest.
bytes serializedManifest;
// If true, will complete the uninstall even if the `onUninstall` callback reverts. Available as an escape
// hatch if a module is blocking uninstall.
bool forceUninstall;
// Maximum amount of gas allowed for each uninstall callback function
// (`onUninstall`), or zero to set no limit. Should
// typically be used with `forceUninstall` to remove modules that are
// preventing uninstallation by consuming all remaining gas.
uint256 callbackGasLimit;
}

error AlwaysDenyRule();
error ExecFromModuleNotPermitted(address module, bytes4 selector);
error ExecFromModuleExternalNotPermitted(
address module,
address target,
uint256 value,
bytes data
);
error NativeTokenSpendingNotPermitted(address module);
error PostExecHookReverted(
address module,
uint8 functionId,
bytes revertReason
);
error PreExecHookReverted(
address module,
uint8 functionId,
bytes revertReason
);
error PreRuntimeValidationHookFailed(
address module,
uint8 functionId,
bytes revertReason
);
error RuntimeValidationFunctionMissing(bytes4 selector);
error RuntimeValidationFunctionReverted(
address module,
uint8 functionId,
bytes revertReason
);
error UnexpectedAggregator(
address module,
uint8 functionId,
address aggregator
);
error UnrecognizedFunction(bytes4 selector);
error UserOpNotFromEntryPoint();
error UserOpValidationFunctionMissing(bytes4 selector);
error ZeroLengthCallBuffer();
error AlreadyInitialized();
error InvalidFunctionLength();
error InvalidCallRoute();

function initializeMSCAModules(
address[] calldata modules,
bytes calldata moduleInitData
) external;

function mscaFallback(
bytes calldata fallbackData
) external payable returns (bytes memory);
}
202 changes: 202 additions & 0 deletions contracts/facets/msca/interfaces/IModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";

// Forge formatter will displace the first comment for the enum field out of the enum itself,
// so annotating here to prevent that.
// forgefmt: disable-start
enum ManifestAssociatedFunctionType {
// Function is not defined.
NONE,
// Function belongs to this module.
SELF,
// Function belongs to an external module provided as a dependency during module installation. Modules MAY depend
// on external validation functions. It MUST NOT depend on external hooks, or installation will fail.
DEPENDENCY,
// Resolves to a magic value to always bypass runtime validation for a given function.
// This is only assignable on runtime validation functions. If it were to be used on a user op validation function,
// it would risk burning gas from the account. When used as a hook in any hook location, it is equivalent to not
// setting a hook and is therefore disallowed.
RUNTIME_VALIDATION_ALWAYS_ALLOW,
// Resolves to a magic value to always fail in a hook for a given function.
// This is only assignable to pre hooks (pre validation and pre execution). It should not be used on
// validation functions themselves, because this is equivalent to leaving the validation functions unset.
// It should not be used in post-exec hooks, because if it is known to always revert, that should happen
// as early as possible to save gas.
PRE_HOOK_ALWAYS_DENY
}
// forgefmt: disable-end

/// @dev For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the module address
/// of the function at `dependencies[dependencyIndex]` during the call to `installModule(config)`.
struct ManifestFunction {
ManifestAssociatedFunctionType functionType;
uint8 functionId;
uint256 dependencyIndex;
}

struct ManifestAssociatedFunction {
bytes4 executionSelector;
ManifestFunction associatedFunction;
}

struct ManifestExecutionHook {
bytes4 executionSelector;
ManifestFunction preExecHook;
ManifestFunction postExecHook;
}

struct ManifestExternalCallPermission {
address externalAddress;
bool permitAnySelector;
bytes4[] selectors;
}

struct SelectorPermission {
bytes4 functionSelector;
string permissionDescription;
}

/// @dev A struct holding fields to describe the module in a purely view context. Intended for front end clients.
struct ModuleMetadata {
// A human-readable name of the module.
string name;
// The version of the module, following the semantic versioning scheme.
string version;
// The author field SHOULD be a username representing the identity of the user or organization
// that created this module.
string author;
// String descriptions of the relative sensitivity of specific functions. The selectors MUST be selectors for
// functions implemented by this module.
SelectorPermission[] permissionDescriptors;
}

/// @dev A struct describing how the module should be installed on a modular account.
struct ModuleManifest {
// List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include
// IModule's interface ID.
bytes4[] interfaceIds;
// If this module depends on other modules' validation functions, the interface IDs of those modules MUST be
// provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction`
// structs used in the manifest.
bytes4[] dependencyInterfaceIds;
// Execution functions defined in this module to be installed on the MSCA.
bytes4[] executionFunctions;
// Module execution functions already installed on the MSCA that this module will be able to call.
bytes4[] permittedExecutionSelectors;
// Boolean to indicate whether the module can call any external address.
bool permitAnyExternalAddress;
// Boolean to indicate whether the module needs access to spend native tokens of the account. If false, the
// module MUST still be able to spend up to the balance that it sends to the account in the same call.
bool canSpendNativeToken;
ManifestExternalCallPermission[] permittedExternalCalls;
ManifestAssociatedFunction[] userOpValidationFunctions;
ManifestAssociatedFunction[] runtimeValidationFunctions;
ManifestAssociatedFunction[] preUserOpValidationHooks;
ManifestAssociatedFunction[] preRuntimeValidationHooks;
ManifestExecutionHook[] executionHooks;
}

/// @title Module Interface
interface IModule {
/// @notice Initialize module data for the modular account.
/// @dev Called by the modular account during `installModule`.
/// @param data Optional bytes array to be decoded and used by the module to setup initial module data for the
/// modular account.
function onInstall(bytes calldata data) external;

/// @notice Clear module data for the modular account.
/// @dev Called by the modular account during `uninstallModule`.
/// @param data Optional bytes array to be decoded and used by the module to clear module data for the modular
/// account.
function onUninstall(bytes calldata data) external;

/// @notice Run the pre user operation validation hook specified by the `functionId`.
/// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param userOp The user operation.
/// @param userOpHash The user operation hash.
/// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes).
function preUserOpValidationHook(
uint8 functionId,
UserOperation calldata userOp,
bytes32 userOpHash
) external returns (uint256);

/// @notice Run the user operation validationFunction specified by the `functionId`.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param userOp The user operation.
/// @param userOpHash The user operation hash.
/// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes).
function userOpValidationFunction(
uint8 functionId,
UserOperation calldata userOp,
bytes32 userOpHash
) external returns (uint256);

/// @notice Run the pre runtime validation hook specified by the `functionId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent.
function preRuntimeValidationHook(
uint8 functionId,
address sender,
uint256 value,
bytes calldata data
) external;

/// @notice Run the runtime validationFunction specified by the `functionId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent.
function runtimeValidationFunction(
uint8 functionId,
address sender,
uint256 value,
bytes calldata data
) external;

/// @notice Run the pre execution hook specified by the `functionId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent.
/// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned.
function preExecutionHook(
uint8 functionId,
address sender,
uint256 value,
bytes calldata data
) external returns (bytes memory);

/// @notice Run the post execution hook specified by the `functionId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param functionId An identifier that routes the call to different internal implementations, should there be
/// more than one.
/// @param preExecHookData The context returned by its associated pre execution hook.
function postExecutionHook(
uint8 functionId,
bytes calldata preExecHookData
) external;

/// @notice Describe the contents and intended configuration of the module.
/// @dev This manifest MUST stay constant over time.
/// @return A manifest describing the contents and intended configuration of the module.
function moduleManifest() external pure returns (ModuleManifest memory);

/// @notice Describe the metadata of the module.
/// @dev This metadata MUST stay constant over time.
/// @return A metadata struct describing the module.
function moduleMetadata() external pure returns (ModuleMetadata memory);
}
27 changes: 27 additions & 0 deletions contracts/facets/msca/interfaces/IModuleExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

/// @title Module Executor Interface
interface IModuleExecutor {
/// @notice Execute a call from a module to another module, via an execution function installed on the account.
/// @dev Modules are not allowed to call native functions on the account. Permissions must be granted to the
/// calling module for the call to go through.
/// @param data The calldata to send to the module.
/// @return The return data from the call.
function executeFromModule(
bytes calldata data
) external payable returns (bytes memory);

/// @notice Execute a call from a module to a non-module address.
/// @dev If the target is a module, the call SHOULD revert. Permissions must be granted to the calling module
/// for the call to go through.
/// @param target The address to be called.
/// @param value The value to send with the call.
/// @param data The calldata to send to the target.
/// @return The return data from the call.
function executeFromModuleExternal(
address target,
uint256 value,
bytes calldata data
) external payable returns (bytes memory);
}
45 changes: 45 additions & 0 deletions contracts/facets/msca/interfaces/IModuleManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

// Treats the first 20 bytes as an address, and the last byte as a function identifier.
type FunctionReference is bytes21;

/// @title Module Manager Interface
interface IModuleManager {
event ModuleInstalled(
address indexed module,
bytes32 manifestHash,
FunctionReference[] dependencies
);
event ModuleUninstalled(
address indexed module,
bool indexed onUninstallSucceeded
);

/// @notice Install a module to the modular account.
/// @param module The module to install.
/// @param manifestHash The hash of the module manifest.
/// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data
/// for the modular account.
/// @param dependencies The dependencies of the module, as described in the manifest. Each FunctionReference
/// MUST be composed of an installed module's address and a function ID of its validation function.
function installModule(
address module,
bytes32 manifestHash,
bytes calldata moduleInstallData,
FunctionReference[] calldata dependencies
) external;

/// @notice Uninstall a module from the modular account.
/// @dev Uninstalling owner modules outside of a replace operation via executeBatch risks losing the account!
/// @param module The module to uninstall.
/// @param config An optional, implementation-specific field that accounts may use to ensure consistency
/// guarantees.
/// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the
/// modular account.
function uninstallModule(
address module,
bytes calldata config,
bytes calldata moduleUninstallData
) external;
}
35 changes: 35 additions & 0 deletions contracts/facets/msca/interfaces/IStandardExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

struct Call {
// The target address for the account to call.
address target;
// The value to send with the call.
uint256 value;
// The calldata for the call.
bytes data;
}

/// @title Standard Executor Interface
interface IStandardExecutor {
/// @notice Standard execute method.
/// @dev If the target is a module, the call SHOULD revert.
/// @param target The target address for the account to call.
/// @param value The value to send with the call.
/// @param data The calldata for the call.
/// @return The return data from the call.
function execute(
address target,
uint256 value,
bytes calldata data
) external payable returns (bytes memory);

/// @notice Standard executeBatch method.
/// @dev If the target is a module, the call SHOULD revert. If any of the calls revert, the entire batch MUST
/// revert.
/// @param calls The array of calls.
/// @return An array containing the return data from the calls.
function executeBatch(
Call[] calldata calls
) external payable returns (bytes[] memory);
}
258 changes: 258 additions & 0 deletions contracts/facets/msca/utils/AccountExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.26;

import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {UserOperation} from "../../../aa-4337/interfaces/UserOperation.sol";
import {IModule} from ".././interfaces/IModule.sol";

/// @title Account Executor
/// @notice Provides internal functions for executing calls on a modular account.
abstract contract AccountExecutor {
error ModuleCallDenied(address module);
error EmptyRuntimeCallBuffer();

/// @dev If the target is a module (as determined by its support for the IModule interface), revert.
/// This prevents the modular account from calling modules (both installed and uninstalled) outside
/// of the normal flow (via execution functions installed on the account), which could lead to data
/// inconsistencies and unexpected behavior.
/// @param target The address of the contract to call.
/// @param value The value to send with the call.
/// @param data The call data.
/// @return result The return data of the call, or the error message from the call if call reverts.
function _exec(
address target,
uint256 value,
bytes memory data
) internal returns (bytes memory result) {
if (
ERC165Checker.supportsInterface(target, type(IModule).interfaceId)
) {
revert ModuleCallDenied(target);
}

bool success;
(success, result) = target.call{value: value}(data);

if (!success) {
// Directly bubble up revert messages
assembly ("memory-safe") {
revert(add(result, 32), mload(result))
}
}
}

/// @dev Performs an `_executeRaw` for a call buffer holding a call to one of:
/// - Pre Runtime Validation Hook
/// - Runtime Validation
/// - Pre Execution Hook
/// And if it fails, reverts with the appropriate custom error.
function _executeRuntimeModuleFunction(
bytes memory buffer,
address module,
bytes4 errorSelector
) internal {
if (!_executeRaw(module, buffer)) {
_revertOnRuntimeModuleFunctionFail(buffer, module, errorSelector);
}
}

function _executeRaw(
address module,
bytes memory buffer
) internal returns (bool success) {
assembly ("memory-safe") {
success := call(
gas(),
module,
/*value*/
0,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length
/*argSize*/
mload(buffer),
/*retOffset*/
0,
/*retSize*/
0
)
}
}

function _executeUserOpModuleFunction(
bytes memory buffer,
address module
) internal returns (uint256 validationData) {
assembly ("memory-safe") {
switch and(
gt(returndatasize(), 0x1f),
call(
/*forward all gas, but can't use gas opcode due to validation opcode restrictions*/
not(0),
module,
/*value*/
0,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length
/*argSize*/
mload(buffer),
/*retOffset*/
0,
/*retSize*/
0x20
)
)
case 0 {
// Bubble up the revert if the call reverts.
let m := mload(0x40)
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
default {
// Otherwise, we return the first word of the return data as the validation data
validationData := mload(0)
}
}
}

function _allocateRuntimeCallBuffer(
bytes calldata data
) internal view returns (bytes memory buffer) {
buffer = abi.encodeWithSelector(
bytes4(0),
0,
msg.sender,
msg.value,
data
);
}

function _allocateUserOpCallBuffer(
bytes4 selector,
UserOperation calldata userOp,
bytes32 userOpHash
) internal pure returns (bytes memory buffer) {
buffer = abi.encodeWithSelector(selector, 0, userOp, userOpHash);
}

/// @dev Updates which module function the buffer will call.
function _updateModuleCallBufferSelector(
bytes memory buffer,
bytes4 moduleSelector
) internal pure {
assembly ("memory-safe") {
// We only want to write to the first 4 bytes, so we first load the first word,
// mask out the fist 4 bytes, then OR in the new selector.
let existingWord := mload(add(buffer, 0x20))
// Clear the upper 4 bytes of the existing word
existingWord := shr(32, shl(32, existingWord))
// Clear the lower 28 bytes of the selector
moduleSelector := shl(224, shr(224, moduleSelector))
// OR in the new selector
existingWord := or(existingWord, moduleSelector)
mstore(add(buffer, 0x20), existingWord)
}
}

function _updateModuleCallBufferFunctionId(
bytes memory buffer,
uint8 functionId
) internal pure {
assembly ("memory-safe") {
// The function ID is a uint8 type, which is left-padded.
// We do want to mask it, however, because this is an internal function and the upper bits may not be
// cleared.
mstore(add(buffer, 0x24), and(functionId, 0xff))
}
}

/// @dev Re-interpret the existing call buffer as just a bytes memory hold msg.data.
/// Since it's already there, and we don't plan on using the buffer again, we can write over the other fields
/// to store calldata length before the data, then return a new memory pointer holding the length.
function _convertRuntimeCallBufferToExecBuffer(
bytes memory runtimeCallBuffer
) internal pure returns (bytes memory execCallBuffer) {
if (runtimeCallBuffer.length == 0) {
revert EmptyRuntimeCallBuffer();
} else {
assembly ("memory-safe") {
// Skip forward to point to the new "length-holding" field.
// Since the existing buffer is already ABI-encoded, we can just skip to the inner callData field.
// This field is location bytes ahead. It skips over:
// - (32 bytes) The original buffer's length field
// - (4 bytes) Selector
// - (32 bytes) Function id
// - (32 bytes) Sender
// - (32 bytes) Value
// - (32 bytes) data offset
// Total: 164 bytes
execCallBuffer := add(runtimeCallBuffer, 164)
}
}
}

/// @dev Used by pre exec hooks to store data for post exec hooks.
function _collectReturnData()
internal
pure
returns (bytes memory returnData)
{
assembly ("memory-safe") {
// Allocate a buffer of that size, advancing the memory pointer to the nearest word
returnData := mload(0x40)
mstore(returnData, returndatasize())
mstore(
0x40,
and(add(add(returnData, returndatasize()), 0x3f), not(0x1f))
)

// Copy over the return data
returndatacopy(add(returnData, 0x20), 0, returndatasize())
}
}

/// @dev This function reverts with one of the following custom error types:
/// - PreRuntimeValidationHookFailed
/// - RuntimeValidationFunctionReverted
/// - PreExecHookReverted
/// Since they all take the same parameters, we can just switch the selector as needed.
/// The last parameter, revertReason, is copied from return data.
function _revertOnRuntimeModuleFunctionFail(
bytes memory buffer,
address module,
bytes4 errorSelector
) internal pure {
assembly ("memory-safe") {
// Call failed, revert with the established error format and the provided selector
// The error format is:
// - Custom error selector
// - module address
// - function id
// - byte offset and length of revert reason
// - byte memory revertReason
// Total size: 132 bytes (4 byte selector + 4 * 32 byte words) + length of revert reason
let errorStart := mload(0x40)
// We add the extra size for the abi encoded fields at the same time as the selector,
// which is after the word-alignment step.
// Pad errorSize to nearest word
let errorSize := and(add(returndatasize(), 0x1f), not(0x1f))
// Add the abi-encoded fields length (128 bytes) and the selector's size (4 bytes)
// to the error size.
errorSize := add(errorSize, 132)
// Store the selector in the start of the error buffer.
// Any set lower bits will be cleared with the subsequest mstore.
mstore(errorStart, errorSelector)
mstore(add(errorStart, 0x04), module)
// Store the function id in the next word, as retrieved from the buffer
mstore(add(errorStart, 0x24), mload(add(buffer, 0x24)))
// Store the offset and length of the revert reason in the next two words
mstore(add(errorStart, 0x44), 0x60)
mstore(add(errorStart, 0x64), returndatasize())

// Copy over the revert reason
returndatacopy(add(errorStart, 0x84), 0, returndatasize())

// Revert
revert(errorStart, errorSize)
}
}
}
15 changes: 15 additions & 0 deletions contracts/facets/msca/utils/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;

type SetValue is bytes30;

/// @dev The sentinel value is used to indicate the head and tail of the list.
bytes32 constant SENTINEL_VALUE = bytes32(uint256(1));

/// @dev Removing the last element will result in this flag not being set correctly, but all operations will
/// function normally, albeit with one extra sload for getAll.
bytes32 constant HAS_NEXT_FLAG = bytes32(uint256(2));

/// @dev As defined by ERC-4337.
uint256 constant SIG_VALIDATION_PASSED = 0;
uint256 constant SIG_VALIDATION_FAILED = 1;
Loading