2024-11-13 12:12:49 -03:00
|
|
|
// SPDX-License-Identifier: MIT-1.0
|
|
|
|
pragma solidity ^0.8.27;
|
|
|
|
|
|
|
|
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
|
|
import { MultiplierPointMath } from "./MultiplierPointMath.sol";
|
2024-11-25 10:31:10 -03:00
|
|
|
import { ExpiredStakeStorage } from "./storage/ExpiredStakeStorage.sol";
|
2024-11-13 12:12:49 -03:00
|
|
|
|
2024-11-25 10:31:10 -03:00
|
|
|
/**
|
|
|
|
* @title EpochMath
|
|
|
|
* @notice Abstract contract for managing epochs, rewards, and multiplier points (MP) within the system.
|
|
|
|
* @author Ricardo Guilherme Schmidt
|
|
|
|
*/
|
2024-11-13 12:12:49 -03:00
|
|
|
abstract contract EpochMath is MultiplierPointMath {
|
2024-11-25 10:31:10 -03:00
|
|
|
function totalSupply() public view virtual returns (uint256);
|
|
|
|
function epochReward() public view virtual returns (uint256);
|
2024-11-13 12:12:49 -03:00
|
|
|
|
2024-11-25 10:31:10 -03:00
|
|
|
uint256 public immutable START_TIME;
|
|
|
|
|
|
|
|
struct Epoch {
|
|
|
|
uint256 epochReward;
|
|
|
|
uint256 totalSupply;
|
|
|
|
uint256 potentialMP;
|
|
|
|
}
|
|
|
|
|
|
|
|
mapping(uint256 index => Epoch value) public epochs;
|
|
|
|
uint256 public pendingReward;
|
|
|
|
uint256 public currentEpoch;
|
|
|
|
uint256 public potentialMP;
|
|
|
|
uint256 public totalMPRate;
|
|
|
|
ExpiredStakeStorage public EXPIRED_STAKE_STORAGE;
|
|
|
|
|
|
|
|
uint256 public currentEpochTotalExpiredMP;
|
|
|
|
|
|
|
|
constructor(uint256 _startTime) {
|
|
|
|
EXPIRED_STAKE_STORAGE = _createStorage();
|
|
|
|
START_TIME = _startTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Creates a new instance of ExpiredStakeStorage.
|
|
|
|
* @return addr A new instance of the ExpiredStakeStorage contract.
|
|
|
|
*/
|
|
|
|
function _createStorage() internal virtual returns (ExpiredStakeStorage addr) {
|
|
|
|
return new ExpiredStakeStorage();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Release rewards for current epoch and increase epoch up to _limitEpoch
|
|
|
|
* @param _limitEpoch Until what epoch it should be executed
|
|
|
|
*/
|
|
|
|
function _finalizeEpoch(uint256 _limitEpoch) internal {
|
|
|
|
uint256 tempCurrentEpoch = currentEpoch;
|
|
|
|
while (tempCurrentEpoch < _limitEpoch) {
|
|
|
|
Epoch storage thisEpoch = epochs[tempCurrentEpoch];
|
|
|
|
uint256 expiredMP = EXPIRED_STAKE_STORAGE.getExpiredMP(tempCurrentEpoch);
|
|
|
|
if (expiredMP > 0) {
|
|
|
|
totalMPRate -= expiredMP;
|
|
|
|
EXPIRED_STAKE_STORAGE.deleteExpiredMP(tempCurrentEpoch);
|
|
|
|
}
|
|
|
|
uint256 epochPotentialMP = totalMPRate;
|
|
|
|
if (tempCurrentEpoch == currentEpoch) {
|
|
|
|
epochPotentialMP -= currentEpochTotalExpiredMP;
|
|
|
|
currentEpochTotalExpiredMP = 0;
|
|
|
|
thisEpoch.epochReward = epochReward();
|
|
|
|
pendingReward += thisEpoch.epochReward;
|
|
|
|
}
|
|
|
|
|
|
|
|
potentialMP += epochPotentialMP;
|
|
|
|
thisEpoch.potentialMP = epochPotentialMP;
|
|
|
|
thisEpoch.totalSupply = totalSupply();
|
|
|
|
tempCurrentEpoch++;
|
|
|
|
}
|
|
|
|
currentEpoch = tempCurrentEpoch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Calculates the new start time of restake based on MP predictions and processing time.
|
|
|
|
* @param _balance The account's token balance.
|
|
|
|
* @param _totalMP The current total multiplier points.
|
|
|
|
* @param _maxMP The maximum multiplier points allowed.
|
|
|
|
* @param _processTime The processing timestamp.
|
|
|
|
* @return newStartTime The adjusted start time.
|
|
|
|
*/
|
|
|
|
function _calculateNewStartTime(
|
|
|
|
uint256 _balance,
|
|
|
|
uint256 _totalMP,
|
|
|
|
uint256 _maxMP,
|
|
|
|
uint256 _processTime
|
|
|
|
)
|
|
|
|
internal
|
|
|
|
pure
|
|
|
|
returns (uint256 newStartTime)
|
|
|
|
{
|
|
|
|
return _processTime - _timeToAccrueMP(_balance, _retrieveAccruedMP(_balance, _totalMP, _maxMP));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Reduces predicted multiplier points (MP) for an account after unstake or restake.
|
|
|
|
* @param _startTime The start time of the account's stake.
|
|
|
|
* @param _balance The account's token balance.
|
|
|
|
*/
|
|
|
|
function _reducePredictiedMP(uint256 _startTime, uint256 _balance) internal {
|
|
|
|
uint256 accountEpoch = getEpoch(_startTime);
|
|
|
|
uint256 epochExpiredTime =
|
|
|
|
accountEpoch == currentEpoch ? getEpochStartTime(currentEpoch + 1) - _startTime : ACCURE_RATE;
|
|
|
|
(uint256 mpRate, uint256 mpFractional, uint256 epochTarget1, uint256 mpRemainder) =
|
|
|
|
_calculateMPPrediction(_balance, accountEpoch, epochExpiredTime);
|
|
|
|
|
|
|
|
currentEpochTotalExpiredMP -= mpFractional;
|
|
|
|
if (mpRemainder > 0) {
|
|
|
|
if (epochTarget1 < currentEpoch) {
|
|
|
|
EXPIRED_STAKE_STORAGE.decrementExpiredMP(epochTarget1, mpRemainder);
|
|
|
|
totalMPRate -= mpRemainder;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (epochTarget1 + 1 < currentEpoch) {
|
|
|
|
EXPIRED_STAKE_STORAGE.decrementExpiredMP(epochTarget1 + 1, mpRate - mpRemainder);
|
|
|
|
totalMPRate -= mpRate - mpRemainder;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (epochTarget1 < currentEpoch) {
|
|
|
|
EXPIRED_STAKE_STORAGE.decrementExpiredMP(epochTarget1, mpRate);
|
|
|
|
totalMPRate -= mpRate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Increases predicted multiplier points (MP) for an account based on new stakes.
|
|
|
|
* @param _startTime The start time of the stake.
|
|
|
|
* @param _balance The token balance for the stake.
|
|
|
|
* @param _maxMP The maximum multiplier points allowed.
|
|
|
|
* @param _totalMP The current total multiplier points.
|
|
|
|
*/
|
|
|
|
function _increasePredictedMP(uint256 _startTime, uint256 _balance, uint256 _maxMP, uint256 _totalMP) internal {
|
|
|
|
uint256 accountEpoch = getEpoch(_startTime);
|
|
|
|
uint256 epochExpiredTime =
|
|
|
|
accountEpoch == currentEpoch ? getEpochStartTime(currentEpoch + 1) - _startTime : ACCURE_RATE;
|
|
|
|
(uint256 mpRate, uint256 mpFractional, uint256 epochTarget1, uint256 mpRemainder) =
|
|
|
|
_calculateMPPrediction(_balance, accountEpoch, epochExpiredTime);
|
|
|
|
|
|
|
|
currentEpochTotalExpiredMP += mpFractional;
|
|
|
|
if (mpRemainder > 0) {
|
|
|
|
if (currentEpoch < epochTarget1) {
|
|
|
|
EXPIRED_STAKE_STORAGE.incrementExpiredMP(epochTarget1, mpRemainder);
|
|
|
|
}
|
|
|
|
if (currentEpoch < epochTarget1 + 1) {
|
|
|
|
EXPIRED_STAKE_STORAGE.incrementExpiredMP(epochTarget1 + 1, mpRate - mpRemainder);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
EXPIRED_STAKE_STORAGE.incrementExpiredMP(epochTarget1, mpRate);
|
|
|
|
}
|
|
|
|
totalMPRate += Math.min(mpRate, _maxMP - _totalMP);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Predicts the multiplier points (MP) for an account based on epoch time difference and balance.
|
|
|
|
* @param _balance The account's token balance.
|
|
|
|
* @param _accountEpoch The epoch of the account's stake.
|
|
|
|
* @param _deltaTime The time difference from current epoch
|
|
|
|
* @return mpRate The MP rate accrued over a full epoch.
|
|
|
|
* @return mpFractional The fractional MP for the time difference.
|
|
|
|
* @return epochTarget1 The target epoch for full MP accrual.
|
|
|
|
* @return mpRemainder The remaining MP for the next epoch.
|
|
|
|
*/
|
2024-11-13 12:12:49 -03:00
|
|
|
function _calculateMPPrediction(
|
2024-11-25 10:31:10 -03:00
|
|
|
uint256 _balance,
|
|
|
|
uint256 _accountEpoch,
|
2024-11-13 12:12:49 -03:00
|
|
|
uint256 _deltaTime
|
|
|
|
)
|
|
|
|
internal
|
|
|
|
pure
|
2024-11-25 10:31:10 -03:00
|
|
|
returns (uint256 mpRate, uint256 mpFractional, uint256 epochTarget1, uint256 mpRemainder)
|
2024-11-13 12:12:49 -03:00
|
|
|
{
|
2024-11-25 12:22:27 -03:00
|
|
|
mpRate = _accrueMP(_balance, ACCURE_RATE);
|
|
|
|
mpFractional = mpRate - _accrueMP(_balance, _deltaTime);
|
2024-11-13 12:12:49 -03:00
|
|
|
|
2024-11-25 12:22:27 -03:00
|
|
|
uint256 mpTarget = _maxAccrueMP(_balance) + mpFractional;
|
2024-11-13 12:12:49 -03:00
|
|
|
uint256 deltaEpochTarget1 = mpTarget / mpRate;
|
|
|
|
|
2024-11-25 10:31:10 -03:00
|
|
|
epochTarget1 = _accountEpoch + deltaEpochTarget1;
|
|
|
|
if (mpTarget % mpRate > 0) {
|
|
|
|
mpRemainder = (mpRate * (deltaEpochTarget1 + 1)) - mpTarget;
|
2024-11-13 12:12:49 -03:00
|
|
|
}
|
|
|
|
}
|
2024-11-25 10:31:10 -03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Returns the epoch of a specific timestamp
|
|
|
|
* @param _timestamp the timestamp to calculate the epoch
|
|
|
|
* @return _epochNum the number of the epoch
|
|
|
|
*/
|
|
|
|
function getEpoch(uint256 _timestamp) public view returns (uint256 _epochNum) {
|
|
|
|
_epochNum = (_timestamp - START_TIME) / ACCURE_RATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Returns the start time of an epoch
|
|
|
|
* @param _epochNum epoch number
|
|
|
|
* @return _epochEnd start time of epoch
|
|
|
|
*/
|
|
|
|
function getEpochStartTime(uint256 _epochNum) public view returns (uint256 _epochEnd) {
|
|
|
|
return START_TIME + (ACCURE_RATE * (_epochNum));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @notice Returns the last epoch that can be processed on current time
|
|
|
|
* @return _newEpoch the number of the epoch after all epochs that can be processed
|
|
|
|
*/
|
|
|
|
function newEpoch() public view returns (uint256 _newEpoch) {
|
|
|
|
_newEpoch = (block.timestamp - START_TIME) / ACCURE_RATE;
|
|
|
|
}
|
2024-11-13 12:12:49 -03:00
|
|
|
}
|