auraBal
0 removals
278 lines
0 additions
278 lines
contract AuraBalStakerStrategy is BaseStrategy {
contract AuraBalStakerStrategy is BaseStrategy {
using SafeMathUpgradeable for uint256;
using SafeMathUpgradeable for uint256;
using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeERC20Upgradeable for IERC20Upgradeable;
bool public claimRewardsOnWithdrawAll;
bool public claimRewardsOnWithdrawAll;
uint256 public balEthBptToAuraBalMinOutBps;
uint256 public balEthBptToAuraBalMinOutBps;
IBaseRewardPool public constant AURABAL_REWARDS =
IBaseRewardPool public constant AURABAL_REWARDS =
IBaseRewardPool(0x5e5ea2048475854a5702F5B8468A51Ba1296EFcC);
IBaseRewardPool(0x5e5ea2048475854a5702F5B8468A51Ba1296EFcC);
IVault public constant GRAVIAURA =
IVault public constant GRAVIAURA =
IVault(0xBA485b556399123261a5F9c95d413B4f93107407);
IVault(0xBA485b556399123261a5F9c95d413B4f93107407);
IBalancerVault public constant BALANCER_VAULT =
IBalancerVault public constant BALANCER_VAULT =
IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IAuraToken public constant AURA =
IAuraToken public constant AURA =
IAuraToken(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF);
IAuraToken(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF);
IERC20Upgradeable public constant AURABAL =
IERC20Upgradeable public constant AURABAL =
IERC20Upgradeable(0x616e8BfA43F920657B3497DBf40D6b1A02D4608d);
IERC20Upgradeable(0x616e8BfA43F920657B3497DBf40D6b1A02D4608d);
IERC20Upgradeable public constant WETH =
IERC20Upgradeable public constant WETH =
IERC20Upgradeable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20Upgradeable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20Upgradeable public constant BAL =
IERC20Upgradeable public constant BAL =
IERC20Upgradeable(0xba100000625a3754423978a60c9317c58a424e3D);
IERC20Upgradeable(0xba100000625a3754423978a60c9317c58a424e3D);
IERC20Upgradeable public constant BALETH_BPT =
IERC20Upgradeable public constant BALETH_BPT =
IERC20Upgradeable(0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56);
IERC20Upgradeable(0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56);
IERC20Upgradeable public constant BB_A_USD =
IERC20Upgradeable public constant BB_A_USD =
IERC20Upgradeable(0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2);
IERC20Upgradeable(0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2);
bytes32 public constant BAL_ETH_POOL_ID =
bytes32 public constant BAL_ETH_POOL_ID =
0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014;
0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014;
bytes32 public constant AURABAL_BALETH_BPT_POOL_ID =
bytes32 public constant AURABAL_BALETH_BPT_POOL_ID =
0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd000200000000000000000249;
0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd000200000000000000000249;
/// @dev Initialize the Strategy with security settings as well as tokens
/// @dev Initialize the Strategy with security settings as well as tokens
/// @notice Proxies will set any non constant variable you declare as default value
/// @notice Proxies will set any non constant variable you declare as default value
/// @dev add any extra changeable variable at end of initializer as shown
/// @dev add any extra changeable variable at end of initializer as shown
function initialize(address _vault) public initializer {
function initialize(address _vault) public initializer {
require(IVault(_vault).token() == address(AURABAL));
require(IVault(_vault).token() == address(AURABAL));
__BaseStrategy_init(_vault);
__BaseStrategy_init(_vault);
want = address(AURABAL);
want = address(AURABAL);
claimRewardsOnWithdrawAll = true;
claimRewardsOnWithdrawAll = true;
balEthBptToAuraBalMinOutBps = 9500; // max 5% slippage
balEthBptToAuraBalMinOutBps = 9500; // max 5% slippage
AURABAL.safeApprove(address(AURABAL_REWARDS), type(uint256).max);
AURABAL.safeApprove(address(AURABAL_REWARDS), type(uint256).max);
BAL.safeApprove(address(BALANCER_VAULT), type(uint256).max);
BAL.safeApprove(address(BALANCER_VAULT), type(uint256).max);
BALETH_BPT.safeApprove(address(BALANCER_VAULT), type(uint256).max);
BALETH_BPT.safeApprove(address(BALANCER_VAULT), type(uint256).max);
AURA.approve(address(GRAVIAURA), type(uint256).max);
AURA.approve(address(GRAVIAURA), type(uint256).max);
}
}
function setClaimRewardsOnWithdrawAll(bool _claimRewardsOnWithdrawAll)
function setClaimRewardsOnWithdrawAll(bool _claimRewardsOnWithdrawAll)
external
external
{
{
_onlyGovernanceOrStrategist();
_onlyGovernanceOrStrategist();
claimRewardsOnWithdrawAll = _claimRewardsOnWithdrawAll;
claimRewardsOnWithdrawAll = _claimRewardsOnWithdrawAll;
}
}
function setBalEthBptToAuraBalMinOutBps(uint256 _minOutBps) external {
function setBalEthBptToAuraBalMinOutBps(uint256 _minOutBps) external {
_onlyGovernanceOrStrategist();
_onlyGovernanceOrStrategist();
require(_minOutBps <= MAX_BPS, "Invalid minOutBps");
require(_minOutBps <= MAX_BPS, "Invalid minOutBps");
balEthBptToAuraBalMinOutBps = _minOutBps;
balEthBptToAuraBalMinOutBps = _minOutBps;
}
}
/// @dev Return the name of the strategy
/// @dev Return the name of the strategy
function getName() external pure override returns (string memory) {
function getName() external pure override returns (string memory) {
return "AuraBalStakerStrategy";
return "AuraBalStakerStrategy";
}
}
/// @dev Return a list of protected tokens
/// @dev Return a list of protected tokens
/// @notice It's very important all tokens that are meant to be in the strategy to be marked as protected
/// @notice It's very important all tokens that are meant to be in the strategy to be marked as protected
/// @notice this provides security guarantees to the depositors they can't be sweeped away
/// @notice this provides security guarantees to the depositors they can't be sweeped away
function getProtectedTokens()
function getProtectedTokens()
public
public
view
view
virtual
virtual
override
override
returns (address[] memory)
returns (address[] memory)
{
{
address[] memory protectedTokens = new address[](3);
address[] memory protectedTokens = new address[](3);
protectedTokens[0] = want; // AURABAL
protectedTokens[0] = want; // AURABAL
protectedTokens[1] = address(AURA);
protectedTokens[1] = address(AURA);
protectedTokens[2] = address(BAL);
protectedTokens[2] = address(BAL);
return protectedTokens;
return protectedTokens;
}
}
/// @dev Deposit `_amount` of want, investing it to earn yield
/// @dev Deposit `_amount` of want, investing it to earn yield
function _deposit(uint256 _amount) internal override {
function _deposit(uint256 _amount) internal override {
// Add code here to invest `_amount` of want to earn yield
// Add code here to invest `_amount` of want to earn yield
AURABAL_REWARDS.stake(_amount);
AURABAL_REWARDS.stake(_amount);
}
}
/// @dev Withdraw all funds, this is used for migrations, most of the time for emergency reasons
/// @dev Withdraw all funds, this is used for migrations, most of the time for emergency reasons
function _withdrawAll() internal override {
function _withdrawAll() internal override {
uint256 poolBalance = balanceOfPool();
uint256 poolBalance = balanceOfPool();
if (poolBalance > 0) {
if (poolBalance > 0) {
AURABAL_REWARDS.withdrawAll(claimRewardsOnWithdrawAll);
AURABAL_REWARDS.withdrawAll(claimRewardsOnWithdrawAll);
}
}
}
}
/// @dev Withdraw `_amount` of want, so that it can be sent to the vault / depositor
/// @dev Withdraw `_amount` of want, so that it can be sent to the vault / depositor
/// @notice just unlock the funds and return the amount you could unlock
/// @notice just unlock the funds and return the amount you could unlock
function _withdrawSome(uint256 _amount)
function _withdrawSome(uint256 _amount)
internal
internal
override
override
returns (uint256)
returns (uint256)
{
{
uint256 wantBalance = balanceOfWant();
uint256 wantBalance = balanceOfWant();
if (wantBalance < _amount) {
if (wantBalance < _amount) {
uint256 toWithdraw = _amount.sub(wantBalance);
uint256 toWithdraw = _amount.sub(wantBalance);
AURABAL_REWARDS.withdraw(toWithdraw, false);
AURABAL_REWARDS.withdraw(toWithdraw, false);
}
}
return MathUpgradeable.min(_amount, balanceOfWant());
return MathUpgradeable.min(_amount, balanceOfWant());
}
}
/// @dev Does this function require `tend` to be called?
/// @dev Does this function require `tend` to be called?
function _isTendable() internal pure override returns (bool) {
function _isTendable() internal pure override returns (bool) {
return false; // Change to true if the strategy should be tended
return false; // Change to true if the strategy should be tended
}
}
function _harvest()
function _harvest()
internal
internal
override
override
returns (TokenAmount[] memory harvested)
returns (TokenAmount[] memory harvested)
{
{
AURABAL_REWARDS.getReward();
AURABAL_REWARDS.getReward();
// Rewards are handled like this:
// Rewards are handled like this:
// BAL --> BAL/ETH BPT --> AURABAL (autocompounded)
// BAL --> BAL/ETH BPT --> AURABAL (autocompounded)
// AURA --> GRAVIAURA (emitted)
// AURA --> GRAVIAURA (emitted)
// BB_A_USD --> Left in strategy to be sweeped later
// BB_A_USD --> Left in strategy to be sweeped later
harvested = new TokenAmount[](2);
harvested = new TokenAmount[](2);
harvested[0].token = address(AURABAL);
harvested[0].token = address(AURABAL);
harvested[1].token = address(GRAVIAURA);
harvested[1].token = address(GRAVIAURA);
// BAL --> BAL/ETH BPT --> AURABAL
// BAL --> BAL/ETH BPT --> AURABAL
uint256 balBalance = BAL.balanceOf(address(this));
uint256 balBalance = BAL.balanceOf(address(this));
uint256 auraBalEarned;
uint256 auraBalEarned;
if (balBalance > 0) {
if (balBalance > 0) {
// Deposit BAL --> BAL/ETH BPT
// Deposit BAL --> BAL/ETH BPT
IAsset[] memory assets = new IAsset[](2);
IAsset[] memory assets = new IAsset[](2);
assets[0] = IAsset(address(BAL));
assets[0] = IAsset(address(BAL));
assets[1] = IAsset(address(WETH));
assets[1] = IAsset(address(WETH));
uint256[] memory maxAmountsIn = new uint256[](2);
uint256[] memory maxAmountsIn = new uint256[](2);
maxAmountsIn[0] = balBalance;
maxAmountsIn[0] = balBalance;
maxAmountsIn[1] = 0;
maxAmountsIn[1] = 0;
BALANCER_VAULT.joinPool(
BALANCER_VAULT.joinPool(
BAL_ETH_POOL_ID,
BAL_ETH_POOL_ID,
address(this),
address(this),
address(this),
address(this),
IBalancerVault.JoinPoolRequest({
IBalancerVault.JoinPoolRequest({
assets: assets,
assets: assets,
maxAmountsIn: maxAmountsIn,
maxAmountsIn: maxAmountsIn,
userData: abi.encode(
userData: abi.encode(
JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,
JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT,
maxAmountsIn,
maxAmountsIn,
0 // minOut
0 // minOut
),
),
fromInternalBalance: false
fromInternalBalance: false
})
})
);
);
// Swap BAL/ETH BPT --> AURABAL
// Swap BAL/ETH BPT --> AURABAL
uint256 balEthBptBalance = IERC20Upgradeable(BALETH_BPT).balanceOf(
uint256 balEthBptBalance = IERC20Upgradeable(BALETH_BPT).balanceOf(
address(this)
address(this)
);
);
// Swap BAL/ETH BPT --> auraBal
// Swap BAL/ETH BPT --> auraBal
IBalancerVault.FundManagement memory fundManagement = IBalancerVault
IBalancerVault.FundManagement memory fundManagement = IBalancerVault
.FundManagement({
.FundManagement({
sender: address(this),
sender: address(this),
fromInternalBalance: false,
fromInternalBalance: false,
recipient: payable(address(this)),
recipient: payable(address(this)),
toInternalBalance: false
toInternalBalance: false
});
});
IBalancerVault.SingleSwap memory singleSwap = IBalancerVault
IBalancerVault.SingleSwap memory singleSwap = IBalancerVault
.SingleSwap({
.SingleSwap({
poolId: AURABAL_BALETH_BPT_POOL_ID,
poolId: AURABAL_BALETH_BPT_POOL_ID,
kind: IBalancerVault.SwapKind.GIVEN_IN,
kind: IBalancerVault.SwapKind.GIVEN_IN,
assetIn: IAsset(address(BALETH_BPT)),
assetIn: IAsset(address(BALETH_BPT)),
assetOut: IAsset(address(AURABAL)),
assetOut: IAsset(address(AURABAL)),
amount: balEthBptBalance,
amount: balEthBptBalance,
userData: new bytes(0)
userData: new bytes(0)
});
});
uint256 minOut = (balEthBptBalance * balEthBptToAuraBalMinOutBps) /
uint256 minOut = (balEthBptBalance * balEthBptToAuraBalMinOutBps) /
MAX_BPS;
MAX_BPS;
auraBalEarned = BALANCER_VAULT.swap(
auraBalEarned = BALANCER_VAULT.swap(
singleSwap,
singleSwap,
fundManagement,
fundManagement,
minOut,
minOut,
type(uint256).max
type(uint256).max
);
);
harvested[0].amount = auraBalEarned;
harvested[0].amount = auraBalEarned;
}
}
// AURA --> graviAURA
// AURA --> graviAURA
uint256 auraBalance = AURA.balanceOf(address(this));
uint256 auraBalance = AURA.balanceOf(address(this));
if (auraBalance > 0) {
if (auraBalance > 0) {
GRAVIAURA.deposit(auraBalance);
GRAVIAURA.deposit(auraBalance);
uint256 graviAuraBalance = GRAVIAURA.balanceOf(address(this));
uint256 graviAuraBalance = GRAVIAURA.balanceOf(address(this));
harvested[1].amount = graviAuraBalance;
harvested[1].amount = graviAuraBalance;
_processExtraToken(address(GRAVIAURA), graviAuraBalance);
_processExtraToken(address(GRAVIAURA), graviAuraBalance);
}
}
// Report harvest
// Report harvest
_reportToVault(auraBalEarned);
_reportToVault(auraBalEarned);
// Stake whatever is earned
// Stake whatever is earned
if (auraBalEarned > 0) {
if (auraBalEarned > 0) {
_deposit(auraBalEarned);
_deposit(auraBalEarned);
}
}
}
}
// Example tend is a no-op which returns the values, could also just revert
// Example tend is a no-op which returns the values, could also just revert
function _tend() internal override returns (TokenAmount[] memory tended) {
function _tend() internal override returns (TokenAmount[] memory tended) {
revert("no op");
revert("no op");
}
}
/// @dev Return the balance (in want) that the strategy has invested somewhere
/// @dev Return the balance (in want) that the strategy has invested somewhere
function balanceOfPool() public view override returns (uint256) {
function balanceOfPool() public view override returns (uint256) {
// Change this to return the amount of want invested in another protocol
// Change this to return the amount of want invested in another protocol
return AURABAL_REWARDS.balanceOf(address(this));
return AURABAL_REWARDS.balanceOf(address(this));
}
}
/// @dev Return the balance of rewards that the strategy has accrued
/// @dev Return the balance of rewards that the strategy has accrued
/// @notice Used for offChain APY and Harvest Health monitoring
/// @notice Used for offChain APY and Harvest Health monitoring
function balanceOfRewards()
function balanceOfRewards()
external
external
view
view
override
override
returns (TokenAmount[] memory rewards)
returns (TokenAmount[] memory rewards)
{
{
uint256 balEarned = AURABAL_REWARDS.earned(address(this));
uint256 balEarned = AURABAL_REWARDS.earned(address(this));
rewards = new TokenAmount[](2);
rewards = new TokenAmount[](2);
rewards[0] = TokenAmount(address(BAL), balEarned);
rewards[0] = TokenAmount(address(BAL), balEarned);
rewards[1] = TokenAmount(
rewards[1] = TokenAmount(
address(AURA),
address(AURA),
getMintableAuraRewards(balEarned)
getMintableAuraRewards(balEarned)
);
);
}
}
/// @notice Returns the expected amount of AURA to be minted given an amount of BAL rewards
/// @notice Returns the expected amount of AURA to be minted given an amount of BAL rewards
/// @dev ref: https://etherscan.io/address/0xc0c293ce456ff0ed870add98a0828dd4d2903dbf#code#F1#L86
/// @dev ref: https://etherscan.io/address/0xc0c293ce456ff0ed870add98a0828dd4d2903dbf#code#F1#L86
function getMintableAuraRewards(uint256 _balAmount)
function getMintableAuraRewards(uint256 _balAmount)
public
public
view
view
returns (uint256 amount)
returns (uint256 amount)
{
{
// NOTE: Only correct if AURA.minterMinted() == 0
// NOTE: Only correct if AURA.minterMinted() == 0
// minterMinted is a private var in the contract, so we can't access it directly
// minterMinted is a private var in the contract, so we can't access it directly
uint256 emissionsMinted = AURA.totalSupply() - AURA.INIT_MINT_AMOUNT();
uint256 emissionsMinted = AURA.totalSupply() - AURA.INIT_MINT_AMOUNT();
uint256 cliff = emissionsMinted.div(AURA.reductionPerCliff());
uint256 cliff = emissionsMinted.div(AURA.reductionPerCliff());
uint256 totalCliffs = AURA.totalCliffs();
uint256 totalCliffs = AURA.totalCliffs();
if (cliff < totalCliffs) {
if (cliff < totalCliffs) {
uint256 reduction = totalCliffs.sub(cliff).mul(5).div(2).add(700);
uint256 reduction = totalCliffs.sub(cliff).mul(5).div(2).add(700);
amount = _balAmount.mul(reduction).div(totalCliffs);
amount = _balAmount.mul(reduction).div(totalCliffs);
uint256 amtTillMax = AURA.EMISSIONS_MAX_SUPPLY().sub(
uint256 amtTillMax = AURA.EMISSIONS_MAX_SUPPLY().sub(
emissionsMinted
emissionsMinted
);
);
if (amount > amtTillMax) {
if (amount > amtTillMax) {
amount = amtTillMax;
amount = amtTillMax;
}
}
}
}
}
}
}
}