pragma solidity ^0.5.17; import "../../lib/os/IsContract.sol"; import "./Modules.sol"; import "./IModuleCache.sol"; import "../clock/ProtocolClock.sol"; import "../config/ProtocolConfig.sol"; contract Controller is IsContract, Modules, ProtocolClock, ProtocolConfig { string private constant ERROR_SENDER_NOT_GOVERNOR = "CTR_SENDER_NOT_GOVERNOR"; string private constant ERROR_INVALID_GOVERNOR_ADDRESS = "CTR_INVALID_GOVERNOR_ADDRESS"; string private constant ERROR_MODULE_NOT_SET = "CTR_MODULE_NOT_SET"; string private constant ERROR_MODULE_ALREADY_ENABLED = "CTR_MODULE_ALREADY_ENABLED"; string private constant ERROR_MODULE_ALREADY_DISABLED = "CTR_MODULE_ALREADY_DISABLED"; string private constant ERROR_CUSTOM_FUNCTION_NOT_SET = "CTR_CUSTOM_FUNCTION_NOT_SET"; string private constant ERROR_IMPLEMENTATION_NOT_CONTRACT = "CTR_IMPLEMENTATION_NOT_CONTRACT"; string private constant ERROR_INVALID_IMPLS_INPUT_LENGTH = "CTR_INVALID_IMPLS_INPUT_LENGTH"; address private constant ZERO_ADDRESS = address(0); /** * @dev Governor of the whole system. Set of three addresses to recover funds, change configuration settings and setup modules */ struct Governor { address funds; // This address can be unset at any time. It is allowed to recover funds from the ControlledRecoverable modules address config; // This address is meant not to be unset. It is allowed to change the different configurations of the whole system address modules; // This address can be unset at any time. It is allowed to plug/unplug modules from the system } /** * @dev Module information */ struct Module { bytes32 id; // ID associated to a module bool disabled; // Whether the module is disabled } // Governor addresses of the system Governor private governor; // List of current modules registered for the system indexed by ID mapping (bytes32 => address) internal currentModules; // List of all modules registered for the system indexed by address mapping (address => Module) internal allModules; // List of custom functions indexed by signature mapping (bytes4 => address) internal customFunctions; event ModuleSet(bytes32 id, address addr); event ModuleEnabled(bytes32 id, address addr); event ModuleDisabled(bytes32 id, address addr); event CustomFunctionSet(bytes4 signature, address target); event FundsGovernorChanged(address previousGovernor, address currentGovernor); event ConfigGovernorChanged(address previousGovernor, address currentGovernor); event ModulesGovernorChanged(address previousGovernor, address currentGovernor); /** * @dev Ensure the msg.sender is the funds governor */ modifier onlyFundsGovernor { require(msg.sender == governor.funds, ERROR_SENDER_NOT_GOVERNOR); _; } /** * @dev Ensure the msg.sender is the modules governor */ modifier onlyConfigGovernor { require(msg.sender == governor.config, ERROR_SENDER_NOT_GOVERNOR); _; } /** * @dev Ensure the msg.sender is the modules governor */ modifier onlyModulesGovernor { require(msg.sender == governor.modules, ERROR_SENDER_NOT_GOVERNOR); _; } /** * @dev Constructor function * @param _termParams Array containing: * 0. _termDuration Duration in seconds per term * 1. _firstTermStartTime Timestamp in seconds when the protocol will open (to give time for guardian on-boarding) * @param _governors Array containing: * 0. _fundsGovernor Address of the funds governor * 1. _configGovernor Address of the config governor * 2. _modulesGovernor Address of the modules governor * @param _feeToken Address of the token contract that is used to pay for fees * @param _fees Array containing: * 0. guardianFee Amount of fee tokens that is paid per guardian per dispute * 1. draftFee Amount of fee tokens per guardian to cover the drafting cost * 2. settleFee Amount of fee tokens per guardian to cover round settlement cost * @param _roundStateDurations Array containing the durations in terms of the different phases of a dispute: * 0. evidenceTerms Max submitting evidence period duration in terms * 1. commitTerms Commit period duration in terms * 2. revealTerms Reveal period duration in terms * 3. appealTerms Appeal period duration in terms * 4. appealConfirmationTerms Appeal confirmation period duration in terms * @param _pcts Array containing: * 0. penaltyPct Permyriad of min active tokens balance to be locked to each drafted guardians (‱ - 1/10,000) * 1. finalRoundReduction Permyriad of fee reduction for the last appeal round (‱ - 1/10,000) * @param _roundParams Array containing params for rounds: * 0. firstRoundGuardiansNumber Number of guardians to be drafted for the first round of disputes * 1. appealStepFactor Increasing factor for the number of guardians of each round of a dispute * 2. maxRegularAppealRounds Number of regular appeal rounds before the final round is triggered * 3. finalRoundLockTerms Number of terms that a coherent guardian in a final round is disallowed to withdraw (to prevent 51% attacks) * @param _appealCollateralParams Array containing params for appeal collateral: * 1. appealCollateralFactor Permyriad multiple of dispute fees required to appeal a preliminary ruling * 2. appealConfirmCollateralFactor Permyriad multiple of dispute fees required to confirm appeal * @param _minActiveBalance Minimum amount of guardian tokens that can be activated */ constructor( uint64[2] memory _termParams, address[3] memory _governors, ERC20 _feeToken, uint256[3] memory _fees, uint64[5] memory _roundStateDurations, uint16[2] memory _pcts, uint64[4] memory _roundParams, uint256[2] memory _appealCollateralParams, uint256 _minActiveBalance ) public ProtocolClock(_termParams) ProtocolConfig(_feeToken, _fees, _roundStateDurations, _pcts, _roundParams, _appealCollateralParams, _minActiveBalance) { _setFundsGovernor(_governors[0]); _setConfigGovernor(_governors[1]); _setModulesGovernor(_governors[2]); } /** * @dev Fallback function allows to forward calls to a specific address in case it was previously registered * Note the sender will be always the controller in case it is forwarded */ function () external payable { address target = customFunctions[msg.sig]; require(target != address(0), ERROR_CUSTOM_FUNCTION_NOT_SET); // solium-disable-next-line security/no-call-value (bool success,) = address(target).call.value(msg.value)(msg.data); assembly { let size := returndatasize let ptr := mload(0x40) returndatacopy(ptr, 0, size) let result := success switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } /** * @notice Change Protocol configuration params * @param _fromTermId Identification number of the term in which the config will be effective at * @param _feeToken Address of the token contract that is used to pay for fees * @param _fees Array containing: * 0. guardianFee Amount of fee tokens that is paid per guardian per dispute * 1. draftFee Amount of fee tokens per guardian to cover the drafting cost * 2. settleFee Amount of fee tokens per guardian to cover round settlement cost * @param _roundStateDurations Array containing the durations in terms of the different phases of a dispute: * 0. evidenceTerms Max submitting evidence period duration in terms * 1. commitTerms Commit period duration in terms * 2. revealTerms Reveal period duration in terms * 3. appealTerms Appeal period duration in terms * 4. appealConfirmationTerms Appeal confirmation period duration in terms * @param _pcts Array containing: * 0. penaltyPct Permyriad of min active tokens balance to be locked to each drafted guardians (‱ - 1/10,000) * 1. finalRoundReduction Permyriad of fee reduction for the last appeal round (‱ - 1/10,000) * @param _roundParams Array containing params for rounds: * 0. firstRoundGuardiansNumber Number of guardians to be drafted for the first round of disputes * 1. appealStepFactor Increasing factor for the number of guardians of each round of a dispute * 2. maxRegularAppealRounds Number of regular appeal rounds before the final round is triggered * 3. finalRoundLockTerms Number of terms that a coherent guardian in a final round is disallowed to withdraw (to prevent 51% attacks) * @param _appealCollateralParams Array containing params for appeal collateral: * 1. appealCollateralFactor Permyriad multiple of dispute fees required to appeal a preliminary ruling * 2. appealConfirmCollateralFactor Permyriad multiple of dispute fees required to confirm appeal * @param _minActiveBalance Minimum amount of guardian tokens that can be activated */ function setConfig( uint64 _fromTermId, ERC20 _feeToken, uint256[3] calldata _fees, uint64[5] calldata _roundStateDurations, uint16[2] calldata _pcts, uint64[4] calldata _roundParams, uint256[2] calldata _appealCollateralParams, uint256 _minActiveBalance ) external onlyConfigGovernor { uint64 currentTermId = _ensureCurrentTerm(); _setConfig( currentTermId, _fromTermId, _feeToken, _fees, _roundStateDurations, _pcts, _roundParams, _appealCollateralParams, _minActiveBalance ); } /** * @notice Delay the Protocol start time to `_newFirstTermStartTime` * @param _newFirstTermStartTime New timestamp in seconds when the protocol will open */ function delayStartTime(uint64 _newFirstTermStartTime) external onlyConfigGovernor { _delayStartTime(_newFirstTermStartTime); } /** * @notice Change funds governor address to `_newFundsGovernor` * @param _newFundsGovernor Address of the new funds governor to be set */ function changeFundsGovernor(address _newFundsGovernor) external onlyFundsGovernor { require(_newFundsGovernor != ZERO_ADDRESS, ERROR_INVALID_GOVERNOR_ADDRESS); _setFundsGovernor(_newFundsGovernor); } /** * @notice Change config governor address to `_newConfigGovernor` * @param _newConfigGovernor Address of the new config governor to be set */ function changeConfigGovernor(address _newConfigGovernor) external onlyConfigGovernor { require(_newConfigGovernor != ZERO_ADDRESS, ERROR_INVALID_GOVERNOR_ADDRESS); _setConfigGovernor(_newConfigGovernor); } /** * @notice Change modules governor address to `_newModulesGovernor` * @param _newModulesGovernor Address of the new governor to be set */ function changeModulesGovernor(address _newModulesGovernor) external onlyModulesGovernor { require(_newModulesGovernor != ZERO_ADDRESS, ERROR_INVALID_GOVERNOR_ADDRESS); _setModulesGovernor(_newModulesGovernor); } /** * @notice Remove the funds governor. Set the funds governor to the zero address. * @dev This action cannot be rolled back, once the funds governor has been unset, funds cannot be recovered from recoverable modules anymore */ function ejectFundsGovernor() external onlyFundsGovernor { _setFundsGovernor(ZERO_ADDRESS); } /** * @notice Remove the modules governor. Set the modules governor to the zero address. * @dev This action cannot be rolled back, once the modules governor has been unset, system modules cannot be changed anymore */ function ejectModulesGovernor() external onlyModulesGovernor { _setModulesGovernor(ZERO_ADDRESS); } /** * @notice Set custom function `_sig` for `_target` * @param _sig Signature of the function to be set * @param _target Address of the target implementation to be registered for the given signature */ function setCustomFunction(bytes4 _sig, address _target) external onlyModulesGovernor { customFunctions[_sig] = _target; emit CustomFunctionSet(_sig, _target); } /** * @notice Set module `_id` to `_addr` * @param _id ID of the module to be set * @param _addr Address of the module to be set */ function setModule(bytes32 _id, address _addr) external onlyModulesGovernor { _setModule(_id, _addr); } /** * @notice Set many modules at once * @param _ids List of ids of each module to be set * @param _addresses List of addresses of each the module to be set */ function setModules(bytes32[] calldata _ids, address[] calldata _addresses) external onlyModulesGovernor { require(_ids.length == _addresses.length, ERROR_INVALID_IMPLS_INPUT_LENGTH); for (uint256 i = 0; i < _ids.length; i++) { _setModule(_ids[i], _addresses[i]); } } /** * @notice Disabled module `_addr` * @dev Current modules can be disabled to allow pausing the protocol. However, these can be enabled back again, see `enableModule` * @param _addr Address of the module to be disabled */ function disableModule(address _addr) external onlyModulesGovernor { Module storage module = allModules[_addr]; _ensureModuleExists(module); require(!module.disabled, ERROR_MODULE_ALREADY_DISABLED); module.disabled = true; emit ModuleDisabled(module.id, _addr); } /** * @notice Enable module `_addr` * @param _addr Address of the module to be enabled */ function enableModule(address _addr) external onlyModulesGovernor { Module storage module = allModules[_addr]; _ensureModuleExists(module); require(module.disabled, ERROR_MODULE_ALREADY_ENABLED); module.disabled = false; emit ModuleEnabled(module.id, _addr); } /** * @notice Sync modules' cache for a list of modules IDs based on their current address * @param _modules List of modules addresses to be synced * @param _ids List of IDs of the modules to be cached */ function cacheModules(IModuleCache[] calldata _modules, bytes32[] calldata _ids) external onlyModulesGovernor { require(_ids.length > 0 && _modules.length > 0, ERROR_INVALID_IMPLS_INPUT_LENGTH); address[] memory addresses = new address[](_ids.length); for (uint256 i = 0; i < _ids.length; i++) { address moduleAddress = currentModules[_ids[i]]; Module storage module = allModules[moduleAddress]; _ensureModuleExists(module); addresses[i] = moduleAddress; } for (uint256 j = 0; j < _modules.length; j++) { _modules[j].cacheModules(_ids, addresses); } } /** * @dev Tell the full Protocol configuration parameters at a certain term * @param _termId Identification number of the term querying the Protocol config of * @return token Address of the token used to pay for fees * @return fees Array containing: * 0. guardianFee Amount of fee tokens that is paid per guardian per dispute * 1. draftFee Amount of fee tokens per guardian to cover the drafting cost * 2. settleFee Amount of fee tokens per guardian to cover round settlement cost * @return roundStateDurations Array containing the durations in terms of the different phases of a dispute: * 0. evidenceTerms Max submitting evidence period duration in terms * 1. commitTerms Commit period duration in terms * 2. revealTerms Reveal period duration in terms * 3. appealTerms Appeal period duration in terms * 4. appealConfirmationTerms Appeal confirmation period duration in terms * @return pcts Array containing: * 0. penaltyPct Permyriad of min active tokens balance to be locked for each drafted guardian (‱ - 1/10,000) * 1. finalRoundReduction Permyriad of fee reduction for the last appeal round (‱ - 1/10,000) * @return roundParams Array containing params for rounds: * 0. firstRoundGuardiansNumber Number of guardians to be drafted for the first round of disputes * 1. appealStepFactor Increasing factor for the number of guardians of each round of a dispute * 2. maxRegularAppealRounds Number of regular appeal rounds before the final round is triggered * 3. finalRoundLockTerms Number of terms that a coherent guardian in a final round is disallowed to withdraw (to prevent 51% attacks) * @return appealCollateralParams Array containing params for appeal collateral: * 0. appealCollateralFactor Multiple of dispute fees required to appeal a preliminary ruling * 1. appealConfirmCollateralFactor Multiple of dispute fees required to confirm appeal */ function getConfig(uint64 _termId) external view returns ( ERC20 feeToken, uint256[3] memory fees, uint64[5] memory roundStateDurations, uint16[2] memory pcts, uint64[4] memory roundParams, uint256[2] memory appealCollateralParams, uint256 minActiveBalance ) { uint64 lastEnsuredTermId = _lastEnsuredTermId(); return _getConfigAt(_termId, lastEnsuredTermId); } /** * @dev Tell the draft config at a certain term * @param _termId Identification number of the term querying the draft config of * @return feeToken Address of the token used to pay for fees * @return draftFee Amount of fee tokens per guardian to cover the drafting cost * @return penaltyPct Permyriad of min active tokens balance to be locked for each drafted guardian (‱ - 1/10,000) */ function getDraftConfig(uint64 _termId) external view returns (ERC20 feeToken, uint256 draftFee, uint16 penaltyPct) { uint64 lastEnsuredTermId = _lastEnsuredTermId(); return _getDraftConfig(_termId, lastEnsuredTermId); } /** * @dev Tell the min active balance config at a certain term * @param _termId Identification number of the term querying the min active balance config of * @return Minimum amount of tokens guardians have to activate to participate in the Protocol */ function getMinActiveBalance(uint64 _termId) external view returns (uint256) { uint64 lastEnsuredTermId = _lastEnsuredTermId(); return _getMinActiveBalance(_termId, lastEnsuredTermId); } /** * @dev Tell the address of the funds governor * @return Address of the funds governor */ function getFundsGovernor() external view returns (address) { return governor.funds; } /** * @dev Tell the address of the config governor * @return Address of the config governor */ function getConfigGovernor() external view returns (address) { return governor.config; } /** * @dev Tell the address of the modules governor * @return Address of the modules governor */ function getModulesGovernor() external view returns (address) { return governor.modules; } /** * @dev Tell if a given module is active * @param _id ID of the module to be checked * @param _addr Address of the module to be checked * @return True if the given module address has the requested ID and is enabled */ function isActive(bytes32 _id, address _addr) external view returns (bool) { Module storage module = allModules[_addr]; return module.id == _id && !module.disabled; } /** * @dev Tell the current ID and disable status of a module based on a given address * @param _addr Address of the requested module * @return id ID of the module being queried * @return disabled Whether the module has been disabled */ function getModuleByAddress(address _addr) external view returns (bytes32 id, bool disabled) { Module storage module = allModules[_addr]; id = module.id; disabled = module.disabled; } /** * @dev Tell the current address and disable status of a module based on a given ID * @param _id ID of the module being queried * @return addr Current address of the requested module * @return disabled Whether the module has been disabled */ function getModule(bytes32 _id) external view returns (address addr, bool disabled) { return _getModule(_id); } /** * @dev Tell the address of the current DisputeManager module * @return addr Current address of the DisputeManager module * @return disabled Whether the module has been disabled */ function getDisputeManager() external view returns (address addr, bool disabled) { return _getModule(DISPUTE_MANAGER); } /** * @dev Tell the current address of the current Treasury module * @return addr Current address of the Treasury module * @return disabled Whether the module has been disabled */ function getTreasury() external view returns (address addr, bool disabled) { return _getModule(TREASURY); } /** * @dev Tell the current address of the current Voting module * @return addr Current address of the Voting module * @return disabled Whether the module has been disabled */ function getVoting() external view returns (address addr, bool disabled) { return _getModule(VOTING); } /** * @dev Tell the current address of the current GuardiansRegistry module * @return addr Current address of the GuardiansRegistry module * @return disabled Whether the module has been disabled */ function getGuardiansRegistry() external view returns (address addr, bool disabled) { return _getModule(GUARDIANS_REGISTRY); } /** * @dev Tell the current address of the current PaymentsBook module * @return addr Current a of the PaymentsBook module * @return disabled Whether the module has been disabled */ function getPaymentsBook() external view returns (address addr, bool disabled) { return _getModule(PAYMENTS_BOOK); } /** * @dev Internal function to set the address of the funds governor * @param _newFundsGovernor Address of the new config governor to be set */ function _setFundsGovernor(address _newFundsGovernor) internal { emit FundsGovernorChanged(governor.funds, _newFundsGovernor); governor.funds = _newFundsGovernor; } /** * @dev Internal function to set the address of the config governor * @param _newConfigGovernor Address of the new config governor to be set */ function _setConfigGovernor(address _newConfigGovernor) internal { emit ConfigGovernorChanged(governor.config, _newConfigGovernor); governor.config = _newConfigGovernor; } /** * @dev Internal function to set the address of the modules governor * @param _newModulesGovernor Address of the new modules governor to be set */ function _setModulesGovernor(address _newModulesGovernor) internal { emit ModulesGovernorChanged(governor.modules, _newModulesGovernor); governor.modules = _newModulesGovernor; } /** * @dev Internal function to set an address as the current implementation for a module * Note that the disabled condition is not affected, if the module was not set before it will be enabled by default * @param _id Id of the module to be set * @param _addr Address of the module to be set */ function _setModule(bytes32 _id, address _addr) internal { require(isContract(_addr), ERROR_IMPLEMENTATION_NOT_CONTRACT); currentModules[_id] = _addr; allModules[_addr].id = _id; emit ModuleSet(_id, _addr); } /** * @dev Internal function to notify when a term has been transitioned * @param _termId Identification number of the new current term that has been transitioned */ function _onTermTransitioned(uint64 _termId) internal { _ensureTermConfig(_termId); } /** * @dev Internal function to check if a module was set * @param _module Module to be checked */ function _ensureModuleExists(Module storage _module) internal view { require(_module.id != bytes32(0), ERROR_MODULE_NOT_SET); } /** * @dev Internal function to tell the current address registered for a module based on a given ID * @param _id ID of the module being queried * @return addr Current address of the requested module * @return disabled Whether the module has been disabled */ function _getModule(bytes32 _id) internal view returns (address addr, bool disabled) { addr = currentModules[_id]; disabled = allModules[addr].disabled; } /** * @dev Internal function to tell the address of the current DisputeManager module * @return Current address of the DisputeManager module */ function _getDisputeManager() internal view returns (address) { return currentModules[DISPUTE_MANAGER]; } /** * @dev Internal function to tell the address of the current PaymentsBook module * @return Current address of the PaymentsBook module */ function _getPaymentsBook() internal view returns (address) { return currentModules[PAYMENTS_BOOK]; } }