Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/farming #218

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions contracts/gov/GovPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ contract GovPool is
emit Withdrawn(amount, nftIds, receiver);
}

function redeemTokens(address receiver, uint256 amount) external override onlyBABTHolder {
_checkBlock(DEPOSIT_WITHDRAW, msg.sender);

_unlock(msg.sender);

_govUserKeeper.redeemTokens(msg.sender, receiver, amount, coreProperties);
}

function delegate(
address delegatee,
uint256 amount,
Expand Down
61 changes: 57 additions & 4 deletions contracts/gov/settings/GovSettings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,18 @@ contract GovSettings is IGovSettings, OwnableUpgradeable {
mapping(uint256 => ProposalSettings) public settings; // settingsId => info
mapping(address => uint256) public executorToSettings; // executor => settingsId

uint256 public totalStakes;

mapping(uint256 => StakingInfo) public stakingList; // stakingId => info

event SettingsChanged(uint256 settingsId, string description);
event ExecutorChanged(uint256 settingsId, address executor);

modifier withCorrectStakingId(uint256 id) {
require(id > 0 && id <= totalStakes, "GovSettings: invalid staking id");
_;
}

function __GovSettings_init(
address govPoolAddress,
address validatorsAddress,
Expand Down Expand Up @@ -91,6 +100,34 @@ contract GovSettings is IGovSettings, OwnableUpgradeable {
}
}

function createNewStaking(
uint64 lockTime,
uint256 rewardMultiplier,
uint256 redeemPenalty,
bool allowStakingUpgrade
) external override onlyOwner {
require(
lockTime > 0 &&
rewardMultiplier > 0 &&
(redeemPenalty == type(uint256).max || redeemPenalty <= PERCENTAGE_100),
"GovSettings: wrong staking info"
);

uint256 id = ++totalStakes;
stakingList[id] = StakingInfo(
lockTime,
rewardMultiplier,
redeemPenalty,
allowStakingUpgrade,
false
);
}

function closeStaking(uint256 id) external override onlyOwner withCorrectStakingId(id) {
StakingInfo storage stake = stakingList[id];
stake.disabled = true;
}

function _validateProposalSettings(ProposalSettings calldata _settings) internal pure {
require(_settings.duration > 0, "GovSettings: invalid vote duration value");
require(_settings.quorum <= PERCENTAGE_100, "GovSettings: invalid quorum value");
Expand All @@ -105,10 +142,6 @@ contract GovSettings is IGovSettings, OwnableUpgradeable {
);
}

function _settingsExist(uint256 settingsId) internal view returns (bool) {
return settings[settingsId].duration > 0;
}

function getDefaultSettings() external view override returns (ProposalSettings memory) {
return settings[uint256(ExecutorType.DEFAULT)];
}
Expand All @@ -123,6 +156,22 @@ contract GovSettings is IGovSettings, OwnableUpgradeable {
return settings[executorToSettings[executor]];
}

function getStakingSettings(
uint256 id
) public view override withCorrectStakingId(id) returns (StakingInfo memory stakingInfo) {
stakingInfo = stakingList[id];
}

function getStakingSettingsList(
uint256[] calldata ids
) external view override returns (StakingInfo[] memory stakingInfos) {
stakingInfos = new StakingInfo[](ids.length);

for (uint256 i = 0; i < ids.length; i++) {
stakingInfos[i] = getStakingSettings(ids[i]);
}
}

function _setExecutor(address executor, uint256 settingsId) internal {
executorToSettings[executor] = settingsId;

Expand All @@ -134,4 +183,8 @@ contract GovSettings is IGovSettings, OwnableUpgradeable {

emit SettingsChanged(settingsId, _settings.executorDescription);
}

function _settingsExist(uint256 settingsId) internal view returns (bool) {
return settings[settingsId].duration > 0;
}
}
114 changes: 104 additions & 10 deletions contracts/gov/user-keeper/GovUserKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol";
import "../../interfaces/core/IContractsRegistry.sol";
import "../../interfaces/core/INetworkProperties.sol";
import "../../interfaces/gov/user-keeper/IGovUserKeeper.sol";
import "../../interfaces/gov/settings/IGovSettings.sol";
import "../../interfaces/gov/IGovPool.sol";
import "../../interfaces/gov/ERC721/powers/IERC721Power.sol";
import "../../interfaces/core/ICoreProperties.sol";

import "../../libs/math/MathHelper.sol";
import "../../libs/gov/gov-user-keeper/GovUserKeeperView.sol";
Expand All @@ -47,6 +49,8 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
address public wethAddress;
address public networkPropertiesAddress;

mapping(address => Stake) internal _stakes;

event SetERC20(address token);
event SetERC721(address token);

Expand All @@ -60,6 +64,11 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
_;
}

modifier whenNotStaked(address user) {
require(!_isStaked(user), "GovUK: staked");
_;
}

function __GovUserKeeper_init(
address _tokenAddress,
address _nftAddress,
Expand Down Expand Up @@ -108,21 +117,36 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
address payer,
address receiver,
uint256 amount
) external override onlyOwner withSupportedToken whenNotStaked(payer) {
_prepareTokenWithdraw(payer, amount);

_sendNativeOrToken(receiver, amount);
}

function redeemTokens(
address payer,
address receiver,
uint256 amount,
ICoreProperties coreProperties
) external override onlyOwner withSupportedToken {
UserInfo storage payerInfo = _usersInfo[payer];
BalanceInfo storage payerBalanceInfo = payerInfo.balances[IGovPool.VoteType.PersonalVote];
require(amount > 0, "GovUK: empty redeem");
require(_isStaked(payer), "GovUK: not staked");

uint256 balance = payerBalanceInfo.tokens;
uint256 maxTokensLocked = payerInfo.maxTokensLocked;
_prepareTokenWithdraw(payer, amount);

require(
amount <= balance.max(maxTokensLocked) - maxTokensLocked,
"GovUK: can't withdraw this"
);
uint256 id = _stakes[payer].stakeId;
IGovSettings.StakingInfo memory stakeInfo = _getStakeInfo(id);

payerBalanceInfo.tokens = balance - amount;
uint256 redeemPenalty = stakeInfo.redeemPenalty;
require(redeemPenalty != type(uint256).max, "GovUK: redeem forbidden");
uint256 redeemToGovPoolAmount = amount.percentage(redeemPenalty);

_sendNativeOrToken(receiver, amount);
(uint256 commission, address dexeTreasury) = coreProperties.getDEXECommissionPercentages();
uint256 redeemToDexeAmount = redeemToGovPoolAmount.percentage(commission);

_sendNativeOrToken(dexeTreasury, redeemToDexeAmount);
_sendNativeOrToken(owner(), redeemToGovPoolAmount - redeemToDexeAmount);
_sendNativeOrToken(receiver, amount - redeemToGovPoolAmount);
}

function delegateTokens(
Expand Down Expand Up @@ -483,6 +507,29 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
IERC721Power(_nftInfo.nftAddress).recalculateNftPowers(nftIds);
}

function stake(uint256 id) external override {
address user = msg.sender;
Stake storage userStake = _stakes[user];

// inderect check for correct id is here
IGovSettings.StakingInfo memory newStakeInfo = _getStakeInfo(id);
require(!newStakeInfo.disabled, "GovUK: staking tier is disabled");

if (!_isStaked(user)) {
_stake(userStake, id);
return;
}

IGovSettings.StakingInfo memory oldStakeInfo = _getStakeInfo(userStake.stakeId);

if (oldStakeInfo.lockTime < newStakeInfo.lockTime && oldStakeInfo.allowStakingUpgrade) {
_stake(userStake, id);
return;
}

revert("GovUK: Already staked");
}

function setERC20Address(address _tokenAddress) external override onlyOwner {
_setERC20Address(_tokenAddress);
}
Expand Down Expand Up @@ -759,6 +806,22 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
nativeAmount -= value;
}

function getStakingMultiplier(address user) public view override returns (uint256) {
Stake storage currentStake = _stakes[user];
uint256 id = currentStake.stakeId;

if (id == 0) return 0;

(address settings, , , , ) = IGovPool(owner()).getHelperContracts();
IGovSettings.StakingInfo memory stakeInfo = IGovSettings(settings).getStakingSettings(id);

if (stakeInfo.disabled) return 0;

if (currentStake.startedAt + stakeInfo.lockTime <= block.timestamp) return 0;

return stakeInfo.rewardMultiplier;
}

function _sendNativeOrToken(address receiver, uint256 amount) internal {
address token = tokenAddress;
amount = amount.from18Safe(token);
Expand Down Expand Up @@ -839,6 +902,21 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad
require(_nftInfo.nftAddress != address(0), "GovUK: nft is not supported");
}

function _prepareTokenWithdraw(address payer, uint256 amount) internal {
UserInfo storage payerInfo = _usersInfo[payer];
BalanceInfo storage payerBalanceInfo = payerInfo.balances[IGovPool.VoteType.PersonalVote];

uint256 balance = payerBalanceInfo.tokens;
uint256 maxTokensLocked = payerInfo.maxTokensLocked;

require(
amount <= balance.max(maxTokensLocked) - maxTokensLocked,
"GovUK: can't withdraw this"
);

payerBalanceInfo.tokens = balance - amount;
}

function _handleNative(uint256 value, bool wrapping) internal {
if (value == 0) {
return;
Expand All @@ -857,4 +935,20 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad

return _wethAddress != address(0) && wethAddress == tokenAddress;
}

function _stake(Stake storage userStake, uint256 newId) internal {
userStake.startedAt = uint64(block.timestamp);
userStake.stakeId = newId;
}

function _isStaked(address user) internal view returns (bool) {
return getStakingMultiplier(user) != 0;
}

function _getStakeInfo(
uint256 id
) internal view returns (IGovSettings.StakingInfo memory stakeInfo) {
(address settings, , , , ) = IGovPool(owner()).getHelperContracts();
stakeInfo = IGovSettings(settings).getStakingSettings(id);
}
}
5 changes: 5 additions & 0 deletions contracts/interfaces/gov/IGovPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ interface IGovPool {
/// @param nftIds the array of nft ids to withdraw
function withdraw(address receiver, uint256 amount, uint256[] calldata nftIds) external;

/// @notice The function for redeemings staked tokens
/// @param receiver the redeeming receiver address
/// @param amount the erc20 redeeming amount
function redeemTokens(address receiver, uint256 amount) external;

/// @notice The function for delegating tokens
/// @param delegatee the target address for delegation (person who will receive the delegation)
/// @param amount the erc20 delegation amount
Expand Down
41 changes: 41 additions & 0 deletions contracts/interfaces/gov/settings/IGovSettings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ interface IGovSettings {
uint256 voteRewardsCoefficient;
}

/// @notice The struct holds information about staking
/// @param lockTime the lock time of the stake
/// @param rewardMultiplier the reward bonus for the staker
/// @param redeemPenalty the percent substracted for early unstake, 0-100*10**25, uint.max if not allowed
/// @param allowStakingUpgrade the possibility to switch to long-term staking without penalties
/// @param disabled the state of staking
struct StakingInfo {
uint64 lockTime;
uint256 rewardMultiplier;
uint256 redeemPenalty;
bool allowStakingUpgrade;
bool disabled;
}

/// @notice The function to get settings of this executor
/// @param executor the executor
/// @return setting id of the executor
Expand All @@ -76,6 +90,21 @@ interface IGovSettings {
uint256[] calldata settingsIds
) external;

/// @notice Create new staking
/// @param lockTime Time to lock assets
/// @param rewardMultiplier The reward multiplier with precision
/// @param redeemPenalty The penalty for early unstake 0-100% precision or uint.max
function createNewStaking(
uint64 lockTime,
uint256 rewardMultiplier,
uint256 redeemPenalty,
bool allowStakingUpgrade
) external;

/// @notice Disables active staking
/// @param id The staking id
function closeStaking(uint256 id) external;

/// @notice The function to get default settings
/// @return default setting
function getDefaultSettings() external view returns (ProposalSettings memory);
Expand All @@ -88,4 +117,16 @@ interface IGovSettings {
/// @param executor Executor address
/// @return `ProposalSettings` by `executor` address
function getExecutorSettings(address executor) external view returns (ProposalSettings memory);

/// @notice The function the get the staking settings
/// @param id Staking id
/// @return `StakingInfo` by staking `id`
function getStakingSettings(uint256 id) external view returns (StakingInfo memory);

/// @notice The function the get the staking settings list
/// @param ids Staking ids list
/// @return `StakingInfo` list by staking `ids` list
function getStakingSettingsList(
uint256[] calldata ids
) external view returns (StakingInfo[] memory);
}
Loading
Loading