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

Dev #30

Merged
merged 8 commits into from
Jan 10, 2024
Merged

Dev #30

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
259 changes: 37 additions & 222 deletions contracts/strategies/mesh/MeshStakingStrategyBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ abstract contract MeshStakingStrategyBase is ProxyStrategyBase {
string public constant override STRATEGY_NAME = "MeshStakingStrategyBase";
/// @notice Version of the contract
/// @dev Should be incremented when contract changed
string public constant VERSION = "1.1.4";
string public constant VERSION = "1.1.5";
/// @dev 5% buybacks, 95% of vested Mesh should go to the targetRewardVault as rewards (not autocompound)
uint256 private constant _BUY_BACK_RATIO = 5_00;
uint256 private constant _MAX_LOCK_PERIOD = 1555200000;
Expand All @@ -45,11 +45,11 @@ abstract contract MeshStakingStrategyBase is ProxyStrategyBase {
address private constant _TETU_MESH = address(0xDcB8F34a3ceb48782c9f3F98dF6C12119c8d168a);
uint256 private constant _TARGET_PPFS = 1e18;


// DO NOT ADD ANY VARIABLES MORE! ONLY CONSTANTS!
/// @dev Deprecated, use slots instead
mapping(bytes32 => uint) private strategyUintStorage;
bytes32 internal constant _DUST_SLOT = bytes32(uint(keccak256("mesh.staking.dust")) - 1);
bytes32 internal constant _NEW_OWNER_SLOT = bytes32(uint(keccak256("mesh.staking.new_owner")) - 1);
bytes32 internal constant _TARGET_VAULT_SLOT = bytes32(uint(keccak256("mesh.staking.target.vault")) - 1);
bytes32 internal constant _REWARDS_TOKENS_SPECIFIC_SLOT = bytes32(uint(keccak256("mesh.staking.rewards.tokens.specific")) - 1);
// DO NOT ADD ANY VARIABLES MORE! ONLY CONSTANTS!
Expand Down Expand Up @@ -77,62 +77,56 @@ abstract contract MeshStakingStrategyBase is ProxyStrategyBase {

// ------------------ GOV actions --------------------------

function claimAirdrop(
address claimable,
address distributor,
address reward,
address liquidityToken
) external hardWorkers {
IClaimable(claimable).claim(distributor);
address forwarder = IController(_controller()).feeRewardForwarder();
modifier onlyNewOwner {
require(msg.sender == _NEW_OWNER_SLOT.getAddress(), "not new owner");
_;
}

uint balance = IERC20(reward).balanceOf(address(this));
require(balance != 0, "zero claim");
IERC20(reward).safeApprove(forwarder, 0);
IERC20(reward).safeApprove(forwarder, balance);
/// @dev End farming and set a new owner of this contract (suppose to be a major holder of tetuMESH)
function endFarming(address _newOwner) external restricted {
require(_NEW_OWNER_SLOT.getAddress() == address(0), "already set");
_setOnPause(true);
_NEW_OWNER_SLOT.set(_newOwner);
}

address[] memory route = new address[](2);
route[0] = reward;
route[1] = liquidityToken;
function unlockMESH() external onlyNewOwner {
VOTING_MESH.unlockMESH();
}

_meshSwap(balance, route);
function unlockMESHUnlimited() external onlyNewOwner {
VOTING_MESH.unlockMESHUnlimited();
}

balance = IERC20(liquidityToken).balanceOf(address(this));
require(balance != 0, "zero claim");
IERC20(liquidityToken).safeApprove(forwarder, 0);
IERC20(liquidityToken).safeApprove(forwarder, balance);
function claimReward() external onlyNewOwner {
VOTING_MESH.claimReward();
}

IFeeRewardForwarder(forwarder).distribute(balance, liquidityToken, targetRewardVault());
function manualWithdraw(address token, uint amount) external onlyNewOwner {
IERC20(token).safeTransfer(_NEW_OWNER_SLOT.getAddress(), amount);
}

/// @dev Manual withdraw for any emergency purposes
function manualWithdraw() external restricted {
IERC20(_underlying()).safeTransfer(_vault(), IERC20(_underlying()).balanceOf(address(this)));
function claimAirdrop(address claimable, address distributor) external onlyNewOwner {
IClaimable(claimable).claim(distributor);
}

function addVoting(address exchange, uint256 amount) external restricted {
function addVoting(address exchange, uint256 amount) external onlyNewOwner {
require(exchange != address(0), "Exchange address should be specified");
POOL_VOTING.addVoting(exchange, amount);
emit VotingAdded(exchange, amount);
}

function removeVoting(address exchange, uint256 amount) external restricted {
function removeVoting(address exchange, uint256 amount) external onlyNewOwner {
require(exchange != address(0), "Exchange address should be specified");
POOL_VOTING.removeVoting(exchange, amount);
emit VotingRemoved(exchange, amount);
}

function removeAllVoting() external restricted {
function removeAllVoting() external onlyNewOwner {
POOL_VOTING.removeAllVoting();
emit VotingRemovedAll();
}

function setTargetRewardVault(address _targetRewardVault) external restricted {
_TARGET_VAULT_SLOT.set(_targetRewardVault);
emit TargetRewardVaultUpdated(_targetRewardVault);
}

function updateRewardTokensFromVoting(address[] memory _rewardTokensFromVoting) external restricted {
function updateRewardTokensFromVoting(address[] memory _rewardTokensFromVoting) external onlyNewOwner {
_REWARDS_TOKENS_SPECIFIC_SLOT.setLength(_rewardTokensFromVoting.length);
for (uint i; i < _rewardTokensFromVoting.length; i++) {
_REWARDS_TOKENS_SPECIFIC_SLOT.setAt(i, _rewardTokensFromVoting[i]);
Expand All @@ -141,189 +135,26 @@ abstract contract MeshStakingStrategyBase is ProxyStrategyBase {

// --------------------------------------------

/// @dev Users assets not invested by the reason of rounding
function dust() public view returns (uint) {
return _DUST_SLOT.getUint();
}

/// @dev Target for all generated profit. Assume to be tetuMESH-MESH-LP vault.
function targetRewardVault() public view returns (address) {
return _TARGET_VAULT_SLOT.getAddress();
}

/// @dev Reward that can be received from POOL_VOTING
function specificRewardTokens() public view returns (address[] memory) {
address[] memory arr = new address[](_REWARDS_TOKENS_SPECIFIC_SLOT.arrayLength());
for (uint i; i < arr.length; i++) {
arr[i] = _REWARDS_TOKENS_SPECIFIC_SLOT.addressAt(i);
}
return arr;
}

/// @dev Returns MESH amount under control
function _rewardPoolBalance() internal override view returns (uint256) {
return VOTING_MESH.lockedMESH(address(this));
}

/// @dev In this version rewards are accumulated in this strategy
function doHardWork() external onlyNotPausedInvesting override hardWorkers {
// rewards claimed on lock action so we can not properly calculate exact reward amount
// assume that hardWork action will ba called on each deposit and immediately liquidate rewards
VOTING_MESH.claimReward();
POOL_VOTING.claimRewardAll();

liquidateReward();
function doHardWork() external override pure {
revert("MSS: Stopped");
}

/// @dev Stake Mesh to vMesh
function depositToPool(uint256 amount) internal override {
uint256 currentPPFS = ISmartVault(_vault()).getPricePerFullShare();
if (currentPPFS > _TARGET_PPFS) {
amount = _adjustPPFS(amount);
}

// lock on max period
// mesh allows only integer values w/o precision e.g 1 mesh token
uint256 roundedAmount = amount / _MESH_PRECISION;
_DUST_SLOT.set(amount - roundedAmount * _MESH_PRECISION);

if (roundedAmount > 0) {
IERC20(_underlying()).safeApprove(address(VOTING_MESH), 0);
IERC20(_underlying()).safeApprove(address(VOTING_MESH), roundedAmount * _MESH_PRECISION);
VOTING_MESH.lockMESH(roundedAmount, _MAX_LOCK_PERIOD);
uint256 underlyingBalAfter = IERC20(_underlying()).balanceOf(address(this));

// assume that all balance should be deposited in the staking
// exist balance is dust + claimed rewards
uint256 rewardsEarned = underlyingBalAfter - dust();
if (rewardsEarned > 0) {
_liquidateReward(rewardsEarned);
}
}
}

function _meshSwap(uint256 amount, address[] memory _route) internal {
if (amount != 0) {
require(IERC20(_route[0]).balanceOf(address(this)) >= amount, "Not enough balance");
IERC20(_route[0]).safeApprove(address(MESH_ROUTER), 0);
IERC20(_route[0]).safeApprove(address(MESH_ROUTER), amount);
MESH_ROUTER.swapExactTokensForTokens(
amount,
0,
_route,
address(this),
block.timestamp
);
}
function depositToPool(uint256 /*amount*/) internal override pure {
revert("MSS: Stopped");
}

function _getMeshReserves() internal view returns (uint256 tetuMeshReserves, uint256 meshReserves){
tetuMeshReserves = IERC20(_TETU_MESH).balanceOf(_MESH_TETU_MESH_PAIR_ADDRESS);
meshReserves = IERC20(_underlying()).balanceOf(_MESH_TETU_MESH_PAIR_ADDRESS);
}


function _liquidateReward(uint256 amount) internal {
uint toBuybacks = (amount * _buyBackRatio() / _BUY_BACK_DENOMINATOR);
address targetVault = targetRewardVault();
uint toVault = amount - toBuybacks;
if (toBuybacks != 0) {
address[] memory route = new address[](2);
route[0] = _underlying();
route[1] = _USDC_ADDRESS;
_meshSwap(toBuybacks, route);
uint usdcAmount = IERC20(_USDC_ADDRESS).balanceOf(address(this));
address forwarder = IController(_controller()).feeRewardForwarder();
IERC20(_USDC_ADDRESS).safeApprove(forwarder, 0);
IERC20(_USDC_ADDRESS).safeApprove(forwarder, toBuybacks);
// it will sell USDC tokens to Target Token and distribute it to SmartVault and PS
uint targetTokenEarned = IFeeRewardForwarder(forwarder).distribute(usdcAmount, _USDC_ADDRESS, targetVault);
if (targetTokenEarned > 0) {
IBookkeeper(IController(_controller()).bookkeeper()).registerStrategyEarned(targetTokenEarned);
}
}

if (toVault != 0) {
_distributeMeshRewards(toVault);
toVault = IERC20(_TETU_MESH).balanceOf(address(this));
IERC20(_TETU_MESH).safeApprove(targetVault, 0);
IERC20(_TETU_MESH).safeApprove(targetVault, toVault);
ISmartVault(targetVault).notifyTargetRewardAmount(_TETU_MESH, toVault);
}
}

function _distributeMeshRewards(uint256 amount) internal {
(uint256 tetuMeshReserve, uint256 meshReserve) = _getMeshReserves();
if (tetuMeshReserve > meshReserve) {
uint256 toSwapMaxAmount = _computeSellAmount(meshReserve, tetuMeshReserve, _MESH_PRECISION);
address[] memory route = new address[](2);
route[0] = _underlying();
route[1] = _TETU_MESH;
uint256 toSwap = Math.min(amount, toSwapMaxAmount);
_meshSwap(toSwap, route);
}
uint256 tokensLeft = IERC20(_underlying()).balanceOf(address(this)) - dust();

if (tokensLeft > 0) {
address underlying = _underlying();
// invest MESH tokens to tetuMESHVault
IERC20(underlying).safeApprove(_TETU_MESH, 0);
IERC20(underlying).safeApprove(_TETU_MESH, tokensLeft);
ISmartVault(_TETU_MESH).depositAndInvest(tokensLeft);
}
}


function _adjustPPFS(uint256 amount) internal returns (uint256) {
uint256 vaultTotalSupply = IERC20(_vault()).totalSupply();
uint256 _investedUnderlyingBalance = _rewardPoolBalance() + IERC20(_underlying()).balanceOf(address(this));
if (_investedUnderlyingBalance > vaultTotalSupply) {
uint256 adjustment = _investedUnderlyingBalance - vaultTotalSupply;
adjustment = Math.min(adjustment, amount);
// we are using it only for migration from autocompound model
// however this mechanic help to keep share price at 1 if somebody send tokens to the vault
// send to governance for properly manual distribution
IERC20(_underlying()).transfer(IController(_controller()).governance(), adjustment);
return amount - adjustment;
} else {
return amount;
}
}

function _computeSellAmount(
uint256 tokenReserve,
uint256 oppositeReserve,
uint256 targetPrice
) internal pure returns (uint256) {
if (targetPrice == 0) {
return 0;
}
// ignore fees
uint base = oppositeReserve * tokenReserve / targetPrice * _MESH_PRECISION;
uint256 sqrtBase = _sqrt(base);
if (sqrtBase < tokenReserve) {
// in this case the price lower than target price, need to sell
return 0;
}
return sqrtBase - tokenReserve;
}

/// @dev Babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function _sqrt(uint y) internal pure returns (uint z) {
z = 0;
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}


/// @dev Not supported by MESH
function withdrawAndClaimFromPool(uint256) internal pure override {
revert("MSS: Withdraw forbidden");
Expand All @@ -335,28 +166,12 @@ abstract contract MeshStakingStrategyBase is ProxyStrategyBase {
}

/// @dev Make something useful with rewards
function liquidateReward() internal override {
_liquidateVotingRewards();
uint256 underlyingBalance = IERC20(_underlying()).balanceOf(address(this));
uint _dust = dust();
require(underlyingBalance >= _dust, "Wrong dust amount");
_liquidateReward(underlyingBalance - _dust);
function liquidateReward() internal override pure {
revert("MSS: Stopped");
}

function _liquidateVotingRewards() internal {
uint256 i = 0;
uint length = _REWARDS_TOKENS_SPECIFIC_SLOT.arrayLength();
address asset = _underlying();
for (i; i < length; i++) {
address rt = _REWARDS_TOKENS_SPECIFIC_SLOT.addressAt(i);
uint256 rtBalance = IERC20(rt).balanceOf(address(this));
if (rtBalance > 0) {
address[] memory route = new address[](2);
route[0] = rt;
route[1] = asset;
_meshSwap(rtBalance, route);
}
}
function _liquidateVotingRewards() internal pure {
revert("MSS: Stopped");
}

/// @dev Not implemented
Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export default {
outDir: "typechain",
},
abiExporter: {
path: './artifacts/abi',
path: './abi',
runOnCompile: false,
spacing: 2,
pretty: false,
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/deploy/DeployerUtilsLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const argv = require('yargs/yargs')()
},
vaultLogic: {
type: "string",
default: "0x28F32AF48F6C33268F437aACDa0Ddf22ef28ADba"
default: "0xF88Cba0461A69cB9B32655F4c79601515fcCe7BA"
},
splitterLogic: {
type: "string",
Expand Down
Loading
Loading