Skip to content

Commit

Permalink
Refactor HookRegistry to support multiple config domain
Browse files Browse the repository at this point in the history
Introdce registryKey to separate different config domain
Integrating BaseModule and Hooks
  • Loading branch information
kingster-will committed Nov 14, 2023
1 parent db7b2cb commit c419f01
Show file tree
Hide file tree
Showing 9 changed files with 699 additions and 145 deletions.
2 changes: 2 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ library Errors {
error HookRegistry_RegisteringZeroAddressHook();
error HookRegistry_CallerNotAdmin();
error HookRegistry_MaxHooksExceeded();
error HookRegistry_HooksConfigLengthMismatch();
error HookRegistry_IndexOutOfBounds(uint hooksIndex);

////////////////////////////////////////////////////////////////////////////
// BaseRelationshipProcessor //
Expand Down
38 changes: 32 additions & 6 deletions contracts/modules/base/BaseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.13;

import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
import { IHook, HookResult } from "contracts/interfaces/hooks/base/IHook.sol";
import { Hook } from "contracts/lib/hooks/Hook.sol";
import { HookRegistry } from "./HookRegistry.sol";
import { Errors } from "contracts/lib/Errors.sol";
import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";
Expand Down Expand Up @@ -63,12 +65,13 @@ abstract contract BaseModule is IModule, HookRegistry {
bytes[] calldata postHookParams_
) external onlyModuleRegistry returns (bytes memory result) {
_verifyExecution(ipOrg_, caller_, moduleParams_);
if (!_executeHooks(preHookParams_, HookType.PreAction)) {
bytes32 registryKey = _hookRegistryKey(ipOrg_, caller_, moduleParams_);
if (!_executeHooks(preHookParams_, HookType.PreAction, registryKey)) {
emit RequestPending(caller_);
return "";
}
result = _performAction(ipOrg_, caller_, moduleParams_);
_executeHooks(postHookParams_, HookType.PostAction);
_executeHooks(postHookParams_, HookType.PostAction, registryKey);
emit RequestCompleted(caller_);
return result;
}
Expand All @@ -81,21 +84,44 @@ abstract contract BaseModule is IModule, HookRegistry {
_configure(ipOrg_, caller_, params_);
}

function _executeHooks(bytes[] calldata params_, HookRegistry.HookType hType_) virtual internal returns (bool) {
address[] memory hooks = _hooksForType(hType_);
function _executeHooks(
bytes[] calldata params_,
HookRegistry.HookType hType_,
bytes32 registryKey_
) virtual internal returns (bool) {
address[] memory hooks = _hooksForType(hType_, registryKey_);
bytes[] memory hooksConfig = _hooksConfigForType(hType_, registryKey_);
uint256 hooksLength = hooks.length;
if (params_.length != hooksLength) {
revert Errors.BaseModule_HooksParamsLengthMismatch(uint8(hType_));
}
for (uint256 i = 0; i < hooksLength; i++) {
// TODO: hook execution and return false if a hook returns false
if (!_executeHook(hType_, hooks[i], hooksConfig[i], params_[i])) {
return false;
}
}
return true;
}

/// @dev Subclasses should override this function, if need to support Async hooks.
function _executeHook(
HookRegistry.HookType,
address hook,
bytes memory hookConfig_,
bytes memory hookParams_
) internal virtual returns (bool) {
Hook.ExecutionContext memory context = Hook.ExecutionContext({
config: hookConfig_,
params: hookParams_
});
HookResult result;
(result,) = IHook(hook).executeSync(abi.encode(context));
return result == HookResult.Completed;
}

function _hookRegistryAdmin() virtual override internal view returns (address);
function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal;
function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal {}
function _performAction(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual internal returns (bytes memory result) {}

function _hookRegistryKey(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal view virtual returns(bytes32);
}
108 changes: 84 additions & 24 deletions contracts/modules/base/HookRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@
pragma solidity ^0.8.13;

import { Errors } from "contracts/lib/Errors.sol";
import { IHook } from "contracts/interfaces/hooks/base/IHook.sol";

abstract contract HookRegistry {
enum HookType {
PreAction,
PostAction
}

address[] private _preActionHooks;
address[] private _postActionHooks;
mapping(bytes32 => address[]) private _preActionHooks;
mapping(bytes32 => address[]) private _postActionHooks;

mapping(bytes32 => bytes[]) private _preActionHooksConfig;
mapping(bytes32 => bytes[]) private _postActionHooksConfig;

uint256 public constant INDEX_NOT_FOUND = type(uint256).max;
uint256 public constant MAX_HOOKS = 10;

event HooksRegistered(HookType indexed hType, address[] indexed hook);
event HooksCleared(HookType indexed hType);
event HooksRegistered(HookType indexed hType, bytes32 indexed registryKey, address[] indexed hook);
event HooksCleared(HookType indexed hType, bytes32 indexed registryKey);

modifier onlyHookRegistryAdmin() {
if (msg.sender != _hookRegistryAdmin())
Expand All @@ -26,71 +30,125 @@ abstract contract HookRegistry {

function registerHooks(
HookType hType_,
address[] calldata hooks_
) external onlyHookRegistryAdmin {
clearHooks(hType_);
_registerHooks(_hooksForType(hType_), hooks_);
emit HooksRegistered(hType_, hooks_);
bytes32 registryKey_,
address[] calldata hooks_,
bytes[] calldata hooksConfig_
) public onlyHookRegistryAdmin {
clearHooks(hType_, registryKey_);
_registerHooks(
_hooksForType(hType_, registryKey_),
_hooksConfigForType(hType_, registryKey_),
hooks_,
hooksConfig_
);
emit HooksRegistered(hType_, registryKey_, hooks_);
}

function isRegistered(
HookType hType_,
bytes32 registryKey_,
address hook_
) external view returns (bool) {
return hookIndex(hType_, hook_) != INDEX_NOT_FOUND;
return hookIndex(hType_, registryKey_, hook_) != INDEX_NOT_FOUND;
}

function hookAt(
HookType hType_,
bytes32 registryKey_,
uint256 index_
) external view returns (address) {
return _hooksForType(hType_)[index_];
address[] memory hooks = _hooksForType(hType_, registryKey_);
if (index_ >= hooks.length) {
revert Errors.HookRegistry_IndexOutOfBounds(index_);
}
return _hooksForType(hType_, registryKey_)[index_];
}

function hookConfigAt(
HookType hType_,
bytes32 registryKey_,
uint256 index_
) external view returns (bytes memory) {
bytes[] memory hooksConfig = _hooksConfigForType(hType_, registryKey_);
if (index_ >= hooksConfig.length) {
revert Errors.HookRegistry_IndexOutOfBounds(index_);
}
return _hooksConfigForType(hType_, registryKey_)[index_];
}

function totalHooks(
HookType hType_
HookType hType_,
bytes32 registryKey_
) external view returns (uint256) {
return _hooksForType(hType_, registryKey_).length;
}

function totalHooksConfig(
HookType hType_,
bytes32 registryKey_
) external view returns (uint256) {
return _hooksForType(hType_).length;
return _hooksConfigForType(hType_, registryKey_).length;
}

function clearHooks(
HookType hType_
HookType hType_,
bytes32 registryKey_
) public onlyHookRegistryAdmin {
if (hType_ == HookType.PreAction && _preActionHooks.length > 0) {
delete _preActionHooks;
} else if (_postActionHooks.length > 0) {
delete _postActionHooks;
if (hType_ == HookType.PreAction && _preActionHooks[registryKey_].length > 0) {
delete _preActionHooks[registryKey_];
delete _preActionHooksConfig[registryKey_];
} else if (_postActionHooks[registryKey_].length > 0) {
delete _postActionHooks[registryKey_];
delete _postActionHooksConfig[registryKey_];
}
emit HooksCleared(hType_);
emit HooksCleared(hType_, registryKey_);
}

function hookIndex(
HookType hType_,
bytes32 registryKey_,
address hook_
) public view returns (uint256) {
return _hookIndex(_hooksForType(hType_), hook_);
return _hookIndex(_hooksForType(hType_, registryKey_), hook_);
}

function _hookRegistryAdmin() internal view virtual returns (address);

function _hooksForType(
HookType hType_
HookType hType_,
bytes32 registryKey_
) internal view returns (address[] storage) {
if (hType_ == HookType.PreAction) {
return _preActionHooks;
return _preActionHooks[registryKey_];
} else {
return _postActionHooks;
return _postActionHooks[registryKey_];
}
}

function _hooksConfigForType(
HookType hType_,
bytes32 registryKey_
) internal view returns (bytes[] storage) {
if (hType_ == HookType.PreAction) {
return _preActionHooksConfig[registryKey_];
} else {
return _postActionHooksConfig[registryKey_];
}
}

function _registerHooks(
address[] storage hooks_,
address[] memory newHooks_
bytes[] storage hooksConfig_,
address[] memory newHooks_,
bytes[] memory newHooksConfig_
) private {
uint256 newLength = newHooks_.length;
if (newLength > MAX_HOOKS) {
revert Errors.HookRegistry_MaxHooksExceeded();
}
if (newHooksConfig_.length != newLength) {
revert Errors.HookRegistry_HooksConfigLengthMismatch();
}
unchecked {
for (uint256 i = 0; i < newLength; i++) {
if (newHooks_[i] == address(0)) {
Expand All @@ -99,7 +157,9 @@ abstract contract HookRegistry {
if (i > 0 && newHooks_[i] == newHooks_[i - 1]) {
revert Errors.HookRegistry_RegisteringDuplicatedHook();
}
IHook(newHooks_[i]).validateConfig(newHooksConfig_[i]);
hooks_.push(newHooks_[i]);
hooksConfig_.push(newHooksConfig_[i]);
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions contracts/modules/relationships/RelationshipModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,13 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled
);
return abi.encode(relationshipId);
}

function _hookRegistryKey(
IIPOrg ipOrg_,
address,
bytes calldata params_
) internal view virtual override returns(bytes32) {
LibRelationship.CreateRelationshipParams memory createParams = abi.decode(params_, (LibRelationship.CreateRelationshipParams));
return keccak256(abi.encode(address(ipOrg_), createParams.relType));
}
}
42 changes: 42 additions & 0 deletions test/foundry/mocks/MockBaseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";
contract MockBaseModule is BaseModule {
address private _admin;

struct ModuleExecutionParams {
uint256 paramA;
uint256 paramC;
string someHookRegisteringRelatedInfo;
}

struct BaseModuleCall {
address ipOrg;
address caller;
Expand Down Expand Up @@ -64,4 +70,40 @@ contract MockBaseModule is BaseModule {
_callStack.push(BaseModuleCall(address(ipOrg_), caller_, params_));
return "";
}

function registerHooks(
HookType hType_,
IIPOrg ipOrg_,
string memory someHookRegisteringRelatedInfo_,
address[] calldata hooks_,
bytes[] calldata hooksConfig_
) external onlyHookRegistryAdmin {
bytes32 registryKey = _generateRegistryKey(address(ipOrg_), someHookRegisteringRelatedInfo_);
registerHooks(hType_, registryKey, hooks_, hooksConfig_);
}

function hookRegistryKey(
address ipOrg_,
string calldata someHookRegisteringRelatedInfo_
) external pure returns(bytes32) {
return _generateRegistryKey(ipOrg_, someHookRegisteringRelatedInfo_);
}

function _hookRegistryKey(
IIPOrg ipOrg_,
address,
bytes calldata params_
) internal view virtual override returns(bytes32) {
ModuleExecutionParams memory moduleParams = abi.decode(params_, (ModuleExecutionParams));
return _generateRegistryKey(address(ipOrg_), moduleParams.someHookRegisteringRelatedInfo);
}

function _generateRegistryKey(
address ipOrg_,
string memory someHookRegisteringRelatedInfo_
) private pure returns(bytes32) {
return keccak256(abi.encode(ipOrg_, someHookRegisteringRelatedInfo_));
}


}
16 changes: 16 additions & 0 deletions test/foundry/mocks/MockHookRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
pragma solidity ^0.8.18;

import { HookRegistry } from "contracts/modules/base/HookRegistry.sol";
import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol";

/// @title Mock Hook Registry
/// @notice This mock contract is used for testing the base hook registry.
contract MockHookRegistry is HookRegistry {
address public immutable ADMIN;


constructor() {
ADMIN = msg.sender;
}
Expand All @@ -21,4 +23,18 @@ contract MockHookRegistry is HookRegistry {
{
return ADMIN;
}

function hookRegistryKey(
address ipOrg_,
string calldata someHookRegisteringRelatedInfo_
) public pure returns(bytes32) {
return _generateRegistryKey(ipOrg_, someHookRegisteringRelatedInfo_);
}

function _generateRegistryKey(
address ipOrg_,
string memory someHookRegisteringRelatedInfo_
) private pure returns(bytes32) {
return keccak256(abi.encode(ipOrg_, someHookRegisteringRelatedInfo_));
}
}
Loading

0 comments on commit c419f01

Please sign in to comment.