SonicSwapXAMOStrategy vs StableSwapAMMStrategy

Erstellt Diff läuft nie ab
394 Entfernungen
667 Zeilen
369 Hinzufügungen
646 Zeilen
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;


/**
/**
* @title SwapX Algorithmic Market Maker (AMO) Strategy
* @title Algebra Algorithmic Market Maker (AMO) Strategy
* @notice AMO strategy for the SwapX OS/wS stable pool
* @notice AMO strategy for the Algebra stable swap pool
* @author Origin Protocol Inc
* @author Origin Protocol Inc
*/
*/
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";


import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { StableMath } from "../../utils/StableMath.sol";
import { StableMath } from "../../utils/StableMath.sol";
import { sqrt } from "../../utils/PRBMath.sol";
import { sqrt } from "../../utils/PRBMath.sol";
import { IBasicToken } from "../../interfaces/IBasicToken.sol";
import { IBasicToken } from "../../interfaces/IBasicToken.sol";
import { IPair } from "../../interfaces/sonic/ISwapXPair.sol";
import { IPair } from "../../interfaces/algebra/IAlgebraPair.sol";
import { IGauge } from "../../interfaces/sonic/ISwapXGauge.sol";
import { IGauge } from "../../interfaces/algebra/IAlgebraGauge.sol";
import { IVault } from "../../interfaces/IVault.sol";
import { IVault } from "../../interfaces/IVault.sol";


contract SonicSwapXAMOStrategy is InitializableAbstractStrategy {
contract StableSwapAMMStrategy is InitializableAbstractStrategy {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
using StableMath for uint256;
using StableMath for uint256;
using SafeCast for uint256;
using SafeCast for uint256;


/**
/**
* @notice a threshold under which the contract no longer allows for the protocol to manually rebalance.
* @notice a threshold under which the contract no longer allows for the protocol to manually rebalance.
* Guarding against a strategist / guardian being taken over and with multiple transactions
* Guarding against a strategist / guardian being taken over and with multiple transactions
* draining the protocol funds.
* draining the protocol funds.
*/
*/
uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;
uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether;


/// @notice Precision for the SwapX Stable AMM (sAMM) invariant k.
/// @notice Precision for the Algebra Stable AMM (sAMM) invariant k.
uint256 public constant PRECISION = 1e18;
uint256 public constant PRECISION = 1e18;


/// @notice Address of the Wrapped S (wS) token.
/// @notice Address of the asset (non OToken) token contract
address public immutable ws;
address public immutable asset;


/// @notice Address of the OS token contract.
/// @notice Address of the OToken token contract.
address public immutable os;
address public immutable oToken;


/// @notice Address of the SwapX Stable pool contract.
/// @notice Address of the Algebra Stable pool contract.
address public immutable pool;
address public immutable pool;


/// @notice Address of the SwapX Gauge contract.
/// @notice Address of the Algebra Gauge contract.
address public immutable gauge;
address public immutable gauge;


/// @notice The max amount the OS/wS price can deviate from peg (1e18)
/// @notice Index of the OToken in the Algebra pool.
uint256 public immutable oTokenPoolIndex;

/// @notice The max amount the OToken/asset price can deviate from peg (1e18)
/// before deposits are reverted scaled to 18 decimals.
/// before deposits are reverted scaled to 18 decimals.
/// eg 0.01e18 or 1e16 is 1% which is 100 basis points.
/// eg 0.01e18 or 1e16 is 1% which is 100 basis points.
/// This is the amount below and above peg so a 50 basis point deviation (0.005e18)
/// This is the amount below and above peg so a 50 basis point deviation (0.005e18)
/// allows a price range from 0.995 to 1.005.
/// allows a price range from 0.995 to 1.005.
uint256 public maxDepeg;
uint256 public maxDepeg;


event SwapOTokensToPool(
event SwapOTokensToPool(
uint256 osMinted,
uint256 oTokenMinted,
uint256 wsDepositAmount,
uint256 assetDepositAmount,
uint256 osDepositAmount,
uint256 oTokenDepositAmount,
uint256 lpTokens
uint256 lpTokens
);
);
event SwapAssetsToPool(
event SwapAssetsToPool(
uint256 wsSwapped,
uint256 assetSwapped,
uint256 lpTokens,
uint256 lpTokens,
uint256 osBurnt
uint256 oTokenBurnt
);
);
event MaxDepegUpdated(uint256 maxDepeg);
event MaxDepegUpdated(uint256 maxDepeg);


/**
/**
* @dev Verifies that the caller is the Strategist of the Vault.
* @dev Verifies that the caller is the Strategist of the Vault.
*/
*/
modifier onlyStrategist() {
modifier onlyStrategist() {
require(
require(
msg.sender == IVault(vaultAddress).strategistAddr(),
msg.sender == IVault(vaultAddress).strategistAddr(),
"Caller is not the Strategist"
"Caller is not the Strategist"
);
);
_;
_;
}
}


/**
/**
* @dev Skim the SwapX pool in case any extra wS or OS tokens were added
* @dev Skim the Algebra pool in case any extra asset or OToken tokens were added
*/
*/
modifier skimPool() {
modifier skimPool() {
IPair(pool).skim(address(this));
IPair(pool).skim(address(this));
_;
_;
}
}


/**
/**
* @dev Checks the pool is balanced enough to allow deposits.
* @dev Checks the pool is balanced enough to allow deposits.
*/
*/
modifier nearBalancedPool() {
modifier nearBalancedPool() {
// OS/wS price = wS / OS
// OToken/asset price = asset / OToken
// Get the OS/wS price for selling 1 OS for wS
// Get the OToken/asset price for selling 1 OToken for asset
// As OS is 1, the wS amount is the OS/wS price
// As OToken is 1, the asset amount is the OToken/asset price
uint256 sellPrice = IPair(pool).getAmountOut(1e18, os);
uint256 sellPrice = IPair(pool).getAmountOut(1e18, oToken);


// Get the amount of OS received from selling 1 wS. This is buying OS.
// Get the amount of OToken received from selling 1 asset. This is buying OToken.
uint256 osAmount = IPair(pool).getAmountOut(1e18, ws);
uint256 oTokenAmount = IPair(pool).getAmountOut(1e18, asset);
// Convert to a OS/wS price = wS / OS
// Convert to a OToken/asset price = asset / OToken
uint256 buyPrice = 1e36 / osAmount;
uint256 buyPrice = 1e36 / oTokenAmount;


uint256 pegPrice = 1e18;
uint256 pegPrice = 1e18;


require(
require(
sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg,
sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg,
"price out of range"
"price out of range"
);
);
_;
_;
}
}


/**
/**
* @dev Checks the pool's balances have improved and the balances
* @dev Checks the pool's balances have improved and the balances
* have not tipped to the other side.
* have not tipped to the other side.
* This modifier is only applied to functions that do swaps against the pool.
* This modifier is only applied to functions that do swaps against the pool.
* Deposits and withdrawals are proportional to the pool's balances hence don't need this check.
* Deposits and withdrawals are proportional to the pool's balances hence don't need this check.
*/
*/
modifier improvePoolBalance() {
modifier improvePoolBalance() {
// Get the asset and OToken balances in the pool
// Get the asset and OToken balances in the pool
(uint256 wsReservesBefore, uint256 osReservesBefore, ) = IPair(pool)
(
.getReserves();
uint256 assetReserveBefore,
// diff = wS balance - OS balance
uint256 oTokenReserveBefore
int256 diffBefore = wsReservesBefore.toInt256() -
) = _getPoolReserves();
osReservesBefore.toInt256();
// diff = asset balance - OToken balance
int256 diffBefore = assetReserveBefore.toInt256() -
oTokenReserveBefore.toInt256();


_;
_;


// Get the asset and OToken balances in the pool
// Get the asset and OToken balances in the pool
(uint256 wsReservesAfter, uint256 osReservesAfter, ) = IPair(pool)
(
.getReserves();
uint256 assetReserveAfter,
// diff = wS balance - OS balance
uint256 oTokenReserveAfter
int256 diffAfter = wsReservesAfter.toInt256() -
) = _getPoolReserves();
osReservesAfter.toInt256();
// diff = asset balance - OToken balance
int256 diffAfter = assetReserveAfter.toInt256() -
oTokenReserveAfter.toInt256();


if (diffBefore == 0) {
if (diffBefore == 0) {
require(diffAfter == 0, "Position balance is worsened");
require(diffAfter == 0, "Position balance is worsened");
} else if (diffBefore < 0) {
} else if (diffBefore < 0) {
// If the pool was originally imbalanced in favor of OS, then
// If the pool was originally imbalanced in favor of OToken, then
// we want to check that the pool is now more balanced
// we want to check that the pool is now more balanced
require(diffAfter <= 0, "Assets overshot peg");
require(diffAfter <= 0, "Assets overshot peg");
require(diffBefore < diffAfter, "OTokens balance worse");
require(diffBefore < diffAfter, "OTokens balance worse");
} else if (diffBefore > 0) {
} else if (diffBefore > 0) {
// If the pool was originally imbalanced in favor of wS, then
// If the pool was originally imbalanced in favor of asset, then
// we want to check that the pool is now more balanced
// we want to check that the pool is now more balanced
require(diffAfter >= 0, "OTokens overshot peg");
require(diffAfter >= 0, "OTokens overshot peg");
require(diffAfter < diffBefore, "Assets balance worse");
require(diffAfter < diffBefore, "Assets balance worse");
}
}
}
}


/**
/**
* @param _baseConfig The `platformAddress` is the address of the SwapX pool.
* @param _baseConfig The `platformAddress` is the address of the Algebra pool.
* The `vaultAddress` is the address of the Origin Sonic Vault.
* The `vaultAddress` is the address of the Origin Sonic Vault.
* @param _os Address of the OS token.
* @param _oToken Address of the OToken.
* @param _ws Address of the Wrapped S (wS) token.
* @param _asset Address of the asset token.
* @param _gauge Address of the SwapX gauge for the pool.
* @param _gauge Address of the Algebra gauge for the pool.
*/
*/
constructor(
constructor(
BaseStrategyConfig memory _baseConfig,
BaseStrategyConfig memory _baseConfig,
address _os,
address _oToken,
address _ws,
address _asset,
address _gauge
address _gauge
) InitializableAbstractStrategy(_baseConfig) {
) InitializableAbstractStrategy(_baseConfig) {
// Check the pool tokens are correct
require(
IPair(_baseConfig.platformAddress).token0() == _ws &&
IPair(_baseConfig.platformAddress).token1() == _os,
"Incorrect pool tokens"
);
// Checked both tokens are to 18 decimals
// Checked both tokens are to 18 decimals
require(
require(
IBasicToken(_ws).decimals() == 18 &&
IBasicToken(_asset).decimals() == 18 &&
IBasicToken(_os).decimals() == 18,
IBasicToken(_oToken).decimals() == 18,
"Incorrect token decimals"
"Incorrect token decimals"
);
);
// Check the SwapX pool is a Stable AMM (sAMM)
// Check the Algebra pool is a Stable AMM (sAMM)
require(
require(
IPair(_baseConfig.platformAddress).isStable() == true,
IPair(_baseConfig.platformAddress).isStable() == true,
"Pool not stable"
"Pool not stable"
);
);
// Check the gauge is for the pool
// Check the gauge is for the pool
require(
require(
IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,
IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,
"Incorrect gauge"
"Incorrect gauge"
);
);
oTokenPoolIndex = IPair(_baseConfig.platformAddress).token0() == _oToken
? 0
: 1;
// Check the pool tokens are correct
require(
IPair(_baseConfig.platformAddress).token0() ==
(oTokenPoolIndex == 0 ? _oToken : _asset) &&
IPair(_baseConfig.platformAddress).token1() ==
(oTokenPoolIndex == 0 ? _asset : _oToken),
"Incorrect pool tokens"
);


// Set the immutable variables
// Set the immutable variables
os = _os;
oToken = _oToken;
ws = _ws;
asset = _asset;
pool = _baseConfig.platformAddress;
pool = _baseConfig.platformAddress;
gauge = _gauge;
gauge = _gauge;


// This is an implementation contract. The governor is set in the proxy contract.
// This is an implementation contract. The governor is set in the proxy contract.
_setGovernor(address(0));
_setGovernor(address(0));
}
}


/**
/**
* Initializer for setting up strategy internal state. This overrides the
* Initializer for setting up strategy internal state. This overrides the
* InitializableAbstractStrategy initializer as SwapX strategies don't fit
* InitializableAbstractStrategy initializer as Algebra strategies don't fit
* well within that abstraction.
* well within that abstraction.
* @param _rewardTokenAddresses Array containing SWPx token address
* @param _rewardTokenAddresses Array containing SWPx token address
* @param _maxDepeg The max amount the OS/wS price can deviate from peg (1e18) before deposits are reverted.
* @param _maxDepeg The max amount the OToken/asset price can deviate from peg (1e18) before deposits are reverted.
*/
*/
function initialize(
function initialize(
address[] calldata _rewardTokenAddresses,
address[] calldata _rewardTokenAddresses,
uint256 _maxDepeg
uint256 _maxDepeg
) external onlyGovernor initializer {
) external onlyGovernor initializer {
address[] memory pTokens = new address[](1);
address[] memory pTokens = new address[](1);
pTokens[0] = pool;
pTokens[0] = pool;


address[] memory _assets = new address[](1);
address[] memory _assets = new address[](1);
_assets[0] = ws;
_assets[0] = asset;


InitializableAbstractStrategy._initialize(
InitializableAbstractStrategy._initialize(
_rewardTokenAddresses,
_rewardTokenAddresses,
_assets,
_assets,
pTokens
pTokens
);
);


maxDepeg = _maxDepeg;
maxDepeg = _maxDepeg;


_approveBase();
_approveBase();
}
}


/***************************************
/***************************************
Deposit
Deposit
****************************************/
****************************************/


/**
/**
* @notice Deposit an amount of Wrapped S (wS) into the SwapX pool.
* @notice Deposit an amount of asset into the Algebra pool.
* Mint OS in proportion to the pool's wS and OS reserves,
* Mint OToken in proportion to the pool's asset and OToken reserves,
* transfer Wrapped S (wS) and OS to the pool,
* transfer asset and OToken to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
* @dev This tx must be wrapped by the VaultValueChecker.
* @dev This tx must be wrapped by the VaultValueChecker.
* To minimize loses, the pool should be rebalanced before depositing.
* To minimize loses, the pool should be rebalanced before depositing.
* The pool's OS/wS price must be within the maxDepeg range.
* The pool's oToken/asset price must be within the maxDepeg range.
* @param _asset Address of Wrapped S (wS) token.
* @param _asset Address of asset token.
* @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.
* @param _assetAmount Amount of asset tokens to deposit.
*/
*/
function deposit(address _asset, uint256 _wsAmount)
function deposit(address _asset, uint256 _assetAmount)
external
external
override
override
onlyVault
onlyVault
nonReentrant
nonReentrant
skimPool
skimPool
nearBalancedPool
nearBalancedPool
{
{
require(_asset == ws, "Unsupported asset");
require(_asset == asset, "Unsupported asset");
require(_wsAmount > 0, "Must deposit something");
require(_assetAmount > 0, "Must deposit something");


(uint256 osDepositAmount, ) = _deposit(_wsAmount);
(uint256 oTokenDepositAmount, ) = _deposit(_assetAmount);


// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();


// Emit event for the deposited wS tokens
// Emit event for the deposited asset tokens
emit Deposit(ws, pool, _wsAmount);
emit Deposit(asset, pool, _assetAmount);
// Emit event for the minted OS tokens
// Emit event for the minted OToken tokens
emit Deposit(os, pool, osDepositAmount);
emit Deposit(oToken, pool, oTokenDepositAmount);
}
}


/**
/**
* @notice Deposit all the strategy's Wrapped S (wS) tokens into the SwapX pool.
* @notice Deposit all the strategy's asset tokens into the Algebra pool.
* Mint OS in proportion to the pool's wS and OS reserves,
* Mint OToken in proportion to the pool's asset and OToken reserves,
* transfer Wrapped S (wS) and OS to the pool,
* transfer asset and OToken to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
* @dev This tx must be wrapped by the VaultValueChecker.
* @dev This tx must be wrapped by the VaultValueChecker.
* To minimize loses, the pool should be rebalanced before depositing.
* To minimize loses, the pool should be rebalanced before depositing.
* The pool's OS/wS price must be within the maxDepeg range.
* The pool's oToken/asset price must be within the maxDepeg range.
*/
*/
function depositAll()
function depositAll()
external
external
override
override
onlyVault
onlyVault
nonReentrant
nonReentrant
skimPool
skimPool
nearBalancedPool
nearBalancedPool
{
{
uint256 wsBalance = IERC20(ws).balanceOf(address(this));
uint256 assetBalance = IERC20(asset).balanceOf(address(this));
if (wsBalance > 0) {
if (assetBalance > 0) {
(uint256 osDepositAmount, ) = _deposit(wsBalance);
(uint256 oTokenDepositAmount, ) = _deposit(assetBalance);


// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();


// Emit event for the deposited wS tokens
// Emit event for the deposited asset tokens
emit Deposit(ws, pool, wsBalance);
emit Deposit(asset, pool, assetBalance);
// Emit event for the minted OS tokens
// Emit event for the minted OToken tokens
emit Deposit(os, pool, osDepositAmount);
emit Deposit(oToken, pool, oTokenDepositAmount);
}
}
}
}


/**
/**
* @dev Mint OS in proportion to the pool's wS and OS reserves,
* @dev Mint OToken in proportion to the pool's asset and OToken reserves,
* transfer Wrapped S (wS) and OS to the pool,
* transfer asset and OToken to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
* @param _wsAmount Amount of Wrapped S (wS) tokens to deposit.
* @param _assetAmount Amount of asset tokens to deposit.
* @return osDepositAmount Amount of OS tokens minted and deposited into the pool.
* @return oTokenDepositAmount Amount of OToken tokens minted and deposited into the pool.
* @return lpTokens Amount of SwapX pool LP tokens minted and deposited into the gauge.
* @return lpTokens Amount of Algebra pool LP tokens minted and deposited into the gauge.
*/
*/
function _deposit(uint256 _wsAmount)
function _deposit(uint256 _assetAmount)
internal
internal
returns (uint256 osDepositAmount, uint256 lpTokens)
returns (uint256 oTokenDepositAmount, uint256 lpTokens)
{
{
// Calculate the required amount of OS to mint based on the wS amount.
// Calculate the required amount of OToken to mint based on the asset amount.
osDepositAmount = _calcTokensToMint(_wsAmount);
oTokenDepositAmount = _calcTokensToMint(_assetAmount);


// Mint the required OS tokens to this strategy
// Mint the required OToken tokens to this strategy
IVault(vaultAddress).mintForStrategy(osDepositAmount);
IVault(vaultAddress).mintForStrategy(oTokenDepositAmount);


// Add wS and OS liquidity to the pool and stake in gauge
// Add asset and OToken liquidity to the pool and stake in gauge
lpTokens = _depositToPoolAndGauge(_wsAmount, osDepositAmount);
lpTokens = _depositToPoolAndGauge(_assetAmount, oTokenDepositAmount);
}
}


/***************************************
/***************************************
Withdraw
Withdraw
****************************************/
****************************************/


/**
/**
* @notice Withdraw wS and OS from the SwapX pool, burn the OS,
* @notice Withdraw asset and OToken from the Algebra pool, burn the OToken,
* and transfer the wS to the recipient.
* and transfer the asset to the recipient.
* @param _recipient Address of the Vault.
* @param _recipient Address of the Vault.
* @param _asset Address of the Wrapped S (wS) contract.
* @param _asset Address of the asset token.
* @param _wsAmount Amount of Wrapped S (wS) to withdraw.
* @param _assetAmount Amount of asset tokens to withdraw.
*/
*/
function withdraw(
function withdraw(
address _recipient,
address _recipient,
address _asset,
address _asset,
uint256 _wsAmount
uint256 _assetAmount
) external override onlyVault nonReentrant skimPool {
) external override onlyVault nonReentrant skimPool {
require(_wsAmount > 0, "Must withdraw something");
require(_assetAmount > 0, "Must withdraw something");
require(_asset == ws, "Unsupported asset");
require(_asset == asset, "Unsupported asset");
// This strategy can't be set as a default strategy for wS in the Vault.
// This strategy can't be set as a default strategy for asset in the Vault.
// This means the recipient must always be the Vault.
// This means the recipient must always be the Vault.
require(_recipient == vaultAddress, "Only withdraw to vault allowed");
require(_recipient == vaultAddress, "Only withdraw to vault allowed");


// Calculate how much pool LP tokens to burn to get the required amount of wS tokens back
// Calculate how much pool LP tokens to burn to get the required amount of asset tokens back
uint256 lpTokens = _calcTokensToBurn(_wsAmount);
uint256 lpTokens = _calcTokensToBurn(_assetAmount);


// Withdraw pool LP tokens from the gauge and remove assets from from the pool
// Withdraw pool LP tokens from the gauge and remove assets from from the pool
_withdrawFromGaugeAndPool(lpTokens);
_withdrawFromGaugeAndPool(lpTokens);


// Burn all the removed OS and any that was left in the strategy
// Burn all the removed OToken and any that was left in the strategy
uint256 osToBurn = IERC20(os).balanceOf(address(this));
uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(osToBurn);
IVault(vaultAddress).burnForStrategy(oTokenToBurn);


// Transfer wS to the recipient
// Transfer asset to the recipient
// Note there can be a dust amount of wS left in the strategy as
// Note there can be a dust amount of asset left in the strategy as
// the burn of the pool's LP tokens is rounded up
// the burn of the pool's LP tokens is rounded up
require(
require(
IERC20(ws).balanceOf(address(this)) >= _wsAmount,
IERC20(asset).balanceOf(address(this)) >= _assetAmount,
"Not enough wS removed from pool"
"Not enough asset removed"
);
);
IERC20(ws).safeTransfer(_recipient, _wsAmount);
IERC20(asset).safeTransfer(_recipient, _assetAmount);


// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();


// Emit event for the withdrawn wS tokens
// Emit event for the withdrawn asset tokens
emit Withdrawal(ws, pool, _wsAmount);
emit Withdrawal(asset, pool, _assetAmount);
// Emit event for the burnt OS tokens
// Emit event for the burnt OToken tokens
emit Withdrawal(os, pool, osToBurn);
emit Withdrawal(oToken, pool, oTokenToBurn);
}
}


/**
/**
* @notice Withdraw all pool LP tokens from the gauge,
* @notice Withdraw all pool LP tokens from the gauge,
* remove all wS and OS from the SwapX pool,
* remove all asset and OToken from the Algebra pool,
* burn all the OS tokens,
* burn all the OToken,
* and transfer all the wS to the Vault contract.
* and transfer all the asset to the Vault contract.
* @dev There is no solvency check here as withdrawAll can be called to
* @dev There is no solvency check here as withdrawAll can be called to
* quickly secure assets to the Vault in emergencies.
* quickly secure assets to the Vault in emergencies.
*/
*/
function withdrawAll()
function withdrawAll()
external
external
override
override
onlyVaultOrGovernor
onlyVaultOrGovernor
nonReentrant
nonReentrant
skimPool
skimPool
{
{
// Get all the pool LP tokens the strategy has staked in the gauge
// Get all the pool LP tokens the strategy has staked in the gauge
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
// Can not withdraw zero LP tokens from the gauge
// Can not withdraw zero LP tokens from the gauge
if (lpTokens == 0) return;
if (lpTokens == 0) return;


if (IGauge(gauge).emergency()) {
if (IGauge(gauge).emergency()) {
// The gauge is in emergency mode
// The gauge is in emergency mode
_emergencyWithdrawFromGaugeAndPool();
_emergencyWithdrawFromGaugeAndPool();
} else {
} else {
// Withdraw pool LP tokens from the gauge and remove assets from from the pool
// Withdraw pool LP tokens from the gauge and remove assets from from the pool
_withdrawFromGaugeAndPool(lpTokens);
_withdrawFromGaugeAndPool(lpTokens);
}
}


// Burn all OS in this strategy contract
// Burn all OToken in this strategy contract
uint256 osToBurn = IERC20(os).balanceOf(address(this));
uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(osToBurn);
IVault(vaultAddress).burnForStrategy(oTokenToBurn);


// Get the strategy contract's wS balance.
// Get the strategy contract's asset balance.
// This includes all that was removed from the SwapX pool and
// This includes all that was removed from the Algebra pool and
// any that was sitting in the strategy contract before the removal.
// any that was sitting in the strategy contract before the removal.
uint256 wsBalance = IERC20(ws).balanceOf(address(this));
uint256 assetBalance = IERC20(asset).balanceOf(address(this));
IERC20(ws).safeTransfer(vaultAddress, wsBalance);
IERC20(asset).safeTransfer(vaultAddress, assetBalance);


// Emit event for the withdrawn wS tokens
// Emit event for the withdrawn asset tokens
emit Withdrawal(ws, pool, wsBalance);
emit Withdrawal(asset, pool, assetBalance);
// Emit event for the burnt OS tokens
// Emit event for the burnt OToken tokens
emit Withdrawal(os, pool, osToBurn);
emit Withdrawal(oToken, pool, oTokenToBurn);
}
}


/***************************************
/***************************************
Pool Rebalancing
Pool Rebalancing
****************************************/
****************************************/


/** @notice Used when there is more OS than wS in the pool.
/** @notice Used when there is more OToken than asset in the pool.
* wS and OS is removed from the pool, the received wS is swapped for OS
* asset and OToken is removed from the pool, the received asset is swapped for OToken
* and the left over OS in the strategy is burnt.
* and the left over OToken in the strategy is burnt.
* The OS/wS price is < 1.0 so OS is being bought at a discount.
* The OToken/asset price is < 1.0 so OToken is being bought at a discount.
* @param _wsAmount Amount of Wrapped S (wS) to swap into the pool.
* @param _assetAmount Amount of asset tokens to swap into the pool.
*/
*/
function swapAssetsToPool(uint256 _wsAmount)
function swapAssetsToPool(uint256 _assetAmount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
require(_wsAmount > 0, "Must swap something");
require(_assetAmount > 0, "Must swap something");


// 1. Partially remove liquidity so there’s enough wS for the swap
// 1. Partially remove liquidity so there’s enough asset for the swap


// Calculate how much pool LP tokens to burn to get the required amount of wS tokens back
// Calculate how much pool LP tokens to burn to get the required amount of asset tokens back
uint256 lpTokens = _calcTokensToBurn(_wsAmount);
uint256 lpTokens = _calcTokensToBurn(_assetAmount);
require(lpTokens > 0, "No LP tokens to burn");
require(lpTokens > 0, "No LP tokens to burn");


_withdrawFromGaugeAndPool(lpTokens);
_withdrawFromGaugeAndPool(lpTokens);


// 2. Swap wS for OS against the pool
// 2. Swap asset for OToken against the pool
// Swap exact amount of wS for OS against the pool
// Swap exact amount of asset for OToken against the pool
// There can be a dust amount of wS left in the strategy as the burn of the pool's LP tokens is rounded up
// There can be a dust amount of asset left in the strategy as the burn of the pool's LP tokens is rounded up
_swapExactTokensForTokens(_wsAmount, ws, os);
_swapExactTokensForTokens(_assetAmount, asset, oToken);


// 3. Burn all the OS left in the strategy from the remove liquidity and swap
// 3. Burn all the OToken left in the strategy from the remove liquidity and swap
uint256 osToBurn = IERC20(os).balanceOf(address(this));
uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(osToBurn);
IVault(vaultAddress).burnForStrategy(oTokenToBurn);


// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();


// Emit event for the burnt OS tokens
// Emit event for the burnt OToken tokens
emit Withdrawal(os, pool, osToBurn);
emit Withdrawal(oToken, pool, oTokenToBurn);
// Emit event for the swap
// Emit event for the swap
emit SwapAssetsToPool(_wsAmount, lpTokens, osToBurn);
emit SwapAssetsToPool(_assetAmount, lpTokens, oTokenToBurn);
}
}


/**
/**
* @notice Used when there is more wS than OS in the pool.
* @notice Used when there is more asset than OToken in the pool.
* OS is minted and swapped for wS against the pool,
* OToken is minted and swapped for asset against the pool,
* more OS is minted and added back into the pool with the swapped out wS.
* more OToken is minted and added back into the pool with the swapped out asset.
* The OS/wS price is > 1.0 so OS is being sold at a premium.
* The OToken/asset price is > 1.0 so OToken is being sold at a premium.
* @param _osAmount Amount of OS to swap into the pool.
* @param _oTokenAmount Amount of OToken to swap into the pool.
*/
*/
function swapOTokensToPool(uint256 _osAmount)
function swapOTokensToPool(uint256 _oTokenAmount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
require(_osAmount > 0, "Must swap something");
require(_oTokenAmount > 0, "Must swap something");


// 1. Mint OS so it can be swapped into the pool
// 1. Mint OToken so it can be swapped into the pool


// There can be OS in the strategy from skimming the pool
// There can be OToken in the strategy from skimming the pool
uint256 osInStrategy = IERC20(os).balanceOf(address(this));
uint256 oTokenInStrategy = IERC20(oToken).balanceOf(address(this));
require(_osAmount >= osInStrategy, "Too much OS in strategy");
require(
uint256 osToMint = _osAmount - osInStrategy;
_oTokenAmount >= oTokenInStrategy,
"Too much OToken in strategy"
);
uint256 oTokenToMint = _oTokenAmount - oTokenInStrategy;


// Mint the required OS tokens to this strategy
// Mint the required OToken tokens to this strategy
IVault(vaultAddress).mintForStrategy(osToMint);
IVault(vaultAddress).mintForStrategy(oTokenToMint);


// 2. Swap OS for wS against the pool
// 2. Swap OToken for asset against the pool
_swapExactTokensForTokens(_osAmount, os, ws);
_swapExactTokensForTokens(_oTokenAmount, oToken, asset);


// The wS is from the swap and any wS that was sitting in the strategy
// The asset is from the swap and any asset that was sitting in the strategy
uint256 wsDepositAmount = IERC20(ws).balanceOf(address(this));
uint256 assetDepositAmount = IERC20(asset).balanceOf(address(this));


// 3. Add wS and OS back to the pool in proportion to the pool's reserves
// 3. Add asset and OToken back to the pool in proportion to the pool's reserves
(uint256 osDepositAmount, uint256 lpTokens) = _deposit(wsDepositAmount);
(uint256 oTokenDepositAmount, uint256 lpTokens) = _deposit(
assetDepositAmount
);


// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();


// Emit event for the minted OS tokens
// Emit event for the minted OToken tokens
emit Deposit(os, pool, osToMint + osDepositAmount);
emit Deposit(oToken, pool, oTokenToMint + oTokenDepositAmount);
// Emit event for the swap
// Emit event for the swap
emit SwapOTokensToPool(
emit SwapOTokensToPool(
osToMint,
oTokenToMint,
wsDepositAmount,
assetDepositAmount,
osDepositAmount,
oTokenDepositAmount,
lpTokens
lpTokens
);
);
}
}


/***************************************
/***************************************
Assets and Rewards
Assets and Rewards
****************************************/
****************************************/


/**
/**
* @notice Get the wS value of assets in the strategy and SwapX pool.
* @notice Get the asset value of assets in the strategy and Algebra pool.
* The value of the assets in the pool is calculated assuming the pool is balanced.
* The value of the assets in the pool is calculated assuming the pool is balanced.
* This way the value can not be manipulated by changing the pool's token balances.
* This way the value can not be manipulated by changing the pool's token balances.
* @param _asset Address of the Wrapped S (wS) token
* @param _asset Address of the asset token
* @return balance Total value in wS.
* @return balance Total value in asset.
*/
*/
function checkBalance(address _asset)
function checkBalance(address _asset)
external
external
view
view
override
override
returns (uint256 balance)
returns (uint256 balance)
{
{
require(_asset == ws, "Unsupported asset");
require(_asset == asset, "Unsupported asset");


// wS balance needed here for the balance check that happens from vault during depositing.
// asset balance needed here for the balance check that happens from vault during depositing.
balance = IERC20(ws).balanceOf(address(this));
balance = IERC20(asset).balanceOf(address(this));


// This assumes 1 gauge LP token = 1 pool LP token
// This assumes 1 gauge LP token = 1 pool LP token
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
if (lpTokens == 0) return balance;
if (lpTokens == 0) return balance;


// Add the strategy’s share of the wS and OS tokens in the SwapX pool if the pool was balanced.
// Add the strategy’s share of the asset and OToken tokens in the Algebra pool if the pool was balanced.
balance += _lpValue(lpTokens);
balance += _lpValue(lpTokens);
}
}


/**
/**
* @notice Returns bool indicating whether asset is supported by strategy
* @notice Returns bool indicating whether asset is supported by strategy
* @param _asset Address of the asset
* @param _asset Address of the asset
*/
*/
function supportsAsset(address _asset) public view override returns (bool) {
function supportsAsset(address _asset) public view override returns (bool) {
return _asset == ws;
return _asset == asset;
}
}


/**
/**
* @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.
* @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.
*/
*/
function collectRewardTokens()
function collectRewardTokens()
external
external
override
override
onlyHarvester
onlyHarvester
nonReentrant
nonReentrant
{
{
// Collect SWPx rewards from the gauge
// Collect SWPx rewards from the gauge
IGauge(gauge).getReward();
IGauge(gauge).getReward();


_collectRewardTokens();
_collectRewardTokens();
}
}


/***************************************
/***************************************
Internal SwapX Pool and Gauge Functions
Internal Algebra Pool and Gauge Functions
****************************************/
****************************************/


/**
/**
* @dev Calculate the required amount of OS to mint based on the wS amount.
* @dev Calculate the required amount of OToken to mint based on the asset amount.
* This ensures the proportion of OS tokens being added to the pool matches the proportion of wS tokens.
* This ensures the proportion of OToken tokens being added to the pool matches the proportion of asset tokens.
* For example, if the added wS tokens is 10% of existing wS tokens in the pool,
* For example, if the added asset tokens is 10% of existing asset tokens in the pool,
* then the OS tokens being added should also be 10% of the OS tokens in the pool.
* then the OToken tokens being added should also be 10% of the OToken tokens in the pool.
* @param _wsAmount Amount of Wrapped S (wS) to be added to the pool.
* @param _assetAmount Amount of asset tokens to be added to the pool.
* @return osAmount Amount of OS to be minted and added to the pool.
* @return oTokenAmount Amount of OToken tokens to be minted and added to the pool.
*/
*/
function _calcTokensToMint(uint256 _wsAmount)
function _calcTokensToMint(uint256 _assetAmount)
internal
internal
view
view
returns (uint256 osAmount)
returns (uint256 oTokenAmount)
{
{
(uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves();
(uint256 assetReserves, uint256 oTokenReserves) = _getPoolReserves();
require(wsReserves > 0, "Empty pool");
require(assetReserves > 0, "Empty pool");


// OS to add = (wS being added * OS in pool) / wS in pool
// OToken to add = (asset being added * OToken in pool) / asset in pool
osAmount = (_wsAmount * osReserves) / wsReserves;
oTokenAmount = (_assetAmount * oTokenReserves) / assetReserves;
}
}


/**
/**
* @dev Calculate how much pool LP tokens to burn to get the required amount of wS tokens back
* @dev Calculate how much pool LP tokens to burn to get the required amount of asset tokens back
* from the pool.
* from the pool.
* @param _wsAmount Amount of Wrapped S (wS) to be removed from the pool.
* @param _assetAmount Amount of asset tokens to be removed from the pool.
* @return lpTokens Amount of SwapX pool LP tokens to burn.
* @return lpTokens Amount of Algebra pool LP tokens to burn.
*/
*/
function _calcTokensToBurn(uint256 _wsAmount)
function _calcTokensToBurn(uint256 _assetAmount)
internal
internal
view
view
returns (uint256 lpTokens)
returns (uint256 lpTokens)
{
{
/* The SwapX pool proportionally returns the reserve tokens when removing liquidity.
/* The Algebra pool proportionally returns the reserve tokens when removing liquidity.
* First, calculate the proportion of required wS tokens against the pools wS reserves.
* First, calculate the proportion of required asset tokens against the pools asset reserves.
* That same proportion is used to calculate the required amount of pool LP tokens.
* That same proportion is used to calculate the required amount of pool LP tokens.
* For example, if the required wS tokens is 10% of the pool's wS reserves,
* For example, if the required asset tokens is 10% of the pool's asset reserves,
* then 10% of the pool's LP supply needs to be burned.
* then 10% of the pool's LP supply needs to be burned.
*
*
* Because we are doing balanced removal we should be making profit when removing liquidity in a
* Because we are doing balanced removal we should be making profit when removing liquidity in a
* pool tilted to either side.
* pool tilted to either side.
*
*
* Important: A downside is that the Strategist / Governor needs to be
* Important: A downside is that the Strategist / Governor needs to be
* cognizant of not removing too much liquidity. And while the proposal to remove liquidity
* cognizant of not removing too much liquidity. And while the proposal to remove liquidity
* is being voted on, the pool tilt might change so much that the proposal that has been valid while
* is being voted on, the pool tilt might change so much that the proposal that has been valid while
* created is no longer valid.
* created is no longer valid.
*/
*/


(uint256 wsReserves, , ) = IPair(pool).getReserves();
(uint256 assetReserves, ) = _getPoolReserves();
require(wsReserves > 0, "Empty pool");
require(assetReserves > 0, "Empty pool");


lpTokens = (_wsAmount * IPair(pool).totalSupply()) / wsReserves;
lpTokens = (_assetAmount * IPair(pool).totalSupply()) / assetReserves;
lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding
lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding
}
}


/**
/**
* @dev Deposit Wrapped S (wS) and OS liquidity to the SwapX pool
* @dev Deposit asset and OToken liquidity to the Algebra pool
* and stake the pool's LP token in the gauge.
* and stake the pool's LP token in the gauge.
* @param _wsAmount Amount of Wrapped S (wS) to deposit.
* @param _assetAmount Amount of asset to deposit.
* @param _osAmount Amount of OS to deposit.
* @param _oTokenAmount Amount of OToken to deposit.
* @return lpTokens Amount of SwapX pool LP tokens minted.
* @return lpTokens Amount of Algebra pool LP tokens minted.
*/
*/
function _depositToPoolAndGauge(uint256 _wsAmount, uint256 _osAmount)
function _depositToPoolAndGauge(uint256 _assetAmount, uint256 _oTokenAmount)
internal
internal
returns (uint256 lpTokens)
returns (uint256 lpTokens)
{
{
// Transfer wS to the pool
// Transfer asset to the pool
IERC20(ws).safeTransfer(pool, _wsAmount);
IERC20(asset).safeTransfer(pool, _assetAmount);
// Transfer OS to the pool
// Transfer OToken to the pool
IERC20(os).safeTransfer(pool, _osAmount);
IERC20(oToken).safeTransfer(pool, _oTokenAmount);


// Mint LP tokens from the pool
// Mint LP tokens from the pool
lpTokens = IPair(pool).mint(address(this));
lpTokens = IPair(pool).mint(address(this));


// Deposit the pool's LP tokens into the gauge
// Deposit the pool's LP tokens into the gauge
IGauge(gauge).deposit(lpTokens);
IGauge(gauge).depos
}

/**
* @dev Withdraw pool LP tokens from the gauge and remove wS and OS from the pool.
* @param _lpTokens Amount of SwapX pool LP tokens to withdraw from the gauge
*/
function _withdrawFromGaugeAndPool(uint256 _lpTokens) internal {
require(
IGauge(gauge).balanceOf(address(this)) >= _lpTokens,
"Not enough LP tokens in gauge"
);

// Withdraw pool LP tokens from the gauge
IGauge(gauge).withdraw(_lpTokens);

// Transfer the pool LP tokens to the pool
IERC20(pool).safeTransfer(pool, _lpTokens);

// Burn the LP tokens and transfer the wS and OS back to the strategy
IPair(pool).burn(address(this));
}

/**
* @dev Withdraw all pool LP tokens from the gauge when it's in emergency mode
* and remove wS and OS from the pool.
*/
function _emergencyWithdrawFromGaugeAndPool() internal {
// Withdraw all pool LP tokens from the gauge
IGauge(gauge).emergencyWithdraw();

// Get the pool LP tokens in strategy
uint256 _lpTokens = IERC20(pool).balanceOf(address(this));

// Transfer the pool LP tokens to the pool
IERC20(pool).safeTransfer(pool, _lpTokens);

// Burn the LP tokens and transfer the wS and OS back to the strategy