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

Add scheduled maintenance contract #17

Merged
merged 17 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions contracts/Maintenance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/IMaintenance.sol";
import "./interfaces/IRoninValidatorSet.sol";
import "./interfaces/IStaking.sol";
import "./extensions/HasValidatorContract.sol";
import "./libraries/Math.sol";

contract Maintenance is IMaintenance, HasValidatorContract, Initializable {
using Math for uint256;

/// @dev Mapping from consensus address => maintenance schedule
mapping(address => Schedule) internal _schedule;

/// @dev The min block period to maintenance
uint256 public minMaintenanceBlockPeriod;
/// @dev The max block period to maintenance
uint256 public maxMaintenanceBlockPeriod;
/// @dev The min blocks from the current block to the start block
uint256 public minOffset;
/// @dev The max number of scheduled maintenances
uint256 public maxSchedules;

constructor() {
_disableInitializers();
}

/**
* @dev Initializes the contract storage.
*/
function initialize(
address __validatorContract,
uint256 _minMaintenanceBlockPeriod,
uint256 _maxMaintenanceBlockPeriod,
uint256 _minOffset,
uint256 _maxSchedules
) external initializer {
_setValidatorContract(__validatorContract);
_setMaintenanceConfig(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules);
}

/**
* @inheritdoc IMaintenance
*/
function setMaintenanceConfig(
uint256 _minMaintenanceBlockPeriod,
uint256 _maxMaintenanceBlockPeriod,
uint256 _minOffset,
uint256 _maxSchedules
) external onlyAdmin {
_setMaintenanceConfig(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules);
}

/**
* @inheritdoc IMaintenance
*/
function schedule(
address _consensusAddr,
uint256 _startedAtBlock,
uint256 _endedAtBlock
) external override {
IRoninValidatorSet _validator = _validatorContract;

require(_validator.isValidator(_consensusAddr), "Maintenance: consensus address must be a validator");
require(
_validator.isCandidateAdmin(_consensusAddr, msg.sender),
"Maintenance: method caller must be a candidate admin"
);
require(!scheduled(_consensusAddr), "Maintenance: already scheduled");
require(totalSchedules() < maxSchedules, "Maintenance: exceeds total of schedules");
require(block.number + minOffset <= _startedAtBlock, "Maintenance: invalid offset size");
require(_startedAtBlock < _endedAtBlock, "Maintenance: start block must be less than end block");
uint256 _blockPeriod = _endedAtBlock - _startedAtBlock;
require(
_blockPeriod.inRange(minMaintenanceBlockPeriod, maxMaintenanceBlockPeriod),
"Maintenance: invalid maintenance block period"
);
require(_validator.epochEndingAt(_startedAtBlock - 1), "Maintenance: start block is not at the start of an epoch");
require(_validator.epochEndingAt(_endedAtBlock), "Maintenance: end block is not at the end of an epoch");

Schedule storage _sSchedule = _schedule[_consensusAddr];
uint256 _period = _validator.periodOf(block.number);
require(
_period > _validator.periodOf(_sSchedule.lastUpdatedBlock) && _period > _validator.periodOf(_sSchedule.to),
"Maintenance: schedule twice in a period is not allowed"
);

_sSchedule.from = _startedAtBlock;
_sSchedule.to = _endedAtBlock;
_sSchedule.lastUpdatedBlock = block.number;
emit MaintenanceScheduled(_consensusAddr, _sSchedule);
}

/**
* @inheritdoc IMaintenance
*/
function getSchedule(address _consensusAddr) external view returns (Schedule memory) {
return _schedule[_consensusAddr];
}

/**
* @inheritdoc IMaintenance
*/
function bulkMaintaining(address[] calldata _addrList, uint256 _block)
external
view
override
returns (bool[] memory _resList)
{
_resList = new bool[](_addrList.length);
for (uint _i = 0; _i < _addrList.length; _i++) {
_resList[_i] = maintaining(_addrList[_i], _block);
}
}

/**
* @inheritdoc IMaintenance
*/
function totalSchedules() public view returns (uint256 _count) {
address[] memory _validators = _validatorContract.getValidators();
for (uint _i = 0; _i < _validators.length; _i++) {
if (scheduled(_validators[_i])) {
_count++;
}
}
}

/**
* @inheritdoc IMaintenance
*/
function maintaining(address _consensusAddr, uint256 _block) public view returns (bool) {
Schedule storage _s = _schedule[_consensusAddr];
return _s.from <= _block && _block <= _s.to;
}

/**
* @inheritdoc IMaintenance
*/
function scheduled(address _consensusAddr) public view override returns (bool) {
return block.number <= _schedule[_consensusAddr].to;
}

/**
* @dev Sets the min block period and max block period to maintenance.
*
* Requirements:
* - The max period is larger than the min period.
*
* Emits the event `MaintenanceConfigUpdated`.
*
*/
function _setMaintenanceConfig(
uint256 _minMaintenanceBlockPeriod,
uint256 _maxMaintenanceBlockPeriod,
uint256 _minOffset,
uint256 _maxSchedules
) internal {
require(_minMaintenanceBlockPeriod < _maxMaintenanceBlockPeriod, "Maintenance: invalid block periods");
minMaintenanceBlockPeriod = _minMaintenanceBlockPeriod;
maxMaintenanceBlockPeriod = _maxMaintenanceBlockPeriod;
minOffset = _minOffset;
maxSchedules = _maxSchedules;
emit MaintenanceConfigUpdated(_minMaintenanceBlockPeriod, _maxMaintenanceBlockPeriod, _minOffset, _maxSchedules);
}
}
116 changes: 77 additions & 39 deletions contracts/SlashIndicator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ pragma solidity ^0.8.9;
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/ISlashIndicator.sol";
import "./extensions/HasValidatorContract.sol";
import "./extensions/HasMaintenanceContract.sol";
import "./libraries/Math.sol";

contract SlashIndicator is ISlashIndicator, HasValidatorContract, HasMaintenanceContract, Initializable {
using Math for uint256;

/// @dev Mapping from validator address => period index => unavailability indicator
mapping(address => mapping(uint256 => uint256)) internal _unavailabilityIndicator;
/// @dev Maping from validator address => period index => slash type
mapping(address => mapping(uint256 => SlashType)) internal _unavailabilitySlashed;

contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable {
/// @dev Mapping from validator address => unavailability indicator
mapping(address => uint256) internal _unavailabilityIndicator;
/// @dev The last block that a validator is slashed
uint256 public lastSlashedBlock;

Expand All @@ -25,7 +32,7 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable
uint256 public felonyJailDuration;

modifier onlyCoinbase() {
require(msg.sender == block.coinbase, "SlashIndicator: method caller is not the coinbase");
require(msg.sender == block.coinbase, "SlashIndicator: method caller must be coinbase");
_;
}

Expand All @@ -47,13 +54,15 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable
*/
function initialize(
address __validatorContract,
address __maintenanceContract,
uint256 _misdemeanorThreshold,
uint256 _felonyThreshold,
uint256 _slashFelonyAmount,
uint256 _slashDoubleSignAmount,
uint256 _felonyJailBlocks
) external initializer {
_setValidatorContract(__validatorContract);
_setMaintenanceContract(__maintenanceContract);
_setSlashThresholds(_felonyThreshold, _misdemeanorThreshold);
_setSlashFelonyAmount(_slashFelonyAmount);
_setSlashDoubleSignAmount(_slashDoubleSignAmount);
Expand All @@ -68,23 +77,31 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable
* @inheritdoc ISlashIndicator
*/
function slash(address _validatorAddr) external override onlyCoinbase oncePerBlock {
if (msg.sender == _validatorAddr) {
if (msg.sender == _validatorAddr || _maintenanceContract.maintaining(_validatorAddr, block.number)) {
return;
}

uint256 _count = ++_unavailabilityIndicator[_validatorAddr];
uint256 _period = _validatorContract.periodOf(block.number);
uint256 _count = ++_unavailabilityIndicator[_validatorAddr][_period];
(uint256 _misdemeanorThreshold, uint256 _felonyThreshold) = unavailabilityThresholdsOf(
_validatorAddr,
block.number
);

// Slashes the validator as either the fenoly or the misdemeanor
if (_count == felonyThreshold) {
emit ValidatorSlashed(_validatorAddr, SlashType.FELONY);
SlashType _slashType = getUnavailabilitySlashType(_validatorAddr, _period);

if (_count >= _felonyThreshold && _slashType < SlashType.FELONY) {
_unavailabilitySlashed[_validatorAddr][_period] = SlashType.FELONY;
emit UnavailabilitySlashed(_validatorAddr, SlashType.FELONY, _period);
_validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount);
delete _unavailabilityIndicator[_validatorAddr];
address[] memory _addrList = new address[](1);
_addrList[0] = _validatorAddr;
emit UnavailabilityIndicatorsReset(_addrList);
} else if (_count == misdemeanorThreshold) {
emit ValidatorSlashed(_validatorAddr, SlashType.MISDEMEANOR);
return;
}

if (_count >= _misdemeanorThreshold && _slashType < SlashType.MISDEMEANOR) {
_unavailabilitySlashed[_validatorAddr][_period] = SlashType.MISDEMEANOR;
emit UnavailabilitySlashed(_validatorAddr, SlashType.MISDEMEANOR, _period);
_validatorContract.slash(_validatorAddr, 0, 0);
return;
}
}

Expand All @@ -101,27 +118,6 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable
}
}

/**
* @inheritdoc ISlashIndicator
*/
function resetCounters(address[] calldata _validatorAddrs) external override onlyValidatorContract {
_resetCounters(_validatorAddrs);
}

/**
* @dev Resets counter for the validator address.
*/
function _resetCounters(address[] calldata _validatorAddrs) private {
if (_validatorAddrs.length == 0) {
return;
}

for (uint _i = 0; _i < _validatorAddrs.length; _i++) {
delete _unavailabilityIndicator[_validatorAddrs[_i]];
}
emit UnavailabilityIndicatorsReset(_validatorAddrs);
}

///////////////////////////////////////////////////////////////////////////////////////
// GOVERNANCE FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -161,17 +157,59 @@ contract SlashIndicator is ISlashIndicator, HasValidatorContract, Initializable
/**
* @inheritdoc ISlashIndicator
*/
function getSlashIndicator(address validator) external view override returns (uint256) {
return _unavailabilityIndicator[validator];
function getUnavailabilitySlashType(address _validatorAddr, uint256 _period) public view returns (SlashType) {
return _unavailabilitySlashed[_validatorAddr][_period];
}

/**
* @inheritdoc ISlashIndicator
*/
function unavailabilityThresholdsOf(address _addr, uint256 _block)
public
view
returns (uint256 _misdemeanorThreshold, uint256 _felonyThreshold)
{
uint256 _blockLength = _validatorContract.numberOfBlocksInEpoch() * _validatorContract.numberOfEpochsInPeriod();
uint256 _start = (_block / _blockLength) * _blockLength;
uint256 _end = _start + _blockLength - 1;
IMaintenance.Schedule memory _s = _maintenanceContract.getSchedule(_addr);

bool _fromInRange = _s.from.inRange(_start, _end);
bool _toInRange = _s.to.inRange(_start, _end);
uint256 _availableDuration = _blockLength;
if (_fromInRange && _toInRange) {
_availableDuration -= _s.to - _s.from + 1;
} else if (_fromInRange) {
_availableDuration -= _end - _s.from + 1;
} else if (_toInRange) {
_availableDuration -= _s.to - _start + 1;
}

_misdemeanorThreshold = misdemeanorThreshold.scale(_availableDuration, _blockLength);
_felonyThreshold = felonyThreshold.scale(_availableDuration, _blockLength);
}

/**
* @inheritdoc ISlashIndicator
*/
function getSlashThresholds() external view override returns (uint256, uint256) {
function currentUnavailabilityIndicator(address _validator) external view override returns (uint256) {
return getUnavailabilityIndicator(_validator, _validatorContract.periodOf(block.number));
}

/**
* @inheritdoc ISlashIndicator
*/
function getUnavailabilityThresholds() external view override returns (uint256, uint256) {
return (misdemeanorThreshold, felonyThreshold);
}

/**
* @inheritdoc ISlashIndicator
*/
function getUnavailabilityIndicator(address _validator, uint256 _period) public view override returns (uint256) {
return _unavailabilityIndicator[_validator][_period];
}

///////////////////////////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////
Expand Down
46 changes: 46 additions & 0 deletions contracts/extensions/HasMaintenanceContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./HasProxyAdmin.sol";
import "../interfaces/collections/IHasMaintenanceContract.sol";
import "../interfaces/IMaintenance.sol";

contract HasMaintenanceContract is IHasMaintenanceContract, HasProxyAdmin {
IMaintenance internal _maintenanceContract;

modifier onlyMaintenanceContract() {
require(
maintenanceContract() == msg.sender,
"HasMaintenanceContract: method caller must be scheduled maintenance contract"
);
_;
}

/**
* @inheritdoc IHasMaintenanceContract
*/
function maintenanceContract() public view override returns (address) {
return address(_maintenanceContract);
}

/**
* @inheritdoc IHasMaintenanceContract
*/
function setMaintenanceContract(address _addr) external override onlyAdmin {
_setMaintenanceContract(_addr);
}

/**
* @dev Sets the scheduled maintenance contract.
*
* Requirements:
* - The new address is a contract.
*
* Emits the event `MaintenanceContractUpdated`.
*
*/
function _setMaintenanceContract(address _addr) internal {
_maintenanceContract = IMaintenance(_addr);
emit MaintenanceContractUpdated(_addr);
}
}
Loading