Skip to content

Commit

Permalink
Update token pools manager
Browse files Browse the repository at this point in the history
  • Loading branch information
SigismundSchlomo committed Sep 11, 2024
1 parent ea54624 commit ee40061
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 123 deletions.
21 changes: 10 additions & 11 deletions contracts/staking/token/DoubleSidePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,17 @@ contract DoubleSidePool is Initializable, AccessControl, IOnBlockListener {
event InterestChanged(bool dependant, uint interest);
event InterestRateChanged(bool dependant, uint interestRate);

event PoolMaxStakeValueChanged(uint poolMaxStakeValue);
event MaxTotalStakeValueChanged(uint poolMaxStakeValue);
event MaxStakePerUserValueChanged(uint maxStakePerUserValue);
event StakeLockPeriodChanged(uint period);
event StakeLimitsMultiplierChanged(uint value);
event ApyBonusMultiplierChanged(uint value);

event StakeChanged(bool dependant, address indexed user, uint amount);
event Claim(bool dependant, address indexed user, uint amount);
event Interest(bool dependant, uint amount);
event UnstakeLocked(bool dependant, address indexed user, uint amount, uint unlockTime, uint creationTime);
event UnstakeFast(bool dependant, address indexed user, uint amount, uint penalty);

// TODO: Add more dependant side specific events

function initialize(
RewardsBank rewardsBank_, LockKeeper lockkeeper_, string memory name_,
MainSideConfig memory mainSideConfig_
Expand All @@ -114,9 +112,10 @@ contract DoubleSidePool is Initializable, AccessControl, IOnBlockListener {

// OWNER METHODS

function addDependantSide(DependantSideConfig memory dependantSideConfig_) public onlyRole(DEFAULT_ADMIN_ROLE) {
function addDependentSide(bytes calldata prams) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(!hasSecondSide, "Second side already exists");
hasSecondSide = true;
DependantSideConfig memory dependantSideConfig_ = abi.decode(prams, (DependantSideConfig));
dependantSideConfig = dependantSideConfig_;
}

Expand Down Expand Up @@ -184,19 +183,19 @@ contract DoubleSidePool is Initializable, AccessControl, IOnBlockListener {

function setMaxTotalStakeValue(uint value) public onlyRole(DEFAULT_ADMIN_ROLE) {
dependantSideConfig.maxTotalStakeValue = value;
emit PoolMaxStakeValueChanged(value);
}

function setStakeLimitsMultiplier(uint value) public onlyRole(DEFAULT_ADMIN_ROLE) {
dependantSideConfig.stakeLimitsMultiplier = value;
emit StakeLimitsMultiplierChanged(value);
emit MaxTotalStakeValueChanged(value);
}

function setStakeLockPeriod(uint period) public onlyRole(DEFAULT_ADMIN_ROLE) {
dependantSideConfig.stakeLockPeriod = period;
emit StakeLockPeriodChanged(period);
}

function setStakeLimitsMultiplier(uint value) public onlyRole(DEFAULT_ADMIN_ROLE) {
dependantSideConfig.stakeLimitsMultiplier = value;
emit StakeLimitsMultiplierChanged(value);
}

//TODO: Add more setters

// PUBLIC METHODS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,43 @@ import "../../LockKeeper.sol";

import "hardhat/console.sol";

contract TokenPool is Initializable, AccessControl, IOnBlockListener {
contract SingleSidePool is Initializable, AccessControl, IOnBlockListener {
uint constant public MILLION = 1_000_000;

IERC20 public token;
RewardsBank public rewardsBank;
LockKeeper public lockKeeper;

string public name;
uint public minStakeValue;
uint public fastUnstakePenalty;
uint public interest;
uint public interestRate; //Time in seconds to how often the stake is increased
uint public lockPeriod;
address public rewardToken;
uint public rewardTokenPrice; // The coefficient to calculate the reward token amount

struct Config {
IERC20 token;
RewardsBank rewardsBank;
LockKeeper lockKeeper;
string name;
address rewardToken;
uint rewardTokenPrice; // The coefficient to calculate the reward token amount
uint minStakeValue;
uint fastUnstakePenalty;
uint interest; //Time in seconds to how often the stake is increased
uint interestRate;
uint lockPeriod;
}

struct Info {
uint totalStake;
uint totalRewards;
uint lastInterestUpdate;
uint totalRewardsDebt;
}

struct Staker {
uint stake;
uint rewardsDebt;
uint claimableRewards;
uint lockedWithdrawal;
}

bool public active;
uint public totalStake;
uint public totalRewards;

uint private lastInterestUpdate;
uint private totalRewardsDebt;
Config public config;
Info public info;

mapping(address => uint) public stakes;
mapping(address => uint) private rewardsDebt;
mapping(address => uint) private claimableRewards;
mapping(address => uint) private lockedWithdrawals;
mapping(address => Staker) public stakers;

//EVENTS

Expand All @@ -53,23 +63,11 @@ contract TokenPool is Initializable, AccessControl, IOnBlockListener {
event UnstakeLocked(address indexed user, uint amount, uint unlockTime, uint creationTime);
event UnstakeFast(address indexed user, uint amount, uint penalty);

function initialize(
address token_, RewardsBank rewardsBank_, LockKeeper lockkeeper_, string memory name_, uint minStakeValue_,
uint fastUnstakePenalty_, uint intereset_, uint interestRate_, uint lockPeriod_, address rewardToken_, uint rewardTokenPrice_
) public initializer {
token = IERC20(token_);
rewardsBank = rewardsBank_;
lockKeeper = lockkeeper_;

name = name_;
minStakeValue = minStakeValue_;
fastUnstakePenalty = fastUnstakePenalty_;
interest = intereset_;
interestRate = interestRate_;
lockPeriod = lockPeriod_;
rewardToken = rewardToken_;
rewardTokenPrice = rewardTokenPrice_;
lastInterestUpdate = block.timestamp;
function initialize(bytes calldata params_) public initializer {
Config memory config_ = abi.decode(params_, (Config));
config = config_;

info.lastInterestUpdate = block.timestamp;

active = true;

Expand All @@ -91,80 +89,80 @@ contract TokenPool is Initializable, AccessControl, IOnBlockListener {
}

function setMinStakeValue(uint value) public onlyRole(DEFAULT_ADMIN_ROLE) {
minStakeValue = value;
config.minStakeValue = value;
emit MinStakeValueChanged(value);
}

function setInterest(uint _interest, uint _interestRate) public onlyRole(DEFAULT_ADMIN_ROLE) {
_addInterest();
interest = _interest;
interestRate = _interestRate;
emit InterestRateChanged(interest, interestRate);
config.interest = _interest;
config.interestRate = _interestRate;
emit InterestRateChanged(config.interest, config.interestRate);
}

function setLockPeriod(uint period) public onlyRole(DEFAULT_ADMIN_ROLE) {
lockPeriod = period;
config.lockPeriod = period;
emit LockPeriodChanged(period);
}

function setRewardTokenPrice(uint price) public onlyRole(DEFAULT_ADMIN_ROLE) {
rewardTokenPrice = price;
config.rewardTokenPrice = price;
emit RewardTokenPriceChanged(price);
}

function setFastUnstakePenalty(uint penalty) public onlyRole(DEFAULT_ADMIN_ROLE) {
fastUnstakePenalty = penalty;
config.fastUnstakePenalty = penalty;
emit FastUnstakePenaltyChanged(penalty);
}

// PUBLIC METHODS

function stake(uint amount) public {
require(active, "Pool is not active");
require(amount >= minStakeValue, "Pool: stake value is too low");
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
require(amount >= config.minStakeValue, "Pool: stake value is too low");
require(config.token.transferFrom(msg.sender, address(this), amount), "Transfer failed");

_stake(msg.sender, amount);

emit StakeChanged(msg.sender, amount);
}

function unstake(uint amount) public {
require(stakes[msg.sender] >= amount, "Not enough stake");
require(stakers[msg.sender].stake >= amount, "Not enough stake");

_unstake(msg.sender, amount);

// cancel previous lock (if exists). canceledAmount will be added to new lock
uint canceledAmount;
if (lockKeeper.getLock(lockedWithdrawals[msg.sender]).totalClaims > 0) // prev lock exists
canceledAmount = lockKeeper.cancelLock(lockedWithdrawals[msg.sender]);
if (config.lockKeeper.getLock(stakers[msg.sender].lockedWithdrawal).totalClaims > 0) // prev lock exists
canceledAmount = config.lockKeeper.cancelLock(stakers[msg.sender].lockedWithdrawal);

token.approve(address(lockKeeper), amount + canceledAmount);
config.token.approve(address(config.lockKeeper), amount + canceledAmount);

// lock funds
lockedWithdrawals[msg.sender] = lockKeeper.lockSingle(
msg.sender, address(token), uint64(block.timestamp + lockPeriod), amount + canceledAmount,
string(abi.encodePacked("TokenStaking unstake: ", _addressToString(address(token))))
stakers[msg.sender].lockedWithdrawal = config.lockKeeper.lockSingle(
msg.sender, address(config.token), uint64(block.timestamp + config.lockPeriod), amount + canceledAmount,
string(abi.encodePacked("TokenStaking unstake: ", _addressToString(address(config.token))))
);

_claimRewards(msg.sender);

emit UnstakeLocked(msg.sender, amount + canceledAmount, block.timestamp + lockPeriod, block.timestamp);
emit StakeChanged(msg.sender, stakes[msg.sender]);
emit UnstakeLocked(msg.sender, amount + canceledAmount, block.timestamp + config.lockPeriod, block.timestamp);
emit StakeChanged(msg.sender, stakers[msg.sender].stake);
}

function unstakeFast(uint amount) public {
require(stakes[msg.sender] >= amount, "Not enough stake");
require(stakers[msg.sender].stake >= amount, "Not enough stake");

_unstake(msg.sender, amount);

uint penalty = amount * fastUnstakePenalty / MILLION;
SafeERC20.safeTransfer(token, msg.sender, amount - penalty);
uint penalty = amount * config.fastUnstakePenalty / MILLION;
SafeERC20.safeTransfer(config.token, msg.sender, amount - penalty);

_claimRewards(msg.sender);

emit UnstakeFast(msg.sender, amount, penalty);
emit StakeChanged(msg.sender, stakes[msg.sender]);
emit StakeChanged(msg.sender, stakers[msg.sender].stake);
}

function claim() public {
Expand All @@ -180,88 +178,88 @@ contract TokenPool is Initializable, AccessControl, IOnBlockListener {
// VIEW METHODS

function getStake(address user) public view returns (uint) {
return stakes[user];
return stakers[user].stake;
}

function getInterest() public view returns (uint) {
return interest;
return config.interest;
}

function getInterestRate() public view returns (uint) {
return interestRate;
return config.interestRate;
}

function getUserRewards(address user) public view returns (uint) {
uint rewardsAmount = _calcRewards(stakes[user]);
if (rewardsAmount + claimableRewards[user] <= rewardsDebt[user])
uint rewardsAmount = _calcRewards(stakers[user].stake);
if (rewardsAmount + stakers[user].claimableRewards <= stakers[user].rewardsDebt)
return 0;

return rewardsAmount + claimableRewards[user] - rewardsDebt[user];
return rewardsAmount + stakers[user].claimableRewards - stakers[user].rewardsDebt;
}

// INTERNAL METHODS

// store claimable rewards
function _calcClaimableRewards(address user) internal {
uint rewardsAmount = _calcRewards(stakes[user]);
uint rewardsWithoutDebt = rewardsAmount - rewardsDebt[user];
claimableRewards[user] += rewardsWithoutDebt;
totalRewardsDebt += rewardsWithoutDebt;
rewardsDebt[user] += rewardsWithoutDebt;
uint rewardsAmount = _calcRewards(stakers[user].stake);
uint rewardsWithoutDebt = rewardsAmount - stakers[user].rewardsDebt;
stakers[user].claimableRewards += rewardsWithoutDebt;
info.totalRewardsDebt += rewardsWithoutDebt;
stakers[user].rewardsDebt += rewardsWithoutDebt;
}

function _addInterest() internal {
if (lastInterestUpdate + interestRate > block.timestamp) return;
uint timePassed = block.timestamp - lastInterestUpdate;
uint newRewards = totalStake * interest * timePassed / MILLION / interestRate;
if (info.lastInterestUpdate + config.interestRate > block.timestamp) return;
uint timePassed = block.timestamp - info.lastInterestUpdate;
uint newRewards = info.totalStake * config.interest * timePassed / MILLION / config.interestRate;

totalRewards += newRewards;
lastInterestUpdate = block.timestamp;
info.totalRewards += newRewards;
info.lastInterestUpdate = block.timestamp;
emit Interest(newRewards);
}

function _stake(address user, uint amount) internal {
uint rewardsAmount = _calcRewards(amount);

stakes[msg.sender] += amount;
totalStake += amount;
stakers[msg.sender].stake += amount;
info.totalStake += amount;

totalRewards += rewardsAmount;
info.totalRewards += rewardsAmount;

_updateRewardsDebt(user, _calcRewards(stakes[user]));
_updateRewardsDebt(user, _calcRewards(stakers[user].stake));
}

function _unstake(address user, uint amount) internal {
uint rewardsAmount = _calcRewards(amount);

stakes[msg.sender] -= amount;
totalStake -= amount;
stakers[msg.sender].stake -= amount;
info.totalStake -= amount;

totalRewards -= rewardsAmount;
_updateRewardsDebt(user, _calcRewards(stakes[user]));
info.totalRewards -= rewardsAmount;
_updateRewardsDebt(user, _calcRewards(stakers[user].stake));
}

function _updateRewardsDebt(address user, uint newDebt) internal {
uint oldDebt = rewardsDebt[user];
if (newDebt < oldDebt) totalRewardsDebt -= oldDebt - newDebt;
else totalRewardsDebt += newDebt - oldDebt;
rewardsDebt[user] = newDebt;
uint oldDebt = stakers[user].rewardsDebt;
if (newDebt < oldDebt) info.totalRewardsDebt -= oldDebt - newDebt;
else info.totalRewardsDebt += newDebt - oldDebt;
stakers[user].rewardsDebt = newDebt;
}

function _claimRewards(address user) internal {
uint amount = claimableRewards[user];
uint amount = stakers[user].claimableRewards;
if (amount == 0) return;

claimableRewards[user] = 0;
stakers[user].claimableRewards = 0;

uint rewardTokenAmount = amount * rewardTokenPrice;
rewardsBank.withdrawErc20(rewardToken, payable(user), rewardTokenAmount);
uint rewardTokenAmount = amount * config.rewardTokenPrice;
config.rewardsBank.withdrawErc20(config.rewardToken, payable(user), rewardTokenAmount);
emit Claim(user, rewardTokenAmount);
}

function _calcRewards(uint amount) internal view returns (uint) {
if (totalStake == 0 && totalRewards == 0) return amount;
return amount * totalRewards / totalStake;
if (info.totalStake == 0 && info.totalRewards == 0) return amount;
return amount * info.totalRewards /info.totalStake;
}

function _addressToString(address x) internal pure returns (string memory) {
Expand Down
Loading

0 comments on commit ee40061

Please sign in to comment.