staking/contracts/StakeManager.sol

225 lines
8.1 KiB
Solidity
Raw Normal View History

2023-05-16 21:59:25 +00:00
// SPDX-License-Identifier: MIT
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;
uint256 public constant YEAR = 365 days;
2023-06-23 16:31:13 +00:00
uint256 public constant MP_APY = 1;
uint256 public constant STAKE_APY = 1;
uint256 public constant MAX_BOOST = 4;
2023-06-23 16:31:13 +00:00
uint256 public constant MAX_MP = 1;
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();
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];
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");
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 {
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-06-25 14:44:32 +00:00
function calcMaxMultiplierIncrease(uint256 _increasedMultiplier, uint256 _currentMp) private pure returns(uint256 _maxToIncrease) {
2023-06-23 16:29:36 +00:00
uint256 newMp = _increasedMultiplier + _currentMp;
return newMp > MAX_MP ? MAX_MP - newMp : _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(
account.balance * (MP_APY * deltaTime),
account.multiplier);
account.multiplier += increasedMultiplier;
multiplierSupply += increasedMultiplier;
}
2023-06-26 14:52:51 +00:00
function mintIntialMultiplier(Account storage account, uint256 lockTime, uint256 amount, uint256 initMint) private {
//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;
uint256 increasedMultiplier = amount * ((dT/YEAR)+initMint);
account.lastMint = block.timestamp;
account.multiplier += increasedMultiplier;
multiplierSupply += 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
}