Skip to content

Commit

Permalink
feat: update hook interface with tests
Browse files Browse the repository at this point in the history
Removes the need for an extra bool flag.
Still broken on some tests
  • Loading branch information
cpb8010 committed Jan 28, 2025
1 parent fc0af34 commit 65ecde7
Show file tree
Hide file tree
Showing 8 changed files with 394 additions and 54 deletions.
6 changes: 6 additions & 0 deletions src/helpers/Logger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ library Logger {
}
}

function logBytes4(bytes4 bytesToLog) internal view {
if (block.chainid == 260) {
console.logBytes4(bytesToLog);
}
}

function logBytes32(bytes32 bytesToLog) internal view {
if (block.chainid == 260) {
console.logBytes32(bytesToLog);
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IHook.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.24;

import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";
Expand Down
9 changes: 3 additions & 6 deletions src/interfaces/IHookManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,25 @@ interface IHookManager {
* @notice Add a hook to the list of hooks and call it's init function
* @dev Can only be called by self
* @param hook - Address of the hook
* @param isValidation bool - True if the hook is a validation hook, false otherwise
* @param initData bytes calldata - Data to pass to the hook's `onInstall` function
*/
function addHook(address hook, bool isValidation, bytes calldata initData) external;
function addHook(address hook, bytes calldata initData) external;

/**
* @notice Remove a hook from the list of hooks
* @dev Can only be called by self
* @param hook address - Address of the hook to remove
* @param isValidation bool - True if the hook is a validation hook, false otherwise
* @param deinitData bytes calldata - Data to pass to the hook's `onUninstall` function
*/
function removeHook(address hook, bool isValidation, bytes calldata deinitData) external;
function removeHook(address hook, bytes calldata deinitData) external;

/**
* @notice Remove a hook from the list of hooks while ignoring reverts from its `onUninstall` teardown function
* @dev Can only be called by self
* @param hook address - Address of the hook to remove
* @param isValidation bool - True if the hook is a validation hook, false otherwise
* @param deinitData bytes calldata - Data to pass to the hook's `onUninstall` function
*/
function unlinkHook(address hook, bool isValidation, bytes calldata deinitData) external;
function unlinkHook(address hook, bytes calldata deinitData) external;

/**
* @notice Check if an address is in the list of hooks
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ library Errors {

// ERC165 module errors
error VALIDATOR_ERC165_FAIL(address validator);
error HOOK_ERC165_FAIL(address hookAddress, bool isValidation);
error HOOK_ERC165_FAIL(address hookAddress);

// Auth errors
error NOT_FROM_BOOTLOADER(address notBootloader);
Expand Down
56 changes: 24 additions & 32 deletions src/managers/HookManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { IModule } from "../interfaces/IModule.sol";
/**
* @title Manager contract for hooks
* @notice Abstract contract for managing the enabled hooks of the account
* @dev Hook addresses are stored in a linked list
* @author https://getclave.io
*/
abstract contract HookManager is IHookManager, Auth {
Expand All @@ -27,19 +26,20 @@ abstract contract HookManager is IHookManager, Auth {
using ExcessivelySafeCall for address;

/// @inheritdoc IHookManager
function addHook(address hook, bool isValidation, bytes calldata initData) external override onlySelf {
_addHook(hook, isValidation, initData);
function addHook(address hook, bytes calldata initData) external override onlySelf {
_addHook(hook);
IModule(hook).onInstall(initData);
}

/// @inheritdoc IHookManager
function removeHook(address hook, bool isValidation, bytes calldata deinitData) external override onlySelf {
_removeHook(hook, isValidation);
function removeHook(address hook, bytes calldata deinitData) external override onlySelf {
_removeHook(hook);
IModule(hook).onUninstall(deinitData);
}

/// @inheritdoc IHookManager
function unlinkHook(address hook, bool isValidation, bytes calldata deinitData) external onlySelf {
_removeHook(hook, isValidation);
function unlinkHook(address hook, bytes calldata deinitData) external onlySelf {
_removeHook(hook);
hook.excessivelySafeCall(gasleft(), 0, abi.encodeWithSelector(IModule.onUninstall.selector, deinitData));
}

Expand Down Expand Up @@ -89,27 +89,29 @@ abstract contract HookManager is IHookManager, Auth {
}
}

function _addHook(address hook, bool isValidation, bytes calldata initData) internal {
if (!_supportsHook(hook, isValidation)) {
revert Errors.HOOK_ERC165_FAIL(hook, isValidation);
function _addHook(address hook) internal {
bool isExecutionHook = hook.supportsInterface(type(IExecutionHook).interfaceId);
bool isValidationHook = hook.supportsInterface(type(IValidationHook).interfaceId);
if (!isExecutionHook && !isValidationHook) {
revert Errors.HOOK_ERC165_FAIL(hook);
}

if (isValidation) {
_validationHooks().add(hook);
} else {
_executionHooks().add(hook);
if (isValidationHook) {
require(_validationHooks().add(hook), "Validation hook already exists");
}
if (isExecutionHook) {
require(_executionHooks().add(hook), "Execution hook already exists");
}

IModule(hook).onInstall(initData);

emit HookAdded(hook);
}

function _removeHook(address hook, bool isValidation) internal {
if (isValidation) {
_validationHooks().remove(hook);
} else {
_executionHooks().remove(hook);
function _removeHook(address hook) internal {
if (_validationHooks().contains(hook)) {
require(_validationHooks().remove(hook), "Validation hook not found");
}

if (_executionHooks().contains(hook)) {
require(_executionHooks().remove(hook), "Execution hook not found");
}

emit HookRemoved(hook);
Expand All @@ -132,14 +134,4 @@ abstract contract HookManager is IHookManager, Auth {
function _executionHooks() private view returns (EnumerableSet.AddressSet storage executionHooks) {
executionHooks = SsoStorage.layout().executionHooks;
}

function _supportsHook(address hook, bool isValidation) private view returns (bool) {
return
hook.supportsInterface(type(IModule).interfaceId) &&
(
isValidation
? hook.supportsInterface(type(IValidationHook).interfaceId)
: hook.supportsInterface(type(IExecutionHook).interfaceId)
);
}
}
137 changes: 137 additions & 0 deletions src/test/SampleHooks.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";

import { IModule } from "../interfaces/IModule.sol";
import { IExecutionHook, IValidationHook } from "../interfaces/IHook.sol";

abstract contract BaseHookValidator is IValidationHook {
/// @notice Emitted during validation
event Validating(address indexed accountAddress);

function onInstall(bytes calldata data) external override {}
function onUninstall(bytes calldata data) external override {}
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return
interfaceId == type(IValidationHook).interfaceId ||
interfaceId == type(IModule).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}

abstract contract BaseHookExecution is IExecutionHook {
/// @notice Emitted during execution
event Preexecute(address indexed accountAddress);
event Postexecute(address indexed accountAddress);

function onInstall(bytes calldata data) external override {}
function onUninstall(bytes calldata data) external override {}
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return
interfaceId == type(IExecutionHook).interfaceId ||
interfaceId == type(IModule).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}

/// @title FailHookValidator
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook validator that always fails (BURNS THE ACCOUNT)
contract FailHookValidator is BaseHookValidator {
function validationHook(bytes32, Transaction calldata) external override {
emit Validating(msg.sender);
require(false, "SampleHookValidator: validationHook failed");
}
}

/// @title SuccessHookValidator
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook validator that always passes
contract SuccessHookValidator is BaseHookValidator {
function validationHook(bytes32, Transaction calldata) external override {
emit Validating(msg.sender);
}
}

/// @title PreFailHookExecutor
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook executor that always fails on pre-execute (BURNS THE ACCOUNT)
contract PreFailHookExecutor is BaseHookExecution {
function preExecutionHook(Transaction calldata) external override returns (bytes memory _context) {
emit Preexecute(msg.sender);
require(false, "SampleHookExecutor: execution hook failed");
}

function postExecutionHook() external override {
emit Postexecute(msg.sender);
}
}

/// @title PostFailHookExecutor
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook executor that always fails on post-execute (BURNS THE ACCOUNT)
contract PostFailHookExecutor is BaseHookExecution {
function preExecutionHook(Transaction calldata) external override returns (bytes memory _context) {
emit Preexecute(msg.sender);
}

function postExecutionHook() external override {
emit Postexecute(msg.sender);
require(false, "SampleHookExecutor: executionHook failed");
}
}

/// @title SuccessHookExecutor
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook validator that always passes
contract SuccessHookExecutor is BaseHookExecution {
function preExecutionHook(Transaction calldata) external override returns (bytes memory _context) {
emit Preexecute(msg.sender);
}

function postExecutionHook() external override {
emit Postexecute(msg.sender);
}
}

/// @title SuccessBothHookExecutor
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev Sample hook execution and validator that always passes
contract SuccessBothHook is IExecutionHook, IValidationHook {
/// @notice Emitted during execution
event Preexecute(address indexed accountAddress);
event Postexecute(address indexed accountAddress);
/// @notice Emitted during validation
event Validating(address indexed accountAddress);

function onInstall(bytes calldata data) external {}
function onUninstall(bytes calldata data) external {}
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return
interfaceId == type(IValidationHook).interfaceId ||
interfaceId == type(IExecutionHook).interfaceId ||
interfaceId == type(IModule).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}

function preExecutionHook(Transaction calldata) external override returns (bytes memory _context) {
emit Preexecute(msg.sender);
}

function postExecutionHook() external override {
emit Postexecute(msg.sender);
}

function validationHook(bytes32, Transaction calldata) external override {
emit Validating(msg.sender);
}
}
Loading

0 comments on commit 65ecde7

Please sign in to comment.