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

[Staking/Validator] Add bonus per block #7

Merged
merged 2 commits into from
Sep 13, 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
89 changes: 89 additions & 0 deletions contracts/StakingVesting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

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

contract StakingVesting is Initializable, IStakingVesting {
/// @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.
*/
function initialize(uint256 __bonusPerBlock, address __validatorContract) external payable initializer {
_setBonusPerBlock(__bonusPerBlock);
_setValidatorContract(__validatorContract);
}

/**
* @inheritdoc IStakingVesting
*/
function receiveRON() external payable {}

/**
* @inheritdoc IStakingVesting
*/
function blockBonus(
uint256 /* _block */
) public view returns (uint256) {
return _bonusPerBlock;
}

/**
* @inheritdoc IStakingVesting
*/
function requestBlockBonus() external onlyValidatorContract returns (uint256 _amount) {
uint256 _block = block.number;

require(_block > lastBonusSentBlock, "Staking: 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);
}
}

/**
* @dev Sets the bonus amount per block.
*
* Emits the event `BonusPerBlockUpdated`.
*
*/
function _setBonusPerBlock(uint256 _amount) internal {
_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);
}
}
5 changes: 5 additions & 0 deletions contracts/interfaces/IRoninValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ interface IRoninValidatorSet {
*/
function stakingContract() external view returns (address);

/**
* @dev Returns the staking vesting contract address.
*/
function stakingVestingContract() external view returns (address);

/**
* @dev Slashes the validator.
*
Expand Down
40 changes: 40 additions & 0 deletions contracts/interfaces/IStakingVesting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

interface IStakingVesting {
/// @dev Emitted when the block bonus is transferred.
event BlockBonusTransferred(uint256 indexed blockNumber, address indexed recipient, uint256 amount);
/// @dev Emitted when the block bonus is updated
event BonusPerBlockUpdated(uint256);
/// @dev Emitted when the address of validator contract is updated.
event ValidatorContractUpdated(address);

/**
* @dev Returns the bonus amount for the block `_block`.
*/
function blockBonus(uint256 _block) external view returns (uint256);

/**
* @dev Receives RON from any address.
*/
function receiveRON() external payable;

/**
* @dev Returns the last block number that the bonus reward is sent.
*/
function lastBonusSentBlock() external view returns (uint256);

/**
* @dev Transfers the bonus reward whenever a new block is mined.
* Returns the amount of RON sent to validator contract.
*
* Requirements:
* - The method caller is validator contract.
* - The method must be called only once per block.
*
* Emits the event `BlockBonusTransferred`.
*
*/
function requestBlockBonus() external returns (uint256);
}
2 changes: 2 additions & 0 deletions contracts/mocks/MockRoninValidatorSetEpochSetter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ contract MockRoninValidatorSetEpochSetter is RoninValidatorSet {
address __governanceAdmin,
address __slashIndicatorContract,
address __stakingContract,
address __stakingVestingContract,
uint256 __maxValidatorNumber
) {
_governanceAdmin = __governanceAdmin;
_slashIndicatorContract = __slashIndicatorContract;
_stakingContract = __stakingContract;
_stakingVestingContract = __stakingVestingContract;
_maxValidatorNumber = __maxValidatorNumber;
}

Expand Down
3 changes: 3 additions & 0 deletions contracts/mocks/MockValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../interfaces/IStaking.sol";

contract MockValidatorSet is IRoninValidatorSet {
address public stakingContract;
address public stakingVestingContract;
address public slashIndicatorContract;

uint256 public numberOfEpochsInPeriod;
Expand All @@ -19,11 +20,13 @@ contract MockValidatorSet is IRoninValidatorSet {
constructor(
address _stakingContract,
address _slashIndicatorContract,
address _stakingVestingContract,
uint256 _numberOfEpochsInPeriod,
uint256 _numberOfBlocksInEpoch
) {
stakingContract = _stakingContract;
slashIndicatorContract = _slashIndicatorContract;
stakingVestingContract = _stakingVestingContract;
numberOfEpochsInPeriod = _numberOfEpochsInPeriod;
numberOfBlocksInEpoch = _numberOfBlocksInEpoch;
}
Expand Down
29 changes: 29 additions & 0 deletions contracts/ronin-validator/RoninValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import "../interfaces/ISlashIndicator.sol";
import "../interfaces/IStaking.sol";
import "../interfaces/IStakingVesting.sol";
import "../interfaces/IRoninValidatorSet.sol";
import "../libraries/Sorting.sol";
import "../libraries/Math.sol";
Expand All @@ -17,6 +18,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable {
address internal _slashIndicatorContract; // Change type to address for testing purpose
/// @dev Staking contract address.
address internal _stakingContract; // Change type to address for testing purpose
/// @dev Staking vesting contract address.
address internal _stakingVestingContract;

/// @dev The total of validators
uint256 public validatorCount;
Expand Down Expand Up @@ -65,20 +68,30 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable {
_disableInitializers();
}

fallback() external payable {
_fallback();
}

receive() external payable {
_fallback();
}

/**
* @dev Initializes the contract storage.
*/
function initialize(
address __governanceAdmin,
address __slashIndicatorContract,
address __stakingContract,
address __stakingVestingContract,
uint256 __maxValidatorNumber,
uint256 __numberOfBlocksInEpoch,
uint256 __numberOfEpochsInPeriod
) external initializer {
_governanceAdmin = __governanceAdmin;
_slashIndicatorContract = __slashIndicatorContract;
_stakingContract = __stakingContract;
_stakingVestingContract = __stakingVestingContract;
_maxValidatorNumber = __maxValidatorNumber;
_numberOfBlocksInEpoch = __numberOfBlocksInEpoch;
_numberOfEpochsInPeriod = __numberOfEpochsInPeriod;
Expand Down Expand Up @@ -106,6 +119,8 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable {
return;
}

_reward += IStakingVesting(_stakingVestingContract).requestBlockBonus();

IStaking _staking = IStaking(_stakingContract);
uint256 _rate = _staking.commissionRateOf(_coinbaseAddr);
uint256 _miningAmount = (_rate * _reward) / 100_00;
Expand Down Expand Up @@ -200,6 +215,13 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable {
return _stakingContract;
}

/**
* @inheritdoc IRoninValidatorSet
*/
function stakingVestingContract() external view override returns (address) {
return _stakingVestingContract;
}

/**
* @inheritdoc IRoninValidatorSet
*/
Expand Down Expand Up @@ -379,4 +401,11 @@ contract RoninValidatorSet is IRoninValidatorSet, Initializable {
_lastUpdatedBlock = block.number;
emit ValidatorSetUpdated(_candidates);
}

/**
* @dev Only receives RON from staking vesting contract.
*/
function _fallback() internal view {
require(msg.sender == _stakingVestingContract, "RoninValidatorSet: method caller must be staking vesting contract");
}
}
21 changes: 21 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface InitAddr {
governanceAdmin: string;
validatorContract?: string;
stakingContract?: string;
stakingVestingContract?: string;
slashIndicator?: string;
};
}
Expand All @@ -30,6 +31,15 @@ export interface StakingConf {
| undefined;
}

export interface StakingVestingConf {
[network: LiteralNetwork]:
| {
bonusPerBlock: BigNumber;
topupAmount: BigNumber;
}
| undefined;
}

export interface SlashIndicatorConf {
[network: LiteralNetwork]:
| {
Expand Down Expand Up @@ -69,6 +79,17 @@ export const stakingConfig: StakingConf = {
[Network.Mainnet]: undefined,
};

// TODO: update config for testnet & mainnet
export const stakingVestingConfig: StakingVestingConf = {
[Network.Hardhat]: undefined,
[Network.Devnet]: {
bonusPerBlock: BigNumber.from(10).pow(18), // 1 RON per block
topupAmount: BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(4)), // 10.000 RON
},
[Network.Testnet]: undefined,
[Network.Mainnet]: undefined,
};

// TODO: update config for testnet & mainnet
export const slashIndicatorConf: SlashIndicatorConf = {
[Network.Hardhat]: undefined,
Expand Down
12 changes: 11 additions & 1 deletion src/deploy/calculate-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ const deploy = async ({ getNamedAccounts }: HardhatRuntimeEnvironment) => {
let nonce = await ethers.provider.getTransactionCount(deployer);
initAddress[network.name].slashIndicator = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ });
initAddress[network.name].stakingContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ });
initAddress[network.name].stakingVestingContract = ethers.utils.getContractAddress({
from: deployer,
nonce: nonce++,
});
initAddress[network.name].validatorContract = ethers.utils.getContractAddress({ from: deployer, nonce: nonce++ });
};

deploy.tags = ['CalculateAddresses'];
deploy.dependencies = ['ProxyAdmin', 'StakingLogic', 'SlashIndicatorLogic', 'RoninValidatorSetLogic'];
deploy.dependencies = [
'ProxyAdmin',
'StakingLogic',
'SlashIndicatorLogic',
'RoninValidatorSetLogic',
'StakingVestingLogic',
];

export default deploy;
17 changes: 17 additions & 0 deletions src/deploy/logic/staking-vesting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';

const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();

await deploy('StakingVestingLogic', {
contract: 'StakingVesting',
from: deployer,
log: true,
});
};

deploy.tags = ['StakingVestingLogic'];
deploy.dependencies = ['ProxyAdmin'];

export default deploy;
File renamed without changes.
3 changes: 2 additions & 1 deletion src/deploy/proxy/ronin-validator-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme
initAddress[network.name]!.governanceAdmin,
initAddress[network.name]!.slashIndicator,
initAddress[network.name]!.stakingContract,
initAddress[network.name]!.stakingVestingContract,
roninValidatorSetConf[network.name]!.maxValidatorNumber,
roninValidatorSetConf[network.name]!.numberOfBlocksInEpoch,
roninValidatorSetConf[network.name]!.numberOfEpochsInPeriod,
Expand All @@ -28,6 +29,6 @@ const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironme
};

deploy.tags = ['RoninValidatorSetProxy'];
deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic'];
deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy', 'StakingVestingProxy'];

export default deploy;
File renamed without changes.
30 changes: 30 additions & 0 deletions src/deploy/proxy/staking-vesting-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { network } from 'hardhat';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { initAddress, stakingVestingConfig } from '../../config';
import { StakingVesting__factory } from '../../types';

const deploy = async ({ getNamedAccounts, deployments }: HardhatRuntimeEnvironment) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();

const proxyAdmin = await deployments.get('ProxyAdmin');
const logicContract = await deployments.get('StakingVestingLogic');

const data = new StakingVesting__factory().interface.encodeFunctionData('initialize', [
stakingVestingConfig[network.name]!.bonusPerBlock,
initAddress[network.name]!.validatorContract,
]);

await deploy('StakingVestingProxy', {
contract: 'TransparentUpgradeableProxy',
from: deployer,
log: true,
args: [logicContract.address, proxyAdmin.address, data],
value: stakingVestingConfig[network.name]!.topupAmount,
});
};

deploy.tags = ['StakingVestingProxy'];
deploy.dependencies = ['ProxyAdmin', 'RoninValidatorSetLogic', 'SlashIndicatorProxy', 'StakingProxy'];

export default deploy;
Loading