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

mStable Strategy Adapter #111

Merged
merged 48 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f45a846
feat: add mStable base strategy components
alsco77 Apr 12, 2021
87266ca
chore: fix gov proxy
alsco77 Apr 13, 2021
9691f9d
chore: finalise strategy
alsco77 Apr 14, 2021
e3f829b
chore: add comments to mStable voter proxy
alsco77 Apr 15, 2021
87afef1
fix: mstable compilation issues
alsco77 Apr 15, 2021
516ab33
Merge branch 'master' into mstable
alsco77 May 18, 2021
5e51946
chore: add sett config
alsco77 May 27, 2021
f34b52f
Merge branch 'master' into mstable
alsco77 May 27, 2021
9fd7079
Merge pull request #1 from Badger-Finance/master
sajanrajdev May 27, 2021
eb9a3c8
Merge pull request #2 from alsco77/mstable
sajanrajdev May 27, 2021
b48e903
test: initial mstable testing implementations
sajanrajdev May 28, 2021
74b3a65
test: added proxy and strategy post setup
sajanrajdev May 28, 2021
f3ea251
test: test fixes and implementations
sajanrajdev May 31, 2021
0aa0de2
test: add strategy flow test
sajanrajdev May 31, 2021
7816229
test: simulation initial implementations
sajanrajdev May 31, 2021
97d5583
test: fixed minideploy and earn flow
sajanrajdev Jun 2, 2021
aeb75af
test: added voterproxy permissions test suite
sajanrajdev Jun 3, 2021
be7e844
test: added rest of voterproxy permissions tests
sajanrajdev Jun 3, 2021
94385ef
fix: added constant type to maxrate
sajanrajdev Jun 3, 2021
0a7ad99
fix: Paths to mint want corrected
alsco77 Jun 4, 2021
c21f3ae
Merge pull request #3 from alsco77/mstable
sajanrajdev Jun 4, 2021
e6243ea
test: fixed harvest resolver and file names
sajanrajdev Jun 7, 2021
616b8f6
test: added fpmbtchbtc setup and test fixes
sajanrajdev Jun 25, 2021
b433c22
test: remove wrong assertion
sajanrajdev Jun 25, 2021
9b53835
test: fix resolver assertion and addded delay
sajanrajdev Jun 28, 2021
8bd6e12
test: add harvest assertions and fix time delay
sajanrajdev Jun 28, 2021
f0f0bd0
test: added loan flow test
sajanrajdev Jun 28, 2021
c0d2946
test: added simulation implementations
sajanrajdev Jun 29, 2021
78b090c
test: added lock implementations to integration flow
sajanrajdev Jun 29, 2021
fa3d5af
test: fixed loan and lock integration flow
sajanrajdev Jun 30, 2021
68924b3
Merge pull request #1 from sajanrajdev/mstable-test
alsco77 Jun 30, 2021
857794d
Merge branch 'master' into mstable
alsco77 Jun 30, 2021
5953454
test: add harvestMts assertions
sajanrajdev Jun 30, 2021
eec188a
Merge pull request #2 from sajanrajdev/mstable-test
alsco77 Jun 30, 2021
9eb5001
Merge branch 'develop' into mstable
alsco77 Jun 30, 2021
a073ba0
fix: removed convexRenCrv repeated config
sajanrajdev Jun 30, 2021
de7433d
Add generic token distribution flag
dapp-whisperer Jul 14, 2021
923c5ca
added deployment script for voterproxy and mstable srats
sajanrajdev Jul 14, 2021
c28cd1c
fix: updated mstable deployment script
sajanrajdev Jul 15, 2021
42ec3d6
fix: hardcoded addresses and style
sajanrajdev Jul 15, 2021
c799f6a
fix: use deployed settv3 logic
sajanrajdev Jul 15, 2021
fefea36
Merge pull request #4 from alsco77/mstable
sajanrajdev Jul 15, 2021
69621b0
fix: compiling issues from changes to basestrategy
sajanrajdev Jul 15, 2021
f0cbfdf
feat: added proxy contract and fixed deploy script
sajanrajdev Jul 15, 2021
e7908c0
Merge pull request #3 from sajanrajdev/mstable-test
alsco77 Jul 16, 2021
7f3ef0d
feat: add ability to increase lock amount
alsco77 Jul 16, 2021
25b944d
chore: fix build issue with new fn
alsco77 Jul 18, 2021
f94f818
Merge branch 'develop' into mstable
sajanrajdev Jul 26, 2021
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
26 changes: 26 additions & 0 deletions config/badger_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

registry = registries.get_registry("eth")
curve = registry.curve
mstable = registry.mstable
pickle = registry.pickle
harvest = registry.harvest
sushi = registry.sushi
Expand Down Expand Up @@ -84,6 +85,31 @@
keepCRV=0,
),
),
imBtc=DotMap(
strategyName="StrategyMStableVaultImbtc",
params=DotMap(
want=mstable.pools.imBtc.token,
vault=mstable.pools.imBtc.vault,
lpComponent=registry.tokens.wbtc,
performanceFeeStrategist=0,
performanceFeeGovernance=1000,
withdrawalFee=75,
govMta=1000,
),
),
fPmBtcHBtc=DotMap(
strategyName="StrategyMStableVaultFpMbtcHbtc",
params=DotMap(
want=mstable.pools.fPmBtcHBtc.token,
vault=mstable.pools.fPmBtcHBtc.vault,
minter=curve.minter,
lpComponent=registry.tokens.wbtc,
performanceFeeStrategist=0,
performanceFeeGovernance=1000,
withdrawalFee=75,
govMta=1000,
),
),
convexRenCrv=DotMap(
strategyName="StrategyConvexStakingOptimizer",
params=DotMap(
Expand Down
308 changes: 308 additions & 0 deletions contracts/badger-sett/strategies/mStable/MStableVoterProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.11;
pragma experimental ABIEncoderV2;

import "deps/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "deps/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "deps/@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol";
import "deps/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";

import "interfaces/mStable/IMStableNexus.sol";
import "interfaces/mStable/IMStableBoostedVault.sol";
import "interfaces/mStable/IMStableVotingLockup.sol";
import "interfaces/mStable/IMStableVoterProxy.sol";
import "interfaces/badger/IStrategy.sol";

import "../../SettAccessControl.sol";

/// @title MStableVoterProxy
/// @author mStable
/// @notice VoterProxy that deposits into mStable vaults and uses MTA stake to boosts rewards.
/// @dev Receives MTA from Strategies and Loans in order to bolster Stake. Any MTA held here is
/// assumed to be invested to staking.
/// This is a dumb contract that:
/// - Deposits and withdraws LP tokens from all mStable vaults
/// - Manages the lock in the MTA staking contract
/// - Earns APY on staked MTA and reinvests
/// - Boosts rewards in vault deposits
/// - Migrates to a new Staking contract if necessary
contract MStableVoterProxy is IMStableVoterProxy, PausableUpgradeable, SettAccessControl {
using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeMathUpgradeable for uint256;

mapping(address => IMStableBoostedVault) public strategyToVault; // strategy => vault
address[] public strategies;

address public badgerGovernance;
IMStableNexus public nexus; // mStable Nexus maintains record of governor address
IMStableVotingLockup public votingLockup; // Current MTA staking contract address

mapping(address => uint256) public loans; // Outstanding loans made to this contract
IERC20Upgradeable public constant mta = IERC20Upgradeable(0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2);

uint256 public constant MAX_RATE = 10000;
uint256 public redistributionRate;

event LockCreated(uint256 amt, uint256 unlockTime);
event MtaHarvested(uint256 existing, uint256 harvested, uint256 distributed, uint256 invested);
event LockExtended(uint256 unlockTime);
event LockIncreased(uint256 amount);
event LockExited();
event LockChanged(address newLock);
event RedistributionRateChanged(uint256 newRate);

event Loaned(address creditor, uint256 amt);
event LoanRepaid(address creditor, uint256 amt);

event StrategyAdded(address strategy, address vault);

function initialize(
address _dualGovernance,
address _badgerGovernance,
address _strategist,
address _keeper,
address[2] memory _config,
uint256[1] memory _rates
) public initializer {
__Pausable_init();
governance = _dualGovernance;
badgerGovernance = _badgerGovernance;
strategist = _strategist;
keeper = _keeper;

nexus = IMStableNexus(_config[0]);
votingLockup = IMStableVotingLockup(_config[1]);

redistributionRate = _rates[0];

mta.safeApprove(address(votingLockup), type(uint256).max);
}

/// @dev Verifies that the caller is an active strategy and returns the address of the vault
function _onlyActiveStrategy() internal view returns (IMStableBoostedVault vault) {
vault = strategyToVault[msg.sender];
require(address(vault) != address(0), "onlyStrategy");
}

/// @dev Callable by either the mStableDAO or the BadgerDAO signers
function _onlyGovernors() internal view {
require(msg.sender == badgerGovernance || msg.sender == nexus.governor(), "onlyGovernors");
}

/// @dev Callable by either the mStableDAO or the BadgerDAO signers
function _onlyHarvesters() internal view {
require(msg.sender == badgerGovernance || msg.sender == nexus.governor() || msg.sender == keeper, "onlyHarvesters");
}

/***************************************
VOTINGLOCK
****************************************/

/// @dev Creates a lock in the mStable MTA staking contract, using the mta balance of
/// this contract, and unlocking at the specified unlock time
/// @param _unlockTime Time at which the stake will unlock
function createLock(uint256 _unlockTime) external override {
_onlyGovernance();

uint256 bal = mta.balanceOf(address(this));
votingLockup.createLock(bal, _unlockTime);

emit LockCreated(bal, _unlockTime);
}

/// @dev Claims MTA rewards from Staking, distributes a percentage proportionately to all
/// active strategies, and reinvests the remainder back into the staking contract.
/// Also picks up any MTA that was transferred here FROM strategies, and adds this to the lock.
/// Callable by either mStable or Badger DAO multisigs, or keeper.
function harvestMta() external override {
_onlyHarvesters();

// balBefore = any MTA that was transferred here as a govMTA % from the stratgies
uint256 balBefore = mta.balanceOf(address(this));
votingLockup.claimReward();
uint256 balAfter = mta.balanceOf(address(this));
// e.g. (2e18 - 1e18) * 1000 / 10000;
uint256 redistribute = balAfter.sub(balBefore).mul(redistributionRate).div(MAX_RATE);
// Redistribute a % of the revenue from staking back to the strategies
if (redistribute > 0) {
uint256 len = strategies.length;
for (uint256 i = 0; i < len; i++) {
mta.safeTransfer(strategies[i], redistribute.div(len));
}
}
// Increase that lock
votingLockup.increaseLockAmount(balAfter.sub(redistribute));

emit MtaHarvested(balBefore, balAfter.sub(balBefore), redistribute, balAfter.sub(redistribute));
}

/// @dev Simply extends the lock period in staking
/// @param _unlockTime New time at which the stake will unlock
function extendLock(uint256 _unlockTime) external override {
_onlyGovernance();

votingLockup.increaseLockLength(_unlockTime);

emit LockExtended(_unlockTime);
}

/// @dev Simply extends the lock amount in staking
function increaseLock() external {
_onlyGovernance();

uint256 bal = mta.balanceOf(address(this));
votingLockup.increaseLockAmount(bal);

emit LockIncreased(bal);
}

/// @dev Exits the lock and keeps MTA in contract
/// @return mtaBalance Balance of MTA in this contract
function exitLock() external override returns (uint256 mtaBalance) {
_onlyGovernors();

votingLockup.exit();

emit LockExited();
}

/// @dev Changes the address of the VotingLockup
/// WARNING - this approves mta on the new contract, so should be taken with care
/// @param _newLock Address of the new VotingLockup
function changeLockAddress(address _newLock) external override {
_onlyGovernance();

require(votingLockup.balanceOf(address(this)) == 0, "Active lockup");

votingLockup = IMStableVotingLockup(_newLock);

IERC20Upgradeable(mta).safeApprove(_newLock, type(uint256).max);

emit LockChanged(_newLock);
}

/// @dev Changes the percentage of MTA earned via staking that gets redistributed to strategies
/// @param _newRate Scaled pct of earnings to redistribute to strategies, where 100% = 10000
function changeRedistributionRate(uint256 _newRate) external override {
_onlyGovernors();
require(_newRate < MAX_RATE, "Invalid rate");

redistributionRate = _newRate;

emit RedistributionRateChanged(_newRate);
}

/***************************************
LOANS
****************************************/

/// @dev Loans the voter proxy a given amt by transferring and logging
/// @param _amt Amt to send to the proxy!
function loan(uint256 _amt) external override {
require(loans[msg.sender] == 0, "Existing loan");

mta.safeTransferFrom(msg.sender, address(this), _amt);
loans[msg.sender] = _amt;

emit Loaned(msg.sender, _amt);
}

/// @dev Repays the initially loaned MTA amount to a creditor
/// @param _creditor Address of the initial creditor
function repayLoan(address _creditor) external override {
_onlyGovernors();

uint256 loanAmt = loans[_creditor];
require(loanAmt != 0, "Non-existing loan");

loans[_creditor] = 0;
mta.safeTransfer(_creditor, loanAmt);

emit LoanRepaid(_creditor, loanAmt);
}

/***************************************
STRATEGIES
****************************************/

/// @dev Adds a new supported strategy, looking up want and approving to vault
/// @param _strategy Address of the BadgerStrategy
/// @param _vault Address of the mStable vault
function supportStrategy(address _strategy, address _vault) external override {
_onlyGovernance();

require(address(strategyToVault[_strategy]) == address(0), "Strategy already supported");

uint256 len = strategies.length;
for (uint256 i = 0; i < len; i++) {
address vaulti = address(strategyToVault[strategies[i]]);
require(vaulti != _vault, "Vault already supported");
}

// Lookup want in strategy
address want = IStrategy(_strategy).want();
// Approve spending to vault
IERC20Upgradeable(want).safeApprove(_vault, type(uint256).max);
// Whitelist strategy
strategyToVault[_strategy] = IMStableBoostedVault(_vault);
strategies.push(_strategy);

emit StrategyAdded(_strategy, _vault);
}

/***************************************
POOL
****************************************/

/// @dev Simply stakes in pool
/// NOTE - Assumes that the want has already been transferred here
/// @param _amt Amt of want that should be staked in the vault
function deposit(uint256 _amt) external override {
IMStableBoostedVault vault = _onlyActiveStrategy();

vault.stake(_amt);
}

/// @dev Withdraws balance from vault, returning to strategy
/// Passes _want to avoid having to read _want again via ext call
/// @param _want Address of the LP token to return back to sender
function withdrawAll(address _want) external override {
IMStableBoostedVault vault = _onlyActiveStrategy();

uint256 rawBal = vault.rawBalanceOf(address(this));
vault.withdraw(rawBal);
IERC20Upgradeable(_want).safeTransfer(msg.sender, rawBal);
}

/// @dev Withdraws _amt from vault, returning to strategy
/// Passes _want to avoid having to read _want again via ext call
/// @param _want Address of the LP token to return back to sender
/// @param _amt Amount of want to withdraw and return
function withdrawSome(address _want, uint256 _amt) external override {
IMStableBoostedVault vault = _onlyActiveStrategy();

vault.withdraw(_amt);
IERC20Upgradeable(_want).safeTransfer(msg.sender, _amt);
}

/// @dev Claims rewards from the matching vault, and returns them to sender.
/// @return immediateUnlock Amount of tokens that were earned without need for vesting
/// @return vested Amount of tokens that were earned post-vesting
function claim() external override returns (uint256 immediateUnlock, uint256 vested) {
IMStableBoostedVault vault = _onlyActiveStrategy();

// Get balance of MTA before (there could be residual MTA here waiting to be reinvested in vMTA)
uint256 balBefore = mta.balanceOf(address(this));
// Get MTA ready for immediate unlock (this is a view fn)
immediateUnlock = vault.earned(address(this));
// Actually claim rewards - both immediately unlocked as well as post-vesting rewards
vault.claimRewards();
// Calc the total amount claimed based on changing bal
uint256 balAfter = mta.balanceOf(address(this));
uint256 totalClaimed = balAfter.sub(balBefore);
// Amount of the claim that was subject to vesting
vested = totalClaimed.sub(immediateUnlock);

mta.safeTransfer(msg.sender, totalClaimed);
}
}
Loading