Skip to content

Commit

Permalink
[Staking/Validator] Refine structures & fix TODO (#18)
Browse files Browse the repository at this point in the history
### Description
- Refine structures
- Adopt `RONTransferHelper` and has contracts 
- Move `validatorCandidates` storage to Ronin Validator contract
- Distribute reward for delegator at the ending of a period
- Remove insufficient candidates at the end of an epoch
- Reset unavailability indicator
  • Loading branch information
ducthotran2010 authored Sep 21, 2022
2 parents c4f6bd4 + fee1024 commit 46f07a3
Show file tree
Hide file tree
Showing 47 changed files with 995 additions and 1,035 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ The ones on top `N` users with the highest amount of staked coins will become va

**Proposing validator**

| Params | Explanation |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] |
| `address consensusAddr` | Address to produce block |
| `address treasuryAddr` | Address to receive block reward |
| Params | Explanation |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| `uint256 commissionRate` | The rate to share for the validator. Values in range [0; 100_00] stands for [0; 100%] |
| `address consensusAddr` | Address to produce block |
| `address treasuryAddr` | Address to receive block reward |
| `msg.value` | The amount of RON to stake, require to be larger than the minimum RON threshold to be validator |

The validator candidates can deposit or withdraw their funds afterwards as long as the staking balance must be greater than the minimum RON threshold.
Expand All @@ -49,7 +49,7 @@ The delegator can choose the validator to stake and receive the commission rewar
| `delegate(consensusAddr)` | Stakes `msg.value` amount of RON for a validator `consensusAddr` |
| `undelegate(consensusAddr, amount)` | Unstakes from a validator |
| `redelegate(consensusAddrSrc, consensusAddrDst, amount)` | Unstakes `amount` RON from the `consensusAddrSrc` and stake for `consensusAddrDst` |
| `getRewards()` | Returns the pending rewards and the claimable rewards |
| `getRewards(consensusAddrList)` | Returns the pending rewards and the claimable rewards |
| `claimRewards(consensusAddrList)` | Claims all the reward from the validators |
| `delegatePendingReward(consensusAddrList, consensusAddr)` | Claims all the reward and delegates them to the consensus address |

Expand Down Expand Up @@ -120,7 +120,7 @@ $ yarn test
$ cp .env.example .env && vim .env
```

- Update the contract configuration in [`config.ts`](./src/config.ts#L55-L96) file
- Update the contract configuration in [`config.ts`](./src/config.ts) file

- Deploy the contracts:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "../interfaces/ISlashIndicator.sol";
import "../interfaces/IRoninValidatorSet.sol";
import "./interfaces/ISlashIndicator.sol";
import "./extensions/HasValidatorContract.sol";

contract SlashIndicator is ISlashIndicator, Initializable {
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
Expand All @@ -24,26 +24,11 @@ contract SlashIndicator is ISlashIndicator, Initializable {
/// @dev The block duration to jail validator that reaches felony thresold.
uint256 public felonyJailDuration;

/// @dev The validator contract
IRoninValidatorSet public validatorContract;
/// @dev The governance admin
address internal _governanceAdmin;

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

modifier onlyValidatorContract() {
require(msg.sender == address(validatorContract), "SlashIndicator: method caller is not the validator contract");
_;
}

modifier onlyGovernanceAdmin() {
require(msg.sender == _governanceAdmin, "SlashIndicator: method caller is not the governance admin");
_;
}

modifier oncePerBlock() {
require(
block.number > lastSlashedBlock,
Expand All @@ -61,16 +46,14 @@ contract SlashIndicator is ISlashIndicator, Initializable {
* @dev Initializes the contract storage.
*/
function initialize(
address __governanceAdmin,
IRoninValidatorSet _validatorSetContract,
address __validatorContract,
uint256 _misdemeanorThreshold,
uint256 _felonyThreshold,
uint256 _slashFelonyAmount,
uint256 _slashDoubleSignAmount,
uint256 _felonyJailBlocks
) external initializer {
validatorContract = _validatorSetContract;
_setGovernanceAdmin(__governanceAdmin);
_setValidatorContract(__validatorContract);
_setSlashThresholds(_felonyThreshold, _misdemeanorThreshold);
_setSlashFelonyAmount(_slashFelonyAmount);
_setSlashDoubleSignAmount(_slashDoubleSignAmount);
Expand All @@ -94,10 +77,14 @@ contract SlashIndicator is ISlashIndicator, Initializable {
// Slashes the validator as either the fenoly or the misdemeanor
if (_count == felonyThreshold) {
emit ValidatorSlashed(_validatorAddr, SlashType.FELONY);
validatorContract.slash(_validatorAddr, block.number + felonyJailDuration, slashFelonyAmount);
_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);
validatorContract.slash(_validatorAddr, 0, 0);
_validatorContract.slash(_validatorAddr, 0, 0);
}
}

Expand All @@ -110,7 +97,7 @@ contract SlashIndicator is ISlashIndicator, Initializable {
) external override onlyCoinbase {
bool _proved = false; // Proves the `_evidence` is right
if (_proved) {
validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount);
_validatorContract.slash(_validatorAddr, type(uint256).max, slashDoubleSignAmount);
}
}

Expand Down Expand Up @@ -142,39 +129,28 @@ contract SlashIndicator is ISlashIndicator, Initializable {
/**
* @inheritdoc ISlashIndicator
*/
function setGovernanceAdmin(address __governanceAdmin) external override onlyGovernanceAdmin {
_setGovernanceAdmin(__governanceAdmin);
}

/**
* @inheritdoc ISlashIndicator
*/
function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold)
external
override
onlyGovernanceAdmin
{
function setSlashThresholds(uint256 _felonyThreshold, uint256 _misdemeanorThreshold) external override onlyAdmin {
_setSlashThresholds(_felonyThreshold, _misdemeanorThreshold);
}

/**
* @inheritdoc ISlashIndicator
*/
function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyGovernanceAdmin {
function setSlashFelonyAmount(uint256 _slashFelonyAmount) external override onlyAdmin {
_setSlashFelonyAmount(_slashFelonyAmount);
}

/**
* @inheritdoc ISlashIndicator
*/
function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyGovernanceAdmin {
function setSlashDoubleSignAmount(uint256 _slashDoubleSignAmount) external override onlyAdmin {
_setSlashDoubleSignAmount(_slashDoubleSignAmount);
}

/**
* @inheritdoc ISlashIndicator
*/
function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyGovernanceAdmin {
function setFelonyJailDuration(uint256 _felonyJailDuration) external override onlyAdmin {
_setFelonyJailDuration(_felonyJailDuration);
}

Expand All @@ -196,31 +172,10 @@ contract SlashIndicator is ISlashIndicator, Initializable {
return (misdemeanorThreshold, felonyThreshold);
}

/**
* @inheritdoc ISlashIndicator
*/
function governanceAdmin() external view override returns (address) {
return _governanceAdmin;
}

///////////////////////////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////

/**
* @dev Updates the address of governance admin
*/
function _setGovernanceAdmin(address __governanceAdmin) internal {
if (__governanceAdmin == _governanceAdmin) {
return;
}

require(__governanceAdmin != address(0), "SlashIndicator: Cannot set admin to zero address");

_governanceAdmin = __governanceAdmin;
emit GovernanceAdminUpdated(__governanceAdmin);
}

/**
* @dev Sets the slash thresholds
*/
Expand Down
37 changes: 10 additions & 27 deletions contracts/StakingVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,19 @@ pragma solidity ^0.8.9;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/IStakingVesting.sol";
import "./extensions/HasValidatorContract.sol";
import "./extensions/RONTransferHelper.sol";

contract StakingVesting is Initializable, IStakingVesting {
contract StakingVesting is IStakingVesting, HasValidatorContract, RONTransferHelper, Initializable {
/// @dev The block bonus whenever a new block is mined.
uint256 internal _bonusPerBlock;
/// @dev The last block number that the bonus reward sent.
uint256 public lastBonusSentBlock;
/// @dev Validator contract address.
address internal _validatorContract;

modifier onlyValidatorContract() {
require(msg.sender == _validatorContract, "Staking: method caller is not the validator contract");
_;
}

constructor() {
_disableInitializers();
}

receive() external payable onlyValidatorContract {}

fallback() external payable onlyValidatorContract {}

/**
* @dev Initializes the contract storage.
*/
Expand Down Expand Up @@ -54,14 +45,17 @@ contract StakingVesting is Initializable, IStakingVesting {
function requestBlockBonus() external onlyValidatorContract returns (uint256 _amount) {
uint256 _block = block.number;

require(_block > lastBonusSentBlock, "Staking: bonus already sent");
require(_block > lastBonusSentBlock, "StakingVesting: bonus already sent");
lastBonusSentBlock = _block;
_amount = blockBonus(_block);

if (_amount > 0) {
(bool _success, ) = payable(_validatorContract).call{ value: _amount }("");
require(_success, "Staking: could not transfer RON to validator contract");
emit BlockBonusTransferred(_block, _validatorContract, _amount);
address payable _validatorContractAddr = payable(validatorContract());
require(
_sendRON(_validatorContractAddr, _amount),
"StakingVesting: could not transfer RON to validator contract"
);
emit BlockBonusTransferred(_block, _validatorContractAddr, _amount);
}
}

Expand All @@ -75,15 +69,4 @@ contract StakingVesting is Initializable, IStakingVesting {
_bonusPerBlock = _amount;
emit BonusPerBlockUpdated(_amount);
}

/**
* @dev Sets the governance admin address.
*
* Emits the `ValidatorContractUpdated` event.
*
*/
function _setValidatorContract(address _newValidatorContract) internal {
_validatorContract = _newValidatorContract;
emit ValidatorContractUpdated(_newValidatorContract);
}
}
21 changes: 21 additions & 0 deletions contracts/extensions/HasProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/StorageSlot.sol";

abstract contract HasProxyAdmin {
// bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

modifier onlyAdmin() {
require(msg.sender == _getAdmin(), "HasProxyAdmin: unauthorized sender");
_;
}

/**
* @dev Returns proxy admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
}
46 changes: 46 additions & 0 deletions contracts/extensions/HasSlashIndicatorContract.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/IHasSlashIndicatorContract.sol";
import "../interfaces/ISlashIndicator.sol";

contract HasSlashIndicatorContract is IHasSlashIndicatorContract, HasProxyAdmin {
ISlashIndicator internal _slashIndicatorContract;

modifier onlySlashIndicatorContract() {
require(
slashIndicatorContract() == msg.sender,
"HasSlashIndicatorContract: method caller must be slash indicator contract"
);
_;
}

/**
* @inheritdoc IHasSlashIndicatorContract
*/
function slashIndicatorContract() public view override returns (address) {
return address(_slashIndicatorContract);
}

/**
* @inheritdoc IHasSlashIndicatorContract
*/
function setSlashIndicatorContract(address _addr) external override onlyAdmin {
_setSlashIndicatorContract(_addr);
}

/**
* @dev Sets the slash indicator contract.
*
* Requirements:
* - The new address is a contract.
*
* Emits the event `SlashIndicatorContractUpdated`.
*
*/
function _setSlashIndicatorContract(address _addr) internal {
_slashIndicatorContract = ISlashIndicator(_addr);
emit SlashIndicatorContractUpdated(_addr);
}
}
43 changes: 43 additions & 0 deletions contracts/extensions/HasStakingContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./HasProxyAdmin.sol";
import "../interfaces/collections/IHasStakingContract.sol";
import "../interfaces/IStaking.sol";

contract HasStakingContract is IHasStakingContract, HasProxyAdmin {
IStaking internal _stakingContract;

modifier onlyStakingContract() {
require(stakingContract() == msg.sender, "HasStakingManager: method caller must be staking contract");
_;
}

/**
* @inheritdoc IHasStakingContract
*/
function stakingContract() public view override returns (address) {
return address(_stakingContract);
}

/**
* @inheritdoc IHasStakingContract
*/
function setStakingContract(address _addr) external override onlyAdmin {
_setStakingContract(_addr);
}

/**
* @dev Sets the staking contract.
*
* Requirements:
* - The new address is a contract.
*
* Emits the event `StakingContractUpdated`.
*
*/
function _setStakingContract(address _addr) internal {
_stakingContract = IStaking(_addr);
emit StakingContractUpdated(_addr);
}
}
Loading

0 comments on commit 46f07a3

Please sign in to comment.