pragma solidity ^0.5.17; import "../../lib/os/ERC20.sol"; import "../../lib/os/SafeMath64.sol"; import "./IConfig.sol"; import "./ProtocolConfigData.sol"; import "../../lib/PctHelpers.sol"; contract ProtocolConfig is IConfig, ProtocolConfigData { using SafeMath64 for uint64; using PctHelpers for uint256; string private constant ERROR_TOO_OLD_TERM = "CONF_TOO_OLD_TERM"; string private constant ERROR_INVALID_PENALTY_PCT = "CONF_INVALID_PENALTY_PCT"; string private constant ERROR_INVALID_FINAL_ROUND_REDUCTION_PCT = "CONF_INVALID_FINAL_ROUND_RED_PCT"; string private constant ERROR_INVALID_MAX_APPEAL_ROUNDS = "CONF_INVALID_MAX_APPEAL_ROUNDS"; string private constant ERROR_LARGE_ROUND_PHASE_DURATION = "CONF_LARGE_ROUND_PHASE_DURATION"; string private constant ERROR_BAD_INITIAL_GUARDIANS_NUMBER = "CONF_BAD_INITIAL_GUARDIANS_NUMBER"; string private constant ERROR_BAD_APPEAL_STEP_FACTOR = "CONF_BAD_APPEAL_STEP_FACTOR"; string private constant ERROR_ZERO_COLLATERAL_FACTOR = "CONF_ZERO_COLLATERAL_FACTOR"; string private constant ERROR_ZERO_MIN_ACTIVE_BALANCE = "CONF_ZERO_MIN_ACTIVE_BALANCE"; // Max number of terms that each of the different adjudication states can last (if lasted 1h, this would be a year) uint64 internal constant MAX_ADJ_STATE_DURATION = 8670; // Cap the max number of regular appeal rounds uint256 internal constant MAX_REGULAR_APPEAL_ROUNDS_LIMIT = 10; // Future term ID in which a config change has been scheduled uint64 private configChangeTermId; // List of all the configs used in the Protocol Config[] private configs; // List of configs indexed by id mapping (uint64 => uint256) private configIdByTerm; // Holders opt-in config for automatic withdrawals mapping (address => bool) private withdrawalsAllowed; event NewConfig(uint64 fromTermId, uint64 protocolConfigId); event AutomaticWithdrawalsAllowedChanged(address indexed holder, bool allowed); /** * @dev Constructor function * @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 for each drafted guardian (‱ - 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: * 0. appealCollateralFactor Multiple of dispute fees required to appeal a preliminary ruling * 1. appealConfirmCollateralFactor Multiple of dispute fees required to confirm appeal * @param _minActiveBalance Minimum amount of guardian tokens that can be activated */ constructor( 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 { // Leave config at index 0 empty for non-scheduled config changes configs.length = 1; _setConfig( 0, 0, _feeToken, _fees, _roundStateDurations, _pcts, _roundParams, _appealCollateralParams, _minActiveBalance ); } /** * @notice Set the automatic withdrawals config for the sender to `_allowed` * @param _allowed Whether or not the automatic withdrawals are allowed by the sender */ function setAutomaticWithdrawals(bool _allowed) external { withdrawalsAllowed[msg.sender] = _allowed; emit AutomaticWithdrawalsAllowedChanged(msg.sender, _allowed); } /** * @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 * @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 * @return minActiveBalance Minimum amount of tokens guardians have to activate to participate in the Protocol */ 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 ); /** * @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); /** * @dev Tell the min active balance config at a certain term * @param _termId 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); /** * @dev Tell whether a certain holder accepts automatic withdrawals of tokens or not * @param _holder Address of the token holder querying if withdrawals are allowed for * @return True if the given holder accepts automatic withdrawals of their tokens, false otherwise */ function areWithdrawalsAllowedFor(address _holder) external view returns (bool) { return withdrawalsAllowed[_holder]; } /** * @dev Tell the term identification number of the next scheduled config change * @return Term identification number of the next scheduled config change */ function getConfigChangeTermId() external view returns (uint64) { return configChangeTermId; } /** * @dev Internal to make sure to set a config for the new term, it will copy the previous term config if none * @param _termId Identification number of the new current term that has been transitioned */ function _ensureTermConfig(uint64 _termId) internal { // If the term being transitioned had no config change scheduled, keep the previous one uint256 currentConfigId = configIdByTerm[_termId]; if (currentConfigId == 0) { uint256 previousConfigId = configIdByTerm[_termId.sub(1)]; configIdByTerm[_termId] = previousConfigId; } } /** * @dev Assumes that sender it's allowed (either it's from governor or it's on init) * @param _termId Identification number of the current Protocol term * @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 for each drafted guardian (‱ - 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: * 0. appealCollateralFactor Multiple of dispute fees required to appeal a preliminary ruling * 1. appealConfirmCollateralFactor Multiple of dispute fees required to confirm appeal * @param _minActiveBalance Minimum amount of guardian tokens that can be activated */ function _setConfig( uint64 _termId, uint64 _fromTermId, ERC20 _feeToken, uint256[3] memory _fees, uint64[5] memory _roundStateDurations, uint16[2] memory _pcts, uint64[4] memory _roundParams, uint256[2] memory _appealCollateralParams, uint256 _minActiveBalance ) internal { // If the current term is not zero, changes must be scheduled at least after the current period. // No need to ensure delays for on-going disputes since these already use their creation term for that. require(_termId == 0 || _fromTermId > _termId, ERROR_TOO_OLD_TERM); // Make sure appeal collateral factors are greater than zero require(_appealCollateralParams[0] > 0 && _appealCollateralParams[1] > 0, ERROR_ZERO_COLLATERAL_FACTOR); // Make sure the given penalty and final round reduction pcts are not greater than 100% require(PctHelpers.isValid(_pcts[0]), ERROR_INVALID_PENALTY_PCT); require(PctHelpers.isValid(_pcts[1]), ERROR_INVALID_FINAL_ROUND_REDUCTION_PCT); // Disputes must request at least one guardian to be drafted initially require(_roundParams[0] > 0, ERROR_BAD_INITIAL_GUARDIANS_NUMBER); // Prevent that further rounds have zero guardians require(_roundParams[1] > 0, ERROR_BAD_APPEAL_STEP_FACTOR); // Make sure the max number of appeals allowed does not reach the limit uint256 _maxRegularAppealRounds = _roundParams[2]; bool isMaxAppealRoundsValid = _maxRegularAppealRounds > 0 && _maxRegularAppealRounds <= MAX_REGULAR_APPEAL_ROUNDS_LIMIT; require(isMaxAppealRoundsValid, ERROR_INVALID_MAX_APPEAL_ROUNDS); // Make sure each adjudication round phase duration is valid for (uint i = 0; i < _roundStateDurations.length; i++) { require(_roundStateDurations[i] > 0 && _roundStateDurations[i] < MAX_ADJ_STATE_DURATION, ERROR_LARGE_ROUND_PHASE_DURATION); } // Make sure min active balance is not zero require(_minActiveBalance > 0, ERROR_ZERO_MIN_ACTIVE_BALANCE); // If there was a config change already scheduled, reset it (in that case we will overwrite last array item). // Otherwise, schedule a new config. if (configChangeTermId > _termId) { configIdByTerm[configChangeTermId] = 0; } else { configs.length++; } uint64 protocolConfigId = uint64(configs.length - 1); Config storage config = configs[protocolConfigId]; config.fees = FeesConfig({ token: _feeToken, guardianFee: _fees[0], draftFee: _fees[1], settleFee: _fees[2], finalRoundReduction: _pcts[1] }); config.disputes = DisputesConfig({ evidenceTerms: _roundStateDurations[0], commitTerms: _roundStateDurations[1], revealTerms: _roundStateDurations[2], appealTerms: _roundStateDurations[3], appealConfirmTerms: _roundStateDurations[4], penaltyPct: _pcts[0], firstRoundGuardiansNumber: _roundParams[0], appealStepFactor: _roundParams[1], maxRegularAppealRounds: _maxRegularAppealRounds, finalRoundLockTerms: _roundParams[3], appealCollateralFactor: _appealCollateralParams[0], appealConfirmCollateralFactor: _appealCollateralParams[1] }); config.minActiveBalance = _minActiveBalance; configIdByTerm[_fromTermId] = protocolConfigId; configChangeTermId = _fromTermId; emit NewConfig(_fromTermId, protocolConfigId); } /** * @dev Internal function to get the Protocol config for a given term * @param _termId Identification number of the term querying the Protocol config of * @param _lastEnsuredTermId Identification number of the last ensured term of the Protocol * @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 * @return minActiveBalance Minimum amount of guardian tokens that can be activated */ function _getConfigAt(uint64 _termId, uint64 _lastEnsuredTermId) internal 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 ) { Config storage config = _getConfigFor(_termId, _lastEnsuredTermId); FeesConfig storage feesConfig = config.fees; feeToken = feesConfig.token; fees = [feesConfig.guardianFee, feesConfig.draftFee, feesConfig.settleFee]; DisputesConfig storage disputesConfig = config.disputes; roundStateDurations = [ disputesConfig.evidenceTerms, disputesConfig.commitTerms, disputesConfig.revealTerms, disputesConfig.appealTerms, disputesConfig.appealConfirmTerms ]; pcts = [disputesConfig.penaltyPct, feesConfig.finalRoundReduction]; roundParams = [ disputesConfig.firstRoundGuardiansNumber, disputesConfig.appealStepFactor, uint64(disputesConfig.maxRegularAppealRounds), disputesConfig.finalRoundLockTerms ]; appealCollateralParams = [disputesConfig.appealCollateralFactor, disputesConfig.appealConfirmCollateralFactor]; minActiveBalance = config.minActiveBalance; } /** * @dev Tell the draft config at a certain term * @param _termId Identification number of the term querying the draft config of * @param _lastEnsuredTermId Identification number of the last ensured term of the Protocol * @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, uint64 _lastEnsuredTermId) internal view returns (ERC20 feeToken, uint256 draftFee, uint16 penaltyPct) { Config storage config = _getConfigFor(_termId, _lastEnsuredTermId); return (config.fees.token, config.fees.draftFee, config.disputes.penaltyPct); } /** * @dev Internal function to get the min active balance config for a given term * @param _termId Identification number of the term querying the min active balance config of * @param _lastEnsuredTermId Identification number of the last ensured term of the Protocol * @return Minimum amount of guardian tokens that can be activated at the given term */ function _getMinActiveBalance(uint64 _termId, uint64 _lastEnsuredTermId) internal view returns (uint256) { Config storage config = _getConfigFor(_termId, _lastEnsuredTermId); return config.minActiveBalance; } /** * @dev Internal function to get the Protocol config for a given term * @param _termId Identification number of the term querying the min active balance config of * @param _lastEnsuredTermId Identification number of the last ensured term of the Protocol * @return Protocol config for the given term */ function _getConfigFor(uint64 _termId, uint64 _lastEnsuredTermId) internal view returns (Config storage) { uint256 id = _getConfigIdFor(_termId, _lastEnsuredTermId); return configs[id]; } /** * @dev Internal function to get the Protocol config ID for a given term * @param _termId Identification number of the term querying the Protocol config of * @param _lastEnsuredTermId Identification number of the last ensured term of the Protocol * @return Identification number of the config for the given terms */ function _getConfigIdFor(uint64 _termId, uint64 _lastEnsuredTermId) internal view returns (uint256) { // If the given term is lower or equal to the last ensured Protocol term, it is safe to use a past Protocol config if (_termId <= _lastEnsuredTermId) { return configIdByTerm[_termId]; } // If the given term is in the future but there is a config change scheduled before it, use the incoming config uint64 scheduledChangeTermId = configChangeTermId; if (scheduledChangeTermId <= _termId) { return configIdByTerm[scheduledChangeTermId]; } // If no changes are scheduled, use the Protocol config of the last ensured term return configIdByTerm[_lastEnsuredTermId]; } }