Skip to content

Commit

Permalink
Separate Modifier and Module into normal and Guardable
Browse files Browse the repository at this point in the history
  • Loading branch information
cristovaoth committed Oct 2, 2023
1 parent 7f4a961 commit 3e9234a
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 240 deletions.
143 changes: 143 additions & 0 deletions contracts/core/MiniAvatar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "../signature/SignatureChecker.sol";

abstract contract MiniAvatar is SignatureChecker, OwnableUpgradeable {
event EnabledModule(address module);
event DisabledModule(address module);
event ExecutionFromModuleSuccess(address indexed module);
event ExecutionFromModuleFailure(address indexed module);

address internal constant SENTINEL_MODULES = address(0x1);
/// Mapping of modules.
mapping(address => address) internal modules;

/// `sender` is not an authorized module.
/// @param sender The address of the sender.
error NotAuthorized(address sender);

/// `module` is invalid.
error InvalidModule(address module);

/// `pageSize` is invalid.
error InvalidPageSize();

/// `module` is already disabled.
error AlreadyDisabledModule(address module);

/// `module` is already enabled.
error AlreadyEnabledModule(address module);

/// @dev `setModules()` was already called.
error SetupModulesAlreadyCalled();

/*
--------------------------------------------------
*/

modifier moduleOnly() {
if (modules[msg.sender] == address(0)) {
if (modules[moduleTxSignedBy()] == address(0)) {
revert NotAuthorized(msg.sender);
}
moduleTxNonceBump();
}

_;
}

/// @dev Disables a module.
/// @notice This can only be called by the owner.
/// @param prevModule Module that pointed to the module to be removed in the linked list.
/// @param module Module to be removed.
function disableModule(
address prevModule,
address module
) public onlyOwner {
if (module == address(0) || module == SENTINEL_MODULES)
revert InvalidModule(module);
if (modules[prevModule] != module) revert AlreadyDisabledModule(module);
modules[prevModule] = modules[module];
modules[module] = address(0);
emit DisabledModule(module);
}

/// @dev Enables a module that can add transactions to the queue
/// @param module Address of the module to be enabled
/// @notice This can only be called by the owner
function enableModule(address module) public onlyOwner {
if (module == address(0) || module == SENTINEL_MODULES)
revert InvalidModule(module);
if (modules[module] != address(0)) revert AlreadyEnabledModule(module);
modules[module] = modules[SENTINEL_MODULES];
modules[SENTINEL_MODULES] = module;
emit EnabledModule(module);
}

/// @dev Returns if an module is enabled
/// @return True if the module is enabled
function isModuleEnabled(address _module) public view returns (bool) {
return SENTINEL_MODULES != _module && modules[_module] != address(0);
}

/// @dev Returns array of modules.
/// If all entries fit into a single page, the next pointer will be 0x1.
/// If another page is present, next will be the last element of the returned array.
/// @param start Start of the page. Has to be a module or start pointer (0x1 address)
/// @param pageSize Maximum number of modules that should be returned. Has to be > 0
/// @return array Array of modules.
/// @return next Start of the next page.
function getModulesPaginated(
address start,
uint256 pageSize
) external view returns (address[] memory array, address next) {
if (start != SENTINEL_MODULES && !isModuleEnabled(start)) {
revert InvalidModule(start);
}
if (pageSize == 0) {
revert InvalidPageSize();
}

// Init array with max page size
array = new address[](pageSize);

// Populate return array
uint256 moduleCount = 0;
next = modules[start];
while (
next != address(0) &&
next != SENTINEL_MODULES &&
moduleCount < pageSize
) {
array[moduleCount] = next;
next = modules[next];
moduleCount++;
}

// Because of the argument validation we can assume that
// the `currentModule` will always be either a module address
// or sentinel address (aka the end). If we haven't reached the end
// inside the loop, we need to set the next pointer to the last element
// because it skipped over to the next module which is neither included
// in the current page nor won't be included in the next one
// if you pass it as a start.
if (next != SENTINEL_MODULES) {
next = array[moduleCount - 1];
}
// Set correct size of returned array
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(array, moduleCount)
}
}

/// @dev Initializes the modules linked list.
/// @notice Should be called as part of the `setUp` / initializing function and can only be called once.
function setupModules() internal {
if (modules[SENTINEL_MODULES] != address(0))
revert SetupModulesAlreadyCalled();
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
}
}
145 changes: 6 additions & 139 deletions contracts/core/Modifier.sol
Original file line number Diff line number Diff line change
@@ -1,36 +1,13 @@
// SPDX-License-Identifier: LGPL-3.0-only

/// @title Modifier Interface - A contract that sits between a Module and an Avatar and enforce some additional logic.
pragma solidity >=0.7.0 <0.9.0;

import "./Module.sol";
import "../interfaces/IAvatar.sol";
import "../signature/SignatureChecker.sol";

abstract contract Modifier is Module, IAvatar, SignatureChecker {
address internal constant SENTINEL_MODULES = address(0x1);
/// Mapping of modules.
mapping(address => address) internal modules;

/// `sender` is not an authorized module.
/// @param sender The address of the sender.
error NotAuthorized(address sender);

/// `module` is invalid.
error InvalidModule(address module);

/// `pageSize` is invalid.
error InvalidPageSize();

/// `module` is already disabled.
error AlreadyDisabledModule(address module);

/// `module` is already enabled.
error AlreadyEnabledModule(address module);

/// @dev `setModules()` was already called.
error SetupModulesAlreadyCalled();
import "./MiniAvatar.sol";
import "./Module.sol";

/// @title Modifier Interface - A contract that sits between a Module and an Avatar and enforces some additional logic.
abstract contract Modifier is Module, MiniAvatar, IAvatar {
/*
--------------------------------------------------
You must override at least one of following two virtual functions,
Expand All @@ -48,7 +25,7 @@ abstract contract Modifier is Module, IAvatar, SignatureChecker {
uint256 value,
bytes calldata data,
Enum.Operation operation
) public virtual override moduleOnly returns (bool success) {}
) public virtual override onlyOwner returns (bool success) {}

/// @dev Passes a transaction to the modifier, expects return data.
/// @notice Can only be called by enabled modules.
Expand All @@ -65,117 +42,7 @@ abstract contract Modifier is Module, IAvatar, SignatureChecker {
public
virtual
override
moduleOnly
onlyOwner
returns (bool success, bytes memory returnData)
{}

/*
--------------------------------------------------
*/

modifier moduleOnly() {
if (modules[msg.sender] == address(0)) {
if (modules[moduleTxSignedBy()] == address(0)) {
revert NotAuthorized(msg.sender);
}
moduleTxNonceBump();
}

_;
}

/// @dev Disables a module on the modifier.
/// @notice This can only be called by the owner.
/// @param prevModule Module that pointed to the module to be removed in the linked list.
/// @param module Module to be removed.
function disableModule(
address prevModule,
address module
) public override onlyOwner {
if (module == address(0) || module == SENTINEL_MODULES)
revert InvalidModule(module);
if (modules[prevModule] != module) revert AlreadyDisabledModule(module);
modules[prevModule] = modules[module];
modules[module] = address(0);
emit DisabledModule(module);
}

/// @dev Enables a module that can add transactions to the queue
/// @param module Address of the module to be enabled
/// @notice This can only be called by the owner
function enableModule(address module) public override onlyOwner {
if (module == address(0) || module == SENTINEL_MODULES)
revert InvalidModule(module);
if (modules[module] != address(0)) revert AlreadyEnabledModule(module);
modules[module] = modules[SENTINEL_MODULES];
modules[SENTINEL_MODULES] = module;
emit EnabledModule(module);
}

/// @dev Returns if an module is enabled
/// @return True if the module is enabled
function isModuleEnabled(
address _module
) public view override returns (bool) {
return SENTINEL_MODULES != _module && modules[_module] != address(0);
}

/// @dev Returns array of modules.
/// If all entries fit into a single page, the next pointer will be 0x1.
/// If another page is present, next will be the last element of the returned array.
/// @param start Start of the page. Has to be a module or start pointer (0x1 address)
/// @param pageSize Maximum number of modules that should be returned. Has to be > 0
/// @return array Array of modules.
/// @return next Start of the next page.
function getModulesPaginated(
address start,
uint256 pageSize
) external view override returns (address[] memory array, address next) {
if (start != SENTINEL_MODULES && !isModuleEnabled(start)) {
revert InvalidModule(start);
}
if (pageSize == 0) {
revert InvalidPageSize();
}

// Init array with max page size
array = new address[](pageSize);

// Populate return array
uint256 moduleCount = 0;
next = modules[start];
while (
next != address(0) &&
next != SENTINEL_MODULES &&
moduleCount < pageSize
) {
array[moduleCount] = next;
next = modules[next];
moduleCount++;
}

// Because of the argument validation we can assume that
// the `currentModule` will always be either a module address
// or sentinel address (aka the end). If we haven't reached the end
// inside the loop, we need to set the next pointer to the last element
// because it skipped over to the next module which is neither included
// in the current page nor won't be included in the next one
// if you pass it as a start.
if (next != SENTINEL_MODULES) {
next = array[moduleCount - 1];
}
// Set correct size of returned array
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(array, moduleCount)
}
}

/// @dev Initializes the modules linked list.
/// @notice Should be called as part of the `setUp` / initializing function and can only be called once.
function setupModules() internal {
if (modules[SENTINEL_MODULES] != address(0))
revert SetupModulesAlreadyCalled();
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
}
}
47 changes: 47 additions & 0 deletions contracts/core/ModifierGuardable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import "../interfaces/IAvatar.sol";
import "./MiniAvatar.sol";
import "./ModuleGuardable.sol";

/// @title Modifier Interface - A contract that sits between a Module and an Avatar and enforce some additional logic.
abstract contract ModifierGuardable is ModuleGuardable, MiniAvatar, IAvatar {
/*
--------------------------------------------------
You must override at least one of following two virtual functions,
execTransactionFromModule() and execTransactionFromModuleReturnData().
*/

/// @dev Passes a transaction to the modifier.
/// @notice Can only be called by enabled modules.
/// @param to Destination address of module transaction.
/// @param value Ether value of module transaction.
/// @param data Data payload of module transaction.
/// @param operation Operation type of module transaction.
function execTransactionFromModule(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation
) public virtual override onlyOwner returns (bool success) {}

/// @dev Passes a transaction to the modifier, expects return data.
/// @notice Can only be called by enabled modules.
/// @param to Destination address of module transaction.
/// @param value Ether value of module transaction.
/// @param data Data payload of module transaction.
/// @param operation Operation type of module transaction.
function execTransactionFromModuleReturnData(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation
)
public
virtual
override
onlyOwner
returns (bool success, bytes memory returnData)
{}
}
Loading

0 comments on commit 3e9234a

Please sign in to comment.