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

HydraChain contract #29

Closed
Closed
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
172 changes: 172 additions & 0 deletions contracts/APRCalculator/APRCalculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

Check warning on line 4 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

Check warning on line 4 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@openzeppelin/contracts/access/AccessControl.sol";

Check warning on line 5 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts/access/AccessControl.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

Check warning on line 5 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @openzeppelin/contracts/access/AccessControl.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

import {Governed} from "./../common/Governed/Governed.sol";

contract APRCalculator is Initializable, Governed {
error InvalidRSI();

uint256 public constant INITIAL_BASE_APR = 500;
uint256 public constant INITIAL_MACRO_FACTOR = 7500;
uint256 public constant MIN_RSI_BONUS = 10000;
uint256 public constant MAX_RSI_BONUS = 17000;
uint256 public constant DENOMINATOR = 10000;
uint256 public constant EPOCHS_YEAR = 31500;
bytes32 public constant MANAGER_ROLE = keccak256("manager_role");

uint256 public base;
uint256 public macroFactor;
uint256 public rsi;
uint256[52] public vestingBonus;

function __APR_init(address manager) internal onlyInitializing {

Check warning on line 25 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase

Check warning on line 25 in contracts/APRCalculator/APRCalculator.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase
base = INITIAL_BASE_APR;
macroFactor = INITIAL_MACRO_FACTOR;

initializeVestingBonus();

_grantRole(DEFAULT_ADMIN_ROLE, manager);
_grantRole(MANAGER_ROLE, manager);
}

function setBase(uint256 newBase) public onlyRole(MANAGER_ROLE) {
base = newBase;
}

function calcVestingBonus(uint256 weeksCount) public view returns (uint256 nominator) {
return vestingBonus[weeksCount - 1];
}

function setMacro(uint256 newMacroFactor) public onlyRole(MANAGER_ROLE) {
macroFactor = newMacroFactor;
}

function getBaseAPR() public view returns (uint256) {
return base;
}

function getRSIBonus() public view returns (uint256) {
return rsi;
}

function getDENOMINATOR() public pure returns (uint256) {
return DENOMINATOR;
}

function getEpochsPerYear() public pure returns (uint256) {
return EPOCHS_YEAR;
}

function setRSI(uint256 newRSI) public onlyRole(MANAGER_ROLE) {
if (newRSI > MAX_RSI_BONUS) revert InvalidRSI();

if (newRSI < MIN_RSI_BONUS) newRSI = 0;

rsi = newRSI;
}

function getMaxAPR() public view returns (uint256 nominator, uint256 denominator) {
// TODO: Base + vesting and RSI must return the max possible value here (implement max base)
uint256 vestBonus = calcVestingBonus(52);

nominator = (base + vestBonus) * macroFactor * MAX_RSI_BONUS;
denominator = 10000 * 10000 * 10000;
}

function applyMaxReward(uint256 reward) public view returns (uint256) {
// TODO: Consider setting max base
// max vesting bonus is 52 weeks
uint256 vestBonus = calcVestingBonus(52);

uint256 bonus = (base + vestBonus) * MAX_RSI_BONUS;

return ((reward * bonus) / (10000 * 10000)) / EPOCHS_YEAR;
}

// TODO: Apply EPOCHS_IN_YEAR everywhere it is needed

function getEpochMaxReward(uint256 totalStaked) public view returns (uint256 reward) {
uint256 nominator;
uint256 denominator;

(nominator, denominator) = getMaxAPR();

// Divide to EPOCHS_YEAR because result is yearly
return (totalStaked * nominator) / denominator / EPOCHS_YEAR;
}

function getVestingBonus(uint256 weeksCount) public view returns (uint256 nominator) {
return vestingBonus[weeksCount - 1];
}

// TODO: Calculate per epoch - currently yearly reward is used
function applyMacro(uint256 totalStaked) public view returns (uint256 reward) {
return (totalStaked * macroFactor) / DENOMINATOR;
}

function magnitude() internal pure returns (uint256) {
return 1e18;
}

function applyBaseAPR(uint256 amount) public view returns (uint256) {
return (amount * base) / DENOMINATOR / EPOCHS_YEAR;
}

function initializeVestingBonus() private {
vestingBonus[0] = 6;
vestingBonus[1] = 16;
vestingBonus[2] = 30;
vestingBonus[3] = 46;
vestingBonus[4] = 65;
vestingBonus[5] = 85;
vestingBonus[6] = 108;
vestingBonus[7] = 131;
vestingBonus[8] = 157;
vestingBonus[9] = 184;
vestingBonus[10] = 212;
vestingBonus[11] = 241;
vestingBonus[12] = 272;
vestingBonus[13] = 304;
vestingBonus[14] = 338;
vestingBonus[15] = 372;
vestingBonus[16] = 407;
vestingBonus[17] = 444;
vestingBonus[18] = 481;
vestingBonus[19] = 520;
vestingBonus[20] = 559;
vestingBonus[21] = 599;
vestingBonus[22] = 641;
vestingBonus[23] = 683;
vestingBonus[24] = 726;
vestingBonus[25] = 770;
vestingBonus[26] = 815;
vestingBonus[27] = 861;
vestingBonus[28] = 907;
vestingBonus[29] = 955;
vestingBonus[30] = 1003;
vestingBonus[31] = 1052;
vestingBonus[32] = 1101;
vestingBonus[33] = 1152;
vestingBonus[34] = 1203;
vestingBonus[35] = 1255;
vestingBonus[36] = 1307;
vestingBonus[37] = 1361;
vestingBonus[38] = 1415;
vestingBonus[39] = 1470;
vestingBonus[40] = 1525;
vestingBonus[41] = 1581;
vestingBonus[42] = 1638;
vestingBonus[43] = 1696;
vestingBonus[44] = 1754;
vestingBonus[45] = 1812;
vestingBonus[46] = 1872;
vestingBonus[47] = 1932;
vestingBonus[48] = 1993;
vestingBonus[49] = 2054;
vestingBonus[50] = 2116;
vestingBonus[51] = 2178;
}
}
27 changes: 27 additions & 0 deletions contracts/APRCalculator/APRCalculatorConnector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import {Unauthorized} from "./../common/Errors.sol";
import {IAPRCalculator} from "./IAPRCalculator.sol";

abstract contract APRCalculatorConnector is Initializable {
IAPRCalculator public aprCalculatorContract;

function __APRCalculatorConnector_init(address aprCalculatorAddr) internal onlyInitializing {

Check warning on line 12 in contracts/APRCalculator/APRCalculatorConnector.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase

Check warning on line 12 in contracts/APRCalculator/APRCalculatorConnector.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase
__APRCalculatorConnector_init_unchained(aprCalculatorAddr);
}

function __APRCalculatorConnector_init_unchained(address aprCalculatorAddr) internal onlyInitializing {

Check warning on line 16 in contracts/APRCalculator/APRCalculatorConnector.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase

Check warning on line 16 in contracts/APRCalculator/APRCalculatorConnector.sol

View workflow job for this annotation

GitHub Actions / lint

Function name must be in mixedCase
aprCalculatorContract = IAPRCalculator(aprCalculatorAddr);
}

modifier onlyAPRCalculator() {
if (msg.sender != address(aprCalculatorContract)) {
revert Unauthorized("ONLY_APR_CALCULATOR");
}

_;
}
}
22 changes: 22 additions & 0 deletions contracts/APRCalculator/IAPRCalculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface IAPRCalculator {
function getBaseAPR() external view returns (uint256);

function getRSIBonus() external view returns (uint256);

function getDENOMINATOR() external pure returns (uint256);

function getEpochsPerYear() external pure returns (uint256);

function applyBaseAPR(uint256 amount) external view returns (uint256);

function applyMaxReward(uint256 reward) external view returns (uint256);

function calcVestingBonus(uint256 weeksCount) external view returns (uint256);

function applyMacro(uint256 totalStaked) external view returns (uint256 reward);

function getVestingBonus(uint256 weeksCount) external view returns (uint256 nominator);
}
117 changes: 117 additions & 0 deletions contracts/HydraChain/HydraChain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {ArraysUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ArraysUpgradeable.sol";

import {System} from "./../common/System/System.sol";
import {Inspector} from "./modules/Inspector/Inspector.sol";
import {PowerExponent} from "./modules/PowerExponent/PowerExponent.sol";
import {ValidatorManager, ValidatorInit} from "./modules/ValidatorManager/ValidatorManager.sol";
import {SafeMathInt} from "./../common/libs/SafeMathInt.sol";
import {IBLS} from "../BLS/IBLS.sol";
import {IHydraChain} from "./IHydraChain.sol";
import {IEpochManager, Epoch} from "./modules/EpochManager/IEpochManager.sol";
import {Uptime} from "./modules/ValidatorManager/IValidatorManager.sol";

// TODO: setup use of reward account that would handle the amounts of rewards

contract HydraChain is IHydraChain, ValidatorManager, Inspector, PowerExponent {
using ArraysUpgradeable for uint256[];

uint256 public currentEpochId;
/// @notice Epoch data linked with the epoch id
mapping(uint256 => Epoch) public epochs;
/// @notice Array with epoch ending blocks
uint256[] public epochEndBlocks;

mapping(uint256 => uint256) internal _commitBlockNumbers;

// _______________ Initializer _______________

/**
* @notice Initializer function for genesis contract, called by the Hydra client at genesis to set up the initial state.
* @dev only callable by client, can only be called once
*/
function initialize(
ValidatorInit[] calldata newValidators,
address governance,
IBLS newBls
) external initializer onlySystemCall {
__PowerExponent_init();
__ValidatorManager_init(newValidators, newBls, governance);
__Inspector_init();

_initialize();
}

function _initialize() private {
currentEpochId = 1;
epochEndBlocks.push(0);
}

// _______________ External functions _______________

/**
* @inheritdoc IEpochManager
*/
function getCurrentEpochId() external view returns (uint256) {}

/**
* @inheritdoc IEpochManager
*/
function totalBlocks(uint256 epochId) external view returns (uint256 length) {
uint256 endBlock = epochs[epochId].endBlock;
length = endBlock == 0 ? 0 : endBlock - epochs[epochId].startBlock + 1;
}

/**
* @inheritdoc IEpochManager
*/
function getEpochByBlock(uint256 blockNumber) external view returns (Epoch memory) {
uint256 epochIndex = epochEndBlocks.findUpperBound(blockNumber);
return epochs[epochIndex];
}

function commitEpoch(
uint256 id,
Epoch calldata epoch,
uint256 epochSize,
Uptime[] calldata uptime
) external onlySystemCall {
uint256 newEpochId = currentEpochId++;
require(id == newEpochId, "UNEXPECTED_EPOCH_ID");
require(epoch.endBlock > epoch.startBlock, "NO_BLOCKS_COMMITTED");
require((epoch.endBlock - epoch.startBlock + 1) % epochSize == 0, "EPOCH_MUST_BE_DIVISIBLE_BY_EPOCH_SIZE");
require(epochs[newEpochId - 1].endBlock + 1 == epoch.startBlock, "INVALID_START_BLOCK");

epochs[newEpochId] = epoch;
_commitBlockNumbers[newEpochId] = block.number;
epochEndBlocks.push(epoch.endBlock);

_applyPendingExp(); // Apply new exponent in case it was changed in the latest epoch

// Update participations
uint256 uptimesCount = uptime.length;
for (uint256 i = 0; i < uptimesCount; i++) {
_updateParticipation(uptime[i].validator);
}

emit NewEpoch(id, epoch.startBlock, epoch.endBlock, epoch.epochRoot);
}

// _______________ Public functions _______________

// Apply custom rules for ban eligibility
function isSubjectToBan(address validator) public view override returns (bool) {
uint256 lastCommittedEndBlock = epochs[currentEpochId - 1].endBlock;
// check if the threshold is reached when the method is not executed by the owner (governance)
if (msg.sender != owner() && lastCommittedEndBlock - validatorsParticipation[validator] < banThreshold) {
return false;
}

return true;
}

// slither-disable-next-line unused-state,naming-convention
uint256[50] private __gap;
}
9 changes: 9 additions & 0 deletions contracts/HydraChain/IHydraChain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {IInspector} from "./modules/Inspector/IInspector.sol";
import {IPowerExponent} from "./modules/PowerExponent/IPowerExponent.sol";
import {IValidatorManager} from "./modules/ValidatorManager/IValidatorManager.sol";
import {IEpochManager} from "./modules/EpochManager/IEpochManager.sol";

interface IHydraChain is IInspector, IEpochManager, IValidatorManager, IPowerExponent {}
Loading
Loading