// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; import "./Executor.sol"; /** * @title Module Manager - A contract managing Safe modules * @notice Modules are extensions with unlimited access to a Safe that can be added to a Safe by its owners. ⚠️ WARNING: Modules are a security risk since they can execute arbitrary transactions, so only trusted and audited modules should be added to a Safe. A malicious module can completely takeover a Safe. * @author Stefan George - @Georgi87 * @author Richard Meissner - @rmeissner */ abstract contract ModuleManager is SelfAuthorized, Executor { event EnabledModule(address indexed module); event DisabledModule(address indexed module); event ExecutionFromModuleSuccess(address indexed module); event ExecutionFromModuleFailure(address indexed module); address internal constant SENTINEL_MODULES = address(0x1); mapping(address => address) internal modules; /** * @notice Setup function sets the initial storage of the contract. * Optionally executes a delegate call to another contract to setup the modules. * @param to Optional destination address of call to execute. * @param data Optional data of call to execute. */ function setupModules(address to, bytes memory data) internal { require(modules[SENTINEL_MODULES] == address(0), "GS100"); modules[SENTINEL_MODULES] = SENTINEL_MODULES; if (to != address(0)) { require(isContract(to), "GS002"); // Setup has to complete successfully or transaction fails. require(execute(to, 0, data, Enum.Operation.DelegateCall, type(uint256).max), "GS000"); } } /** * @notice Enables the module `module` for the Safe. * @dev This can only be done via a Safe transaction. * @param module Module to be whitelisted. */ function enableModule(address module) public authorized { // Module address cannot be null or sentinel. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); // Module cannot be added twice. require(modules[module] == address(0), "GS102"); modules[module] = modules[SENTINEL_MODULES]; modules[SENTINEL_MODULES] = module; emit EnabledModule(module); } /** * @notice Disables the module `module` for the Safe. * @dev This can only be done via a Safe transaction. * @param prevModule Previous module in the modules linked list. * @param module Module to be removed. */ function disableModule(address prevModule, address module) public authorized { // Validate module address and check that it corresponds to module index. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); require(modules[prevModule] == module, "GS103"); modules[prevModule] = modules[module]; modules[module] = address(0); emit DisabledModule(module); } /** * @notice Execute `operation` (0: Call, 1: DelegateCall) to `to` with `value` (Native Token) * @dev Function is virtual to allow overriding for L2 singleton to emit an event for indexing. * @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. * @return success Boolean flag indicating if the call succeeded. */ function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) public virtual returns (bool success) { // Only whitelisted modules are allowed. require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104"); // Execute transaction without further confirmations. success = execute(to, value, data, operation, type(uint256).max); if (success) emit ExecutionFromModuleSuccess(msg.sender); else emit ExecutionFromModuleFailure(msg.sender); } /** * @notice Execute `operation` (0: Call, 1: DelegateCall) to `to` with `value` (Native Token) and return data * @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. * @return success Boolean flag indicating if the call succeeded. * @return returnData Data returned by the call. */ function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) public returns (bool success, bytes memory returnData) { success = execTransactionFromModule(to, value, data, operation); // solhint-disable-next-line no-inline-assembly assembly { // Load free memory location let ptr := mload(0x40) // We allocate memory for the return data by setting the free memory location to // current free memory location + data size + 32 bytes for data size value mstore(0x40, add(ptr, add(returndatasize(), 0x20))) // Store the size mstore(ptr, returndatasize()) // Store the data returndatacopy(add(ptr, 0x20), 0, returndatasize()) // Point the return data to the correct memory location returnData := ptr } } /** * @notice 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); } /** * @notice Returns an 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) { require(start == SENTINEL_MODULES || isModuleEnabled(start), "GS105"); require(pageSize > 0, "GS106"); // 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 loop will always iterate over the valid module list values and the `next` variable will either be an enabled module or a sentinel address (signalling the end). If we haven't reached the end inside the loop, we need to set the next pointer to the last element of the modules array because the `next` variable (which is a module by itself) acting as a pointer to the start of the next page is neither included to the current page, nor will it 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) } } /** * @notice Returns true if `account` is a contract. * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param account The address being queried */ function isContract(address account) internal view returns (bool) { uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } }