This commit is contained in:
r4bbit 2024-03-20 14:02:58 +01:00
parent 4a04b46e14
commit 2b33c994fa
No known key found for this signature in database
GPG Key ID: E95F1E9447DC91A9
4 changed files with 676 additions and 80 deletions

View File

@ -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";
@ -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;
@ -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;
}
_;
@ -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) {
@ -248,6 +262,7 @@ contract StakeManager is Ownable {
onlyAccountInitialized(_vault)
finalizeEpoch
{
console.log("Processing account: ", _vault);
_processAccount(accounts[_vault], _limitEpoch);
}
@ -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) {
@ -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;
@ -432,6 +465,8 @@ contract StakeManager is Ownable {
account.totalMP
);
console.log("--------- increasedMP: ", increasedMP);
//update storage
account.lastMint = processTime;
account.totalMP += increasedMP;
@ -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];
}
}

View File

@ -14,15 +14,55 @@ 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);
@ -30,26 +70,55 @@ contract StakeVault is Ownable {
stakeManager = _stakeManager;
}
function stake(uint256 _amount, uint256 _time) external onlyOwner {
bool success = STAKED_TOKEN.transferFrom(msg.sender, address(this), _amount);
if (!success) {
revert StakeVault__StakingFailed();
}
stakeManager.stake(_amount, _time);
function deposit(uint256 _amount) external onlyOwner whenNotInDepositCooldown {
depositCooldownUntil = block.timestamp + stakeManager.DEPOSIT_COOLDOWN_PERIOD();
_deposit(msg.sender, _amount);
}
emit Staked(msg.sender, address(this), _amount, _time);
function withdraw(uint256 _amount) public onlyOwner whenNotInWithdrawCooldown onlySufficientBalance(_amount) {
balance -= _amount;
bool success = STAKED_TOKEN.transfer(msg.sender, _amount);
if (!success) {
revert StakeVault__WithdrawFailed();
}
emit Withdrawn(_amount);
}
function stake(
uint256 _amount,
uint256 _time
)
public
onlyOwner
whenNotInDepositCooldown
onlySufficientBalance(_amount)
{
_stake(_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 {
@ -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;
}
}

View File

@ -76,7 +76,14 @@ contract StakeManagerTest is Test {
userVault = _createTestVault(owner);
vm.startPrank(owner);
ERC20(stakeToken).approve(address(userVault), mintAmount);
userVault.stake(amount, lockTime);
if (lockTime > 0) {
userVault.depositAndStake(amount, lockTime);
} else {
userVault.deposit(amount);
vm.warp(userVault.depositCooldownUntil() + 1);
userVault.stake(amount, lockTime);
}
vm.stopPrank();
}
}
@ -97,11 +104,11 @@ contract StakeTest is StakeManagerTest {
uint256 lockTime = stakeManager.MIN_LOCKUP_PERIOD() - 1;
vm.expectRevert(StakeManager.StakeManager__InvalidLockTime.selector);
userVault.stake(100, lockTime);
userVault.depositAndStake(100, lockTime);
lockTime = stakeManager.MAX_LOCKUP_PERIOD() + 1;
vm.expectRevert(StakeManager.StakeManager__InvalidLockTime.selector);
userVault.stake(100, lockTime);
userVault.depositAndStake(100, lockTime);
}
function test_StakeWithoutLockUpTimeMintsMultiplierPoints() public {
@ -129,7 +136,7 @@ contract StakeTest is StakeManagerTest {
StakeVault userVault = _createStakingAccount(testUser, stakeAmount, lockToIncrease, mintAmount);
vm.prank(testUser);
userVault.stake(stakeAmount2, 0);
userVault.depositAndStake(stakeAmount2, 0);
(, uint256 balance,, uint256 totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2, "account balance");
@ -138,7 +145,7 @@ contract StakeTest is StakeManagerTest {
vm.warp(stakeManager.epochEnd());
vm.prank(testUser);
userVault.stake(stakeAmount3, 0);
userVault.depositAndStake(stakeAmount3, 0);
(, balance,, totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2 + stakeAmount3, "account balance 2");
@ -154,9 +161,9 @@ contract StakeTest is StakeManagerTest {
_createStakingAccount(testUser2, stakeAmount, stakeManager.MIN_LOCKUP_PERIOD(), mintAmount);
vm.prank(testUser);
userVault.stake(stakeAmount2, 0);
userVault.depositAndStake(stakeAmount2, 0);
vm.prank(testUser2);
userVault2.stake(stakeAmount2, 0);
userVault2.depositAndStake(stakeAmount2, 0);
(, uint256 balance,, uint256 totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2, "account balance");
@ -168,9 +175,9 @@ contract StakeTest is StakeManagerTest {
vm.warp(stakeManager.epochEnd());
vm.prank(testUser);
userVault.stake(stakeAmount2, 0);
userVault.depositAndStake(stakeAmount2, 0);
vm.prank(testUser2);
userVault2.stake(stakeAmount2, 0);
userVault2.depositAndStake(stakeAmount2, 0);
(, balance,, totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2 + stakeAmount2, "account balance 2");
@ -187,9 +194,9 @@ contract StakeTest is StakeManagerTest {
StakeVault userVault = _createStakingAccount(testUser, stakeAmount, 0, mintAmount);
StakeVault userVault2 = _createStakingAccount(testUser2, stakeAmount, lockToIncrease, mintAmount);
vm.prank(testUser);
userVault.stake(0, lockToIncrease);
userVault.depositAndStake(0, lockToIncrease);
vm.prank(testUser2);
userVault2.stake(0, lockToIncrease);
userVault2.depositAndStake(0, lockToIncrease);
(, uint256 balance,, uint256 totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount, "account balance");
@ -201,9 +208,9 @@ contract StakeTest is StakeManagerTest {
vm.warp(stakeManager.epochEnd());
vm.prank(testUser);
userVault.stake(0, lockToIncrease);
userVault.depositAndStake(0, lockToIncrease);
vm.prank(testUser2);
userVault2.stake(0, lockToIncrease);
userVault2.depositAndStake(0, lockToIncrease);
(, balance,, totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount, "account balance 2");
@ -222,9 +229,9 @@ contract StakeTest is StakeManagerTest {
StakeVault userVault2 = _createStakingAccount(testUser2, stakeAmount, lockToIncrease, mintAmount);
vm.prank(testUser);
userVault.stake(stakeAmount2, lockToIncrease);
userVault.depositAndStake(stakeAmount2, lockToIncrease);
vm.prank(testUser2);
userVault2.stake(stakeAmount2, lockToIncrease);
userVault2.depositAndStake(stakeAmount2, lockToIncrease);
(, uint256 balance,, uint256 totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2, "account balance");
@ -236,9 +243,9 @@ contract StakeTest is StakeManagerTest {
vm.warp(stakeManager.epochEnd());
vm.prank(testUser);
userVault.stake(stakeAmount2, lockToIncrease);
userVault.depositAndStake(stakeAmount2, lockToIncrease);
vm.prank(testUser2);
userVault2.stake(stakeAmount2, lockToIncrease);
userVault2.depositAndStake(stakeAmount2, lockToIncrease);
(, balance,, totalMP,,,) = stakeManager.accounts(address(userVault));
assertEq(balance, stakeAmount + stakeAmount2 + stakeAmount2, "account balance 2");
@ -518,57 +525,259 @@ contract ExecuteAccountTest is StakeManagerTest {
stakeManager.executeAccount(address(userVault), currentEpoch + 1);
}
function test_ExecuteAccountBug() public {
uint256 stakeAmount = 10_000_000;
deal(stakeToken, testUser, stakeAmount);
// initial assumptions
assertEq(stakeManager.currentEpoch(), 0);
assertEq(stakeManager.pendingReward(), 0);
assertEq(stakeManager.totalSupplyMP(), 0);
assertEq(stakeManager.totalSupplyBalance(), 0);
StakeManager.Epoch memory currentEpoch = stakeManager.getEpoch(0);
assertEq(currentEpoch.startTime, block.timestamp);
assertEq(currentEpoch.epochReward, 0);
assertEq(currentEpoch.totalSupply, 0);
userVaults.push(_createStakingAccount(testUser, stakeAmount, 0));
assertEq(stakeManager.currentEpoch(), 1);
assertEq(stakeManager.pendingReward(), 0);
assertEq(stakeManager.totalSupplyMP(), stakeAmount);
assertEq(stakeManager.totalSupplyBalance(), stakeAmount);
// epoch `1` hasn't been processed yet, so no expected rewards
currentEpoch = stakeManager.getEpoch(1);
assertEq(currentEpoch.epochReward, 0);
assertEq(currentEpoch.totalSupply, 0);
// however, account should have balance and MPs
StakeManager.Account memory account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.balance, stakeAmount);
assertEq(account.bonusMP, stakeAmount);
assertEq(account.totalMP, stakeAmount);
assertEq(account.epoch, 1);
// -------- ADD REVENUE AND ADVANCE EPOCHS --------
// emulate revenue increase
console.log("--- Adding revenue: ", 10 ether);
deal(stakeToken, address(stakeManager), 10 ether);
// ensure current `epoch` has is complete
vm.warp(stakeManager.epochEnd());
// calculate account rewards and pending rewwards
stakeManager.executeEpoch();
assertEq(stakeManager.currentEpoch(), 2);
// emulate revenue increase
console.log("--- Adding revenue: ", 10 ether);
deal(stakeToken, address(stakeManager), 20 ether);
// ensure current `epoch` has is complete
vm.warp(stakeManager.epochEnd());
// calculate account rewards and pending rewwards
stakeManager.executeEpoch();
// account epoch is still at 1
account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.balance, stakeAmount);
assertEq(account.bonusMP, stakeAmount);
assertEq(account.totalMP, stakeAmount);
assertEq(account.epoch, 1);
stakeManager.executeAccount(address(userVaults[0]), 2);
stakeManager.executeAccount(address(userVaults[0]), 3);
}
function test_ExecuteAccountMintMP() public {
uint256 stakeAmount = 10_000_000;
deal(stakeToken, testUser, stakeAmount);
userVaults.push(_createStakingAccount(makeAddr("testUser"), stakeAmount, 0));
userVaults.push(_createStakingAccount(makeAddr("testUser2"), stakeAmount, 0));
userVaults.push(_createStakingAccount(makeAddr("testUser3"), stakeAmount, 0));
// console.log("# NOW", block.timestamp);
// console.log("# START EPOCH", stakeManager.currentEpoch());
// console.log("# PND_REWARDS", stakeManager.pendingReward());
// console.log("------- CREATING ACCOUNT 1");
// userVaults.push(_createStakingAccount(testUser, stakeAmount, 0));
// (,,, , ,, uint256 epoch) = stakeManager.accounts(address(userVaults[0]));
// stakeManager.executeEpoch();
// console.log("# NOW", block.timestamp);
// vm.warp(stakeManager.epochEnd());
// console.log("# EPOCH END", block.timestamp);
// console.log("# START EPOCH", stakeManager.currentEpoch());
// console.log("# USER 1 EPOCH", epoch);
// console.log("# PND_REWARDS", stakeManager.pendingReward());
// vm.warp(stakeManager.epochEnd());
// stakeManager.executeEpoch();
// console.log("------- CREATING ACCOUNT 2");
// userVaults.push(_createStakingAccount(testUser2, stakeAmount, 0));
// (,,, , ,, epoch) = stakeManager.accounts(address(userVaults[1]));
// console.log("# NOW", block.timestamp);
// console.log("# START EPOCH", stakeManager.currentEpoch());
// console.log("# USER 2 EPOCH", epoch);
// console.log("# PND_REWARDS", stakeManager.pendingReward());
// userVaults.push(_createStakingAccount(makeAddr("testUser3"), stakeAmount, 0));
console.log("######### NOW", block.timestamp);
console.log("# START EPOCH", stakeManager.currentEpoch());
console.log("# PND_REWARDS", stakeManager.pendingReward());
// console.log("######### NOW", block.timestamp);
// console.log("# START EPOCH", stakeManager.currentEpoch());
// console.log("# PND_REWARDS", stakeManager.pendingReward());
for (uint256 i = 0; i < 3; i++) {
deal(stakeToken, address(stakeManager), 100 ether);
vm.warp(stakeManager.epochEnd());
console.log("######### NOW", block.timestamp);
stakeManager.executeEpoch();
console.log("##### NEW EPOCH", stakeManager.currentEpoch());
console.log("# PND_REWARDS", stakeManager.pendingReward());
// initial assumptions
assertEq(stakeManager.currentEpoch(), 0);
assertEq(stakeManager.pendingReward(), 0);
assertEq(stakeManager.totalSupplyMP(), 0);
assertEq(stakeManager.totalSupplyBalance(), 0);
StakeManager.Epoch memory currentEpoch = stakeManager.getEpoch(0);
assertEq(currentEpoch.startTime, block.timestamp);
assertEq(currentEpoch.epochReward, 0);
assertEq(currentEpoch.totalSupply, 0);
for (uint256 j = 0; j < userVaults.length; j++) {
(address rewardAddress,,, uint256 totalMPBefore, uint256 lastMintBefore,, uint256 epochBefore) =
stakeManager.accounts(address(userVaults[j]));
uint256 rewardsBefore = ERC20(stakeToken).balanceOf(rewardAddress);
console.log("-Vault number", j);
console.log("--=====BEFORE=====");
console.log("---### totalMP :", totalMPBefore);
console.log("---#### lastMint :", lastMintBefore);
console.log("---## user_epoch :", epochBefore);
console.log("---##### rewards :", rewardsBefore);
console.log("--=====AFTER======");
stakeManager.executeAccount(address(userVaults[j]), epochBefore + 1);
(,,, uint256 totalMP, uint256 lastMint,, uint256 epoch) = stakeManager.accounts(address(userVaults[j]));
uint256 rewards = ERC20(stakeToken).balanceOf(rewardAddress);
console.log("---### deltaTime :", lastMint - lastMintBefore);
console.log("---### totalMP :", totalMP);
console.log("---#### lastMint :", lastMint);
console.log("---## user_epoch :", epoch);
console.log("---##### rewards :", rewards);
console.log("--=======#=======");
console.log("--# TOTAL_SUPPLY", stakeManager.totalSupply());
console.log("--# PND_REWARDS", stakeManager.pendingReward());
assertEq(lastMint, lastMintBefore + stakeManager.EPOCH_SIZE(), "must increaase lastMint");
assertEq(epoch, epochBefore + 1, "must increase epoch");
assertGt(totalMP, totalMPBefore, "must increase MPs");
assertGt(rewards, rewardsBefore, "must increase rewards");
lastMintBefore = lastMint;
epochBefore = epoch;
totalMPBefore = totalMP;
}
}
// Create stake vaults and deposit + stake `stakeAmount`
// Keep in mind that this advances `block.timestamp` to get past the
// `depositCooldownUntil` time
userVaults.push(_createStakingAccount(testUser, stakeAmount, 0));
assertEq(stakeManager.currentEpoch(), 1);
assertEq(stakeManager.pendingReward(), 0);
assertEq(stakeManager.totalSupplyMP(), stakeAmount);
assertEq(stakeManager.totalSupplyBalance(), stakeAmount);
// epoch `1` hasn't been processed yet, so no expected rewards
currentEpoch = stakeManager.getEpoch(1);
assertEq(currentEpoch.epochReward, 0);
assertEq(currentEpoch.totalSupply, 0);
// however, account should have balance and MPs
StakeManager.Account memory account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.balance, stakeAmount);
assertEq(account.bonusMP, stakeAmount);
assertEq(account.totalMP, stakeAmount);
assertEq(account.epoch, 1);
// emulate revenue increase
console.log("--- Adding revenue: ", 100 ether);
deal(stakeToken, address(stakeManager), 100 ether);
// ensure current `epoch` has is complete
vm.warp(stakeManager.epochEnd());
// calculate account rewards and pending rewwards
stakeManager.executeEpoch();
assertEq(stakeManager.currentEpoch(), 2);
// previous epoch rewards should be added to `pendingReward`
currentEpoch = stakeManager.getEpoch(1);
assertEq(currentEpoch.epochReward, 100 ether, "epoch reward");
assertEq(stakeManager.pendingReward(), 100 ether, "pending rewards");
assertEq(stakeManager.totalSupplyMP(), stakeAmount);
assertEq(stakeManager.totalSupplyBalance(), stakeAmount);
assertEq(currentEpoch.totalSupply, account.balance + account.totalMP);
// account epoch is still at 1
account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.balance, stakeAmount);
assertEq(account.bonusMP, stakeAmount);
assertEq(account.totalMP, stakeAmount);
assertEq(account.epoch, 1);
stakeManager.executeAccount(address(userVaults[0]), 2);
assertEq(stakeManager.pendingReward(), 0);
account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.balance, stakeAmount);
assertEq(account.bonusMP, stakeAmount);
assertGt(account.totalMP, stakeAmount);
assertEq(ERC20(stakeToken).balanceOf(userVaults[0].owner()), 100 ether);
assertEq(account.epoch, 2);
// second staker comes in
userVaults.push(_createStakingAccount(testUser2, stakeAmount, 0));
StakeManager.Account memory secondAccount = stakeManager.getAccount(address(userVaults[1]));
assertEq(stakeManager.currentEpoch(), 3);
assertEq(stakeManager.pendingReward(), 0);
assertEq(secondAccount.epoch, 3);
assertEq(secondAccount.balance, stakeAmount);
assertEq(secondAccount.bonusMP, stakeAmount);
assertEq(secondAccount.totalMP, stakeAmount);
// assertEq(stakeManager.totalSupplyMP(), stakeAmount);
assertEq(stakeManager.totalSupplyBalance(), stakeAmount * 2);
// emulate revenue increase
deal(stakeToken, address(stakeManager), 100 ether);
vm.warp(stakeManager.epochEnd());
// calculate account rewards and pending rewwards
// stakeManager.executeEpoch();
// assertEq(stakeManager.pendingReward(), 100 ether);
stakeManager.executeAccount(address(userVaults[1]), 4);
secondAccount = stakeManager.getAccount(address(userVaults[1]));
assertEq(secondAccount.epoch, 4);
assertEq(secondAccount.balance, stakeAmount);
assertEq(secondAccount.bonusMP, stakeAmount);
assertGt(secondAccount.totalMP, stakeAmount);
assertEq(stakeManager.pendingReward(), 50 ether);
account = stakeManager.getAccount(address(userVaults[0]));
assertEq(account.epoch, 2);
stakeManager.executeAccount(address(userVaults[0]), 4);
// assertEq(stakeManager.pendingReward(), 50 ether);
// stakeManager.executeAccount(address(userVaults[0]), 4);
assertEq(stakeManager.pendingReward(), 0);
// ensure current `epoch` has is complete
// userVaults.push(_createStakingAccount(makeAddr("testUser3"), stakeAmount, 0));
// for (uint256 i = 0; i < 3; i++) {
// for (uint256 i = 0; i < 1; i++) {
// // emulate revenue increase
// deal(stakeToken, address(stakeManager), 100 ether);
// // ensure current `epoch` has is complete
// vm.warp(stakeManager.epochEnd());
// // calculate account rewards and pending rewwards
// stakeManager.executeEpoch();
//
// // uint256 currentEpoch = stakeManager.currentEpoch();
// // (uint256 startTime, uint256 epochReward, uint256 totalSupply) = stakeManager.epochs(currentEpoch);
// //
// // console.log("######### NOW", block.timestamp);
// // console.log("##### NEW EPOCH", stakeManager.currentEpoch());
// // console.log("# PND_REWARDS", stakeManager.pendingReward());
//
// for (uint256 j = 0; j < userVaults.length; j++) {
// // (address rewardAddress,,, uint256 totalMPBefore, uint256 lastMintBefore,, uint256 epochBefore) =
// // stakeManager.accounts(address(userVaults[j]));
// // uint256 rewardsBefore = ERC20(stakeToken).balanceOf(rewardAddress);
// // console.log("-Vault number", j);
// // console.log("--=====BEFORE=====");
// // console.log("---### totalMP :", totalMPBefore);
// // console.log("---#### lastMint :", lastMintBefore);
// // console.log("---## user_epoch :", epochBefore);
// // console.log("---##### rewards :", rewardsBefore);
// // console.log("--=====AFTER======");
// // stakeManager.executeAccount(address(userVaults[j]), epochBefore + 1);
// // (,,, uint256 totalMP, uint256 lastMint,, uint256 epoch) =
// // stakeManager.accounts(address(userVaults[j]));
// // uint256 rewards = ERC20(stakeToken).balanceOf(rewardAddress);
// // console.log("---### deltaTime :", lastMint - lastMintBefore);
// // console.log("---### totalMP :", totalMP);
// // console.log("---#### lastMint :", lastMint);
// // console.log("---## user_epoch :", epoch);
// // console.log("---##### rewards :", rewards);
// // console.log("--=======#=======");
// // console.log("--# TOTAL_SUPPLY", stakeManager.totalSupply());
// // console.log("--# PND_REWARDS", stakeManager.pendingReward());
// // assertEq(lastMint, lastMintBefore + stakeManager.EPOCH_SIZE(), "must increaase lastMint");
// // assertEq(epoch, epochBefore + 1, "must increase epoch");
// // assertGt(totalMP, totalMPBefore, "must increase MPs");
// // assertGt(rewards, rewardsBefore, "must increase rewards");
// // lastMintBefore = lastMint;
// // epochBefore = epoch;
// // totalMPBefore = totalMP;
// }
// }
}
function test_ShouldNotMintMoreThanCap() public {

View File

@ -20,10 +20,14 @@ contract StakeVaultTest is Test {
StakeVault internal stakeVault;
StakeVault internal stakeVault2;
address internal deployer;
address internal testUser = makeAddr("testUser");
address internal testUser2 = makeAddr("testUser2");
address internal stakeToken;
function setUp() public virtual {
@ -33,6 +37,12 @@ contract StakeVaultTest is Test {
vm.prank(testUser);
stakeVault = vaultFactory.createVault();
vm.prank(testUser2);
stakeVault2 = vaultFactory.createVault();
vm.prank(deployer);
stakeManager.setVault(address(stakeVault).codehash);
}
}
@ -46,7 +56,211 @@ contract StakedTokenTest is StakeVaultTest {
}
}
contract DepositTest is StakeVaultTest {
event Deposited(uint256 amount);
function setUp() public override {
StakeVaultTest.setUp();
}
function test_RevertWhen_DepositAndInDepositCooldown() public {
deal(stakeToken, testUser, 1000);
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), 100);
stakeVault.deposit(100);
vm.expectRevert(StakeVault.StakeVault__InDepositCooldown.selector);
stakeVault.deposit(100);
}
function test_Deposit() public {
uint256 userFunds = 1000;
uint256 depositAmount = 100;
deal(stakeToken, testUser, userFunds);
deal(stakeToken, testUser2, userFunds);
// first user
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), depositAmount);
vm.expectEmit(true, true, true, true);
emit Deposited(depositAmount);
stakeVault.deposit(depositAmount);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), depositAmount);
assertEq(stakeVault.balance(), depositAmount);
assertEq(stakeVault.depositCooldownUntil(), block.timestamp + stakeManager.DEPOSIT_COOLDOWN_PERIOD());
// ensure funds haven't reached stake manager yet
assertEq(stakeManager.totalSupply(), 0);
}
function test_DepositAfterCooldown() public {
uint256 userFunds = 1000;
uint256 depositAmount = 100;
deal(stakeToken, testUser, userFunds);
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), depositAmount);
// make first deposit
vm.expectEmit(true, true, true, true);
emit Deposited(depositAmount);
stakeVault.deposit(depositAmount);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), depositAmount);
assertEq(stakeVault.balance(), depositAmount);
assertEq(stakeVault.depositCooldownUntil(), block.timestamp + stakeManager.DEPOSIT_COOLDOWN_PERIOD());
assertEq(stakeManager.totalSupply(), 0);
// wait for deposit cooldown to elapse
vm.warp(stakeVault.depositCooldownUntil() + 1);
ERC20(stakeToken).approve(address(stakeVault), depositAmount);
// make second deposit after first deposit has cooled down
vm.expectEmit(true, true, true, true);
emit Deposited(depositAmount);
stakeVault.deposit(depositAmount);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), depositAmount * 2);
assertEq(stakeVault.balance(), depositAmount * 2);
assertEq(stakeVault.depositCooldownUntil(), block.timestamp + stakeManager.DEPOSIT_COOLDOWN_PERIOD());
assertEq(stakeManager.totalSupply(), 0);
}
}
contract WithdrawTest is StakeVaultTest {
event Withdrawn(uint256 amount);
uint256 internal stakeAmount = 100;
uint256 internal userFunds = 1000;
function setUp() public override {
StakeVaultTest.setUp();
deal(stakeToken, testUser, userFunds);
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), stakeAmount);
stakeVault.deposit(stakeAmount);
}
function test_RevertWhen_InWithdrawCooldown() public {
// ensure deposit cooldown has passed
vm.warp(stakeVault.depositCooldownUntil() + 1);
// stake funds so we can unstake after that (and initialize withdraw cooldown)
stakeVault.stake(stakeAmount, 0);
stakeVault.unstake(stakeAmount);
assertEq(stakeVault.withdrawCooldownUntil(), block.timestamp + stakeManager.WITHDRAW_COOLDOWN_PERIOD());
vm.expectRevert(StakeVault.StakeVault__InWithdrawCooldown.selector);
stakeVault.withdraw(stakeAmount);
}
function test_RevertWhen_WithdrawInsufficientFunds() public {
vm.startPrank(testUser);
vm.expectRevert(StakeVault.StakeVault__InsufficientFunds.selector);
stakeVault.withdraw(stakeAmount + 1);
}
function test_Withdraw() public {
vm.startPrank(testUser);
vm.expectEmit(true, true, true, true);
emit Withdrawn(stakeAmount);
stakeVault.withdraw(stakeAmount);
assertEq(stakeVault.balance(), 0);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), 0);
assertEq(ERC20(stakeToken).balanceOf(testUser), userFunds);
}
function test_WithdrawLessAmountThanAvailable() public {
uint256 remainingAmount = 50;
vm.startPrank(testUser);
vm.expectEmit(true, true, true, true);
emit Withdrawn(stakeAmount - remainingAmount);
stakeVault.withdraw(stakeAmount - remainingAmount);
assertEq(stakeVault.balance(), remainingAmount);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), remainingAmount);
assertEq(ERC20(stakeToken).balanceOf(testUser), userFunds - remainingAmount);
// try to withdraw the remaining amount
emit Withdrawn(remainingAmount);
stakeVault.withdraw(remainingAmount);
assertEq(stakeVault.balance(), 0);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), 0);
assertEq(ERC20(stakeToken).balanceOf(testUser), userFunds);
}
}
contract StakeTest is StakeVaultTest {
event Staked(uint256 amount, uint256 time);
uint256 internal userFunds = 1000;
uint256 internal stakeAmount = 100;
function setUp() public override {
StakeVaultTest.setUp();
deal(stakeToken, testUser, userFunds);
}
function test_RevertWhen_StakeAndInDepositCooldown() public {
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), stakeAmount);
stakeVault.deposit(stakeAmount);
vm.expectRevert(StakeVault.StakeVault__InDepositCooldown.selector);
stakeVault.stake(stakeAmount, 0);
}
function test_RevertWhen_StakeAndInsufficientFunds() public {
vm.startPrank(testUser);
vm.expectRevert(StakeVault.StakeVault__InsufficientFunds.selector);
stakeVault.stake(stakeAmount + 1, 0);
// do another one, this time with deposited funds (but too little)
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), stakeAmount);
stakeVault.deposit(stakeAmount);
// make sure deposit cooldown has passed
vm.warp(stakeVault.depositCooldownUntil() + 1);
vm.expectRevert(StakeVault.StakeVault__InsufficientFunds.selector);
stakeVault.stake(stakeAmount + 1, 0);
}
function test_Stake() public {
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), stakeAmount);
stakeVault.deposit(stakeAmount);
assertEq(stakeManager.totalSupply(), 0);
// make sure deposit cooldown has passed
vm.warp(stakeVault.depositCooldownUntil() + 1);
vm.expectEmit(true, true, true, true);
emit Staked(stakeAmount, 0);
stakeVault.stake(stakeAmount, 0);
assertEq(stakeVault.balance(), stakeAmount);
assertEq(ERC20(stakeToken).balanceOf(address(stakeVault)), stakeAmount);
assertEq(ERC20(stakeToken).balanceOf(testUser), userFunds - stakeAmount);
(, uint256 stakeBalance, uint256 initialMP, uint256 currentMP,,,) = stakeManager.accounts(address(stakeVault));
assertEq(stakeBalance, stakeAmount);
assertEq(currentMP, stakeAmount);
assertEq(initialMP, stakeAmount);
assertEq(stakeManager.totalSupply(), stakeAmount + initialMP);
}
}
contract StakeWithBrokenTokenTest is StakeVaultTest {
function setUp() public override {
DeployBroken deployment = new DeployBroken();
(vaultFactory, stakeManager, stakeToken) = deployment.run();
@ -61,7 +275,44 @@ contract StakeTest is StakeVaultTest {
vm.startPrank(address(testUser));
ERC20(stakeToken).approve(address(stakeVault), 100);
vm.expectRevert(StakeVault.StakeVault__StakingFailed.selector);
stakeVault.stake(100, 0);
vm.expectRevert(StakeVault.StakeVault__DepositFailed.selector);
stakeVault.deposit(100);
}
}
contract UnstakeTest is StakeVaultTest {
uint256 internal userFunds = 1000;
uint256 internal stakeAmount = 100;
function setUp() public override {
StakeVaultTest.setUp();
deal(stakeToken, testUser, userFunds);
vm.startPrank(testUser);
ERC20(stakeToken).approve(address(stakeVault), stakeAmount);
stakeVault.deposit(stakeAmount);
// ensure deposit cooldown has passed
vm.warp(stakeVault.depositCooldownUntil() + 1);
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.stopPrank();
vm.prank(deployer);
vm.expectRevert(bytes("Ownable: caller is not the owner"));
stakeVault.unstake(stakeAmount);
}
function test_RevertWhen_UnstakeAndInWithdrawCooldown() public {
uint256 unstakeAmount = stakeAmount / 2;
// stake funds so we can unstake after that (and initialize withdraw cooldown)
stakeVault.stake(stakeAmount, 0);
stakeVault.unstake(unstakeAmount);
assertEq(stakeVault.withdrawCooldownUntil(), block.timestamp + stakeManager.WITHDRAW_COOLDOWN_PERIOD());
vm.expectRevert(StakeVault.StakeVault__InWithdrawCooldown.selector);
stakeVault.unstake(unstakeAmount);
}
}