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

Base module #149

Merged
merged 12 commits into from
Nov 1, 2023
13 changes: 13 additions & 0 deletions contracts/interfaces/modules/base/IModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.13;

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

interface IModule {

event RequestPending(address indexed sender);
event RequestCompleted(address indexed sender);

function execute(bytes calldata selfParams, bytes[] calldata preHooksParams, bytes[] calldata postHooksParams) external;

}
25 changes: 22 additions & 3 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { IPAsset } from "contracts/lib/IPAsset.sol";
/// @title Errors
/// @notice Library for all contract errors, including a set of global errors.
library Errors {

////////////////////////////////////////////////////////////////////////////
// Globals //
////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -35,6 +34,23 @@ library Errors {
/// @notice The amount specified may not be zero.
error ZeroAmount();

////////////////////////////////////////////////////////////////////////////
// BaseModule //
////////////////////////////////////////////////////////////////////////////

/// @notice The caller to base module is not the module registry.
error BaseModule_CallerNotModuleRegistry();

////////////////////////////////////////////////////////////////////////////
// HookRegistry //
////////////////////////////////////////////////////////////////////////////

/// @notice The hook is already registered.
error HookRegistry_RegisteringDuplicatedHook();
error HookRegistry_RegisteringZeroAddressHook();
error HookRegistry_CallerNotAdmin();
error HookRegistry_MaxHooksExceeded();

////////////////////////////////////////////////////////////////////////////
// BaseRelationshipProcessor //
////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -67,7 +83,7 @@ library Errors {
////////////////////////////////////////////////////////////////////////////
// CollectPaymentModule //
////////////////////////////////////////////////////////////////////////////
//

/// @notice The configured collect module payment amount is invalid.
error CollectPaymentModule_AmountInvalid();

Expand Down Expand Up @@ -279,7 +295,10 @@ library Errors {
////////////////////////////////////////////////////////////////////////////

/// @notice Mismatch between parity of accounts and their respective allocations.
error RoyaltyNFT_AccountsAndAllocationsMismatch(uint256 accountsLength, uint256 allocationsLength);
error RoyaltyNFT_AccountsAndAllocationsMismatch(
uint256 accountsLength,
uint256 allocationsLength
);

/// @notice Invalid summation for royalty NFT allocations.
error RoyaltyNFT_InvalidAllocationsSum(uint32 allocationsSum);
Expand Down
59 changes: 59 additions & 0 deletions contracts/modules/base/BaseModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.13;

import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol";
import { HookRegistry } from "./HookRegistry.sol";
import { Errors } from "contracts/lib/Errors.sol";

abstract contract BaseModule is IModule, HookRegistry {
Ramarti marked this conversation as resolved.
Show resolved Hide resolved

struct ModuleConstruction {
address ipaRegistry;
address moduleRegistry;
}

address public immutable IPA_REGISTRY;
address public immutable MODULE_REGISTRY;

modifier onlyModuleRegistry() {
// TODO: Enforce this
// if (msg.sender != MODULE_REGISTRY)
// revert Errors.BaseModule_CallerNotModuleRegistry();
_;
}

constructor(ModuleConstruction memory params) {
IPA_REGISTRY = params.ipaRegistry;
MODULE_REGISTRY = params.moduleRegistry;
}

function execute(
address caller,
bytes calldata selfParams,
bytes[] calldata preHookParams,
bytes[] calldata postHookParams
) external onlyModuleRegistry {
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
_verifyExecution(caller, selfParams);
if (!_executePreHooks(preHookParams)) {
emit RequestPending(caller);
return;
}
_performAction(selfParams);
_executePostHooks(postHookParams);
emit RequestCompleted(caller);
}

function configure(bytes calldata params) external onlyModuleRegistry {
_configure(msg.sender, params);
}
Ramarti marked this conversation as resolved.
Show resolved Hide resolved

function _hookRegistryAdmin() virtual override internal view returns (address);
function _configure(address caller, bytes calldata params) virtual internal;
function _verifyExecution(address caller, bytes calldata selfParams) virtual internal {}
function _executePreHooks(bytes[] calldata params) virtual internal returns (bool) {}
function _performAction(bytes calldata params) virtual internal {}
function _executePostHooks(bytes[] calldata params) virtual internal {}

}
122 changes: 122 additions & 0 deletions contracts/modules/base/HookRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.13;

import { Errors } from "contracts/lib/Errors.sol";

abstract contract HookRegistry {
enum HookType {
PreAction,
PostAction
}

address[] private _preActionHooks;
address[] private _postActionHooks;

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);

modifier onlyHookRegistryAdmin() {
if (msg.sender != _hookRegistryAdmin())
revert Errors.HookRegistry_CallerNotAdmin();
_;
}

function registerHooks(
HookType hType_,
address[] calldata hooks_
) external onlyHookRegistryAdmin {
clearHooks(hType_);
_registerHooks(_hooksForType(hType_), hooks_);
emit HooksRegistered(hType_, hooks_);
}

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

function hookAt(
HookType hType_,
uint256 index_
) external view returns (address) {
return _hooksForType(hType_)[index_];
}

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

function clearHooks(
HookType hType_
) public onlyHookRegistryAdmin {
if (hType_ == HookType.PreAction && _preActionHooks.length > 0) {
delete _preActionHooks;
} else if (_postActionHooks.length > 0) {
delete _postActionHooks;
}
emit HooksCleared(hType_);
}

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

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

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

function _registerHooks(
address[] storage hooks_,
address[] memory newHooks_
) private {
uint256 newLength = newHooks_.length;
if (newLength > MAX_HOOKS) {
revert Errors.HookRegistry_MaxHooksExceeded();
}
unchecked {
for (uint256 i = 0; i < newLength; i++) {
if (newHooks_[i] == address(0)) {
revert Errors.HookRegistry_RegisteringZeroAddressHook();
}
if (i > 0 && newHooks_[i] == newHooks_[i - 1]) {
revert Errors.HookRegistry_RegisteringDuplicatedHook();
}
hooks_.push(newHooks_[i]);
}
}
}

function _hookIndex(
address[] storage hooks,
address hook_
) private view returns (uint256) {
uint256 length = hooks.length;
for (uint256 i = 0; i < length; ) {
if (hooks[i] == hook_) {
return i;
}
unchecked {
i++;
}
}
return INDEX_NOT_FOUND;
}
}
24 changes: 24 additions & 0 deletions test/foundry/mocks/MockHookRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.18;

import { HookRegistry } from "contracts/modules/base/HookRegistry.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;
}

function _hookRegistryAdmin()
internal
view
virtual
override
returns (address)
{
return ADMIN;
}
}
Loading