Skip to content
Draft
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
47 changes: 45 additions & 2 deletions contracts/StakeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pragma solidity ^0.8.18;

import { console } from "forge-std/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
Expand Down Expand Up @@ -43,6 +44,8 @@ contract StakeManager is Ownable {
uint256 public constant MAX_LOCKUP_PERIOD = 4 * YEAR; // 4 years
uint256 public constant MP_APY = 1;
uint256 public constant MAX_BOOST = 4;
uint256 public constant DEPOSIT_COOLDOWN_PERIOD = 2 weeks;
uint256 public constant WITHDRAW_COOLDOWN_PERIOD = 2 weeks;

mapping(address index => Account value) public accounts;
mapping(uint256 index => Epoch value) public epochs;
Expand Down Expand Up @@ -108,12 +111,22 @@ contract StakeManager is Ownable {
*/
modifier finalizeEpoch() {
if (block.timestamp >= epochEnd() && address(migration) == address(0)) {
console.log("Processing epoch...");
console.log("--- currentEpoch: ", currentEpoch);

uint256 _epochReward = epochReward();
console.log("--- epochReward: ", _epochReward);
//finalize current epoch
epochs[currentEpoch].epochReward = epochReward();
epochs[currentEpoch].epochReward = _epochReward;
epochs[currentEpoch].totalSupply = totalSupply();
pendingReward += epochs[currentEpoch].epochReward;

console.log("--- totalSupply: ", totalSupply());
console.log("--- pendingReward: ", pendingReward);
//create new epoch
currentEpoch++;
console.log("Done. New epoch started: ", currentEpoch);
console.log("\n");
epochs[currentEpoch].startTime = block.timestamp;
}
_;
Expand All @@ -133,6 +146,7 @@ contract StakeManager is Ownable {
* @dev Reverts when resulting locked time is not in range of [MIN_LOCKUP_PERIOD, MAX_LOCKUP_PERIOD]
*/
function stake(uint256 _amount, uint256 _timeToIncrease) external onlyVault noPendingMigration finalizeEpoch {
console.log("--- Staking: ", _amount);
Account storage account = accounts[msg.sender];

if (account.lockUntil == 0) {
Expand Down Expand Up @@ -248,6 +262,7 @@ contract StakeManager is Ownable {
onlyAccountInitialized(_vault)
finalizeEpoch
{
console.log("Processing account: ", _vault);
_processAccount(accounts[_vault], _limitEpoch);
}

Expand Down Expand Up @@ -367,14 +382,30 @@ contract StakeManager is Ownable {
uint256 userEpoch = account.epoch;
uint256 mpDifference = account.totalMP;
for (Epoch storage iEpoch = epochs[userEpoch]; userEpoch < _limitEpoch; userEpoch++) {
console.log("--- processing account epoch: ", userEpoch);
uint256 userSupply = account.balance + account.totalMP;
console.log("--- userSupply in epoch (before): ", userSupply);
console.log("------ account.balance: ", account.balance);
console.log("------ account.totalMP: ", account.totalMP);
console.log("--- epoch totalSupply (before): ", iEpoch.totalSupply);
console.log("------ Minting multiplier points for epoch...");
//mint multiplier points to that epoch
_mintMP(account, iEpoch.startTime + EPOCH_SIZE, iEpoch);
uint256 userSupply = account.balance + account.totalMP;
userSupply = account.balance + account.totalMP;
console.log("--- userSupply in epoch (after): ", userSupply);
console.log("------ account.balance: ", account.balance);
console.log("------ account.totalMP: ", account.totalMP);
console.log("--- epoch totalSupply (after): ", iEpoch.totalSupply);
uint256 userEpochReward = Math.mulDiv(userSupply, iEpoch.epochReward, iEpoch.totalSupply);
console.log("--- userEpochReward: ", userEpochReward);

userReward += userEpochReward;
iEpoch.epochReward -= userEpochReward;
console.log("--- removing epoch userSupply from epoch totalSupply: ", userSupply);
iEpoch.totalSupply -= userSupply;
console.log("--- New epoch epochReward: ", iEpoch.epochReward);
console.log("--- New epoch totalSupply: ", iEpoch.totalSupply);
console.log("\n");
}
account.epoch = userEpoch;
if (userReward > 0) {
Expand Down Expand Up @@ -411,6 +442,8 @@ contract StakeManager is Ownable {
//bonus for increased lock time
mpToMint += _getMPToMint(account.balance + amount, increasedLockTime);
}

console.log("--- Minting MP: ", mpToMint);
//update storage
totalSupplyMP += mpToMint;
account.bonusMP += mpToMint;
Expand All @@ -432,6 +465,8 @@ contract StakeManager is Ownable {
account.totalMP
);

console.log("--------- increasedMP: ", increasedMP);

//update storage
account.lastMint = processTime;
account.totalMP += increasedMP;
Expand Down Expand Up @@ -501,4 +536,12 @@ contract StakeManager is Ownable {
function epochEnd() public view returns (uint256 _epochEnd) {
return epochs[currentEpoch].startTime + EPOCH_SIZE;
}

function getEpoch(uint256 _epoch) public view returns (Epoch memory) {
return epochs[_epoch];
}

function getAccount(address _account) public view returns (Account memory) {
return accounts[_account];
}
}
119 changes: 106 additions & 13 deletions contracts/StakeVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,111 @@ import { StakeManager } from "./StakeManager.sol";
contract StakeVault is Ownable {
error StakeVault__MigrationNotAvailable();

error StakeVault__StakingFailed();
error StakeVault__InDepositCooldown();

error StakeVault__UnstakingFailed();
error StakeVault__InWithdrawCooldown();

error StakeVault__DepositFailed();

error StakeVault__WithdrawFailed();

error StakeVault__InsufficientFunds();

error StakeVault__InvalidLockTime();

event Deposited(uint256 amount);

event Withdrawn(uint256 amount);

event Staked(uint256 _amount, uint256 time);

StakeManager private stakeManager;

ERC20 public immutable STAKED_TOKEN;

event Staked(address from, address to, uint256 _amount, uint256 time);
uint256 public balance;

uint256 public depositCooldownUntil;

uint256 public withdrawCooldownUntil;

modifier whenNotInDepositCooldown() {
if (block.timestamp <= depositCooldownUntil) {
revert StakeVault__InDepositCooldown();
}
_;
}

modifier whenNotInWithdrawCooldown() {
if (block.timestamp <= withdrawCooldownUntil) {
revert StakeVault__InWithdrawCooldown();
}
_;
}

modifier onlySufficientBalance(uint256 _amount) {
uint256 availableFunds = _unstakedBalance();
if (_amount > availableFunds) {
revert StakeVault__InsufficientFunds();
}
_;
}

constructor(address _owner, ERC20 _stakedToken, StakeManager _stakeManager) {
_transferOwnership(_owner);
STAKED_TOKEN = _stakedToken;
stakeManager = _stakeManager;
}

function stake(uint256 _amount, uint256 _time) external onlyOwner {
bool success = STAKED_TOKEN.transferFrom(msg.sender, address(this), _amount);
function deposit(uint256 _amount) external onlyOwner whenNotInDepositCooldown {
depositCooldownUntil = block.timestamp + stakeManager.DEPOSIT_COOLDOWN_PERIOD();
_deposit(msg.sender, _amount);
}

function withdraw(uint256 _amount) public onlyOwner whenNotInWithdrawCooldown onlySufficientBalance(_amount) {
balance -= _amount;
bool success = STAKED_TOKEN.transfer(msg.sender, _amount);
if (!success) {
revert StakeVault__StakingFailed();
revert StakeVault__WithdrawFailed();
}
stakeManager.stake(_amount, _time);
emit Withdrawn(_amount);
}

function stake(
uint256 _amount,
uint256 _time
)
public
onlyOwner
whenNotInDepositCooldown
onlySufficientBalance(_amount)
{
_stake(_amount, _time);
}

emit Staked(msg.sender, address(this), _amount, _time);
function depositAndStake(uint256 _amount, uint256 _time) external onlyOwner whenNotInDepositCooldown {
uint256 stakedBalance = _stakedBalance();
if (stakedBalance == 0 && _time == 0) {
// we expect `depositAndStake` to be called either with a lock time,
// or when there's already funds staked (because it's possible to top up stake without locking)
revert StakeVault__InvalidLockTime();
}
_deposit(msg.sender, _amount);
_stake(_amount, _time);
}

function lock(uint256 _time) external onlyOwner {
stakeManager.lock(_time);
}

function unstake(uint256 _amount) external onlyOwner {
function unstake(uint256 _amount) external onlyOwner whenNotInWithdrawCooldown {
withdrawCooldownUntil = block.timestamp + stakeManager.WITHDRAW_COOLDOWN_PERIOD();
stakeManager.unstake(_amount);
bool success = STAKED_TOKEN.transfer(msg.sender, _amount);
if (!success) {
revert StakeVault__UnstakingFailed();
}
}

function unstakeAndWithdraw(uint256 _amount) external onlyOwner {
stakeManager.unstake(_amount);
withdraw(_amount);
}

function leave() external onlyOwner {
Expand All @@ -69,4 +138,28 @@ contract StakeVault is Ownable {
function stakedToken() external view returns (ERC20) {
return STAKED_TOKEN;
}

function _deposit(address _from, uint256 _amount) internal {
balance += _amount;
bool success = STAKED_TOKEN.transferFrom(_from, address(this), _amount);
if (!success) {
revert StakeVault__DepositFailed();
}
emit Deposited(_amount);
}

function _stake(uint256 _amount, uint256 _time) internal {
stakeManager.stake(_amount, _time);
emit Staked(_amount, _time);
}

function _unstakedBalance() internal view returns (uint256) {
(, uint256 stakedBalance,,,,,) = stakeManager.accounts(address(this));
return balance - stakedBalance;
}

function _stakedBalance() internal view returns (uint256) {
(, uint256 stakedBalance,,,,,) = stakeManager.accounts(address(this));
return stakedBalance;
}
}
Loading