refactor: move brother_node development artifact to dev/test-nodes subdirectory

Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
2026-03-30 17:09:06 +02:00
parent bf730dcb4a
commit 816e258d4c
11734 changed files with 2001707 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/Account.sol)
pragma solidity ^0.8.20;
import {PackedUserOperation, IAccount, IEntryPoint} from "../interfaces/draft-IERC4337.sol";
import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol";
import {AbstractSigner} from "../utils/cryptography/signers/AbstractSigner.sol";
/**
* @dev A simple ERC4337 account implementation. This base implementation only includes the minimal logic to process
* user operations.
*
* Developers must implement the {AbstractSigner-_rawSignatureValidation} function to define the account's validation logic.
*
* NOTE: This core account doesn't include any mechanism for performing arbitrary external calls. This is an essential
* feature that all Account should have. We leave it up to the developers to implement the mechanism of their choice.
* Common choices include ERC-6900, ERC-7579 and ERC-7821 (among others).
*
* IMPORTANT: Implementing a mechanism to validate signatures is a security-sensitive operation as it may allow an
* attacker to bypass the account's security measures. Check out {SignerECDSA}, {SignerP256}, or {SignerRSA} for
* digital signature validation implementations.
*
* @custom:stateless
*/
abstract contract Account is AbstractSigner, IAccount {
/**
* @dev Unauthorized call to the account.
*/
error AccountUnauthorized(address sender);
/**
* @dev Revert if the caller is not the entry point or the account itself.
*/
modifier onlyEntryPointOrSelf() {
_checkEntryPointOrSelf();
_;
}
/**
* @dev Revert if the caller is not the entry point.
*/
modifier onlyEntryPoint() {
_checkEntryPoint();
_;
}
/**
* @dev Canonical entry point for the account that forwards and validates user operations.
*/
function entryPoint() public view virtual returns (IEntryPoint) {
return ERC4337Utils.ENTRYPOINT_V08;
}
/**
* @dev Return the account nonce for the canonical sequence.
*/
function getNonce() public view virtual returns (uint256) {
return getNonce(0);
}
/**
* @dev Return the account nonce for a given sequence (key).
*/
function getNonce(uint192 key) public view virtual returns (uint256) {
return entryPoint().getNonce(address(this), key);
}
/**
* @inheritdoc IAccount
*/
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) public virtual onlyEntryPoint returns (uint256) {
uint256 validationData = _validateUserOp(userOp, userOpHash);
_payPrefund(missingAccountFunds);
return validationData;
}
/**
* @dev Returns the validationData for a given user operation. By default, this checks the signature of the
* signable hash (produced by {_signableUserOpHash}) using the abstract signer ({AbstractSigner-_rawSignatureValidation}).
*
* NOTE: The userOpHash is assumed to be correct. Calling this function with a userOpHash that does not match the
* userOp will result in undefined behavior.
*/
function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual returns (uint256) {
return
_rawSignatureValidation(_signableUserOpHash(userOp, userOpHash), userOp.signature)
? ERC4337Utils.SIG_VALIDATION_SUCCESS
: ERC4337Utils.SIG_VALIDATION_FAILED;
}
/**
* @dev Virtual function that returns the signable hash for a user operations. Since v0.8.0 of the entrypoint,
* `userOpHash` is an EIP-712 hash that can be signed directly.
*/
function _signableUserOpHash(
PackedUserOperation calldata /*userOp*/,
bytes32 userOpHash
) internal view virtual returns (bytes32) {
return userOpHash;
}
/**
* @dev Sends the missing funds for executing the user operation to the {entrypoint}.
* The `missingAccountFunds` must be defined by the entrypoint when calling {validateUserOp}.
*/
function _payPrefund(uint256 missingAccountFunds) internal virtual {
if (missingAccountFunds > 0) {
(bool success, ) = payable(msg.sender).call{value: missingAccountFunds}("");
success; // Silence warning. The entrypoint should validate the result.
}
}
/**
* @dev Ensures the caller is the {entrypoint}.
*/
function _checkEntryPoint() internal view virtual {
address sender = msg.sender;
if (sender != address(entryPoint())) {
revert AccountUnauthorized(sender);
}
}
/**
* @dev Ensures the caller is the {entrypoint} or the account itself.
*/
function _checkEntryPointOrSelf() internal view virtual {
address sender = msg.sender;
if (sender != address(this) && sender != address(entryPoint())) {
revert AccountUnauthorized(sender);
}
}
/**
* @dev Receive Ether.
*/
receive() external payable virtual {}
}

View File

@@ -0,0 +1,405 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/extensions/draft-AccountERC7579.sol)
pragma solidity ^0.8.26;
import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
import {IERC7579Module, IERC7579Validator, IERC7579Execution, IERC7579AccountConfig, IERC7579ModuleConfig, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK} from "../../interfaces/draft-IERC7579.sol";
import {ERC7579Utils, Mode, CallType, ExecType} from "../../account/utils/draft-ERC7579Utils.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
import {Bytes} from "../../utils/Bytes.sol";
import {Packing} from "../../utils/Packing.sol";
import {Address} from "../../utils/Address.sol";
import {Calldata} from "../../utils/Calldata.sol";
import {Account} from "../Account.sol";
/**
* @dev Extension of {Account} that implements support for ERC-7579 modules.
*
* To comply with the ERC-1271 support requirement, this contract defers signature validation to
* installed validator modules by calling {IERC7579Validator-isValidSignatureWithSender}.
*
* This contract does not implement validation logic for user operations since this functionality
* is often delegated to self-contained validation modules. Developers must install a validator module
* upon initialization (or any other mechanism to enable execution from the account):
*
* ```solidity
* contract MyAccountERC7579 is AccountERC7579, Initializable {
* function initializeAccount(address validator, bytes calldata validatorData) public initializer {
* _installModule(MODULE_TYPE_VALIDATOR, validator, validatorData);
* }
* }
* ```
*
* [NOTE]
* ====
* * Hook support is not included. See {AccountERC7579Hooked} for a version that hooks to execution.
* * Validator selection, when verifying either ERC-1271 signature or ERC-4337 UserOperation is implemented in
* internal virtual functions {_extractUserOpValidator} and {_extractSignatureValidator}. Both are implemented
* following common practices. However, this part is not standardized in ERC-7579 (or in any follow-up ERC). Some
* accounts may want to override these internal functions.
* * When combined with {ERC7739}, resolution ordering of {isValidSignature} may have an impact ({ERC7739} does not
* call super). Manual resolution might be necessary.
* * Static calls (using callType `0xfe`) are currently NOT supported.
* ====
*
* WARNING: Removing all validator modules will render the account inoperable, as no user operations can be validated thereafter.
*/
abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC7579AccountConfig, IERC7579ModuleConfig {
using Bytes for *;
using ERC7579Utils for *;
using EnumerableSet for *;
using Packing for bytes32;
EnumerableSet.AddressSet private _validators;
EnumerableSet.AddressSet private _executors;
mapping(bytes4 selector => address) private _fallbacks;
/// @dev The account's {fallback} was called with a selector that doesn't have an installed handler.
error ERC7579MissingFallbackHandler(bytes4 selector);
/// @dev Modifier that checks if the caller is an installed module of the given type.
modifier onlyModule(uint256 moduleTypeId, bytes calldata additionalContext) {
_checkModule(moduleTypeId, msg.sender, additionalContext);
_;
}
/// @dev See {_fallback}.
fallback(bytes calldata) external payable virtual returns (bytes memory) {
return _fallback();
}
/// @inheritdoc IERC7579AccountConfig
function accountId() public view virtual returns (string memory) {
// vendorname.accountname.semver
return "@openzeppelin/community-contracts.AccountERC7579.v0.0.0";
}
/**
* @inheritdoc IERC7579AccountConfig
*
* @dev Supported call types:
* * Single (`0x00`): A single transaction execution.
* * Batch (`0x01`): A batch of transactions execution.
* * Delegate (`0xff`): A delegate call execution.
*
* Supported exec types:
* * Default (`0x00`): Default execution type (revert on failure).
* * Try (`0x01`): Try execution type (emits ERC7579TryExecuteFail on failure).
*/
function supportsExecutionMode(bytes32 encodedMode) public view virtual returns (bool) {
(CallType callType, ExecType execType, , ) = Mode.wrap(encodedMode).decodeMode();
return
(callType == ERC7579Utils.CALLTYPE_SINGLE ||
callType == ERC7579Utils.CALLTYPE_BATCH ||
callType == ERC7579Utils.CALLTYPE_DELEGATECALL) &&
(execType == ERC7579Utils.EXECTYPE_DEFAULT || execType == ERC7579Utils.EXECTYPE_TRY);
}
/**
* @inheritdoc IERC7579AccountConfig
*
* @dev Supported module types:
*
* * Validator: A module used during the validation phase to determine if a transaction is valid and
* should be executed on the account.
* * Executor: A module that can execute transactions on behalf of the smart account via a callback.
* * Fallback Handler: A module that can extend the fallback functionality of a smart account.
*/
function supportsModule(uint256 moduleTypeId) public view virtual returns (bool) {
return
moduleTypeId == MODULE_TYPE_VALIDATOR ||
moduleTypeId == MODULE_TYPE_EXECUTOR ||
moduleTypeId == MODULE_TYPE_FALLBACK;
}
/// @inheritdoc IERC7579ModuleConfig
function installModule(
uint256 moduleTypeId,
address module,
bytes calldata initData
) public virtual onlyEntryPointOrSelf {
_installModule(moduleTypeId, module, initData);
}
/// @inheritdoc IERC7579ModuleConfig
function uninstallModule(
uint256 moduleTypeId,
address module,
bytes calldata deInitData
) public virtual onlyEntryPointOrSelf {
_uninstallModule(moduleTypeId, module, deInitData);
}
/// @inheritdoc IERC7579ModuleConfig
function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
) public view virtual returns (bool) {
if (moduleTypeId == MODULE_TYPE_VALIDATOR) return _validators.contains(module);
if (moduleTypeId == MODULE_TYPE_EXECUTOR) return _executors.contains(module);
if (moduleTypeId == MODULE_TYPE_FALLBACK) return _fallbacks[bytes4(additionalContext[0:4])] == module;
return false;
}
/// @inheritdoc IERC7579Execution
function execute(bytes32 mode, bytes calldata executionCalldata) public payable virtual onlyEntryPointOrSelf {
_execute(Mode.wrap(mode), executionCalldata);
}
/// @inheritdoc IERC7579Execution
function executeFromExecutor(
bytes32 mode,
bytes calldata executionCalldata
)
public
payable
virtual
onlyModule(MODULE_TYPE_EXECUTOR, Calldata.emptyBytes())
returns (bytes[] memory returnData)
{
return _execute(Mode.wrap(mode), executionCalldata);
}
/**
* @dev Implement ERC-1271 through IERC7579Validator modules. If module based validation fails, fallback to
* "native" validation by the abstract signer.
*
* NOTE: when combined with {ERC7739}, resolution ordering may have an impact ({ERC7739} does not call super).
* Manual resolution might be necessary.
*/
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
// check signature length is enough for extraction
if (signature.length >= 20) {
(address module, bytes calldata innerSignature) = _extractSignatureValidator(signature);
// if module is not installed, skip
if (isModuleInstalled(MODULE_TYPE_VALIDATOR, module, Calldata.emptyBytes())) {
// try validation, skip any revert
try IERC7579Validator(module).isValidSignatureWithSender(msg.sender, hash, innerSignature) returns (
bytes4 magic
) {
return magic;
} catch {}
}
}
return bytes4(0xffffffff);
}
/**
* @dev Validates a user operation with {_signableUserOpHash} and returns the validation data
* if the module specified by the first 20 bytes of the nonce key is installed. Falls back to
* {Account-_validateUserOp} otherwise.
*
* See {_extractUserOpValidator} for the module extraction logic.
*/
function _validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override returns (uint256) {
address module = _extractUserOpValidator(userOp);
return
isModuleInstalled(MODULE_TYPE_VALIDATOR, module, Calldata.emptyBytes())
? IERC7579Validator(module).validateUserOp(userOp, _signableUserOpHash(userOp, userOpHash))
: super._validateUserOp(userOp, userOpHash);
}
/**
* @dev ERC-7579 execution logic. See {supportsExecutionMode} for supported modes.
*
* Reverts if the call type is not supported.
*/
function _execute(
Mode mode,
bytes calldata executionCalldata
) internal virtual returns (bytes[] memory returnData) {
(CallType callType, ExecType execType, , ) = mode.decodeMode();
if (callType == ERC7579Utils.CALLTYPE_SINGLE) return executionCalldata.execSingle(execType);
if (callType == ERC7579Utils.CALLTYPE_BATCH) return executionCalldata.execBatch(execType);
if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL) return executionCalldata.execDelegateCall(execType);
revert ERC7579Utils.ERC7579UnsupportedCallType(callType);
}
/**
* @dev Installs a module of the given type with the given initialization data.
*
* For the fallback module type, the `initData` is expected to be the (packed) concatenation of a 4-byte
* selector and the rest of the data to be sent to the handler when calling {IERC7579Module-onInstall}.
*
* Requirements:
*
* * Module type must be supported. See {supportsModule}. Reverts with {ERC7579Utils-ERC7579UnsupportedModuleType}.
* * Module must be of the given type. Reverts with {ERC7579Utils-ERC7579MismatchedModuleTypeId}.
* * Module must not be already installed. Reverts with {ERC7579Utils-ERC7579AlreadyInstalledModule}.
*
* Emits a {IERC7579ModuleConfig-ModuleInstalled} event.
*/
function _installModule(uint256 moduleTypeId, address module, bytes memory initData) internal virtual {
require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));
require(
IERC7579Module(module).isModuleType(moduleTypeId),
ERC7579Utils.ERC7579MismatchedModuleTypeId(moduleTypeId, module)
);
if (moduleTypeId == MODULE_TYPE_VALIDATOR) {
require(_validators.add(module), ERC7579Utils.ERC7579AlreadyInstalledModule(moduleTypeId, module));
} else if (moduleTypeId == MODULE_TYPE_EXECUTOR) {
require(_executors.add(module), ERC7579Utils.ERC7579AlreadyInstalledModule(moduleTypeId, module));
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
bytes4 selector;
(selector, initData) = _decodeFallbackData(initData);
require(
_fallbacks[selector] == address(0),
ERC7579Utils.ERC7579AlreadyInstalledModule(moduleTypeId, module)
);
_fallbacks[selector] = module;
}
IERC7579Module(module).onInstall(initData);
emit ModuleInstalled(moduleTypeId, module);
}
/**
* @dev Uninstalls a module of the given type with the given de-initialization data.
*
* For the fallback module type, the `deInitData` is expected to be the (packed) concatenation of a 4-byte
* selector and the rest of the data to be sent to the handler when calling {IERC7579Module-onUninstall}.
*
* Requirements:
*
* * Module must be already installed. Reverts with {ERC7579Utils-ERC7579UninstalledModule} otherwise.
*/
function _uninstallModule(uint256 moduleTypeId, address module, bytes memory deInitData) internal virtual {
require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));
if (moduleTypeId == MODULE_TYPE_VALIDATOR) {
require(_validators.remove(module), ERC7579Utils.ERC7579UninstalledModule(moduleTypeId, module));
} else if (moduleTypeId == MODULE_TYPE_EXECUTOR) {
require(_executors.remove(module), ERC7579Utils.ERC7579UninstalledModule(moduleTypeId, module));
} else if (moduleTypeId == MODULE_TYPE_FALLBACK) {
bytes4 selector;
(selector, deInitData) = _decodeFallbackData(deInitData);
require(
_fallbackHandler(selector) == module && module != address(0),
ERC7579Utils.ERC7579UninstalledModule(moduleTypeId, module)
);
delete _fallbacks[selector];
}
IERC7579Module(module).onUninstall(deInitData);
emit ModuleUninstalled(moduleTypeId, module);
}
/**
* @dev Fallback function that delegates the call to the installed handler for the given selector.
*
* Reverts with {ERC7579MissingFallbackHandler} if the handler is not installed.
*
* Calls the handler with the original `msg.sender` appended at the end of the calldata following
* the ERC-2771 format.
*/
function _fallback() internal virtual returns (bytes memory) {
address handler = _fallbackHandler(msg.sig);
require(handler != address(0), ERC7579MissingFallbackHandler(msg.sig));
// From https://eips.ethereum.org/EIPS/eip-7579#fallback[ERC-7579 specifications]:
// - MUST utilize ERC-2771 to add the original msg.sender to the calldata sent to the fallback handler
// - MUST use call to invoke the fallback handler
(bool success, bytes memory returndata) = handler.call{value: msg.value}(
abi.encodePacked(msg.data, msg.sender)
);
if (success) return returndata;
assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
}
}
/// @dev Returns the fallback handler for the given selector. Returns `address(0)` if not installed.
function _fallbackHandler(bytes4 selector) internal view virtual returns (address) {
return _fallbacks[selector];
}
/// @dev Checks if the module is installed. Reverts if the module is not installed.
function _checkModule(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
) internal view virtual {
require(
isModuleInstalled(moduleTypeId, module, additionalContext),
ERC7579Utils.ERC7579UninstalledModule(moduleTypeId, module)
);
}
/**
* @dev Extracts the nonce validator from the user operation.
*
* To construct a nonce key, set nonce as follows:
*
* ```
* <module address (20 bytes)> | <key (4 bytes)> | <nonce (8 bytes)>
* ```
* NOTE: The default behavior of this function replicates the behavior of
* https://github.com/rhinestonewtf/safe7579/blob/bb29e8b1a66658790c4169e72608e27d220f79be/src/Safe7579.sol#L266[Safe adapter],
* https://github.com/etherspot/etherspot-prime-contracts/blob/cfcdb48c4172cea0d66038324c0bae3288aa8caa/src/modular-etherspot-wallet/wallet/ModularEtherspotWallet.sol#L227[Etherspot's Prime Account], and
* https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L247[ERC7579 reference implementation].
*
* This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
*
* For example, https://github.com/bcnmy/nexus/blob/54f4e19baaff96081a8843672977caf712ef19f4/contracts/lib/NonceLib.sol#L17[Biconomy's Nexus]
* uses a similar yet incompatible approach (the validator address is also part of the nonce, but not at the same location)
*/
function _extractUserOpValidator(PackedUserOperation calldata userOp) internal pure virtual returns (address) {
return address(bytes32(userOp.nonce).extract_32_20(0));
}
/**
* @dev Extracts the signature validator from the signature.
*
* To construct a signature, set the first 20 bytes as the module address and the remaining bytes as the
* signature data:
*
* ```
* <module address (20 bytes)> | <signature data>
* ```
*
* NOTE: The default behavior of this function replicates the behavior of
* https://github.com/rhinestonewtf/safe7579/blob/bb29e8b1a66658790c4169e72608e27d220f79be/src/Safe7579.sol#L350[Safe adapter],
* https://github.com/bcnmy/nexus/blob/54f4e19baaff96081a8843672977caf712ef19f4/contracts/Nexus.sol#L239[Biconomy's Nexus],
* https://github.com/etherspot/etherspot-prime-contracts/blob/cfcdb48c4172cea0d66038324c0bae3288aa8caa/src/modular-etherspot-wallet/wallet/ModularEtherspotWallet.sol#L252[Etherspot's Prime Account], and
* https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L296[ERC7579 reference implementation].
*
* This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
*/
function _extractSignatureValidator(
bytes calldata signature
) internal pure virtual returns (address module, bytes calldata innerSignature) {
return (address(bytes20(signature[0:20])), signature[20:]);
}
/**
* @dev Extract the function selector from initData/deInitData for MODULE_TYPE_FALLBACK
*
* NOTE: If we had calldata here, we could use calldata slice which are cheaper to manipulate and don't require
* actual copy. However, this would require `_installModule` to get a calldata bytes object instead of a memory
* bytes object. This would prevent calling `_installModule` from a contract constructor and would force the use
* of external initializers. That may change in the future, as most accounts will probably be deployed as
* clones/proxy/ERC-7702 delegates and therefore rely on initializers anyway.
*/
function _decodeFallbackData(
bytes memory data
) internal pure virtual returns (bytes4 selector, bytes memory remaining) {
return (bytes4(data), data.slice(4));
}
/// @dev By default, only use the modules for validation of userOp and signature. Disable raw signatures.
function _rawSignatureValidation(
bytes32 /*hash*/,
bytes calldata /*signature*/
) internal view virtual override returns (bool) {
return false;
}
}

View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/extensions/draft-AccountERC7579Hooked.sol)
pragma solidity ^0.8.26;
import {IERC7579Hook, MODULE_TYPE_HOOK} from "../../interfaces/draft-IERC7579.sol";
import {ERC7579Utils, Mode} from "../../account/utils/draft-ERC7579Utils.sol";
import {AccountERC7579} from "./draft-AccountERC7579.sol";
/**
* @dev Extension of {AccountERC7579} with support for a single hook module (type 4).
*
* If installed, this extension will call the hook module's {IERC7579Hook-preCheck} before executing any operation
* with {_execute} (including {execute} and {executeFromExecutor} by default) and {IERC7579Hook-postCheck} thereafter.
*
* NOTE: Hook modules break the check-effect-interaction pattern. In particular, the {IERC7579Hook-preCheck} hook can
* lead to potentially dangerous reentrancy. Using the `withHook()` modifier is safe if no effect is performed
* before the preHook or after the postHook. That is the case on all functions here, but it may not be the case if
* functions that have this modifier are overridden. Developers should be extremely careful when implementing hook
* modules or further overriding functions that involve hooks.
*/
abstract contract AccountERC7579Hooked is AccountERC7579 {
address private _hook;
/// @dev A hook module is already present. This contract only supports one hook module.
error ERC7579HookModuleAlreadyPresent(address hook);
/**
* @dev Calls {IERC7579Hook-preCheck} before executing the modified function and {IERC7579Hook-postCheck}
* thereafter.
*/
modifier withHook() {
address hook_ = hook();
bytes memory hookData;
// slither-disable-next-line reentrancy-no-eth
if (hook_ != address(0)) hookData = IERC7579Hook(hook_).preCheck(msg.sender, msg.value, msg.data);
_;
if (hook_ != address(0)) IERC7579Hook(hook_).postCheck(hookData);
}
/// @inheritdoc AccountERC7579
function accountId() public view virtual override returns (string memory) {
// vendorname.accountname.semver
return "@openzeppelin/community-contracts.AccountERC7579Hooked.v0.0.0";
}
/// @dev Returns the hook module address if installed, or `address(0)` otherwise.
function hook() public view virtual returns (address) {
return _hook;
}
/// @dev Supports hook modules. See {AccountERC7579-supportsModule}
function supportsModule(uint256 moduleTypeId) public view virtual override returns (bool) {
return moduleTypeId == MODULE_TYPE_HOOK || super.supportsModule(moduleTypeId);
}
/// @inheritdoc AccountERC7579
function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata data
) public view virtual override returns (bool) {
return
(moduleTypeId == MODULE_TYPE_HOOK && module == hook()) ||
super.isModuleInstalled(moduleTypeId, module, data);
}
/// @dev Installs a module with support for hook modules. See {AccountERC7579-_installModule}
function _installModule(
uint256 moduleTypeId,
address module,
bytes memory initData
) internal virtual override withHook {
if (moduleTypeId == MODULE_TYPE_HOOK) {
require(_hook == address(0), ERC7579HookModuleAlreadyPresent(_hook));
_hook = module;
}
super._installModule(moduleTypeId, module, initData);
}
/// @dev Uninstalls a module with support for hook modules. See {AccountERC7579-_uninstallModule}
function _uninstallModule(
uint256 moduleTypeId,
address module,
bytes memory deInitData
) internal virtual override withHook {
if (moduleTypeId == MODULE_TYPE_HOOK) {
require(_hook == module, ERC7579Utils.ERC7579UninstalledModule(moduleTypeId, module));
_hook = address(0);
}
super._uninstallModule(moduleTypeId, module, deInitData);
}
/// @dev Hooked version of {AccountERC7579-_execute}.
function _execute(
Mode mode,
bytes calldata executionCalldata
) internal virtual override withHook returns (bytes[] memory) {
return super._execute(mode, executionCalldata);
}
/// @dev Hooked version of {AccountERC7579-_fallback}.
function _fallback() internal virtual override withHook returns (bytes memory) {
return super._fallback();
}
}

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/extensions/draft-ERC7821.sol)
pragma solidity ^0.8.20;
import {ERC7579Utils, Mode, CallType, ExecType, ModeSelector} from "../utils/draft-ERC7579Utils.sol";
import {IERC7821} from "../../interfaces/draft-IERC7821.sol";
import {Account} from "../Account.sol";
/**
* @dev Minimal batch executor following ERC-7821.
*
* Only supports supports single batch mode (`0x01000000000000000000`). Does not support optional "opData".
*
* @custom:stateless
*/
abstract contract ERC7821 is IERC7821 {
using ERC7579Utils for *;
error UnsupportedExecutionMode();
/**
* @dev Executes the calls in `executionData` with no optional `opData` support.
*
* NOTE: Access to this function is controlled by {_erc7821AuthorizedExecutor}. Changing access permissions, for
* example to approve calls by the ERC-4337 entrypoint, should be implemented by overriding it.
*
* Reverts and bubbles up error if any call fails.
*/
function execute(bytes32 mode, bytes calldata executionData) public payable virtual {
if (!_erc7821AuthorizedExecutor(msg.sender, mode, executionData))
revert Account.AccountUnauthorized(msg.sender);
if (!supportsExecutionMode(mode)) revert UnsupportedExecutionMode();
executionData.execBatch(ERC7579Utils.EXECTYPE_DEFAULT);
}
/// @inheritdoc IERC7821
function supportsExecutionMode(bytes32 mode) public view virtual returns (bool result) {
(CallType callType, ExecType execType, ModeSelector modeSelector, ) = Mode.wrap(mode).decodeMode();
return
callType == ERC7579Utils.CALLTYPE_BATCH &&
execType == ERC7579Utils.EXECTYPE_DEFAULT &&
modeSelector == ModeSelector.wrap(0x00000000);
}
/**
* @dev Access control mechanism for the {execute} function.
* By default, only the contract itself is allowed to execute.
*
* Override this function to implement custom access control, for example to allow the
* ERC-4337 entrypoint to execute.
*
* ```solidity
* function _erc7821AuthorizedExecutor(
* address caller,
* bytes32 mode,
* bytes calldata executionData
* ) internal view virtual override returns (bool) {
* return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
* }
* ```
*/
function _erc7821AuthorizedExecutor(
address caller,
bytes32 /* mode */,
bytes calldata /* executionData */
) internal view virtual returns (bool) {
return caller == address(this);
}
}

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/utils/EIP7702Utils.sol)
pragma solidity ^0.8.20;
/**
* @dev Library with common EIP-7702 utility functions.
*
* See https://eips.ethereum.org/EIPS/eip-7702[ERC-7702].
*/
library EIP7702Utils {
bytes3 internal constant EIP7702_PREFIX = 0xef0100;
/**
* @dev Returns the address of the delegate if `account` as an EIP-7702 delegation setup, or address(0) otherwise.
*/
function fetchDelegate(address account) internal view returns (address) {
bytes23 delegation = bytes23(account.code);
return bytes3(delegation) == EIP7702_PREFIX ? address(bytes20(delegation << 24)) : address(0);
}
}

View File

@@ -0,0 +1,159 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (account/utils/draft-ERC4337Utils.sol)
pragma solidity ^0.8.20;
import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
import {Math} from "../../utils/math/Math.sol";
import {Calldata} from "../../utils/Calldata.sol";
import {Packing} from "../../utils/Packing.sol";
/// @dev This is available on all entrypoint since v0.4.0, but is not formally part of the ERC.
interface IEntryPointExtra {
function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32);
}
/**
* @dev Library with common ERC-4337 utility functions.
*
* See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337].
*/
library ERC4337Utils {
using Packing for *;
/// @dev Address of the entrypoint v0.7.0
IEntryPoint internal constant ENTRYPOINT_V07 = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032);
/// @dev Address of the entrypoint v0.8.0
IEntryPoint internal constant ENTRYPOINT_V08 = IEntryPoint(0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108);
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success.
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert.
uint256 internal constant SIG_VALIDATION_FAILED = 1;
/// @dev Parses the validation data into its components. See {packValidationData}.
function parseValidationData(
uint256 validationData
) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
validAfter = uint48(bytes32(validationData).extract_32_6(0));
validUntil = uint48(bytes32(validationData).extract_32_6(6));
aggregator = address(bytes32(validationData).extract_32_20(12));
if (validUntil == 0) validUntil = type(uint48).max;
}
/// @dev Packs the validation data into a single uint256. See {parseValidationData}.
function packValidationData(
address aggregator,
uint48 validAfter,
uint48 validUntil
) internal pure returns (uint256) {
return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator)));
}
/// @dev Same as {packValidationData}, but with a boolean signature success flag.
function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) {
return
packValidationData(
address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))),
validAfter,
validUntil
);
}
/**
* @dev Combines two validation data into a single one.
*
* The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while
* the `validAfter` is the maximum and the `validUntil` is the minimum of both.
*/
function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) {
(address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1);
(address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2);
bool success = aggregator1 == address(uint160(SIG_VALIDATION_SUCCESS)) &&
aggregator2 == address(uint160(SIG_VALIDATION_SUCCESS));
uint48 validAfter = uint48(Math.max(validAfter1, validAfter2));
uint48 validUntil = uint48(Math.min(validUntil1, validUntil2));
return packValidationData(success, validAfter, validUntil);
}
/// @dev Returns the aggregator of the `validationData` and whether it is out of time range.
function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) {
(address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData);
return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
}
/// @dev Get the hash of a user operation for a given entrypoint
function hash(PackedUserOperation calldata self, address entrypoint) internal view returns (bytes32) {
// NOTE: getUserOpHash is available since v0.4.0
//
// Prior to v0.8.0, this was easy to replicate for any entrypoint and chainId. Since v0.8.0 of the
// entrypoint, this depends on the Entrypoint's domain separator, which cannot be hardcoded and is complex
// to recompute. Domain separator could be fetch using the `getDomainSeparatorV4` getter, or recomputed from
// the ERC-5267 getter, but both operation would require doing a view call to the entrypoint. Overall it feels
// simpler and less error prone to get that functionality from the entrypoint directly.
return IEntryPointExtra(entrypoint).getUserOpHash(self);
}
/// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
function factory(PackedUserOperation calldata self) internal pure returns (address) {
return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
}
/// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
return self.initCode.length < 20 ? Calldata.emptyBytes() : self.initCode[20:];
}
/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.accountGasLimits.extract_32_16(0));
}
/// @dev Returns `callGasLimit` from the {PackedUserOperation}.
function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.accountGasLimits.extract_32_16(16));
}
/// @dev Returns the first section of `gasFees` from the {PackedUserOperation}.
function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.gasFees.extract_32_16(0));
}
/// @dev Returns the second section of `gasFees` from the {PackedUserOperation}.
function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.gasFees.extract_32_16(16));
}
/// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`).
function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) {
unchecked {
// Following values are "per gas"
uint256 maxPriorityFee = maxPriorityFeePerGas(self);
uint256 maxFee = maxFeePerGas(self);
return Math.min(maxFee, maxPriorityFee + block.basefee);
}
}
/// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20]));
}
/// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36]));
}
/// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
}
/// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
return self.paymasterAndData.length < 52 ? Calldata.emptyBytes() : self.paymasterAndData[52:];
}
}

View File

@@ -0,0 +1,280 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (account/utils/draft-ERC7579Utils.sol)
pragma solidity ^0.8.20;
import {Execution} from "../../interfaces/draft-IERC7579.sol";
import {Packing} from "../../utils/Packing.sol";
import {Address} from "../../utils/Address.sol";
type Mode is bytes32;
type CallType is bytes1;
type ExecType is bytes1;
type ModeSelector is bytes4;
type ModePayload is bytes22;
/**
* @dev Library with common ERC-7579 utility functions.
*
* See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579].
*/
// slither-disable-next-line unused-state
library ERC7579Utils {
using Packing for *;
/// @dev A single `call` execution.
CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00);
/// @dev A batch of `call` executions.
CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01);
/// @dev A `delegatecall` execution.
CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
/// @dev Default execution type that reverts on failure.
ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
/// @dev Execution type that does not revert on failure.
ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01);
/**
* @dev Emits when an {EXECTYPE_TRY} execution fails.
* @param batchExecutionIndex The index of the failed call in the execution batch.
* @param returndata The returned data from the failed call.
*/
event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata);
/// @dev The provided {CallType} is not supported.
error ERC7579UnsupportedCallType(CallType callType);
/// @dev The provided {ExecType} is not supported.
error ERC7579UnsupportedExecType(ExecType execType);
/// @dev The provided module doesn't match the provided module type.
error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module);
/// @dev The module is not installed.
error ERC7579UninstalledModule(uint256 moduleTypeId, address module);
/// @dev The module is already installed.
error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module);
/// @dev The module type is not supported.
error ERC7579UnsupportedModuleType(uint256 moduleTypeId);
/// @dev Input calldata not properly formatted and possibly malicious.
error ERC7579DecodingError();
/// @dev Executes a single call.
function execSingle(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
(address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata);
returnData = new bytes[](1);
returnData[0] = _call(0, execType, target, value, callData);
}
/// @dev Executes a batch of calls.
function execBatch(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
Execution[] calldata executionBatch = decodeBatch(executionCalldata);
returnData = new bytes[](executionBatch.length);
for (uint256 i = 0; i < executionBatch.length; ++i) {
returnData[i] = _call(
i,
execType,
executionBatch[i].target,
executionBatch[i].value,
executionBatch[i].callData
);
}
}
/// @dev Executes a delegate call.
function execDelegateCall(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
(address target, bytes calldata callData) = decodeDelegate(executionCalldata);
returnData = new bytes[](1);
returnData[0] = _delegatecall(0, execType, target, callData);
}
/// @dev Encodes the mode with the provided parameters. See {decodeMode}.
function encodeMode(
CallType callType,
ExecType execType,
ModeSelector selector,
ModePayload payload
) internal pure returns (Mode mode) {
return
Mode.wrap(
CallType
.unwrap(callType)
.pack_1_1(ExecType.unwrap(execType))
.pack_2_4(bytes4(0))
.pack_6_4(ModeSelector.unwrap(selector))
.pack_10_22(ModePayload.unwrap(payload))
);
}
/// @dev Decodes the mode into its parameters. See {encodeMode}.
function decodeMode(
Mode mode
) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) {
return (
CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)),
ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)),
ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)),
ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10))
);
}
/// @dev Encodes a single call execution. See {decodeSingle}.
function encodeSingle(
address target,
uint256 value,
bytes calldata callData
) internal pure returns (bytes memory executionCalldata) {
return abi.encodePacked(target, value, callData);
}
/// @dev Decodes a single call execution. See {encodeSingle}.
function decodeSingle(
bytes calldata executionCalldata
) internal pure returns (address target, uint256 value, bytes calldata callData) {
target = address(bytes20(executionCalldata[0:20]));
value = uint256(bytes32(executionCalldata[20:52]));
callData = executionCalldata[52:];
}
/// @dev Encodes a delegate call execution. See {decodeDelegate}.
function encodeDelegate(
address target,
bytes calldata callData
) internal pure returns (bytes memory executionCalldata) {
return abi.encodePacked(target, callData);
}
/// @dev Decodes a delegate call execution. See {encodeDelegate}.
function decodeDelegate(
bytes calldata executionCalldata
) internal pure returns (address target, bytes calldata callData) {
target = address(bytes20(executionCalldata[0:20]));
callData = executionCalldata[20:];
}
/// @dev Encodes a batch of executions. See {decodeBatch}.
function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) {
return abi.encode(executionBatch);
}
/// @dev Decodes a batch of executions. See {encodeBatch}.
///
/// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted.
function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) {
unchecked {
uint256 bufferLength = executionCalldata.length;
// Check executionCalldata is not empty.
if (bufferLength < 32) revert ERC7579DecodingError();
// Get the offset of the array (pointer to the array length).
uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0:32]));
// The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the
// buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk.
if (arrayLengthOffset > bufferLength - 32) revert ERC7579DecodingError();
// Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow.
uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 32]));
// Check that the buffer is long enough to store the array elements as "offset pointer":
// - each element of the array is an "offset pointer" to the data.
// - each "offset pointer" (to an array element) takes 32 bytes.
// - validity of the calldata at that location is checked when the array element is accessed, so we only
// need to check that the buffer is large enough to hold the pointers.
//
// Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk.
// Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow.
if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 32 < arrayLength * 32)
revert ERC7579DecodingError();
assembly ("memory-safe") {
executionBatch.offset := add(add(executionCalldata.offset, arrayLengthOffset), 0x20)
executionBatch.length := arrayLength
}
}
}
/// @dev Executes a `call` to the target with the provided {ExecType}.
function _call(
uint256 index,
ExecType execType,
address target,
uint256 value,
bytes calldata data
) private returns (bytes memory) {
(bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).call{value: value}(
data
);
return _validateExecutionMode(index, execType, success, returndata);
}
/// @dev Executes a `delegatecall` to the target with the provided {ExecType}.
function _delegatecall(
uint256 index,
ExecType execType,
address target,
bytes calldata data
) private returns (bytes memory) {
(bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).delegatecall(data);
return _validateExecutionMode(index, execType, success, returndata);
}
/// @dev Validates the execution mode and returns the returndata.
function _validateExecutionMode(
uint256 index,
ExecType execType,
bool success,
bytes memory returndata
) private returns (bytes memory) {
if (execType == ERC7579Utils.EXECTYPE_DEFAULT) {
Address.verifyCallResult(success, returndata);
} else if (execType == ERC7579Utils.EXECTYPE_TRY) {
if (!success) emit ERC7579TryExecuteFail(index, returndata);
} else {
revert ERC7579UnsupportedExecType(execType);
}
return returndata;
}
}
// Operators
using {eqCallType as ==} for CallType global;
using {eqExecType as ==} for ExecType global;
using {eqModeSelector as ==} for ModeSelector global;
using {eqModePayload as ==} for ModePayload global;
/// @dev Compares two `CallType` values for equality.
function eqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) == CallType.unwrap(b);
}
/// @dev Compares two `ExecType` values for equality.
function eqExecType(ExecType a, ExecType b) pure returns (bool) {
return ExecType.unwrap(a) == ExecType.unwrap(b);
}
/// @dev Compares two `ModeSelector` values for equality.
function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) {
return ModeSelector.unwrap(a) == ModeSelector.unwrap(b);
}
/// @dev Compares two `ModePayload` values for equality.
function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) {
return ModePayload.unwrap(a) == ModePayload.unwrap(b);
}