2023-05-16 21:59:25 +00:00
// SPDX-License-Identifier: MIT
2023-07-24 15:16:44 +00:00
pragma solidity ^ 0 . 8 . 18 ;
2023-05-16 21:59:25 +00:00
import " @openzeppelin/contracts/token/ERC20/ERC20.sol " ;
2023-06-23 21:38:55 +00:00
import " @openzeppelin/contracts/access/Ownable.sol " ;
2023-06-26 14:52:51 +00:00
import " ./StakeVault.sol " ;
2023-05-16 21:59:25 +00:00
2023-06-23 21:38:55 +00:00
contract StakeManager is Ownable {
2023-05-16 21:59:25 +00:00
2023-06-20 14:53:34 +00:00
struct Account {
2023-06-23 16:09:07 +00:00
uint256 lockUntil ;
2023-06-20 14:53:34 +00:00
uint256 balance ;
uint256 multiplier ;
2023-06-26 14:52:51 +00:00
uint256 lastMint ;
2023-06-21 14:20:23 +00:00
uint256 epoch ;
2023-06-26 14:52:51 +00:00
address rewardAddress ;
2023-06-20 14:53:34 +00:00
}
2023-05-16 21:59:25 +00:00
2023-06-21 14:20:23 +00:00
struct Epoch {
uint256 startTime ;
2023-06-26 14:52:51 +00:00
uint256 epochReward ;
uint256 totalSupply ;
2023-06-21 14:20:23 +00:00
}
2023-06-23 23:01:59 +00:00
uint256 public constant EPOCH_SIZE = 1 weeks ;
2023-07-11 09:56:34 +00:00
uint256 public constant YEAR = 365 days ;
2023-06-23 16:31:13 +00:00
uint256 public constant MP_APY = 1 ;
2023-07-11 09:56:34 +00:00
uint256 public constant MAX_BOOST = 4 ;
2023-07-11 13:47:55 +00:00
2023-06-23 16:31:13 +00:00
mapping ( address => Account ) accounts ;
2023-06-26 14:52:51 +00:00
mapping ( uint256 => Epoch ) epochs ;
2023-06-23 18:03:52 +00:00
mapping ( bytes32 => bool ) isVault ;
2023-06-23 16:31:13 +00:00
2023-06-23 23:01:59 +00:00
2023-06-26 14:52:51 +00:00
uint256 public currentEpoch ;
uint256 public pendingReward ;
2023-06-23 23:01:59 +00:00
uint256 public multiplierSupply ;
2023-06-26 14:52:51 +00:00
uint256 public stakeSupply ;
2023-06-23 21:38:55 +00:00
StakeManager public migration ;
2023-06-23 23:01:59 +00:00
StakeManager public immutable oldManager ;
ERC20 public immutable stakedToken ;
2023-06-23 18:03:52 +00:00
modifier onlyVault {
2023-06-23 23:01:59 +00:00
require ( isVault [ msg . sender . codehash ] , " Not a vault " ) ;
2023-06-23 18:03:52 +00:00
_ ;
}
2023-06-23 23:01:59 +00:00
constructor ( ERC20 _stakedToken , StakeManager _oldManager ) Ownable ( ) {
2023-06-26 14:52:51 +00:00
epochs [ 0 ] . startTime = block . timestamp ;
2023-06-23 23:01:59 +00:00
oldManager = _oldManager ;
stakedToken = _stakedToken ;
2023-06-21 14:20:23 +00:00
}
2023-06-23 16:47:37 +00:00
/**
* Increases balance of msg . sender ;
* @ param _amount Amount of balance to be decreased .
2023-06-23 23:01:59 +00:00
* @ param _time Seconds from block . timestamp to lock balance .
2023-06-23 16:47:37 +00:00
* /
2023-06-25 14:44:32 +00:00
function stake ( uint256 _amount , uint256 _time ) external onlyVault {
2023-06-22 18:42:42 +00:00
Account storage account = accounts [ msg . sender ] ;
2023-06-26 14:52:51 +00:00
processAccount ( account , currentEpoch ) ;
2023-06-22 18:42:42 +00:00
account . balance += _amount ;
2023-06-26 14:52:51 +00:00
account . rewardAddress = StakeVault ( msg . sender ) . owner ( ) ;
2023-07-11 10:08:30 +00:00
mintIntialMultiplier ( account , _time , _amount , 1 ) ;
2023-06-26 14:52:51 +00:00
stakeSupply += _amount ;
2023-05-16 21:59:25 +00:00
}
2023-06-23 16:47:37 +00:00
/**
* Decreases balance of msg . sender ;
* @ param _amount Amount of balance to be decreased
* /
2023-06-25 14:44:32 +00:00
function unstake ( uint256 _amount ) external onlyVault {
2023-06-22 18:42:42 +00:00
Account storage account = accounts [ msg . sender ] ;
2023-07-06 12:12:01 +00:00
require ( account . lockUntil <= block . timestamp , " Funds are locked " ) ;
2023-06-26 14:52:51 +00:00
processAccount ( account , currentEpoch ) ;
2023-06-23 16:09:07 +00:00
uint256 reducedMultiplier = ( _amount * account . multiplier ) / account . balance ;
account . multiplier -= reducedMultiplier ;
2023-06-22 18:42:42 +00:00
account . balance -= _amount ;
2023-06-23 16:09:07 +00:00
multiplierSupply -= reducedMultiplier ;
2023-06-26 14:52:51 +00:00
stakeSupply -= _amount ;
2023-05-16 21:59:25 +00:00
}
2023-06-23 16:29:36 +00:00
/**
2023-06-23 16:47:37 +00:00
* @ notice Locks entire balance for more amount of time .
2023-06-23 16:29:36 +00:00
* @ param _time amount of time to lock from now .
* /
2023-06-25 14:44:32 +00:00
function lock ( uint256 _time ) external onlyVault {
2023-06-22 18:42:42 +00:00
Account storage account = accounts [ msg . sender ] ;
2023-06-26 14:52:51 +00:00
processAccount ( account , currentEpoch ) ;
2023-06-23 23:01:59 +00:00
require ( block . timestamp + _time > account . lockUntil , " Cannot decrease lock time " ) ;
2023-07-11 10:08:30 +00:00
mintIntialMultiplier ( account , _time , account . balance , 0 ) ;
2023-05-16 21:59:25 +00:00
}
2023-06-26 15:26:02 +00:00
/**
* @ notice leave without processing account
* /
function leave ( ) external onlyVault {
2023-07-06 12:12:01 +00:00
require ( address ( migration ) != address ( 0 ) , " Leave only during migration " ) ;
2023-06-26 15:26:02 +00:00
Account memory account = accounts [ msg . sender ] ;
delete accounts [ msg . sender ] ;
multiplierSupply -= account . multiplier ;
2023-06-26 15:30:48 +00:00
stakeSupply -= account . balance ;
2023-06-26 15:26:02 +00:00
}
2023-06-23 16:47:37 +00:00
/**
* @ notice Release rewards for current epoch and increase epoch .
* /
2023-06-26 14:52:51 +00:00
function executeEpoch ( ) external {
processEpoch ( ) ;
2023-06-21 14:20:23 +00:00
}
2023-06-23 16:47:37 +00:00
/**
* @ notice Execute rewards for account until limit has reached
* @ param _vault Referred account
2023-06-23 23:01:59 +00:00
* @ param _limitEpoch Until what epoch it should be executed
2023-06-23 16:47:37 +00:00
* /
2023-06-26 14:52:51 +00:00
function executeAccount ( address _vault , uint256 _limitEpoch ) external {
processAccount ( accounts [ _vault ] , _limitEpoch ) ;
2023-06-21 14:20:23 +00:00
}
2023-06-23 18:03:52 +00:00
/**
* @ notice Enables a contract class to interact with staking functions
* @ param _codehash bytecode hash of contract
* /
2023-06-23 21:38:55 +00:00
function setVault ( bytes32 _codehash ) external onlyOwner {
2023-06-23 18:03:52 +00:00
isVault [ _codehash ] = true ;
}
2023-06-23 23:01:59 +00:00
/**
* @ notice Migrate account to new manager .
* /
function migrate ( ) external onlyVault returns ( StakeManager newManager ) {
require ( address ( migration ) != address ( 0 ) , " Migration not available " ) ;
2023-06-23 21:38:55 +00:00
Account storage account = accounts [ msg . sender ] ;
2023-06-23 23:01:59 +00:00
stakedToken . approve ( address ( migration ) , account . balance ) ;
2023-06-23 21:38:55 +00:00
migration . migrate ( msg . sender , account ) ;
2023-06-23 23:01:59 +00:00
delete accounts [ msg . sender ] ;
return migration ;
2023-06-23 21:38:55 +00:00
}
2023-06-23 23:01:59 +00:00
/**
* @ dev Only callable from old manager .
* @ notice Migrate account from old manager
* @ param _vault Account address
* @ param _account Account data
* /
2023-06-23 21:38:55 +00:00
function migrate ( address _vault , Account memory _account ) external {
2023-06-23 23:01:59 +00:00
require ( msg . sender == address ( oldManager ) , " Unauthorized " ) ;
stakedToken . transferFrom ( address ( oldManager ) , address ( this ) , _account . balance ) ;
2023-06-26 14:52:51 +00:00
accounts [ _vault ] = _account ;
}
2023-06-23 21:38:55 +00:00
2023-07-24 15:16:44 +00:00
function calcMaxMultiplierIncrease ( uint256 _increasedMultiplier , uint256 _currentMp , uint256 _lockUntil , uint256 _stake ) private view returns ( uint256 _maxToIncrease ) {
2023-06-23 16:29:36 +00:00
uint256 newMp = _increasedMultiplier + _currentMp ;
2023-07-11 13:47:55 +00:00
if ( block . timestamp > _lockUntil ) {
//not locked, limit to max_boost
return newMp > _stake * MAX_BOOST ? _stake * MAX_BOOST - _currentMp : _increasedMultiplier ;
} else {
// locked, ignore cap
return _increasedMultiplier ;
}
2023-05-24 13:25:52 +00:00
}
2023-06-26 14:52:51 +00:00
function processEpoch ( ) private {
if ( block . timestamp >= epochEnd ( ) ) {
//finalize current epoch
epochs [ currentEpoch ] . epochReward = epochReward ( ) ;
epochs [ currentEpoch ] . totalSupply = totalSupply ( ) ;
pendingReward += epochs [ currentEpoch ] . epochReward ;
//create new epoch
currentEpoch ++ ;
epochs [ currentEpoch ] . startTime = block . timestamp ;
}
}
function processAccount ( Account storage account , uint256 _limitEpoch ) private {
processEpoch ( ) ;
2023-06-26 15:26:02 +00:00
require ( address ( migration ) == address ( 0 ) , " Contract ended, please migrate " ) ;
require ( _limitEpoch <= currentEpoch , " Non-sense call " ) ;
2023-06-26 14:52:51 +00:00
uint256 userReward ;
uint256 userEpoch = account . epoch ;
for ( Epoch memory iEpoch = epochs [ userEpoch ] ; userEpoch < _limitEpoch ; userEpoch ++ ) {
//mint multipliers to that epoch
mintMultiplier ( account , iEpoch . startTime + EPOCH_SIZE ) ;
uint256 userSupply = account . balance + account . multiplier ;
uint256 userShare = userSupply / iEpoch . totalSupply ; //TODO: might lose precision, multiply by 100 and divide back later?
userReward += userShare * iEpoch . epochReward ;
}
account . epoch = userEpoch ;
if ( userReward > 0 ) {
pendingReward -= userReward ;
stakedToken . transfer ( account . rewardAddress , userReward ) ;
}
mintMultiplier ( account , block . timestamp ) ;
}
function mintMultiplier ( Account storage account , uint256 processTime ) private {
uint256 deltaTime = processTime - account . lastMint ;
account . lastMint = processTime ;
uint256 increasedMultiplier = calcMaxMultiplierIncrease (
2023-07-11 13:47:55 +00:00
account . balance * ( MP_APY / YEAR * deltaTime ) ,
account . multiplier , account . lockUntil , account . balance ) ;
2023-06-26 14:52:51 +00:00
account . multiplier += increasedMultiplier ;
multiplierSupply += increasedMultiplier ;
2023-07-06 12:12:01 +00:00
}
2023-06-26 14:52:51 +00:00
2023-07-11 10:08:30 +00:00
function mintIntialMultiplier ( Account storage account , uint256 lockTime , uint256 amount , uint256 initMint ) private {
2023-07-06 12:12:01 +00:00
//if balance still locked, multipliers must be minted from difference of time.
uint256 dT = account . lockUntil > block . timestamp ? block . timestamp + lockTime - account . lockUntil : lockTime ;
account . lockUntil = block . timestamp + lockTime ;
2023-07-11 10:08:30 +00:00
uint256 increasedMultiplier = amount * ( ( dT / YEAR ) + initMint ) ;
2023-07-06 12:12:01 +00:00
account . lastMint = block . timestamp ;
2023-07-11 13:47:55 +00:00
increasedMultiplier = account . multiplier + increasedMultiplier > ( account . balance * ( MAX_BOOST + ( dT / YEAR ) ) ) ? account . balance * ( MAX_BOOST + ( dT / YEAR ) ) - account . multiplier : increasedMultiplier ; // checks if MPs are within (lock_time_in_years+MAX_BOOST)*stake
2023-07-06 12:12:01 +00:00
multiplierSupply += increasedMultiplier ;
2023-07-11 13:47:55 +00:00
account . multiplier += increasedMultiplier ;
2023-06-26 14:52:51 +00:00
}
function totalSupply ( ) public view returns ( uint256 ) {
return multiplierSupply + stakeSupply ;
}
function epochReward ( ) public view returns ( uint256 ) {
return stakedToken . balanceOf ( address ( this ) ) - pendingReward ;
}
function epochEnd ( ) public view returns ( uint256 ) {
return epochs [ currentEpoch ] . startTime + EPOCH_SIZE ;
}
2023-05-16 21:59:25 +00:00
}