Diff
checker
Text
Text
Bilder
Dokumente
Excel
Ordner
Legal
Enterprise
Desktop-App
Preise
Einloggen
Diffchecker Desktop herunterladen
Texte vergleichen
Finde den Unterschied zwischen zwei Textdateien
Werkzeuge
Verlauf
Live-Editor
Leerzeichen ausblenden
Gleiches ausblenden
Zeilenumbruch aus
Ansicht
Zweispaltig
Einspaltig
Vergleichsgenauigkeit
Intelligent
Wort
Zeichen
Textstile
Darstellung ändern
Syntaxhervorhebung
Syntax auswählen
Ignorieren
Text umwandeln
Zur ersten Änderung
Eingabe bearbeiten
Diffchecker Desktop
Der sicherste Weg, Diffchecker zu nutzen. Hol dir die Desktop-App: Deine Diffs verlassen nie deinen Computer!
Desktop holen
SonicSwapXAMOStrategy vs StableSwapAMMStrategy
Erstellt
vor 5 Monaten
Diff läuft nie ab
Löschen
Exportieren
Teilen
Erklären
394 Entfernungen
Zeilen
Gesamt
Entfernt
Zeichen
Gesamt
Entfernt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
667 Zeilen
Kopieren
369 Hinzufügungen
Zeilen
Gesamt
Hinzugefügt
Zeichen
Gesamt
Hinzugefügt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
646 Zeilen
Kopieren
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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";
Kopieren
Kopiert
Kopieren
Kopiert
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";
Kopieren
Kopiert
Kopieren
Kopiert
contract S
onic
Swap
XAMO
Strategy is InitializableAbstractStrategy {
contract S
table
Swap
AMM
Strategy 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;
Kopieren
Kopiert
Kopieren
Kopiert
/// @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;
Kopieren
Kopiert
Kopieren
Kopiert
/// @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
;
Kopieren
Kopiert
Kopieren
Kopiert
/// @notice Address of the
OS
token contract.
/// @notice Address of the
OToken
token contract.
address public immutable
os
;
address public immutable
oToken
;
Kopieren
Kopiert
Kopieren
Kopiert
/// @notice Address of the
SwapX
Stable pool contract.
/// @notice Address of the
Algebra
Stable pool contract.
address public immutable pool;
address public immutable pool;
Kopieren
Kopiert
Kopieren
Kopiert
/// @notice Address of the
SwapX
Gauge contract.
/// @notice Address of the
Algebra
Gauge contract.
address public immutable gauge;
address public immutable gauge;
Kopieren
Kopiert
Kopieren
Kopiert
/// @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(
Kopieren
Kopiert
Kopieren
Kopiert
uint256 o
s
Minted,
uint256 o
Token
Minted,
uint256
ws
DepositAmount,
uint256
asset
DepositAmount,
uint256 o
s
DepositAmount,
uint256 o
Token
DepositAmount,
uint256 lpTokens
uint256 lpTokens
);
);
event SwapAssetsToPool(
event SwapAssetsToPool(
Kopieren
Kopiert
Kopieren
Kopiert
uint256
ws
Swapped,
uint256
asset
Swapped,
uint256 lpTokens,
uint256 lpTokens,
Kopieren
Kopiert
Kopieren
Kopiert
uint256 o
s
Burnt
uint256 o
Token
Burnt
);
);
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"
);
);
_;
_;
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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() {
Kopieren
Kopiert
Kopieren
Kopiert
//
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
);
Kopieren
Kopiert
Kopieren
Kopiert
// 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 o
s
Amount = IPair(pool).getAmountOut(1e18,
ws
);
uint256 o
Token
Amount = IPair(pool).getAmountOut(1e18,
asset
);
// Convert to a
OS/wS
price =
wS
/
OS
// Convert to a
OToken/asset
price =
asset
/
OToken
uint256 buyPrice = 1e36 / o
s
Amount;
uint256 buyPrice = 1e36 / o
Token
Amount;
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
Kopieren
Kopiert
Kopieren
Kopiert
(
uint256
ws
Reserve
s
Before,
uint256 o
s
Reserve
s
Before
, ) = IPair(pool)
(
.
get
Reserves();
uint256
asset
Reserve
Before,
// diff =
wS
balance -
OS
balance
uint256 o
Token
Reserve
Before
int256 diffBefore =
ws
Reserve
s
Before.toInt256() -
) = _
get
Pool
Reserves();
o
s
Reserve
s
Before.toInt256();
// diff =
asset
balance -
OToken
balance
int256 diffBefore =
asset
Reserve
Before.toInt256() -
o
Token
Reserve
Before.toInt256();
_;
_;
// Get the asset and OToken balances in the pool
// Get the asset and OToken balances in the pool
Kopieren
Kopiert
Kopieren
Kopiert
(
uint256
ws
Reserve
s
After,
uint256 o
s
Reserve
s
After
, ) = IPair(pool)
(
.
get
Reserves();
uint256
asset
Reserve
After,
// diff =
wS
balance -
OS
balance
uint256 o
Token
Reserve
After
int256 diffAfter =
ws
Reserve
s
After.toInt256() -
) = _
get
Pool
Reserves();
o
s
Reserve
s
After.toInt256();
// diff =
asset
balance -
OToken
balance
int256 diffAfter =
asset
Reserve
After.toInt256() -
o
Token
Reserve
After.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) {
Kopieren
Kopiert
Kopieren
Kopiert
// 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) {
Kopieren
Kopiert
Kopieren
Kopiert
// 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");
}
}
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @param _
os
Address of the O
S t
oken.
* @param _
oToken
Address of the O
T
oken.
* @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,
Kopieren
Kopiert
Kopieren
Kopiert
address _
os
,
address _
oToken
,
address _
ws
,
address _
asset
,
address _gauge
address _gauge
) InitializableAbstractStrategy(_baseConfig) {
) InitializableAbstractStrategy(_baseConfig) {
Kopieren
Kopiert
Kopieren
Kopiert
// 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(
Kopieren
Kopiert
Kopieren
Kopiert
IBasicToken(_
ws
).decimals() == 18 &&
IBasicToken(_
asset
).decimals() == 18 &&
IBasicToken(_
os
).decimals() == 18,
IBasicToken(_
oToken
).decimals() == 18,
"Incorrect token decimals"
"Incorrect token decimals"
);
);
Kopieren
Kopiert
Kopieren
Kopiert
// 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"
);
);
Kopieren
Kopiert
Kopieren
Kopiert
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
Kopieren
Kopiert
Kopieren
Kopiert
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
Kopieren
Kopiert
Kopieren
Kopiert
* 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
Kopieren
Kopiert
Kopieren
Kopiert
* @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);
Kopieren
Kopiert
Kopieren
Kopiert
_assets[0] =
ws
;
_assets[0] =
asset
;
InitializableAbstractStrategy._initialize(
InitializableAbstractStrategy._initialize(
_rewardTokenAddresses,
_rewardTokenAddresses,
_assets,
_assets,
pTokens
pTokens
);
);
maxDepeg = _maxDepeg;
maxDepeg = _maxDepeg;
_approveBase();
_approveBase();
}
}
/***************************************
/***************************************
Deposit
Deposit
****************************************/
****************************************/
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* 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 _
ws
Amount Amount of
Wrapped S (wS)
tokens to deposit.
* @param _
asset
Amount Amount of
asset
tokens to deposit.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function deposit(address _asset, uint256 _
ws
Amount)
function deposit(address _asset, uint256 _
asset
Amount)
external
external
override
override
onlyVault
onlyVault
nonReentrant
nonReentrant
skimPool
skimPool
nearBalancedPool
nearBalancedPool
{
{
Kopieren
Kopiert
Kopieren
Kopiert
require(_asset ==
ws
, "Unsupported asset");
require(_asset ==
asset
, "Unsupported asset");
require(_
ws
Amount > 0, "Must deposit something");
require(_
asset
Amount > 0, "Must deposit something");
Kopieren
Kopiert
Kopieren
Kopiert
(uint256 o
s
DepositAmount, ) = _deposit(_
ws
Amount);
(uint256 o
Token
DepositAmount, ) = _deposit(_
asset
Amount);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the deposited
wS
tokens
// Emit event for the deposited
asset
tokens
emit Deposit(
ws
, pool, _
ws
Amount);
emit Deposit(
asset
, pool, _
asset
Amount);
// Emit event for the minted
OS
tokens
// Emit event for the minted
OToken
tokens
emit Deposit(
os
, pool, o
s
DepositAmount);
emit Deposit(
oToken
, pool, o
Token
DepositAmount);
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* 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
{
{
Kopieren
Kopiert
Kopieren
Kopiert
uint256
ws
Balance = IERC20(
ws
).balanceOf(address(this));
uint256
asset
Balance = IERC20(
asset
).balanceOf(address(this));
if (
ws
Balance > 0) {
if (
asset
Balance > 0) {
(uint256 o
s
DepositAmount, ) = _deposit(
ws
Balance);
(uint256 o
Token
DepositAmount, ) = _deposit(
asset
Balance);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the deposited
wS
tokens
// Emit event for the deposited
asset
tokens
emit Deposit(
ws
, pool,
ws
Balance);
emit Deposit(
asset
, pool,
asset
Balance);
// Emit event for the minted
OS
tokens
// Emit event for the minted
OToken
tokens
emit Deposit(
os
, pool, o
s
DepositAmount);
emit Deposit(
oToken
, pool, o
Token
DepositAmount);
}
}
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @param _
ws
Amount Amount of
Wrapped S (wS)
tokens to deposit.
* @param _
asset
Amount Amount of
asset
tokens to deposit.
* @return o
s
DepositAmount Amount of
OS
tokens minted and deposited into the pool.
* @return o
Token
DepositAmount 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.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function _deposit(uint256 _
ws
Amount)
function _deposit(uint256 _
asset
Amount)
internal
internal
Kopieren
Kopiert
Kopieren
Kopiert
returns (uint256 o
s
DepositAmount, uint256 lpTokens)
returns (uint256 o
Token
DepositAmount, uint256 lpTokens)
{
{
Kopieren
Kopiert
Kopieren
Kopiert
// 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.
o
s
DepositAmount = _calcTokensToMint(_
ws
Amount);
o
Token
DepositAmount = _calcTokensToMint(_
asset
Amount);
Kopieren
Kopiert
Kopieren
Kopiert
// Mint the required
OS
tokens to this strategy
// Mint the required
OToken
tokens to this strategy
IVault(vaultAddress).mintForStrategy(o
s
DepositAmount);
IVault(vaultAddress).mintForStrategy(o
Token
DepositAmount);
Kopieren
Kopiert
Kopieren
Kopiert
// 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(_
ws
Amount, o
s
DepositAmount);
lpTokens = _depositToPoolAndGauge(_
asset
Amount, o
Token
DepositAmount);
}
}
/***************************************
/***************************************
Withdraw
Withdraw
****************************************/
****************************************/
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @param _asset Address of the
Wrapped S (wS) contract
.
* @param _asset Address of the
asset token
.
* @param _
ws
Amount Amount of
Wrapped S (wS)
to withdraw.
* @param _
asset
Amount Amount of
asset tokens
to withdraw.
*/
*/
function withdraw(
function withdraw(
address _recipient,
address _recipient,
address _asset,
address _asset,
Kopieren
Kopiert
Kopieren
Kopiert
uint256 _
ws
Amount
uint256 _
asset
Amount
) external override onlyVault nonReentrant skimPool {
) external override onlyVault nonReentrant skimPool {
Kopieren
Kopiert
Kopieren
Kopiert
require(_
ws
Amount > 0, "Must withdraw something");
require(_
asset
Amount > 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");
Kopieren
Kopiert
Kopieren
Kopiert
// 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(_
ws
Amount);
uint256 lpTokens = _calcTokensToBurn(_
asset
Amount);
// 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);
Kopieren
Kopiert
Kopieren
Kopiert
// 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 o
s
ToBurn = IERC20(
os
).balanceOf(address(this));
uint256 o
Token
ToBurn = IERC20(
oToken
).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(o
s
ToBurn);
IVault(vaultAddress).burnForStrategy(o
Token
ToBurn);
Kopieren
Kopiert
Kopieren
Kopiert
// 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(
Kopieren
Kopiert
Kopieren
Kopiert
IERC20(
ws
).balanceOf(address(this)) >= _
ws
Amount,
IERC20(
asset
).balanceOf(address(this)) >= _
asset
Amount,
"Not enough
wS
removed
from pool
"
"Not enough
asset
removed
"
);
);
Kopieren
Kopiert
Kopieren
Kopiert
IERC20(
ws
).safeTransfer(_recipient, _
ws
Amount);
IERC20(
asset
).safeTransfer(_recipient, _
asset
Amount);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the withdrawn
wS
tokens
// Emit event for the withdrawn
asset
tokens
emit Withdrawal(
ws
, pool, _
ws
Amount);
emit Withdrawal(
asset
, pool, _
asset
Amount);
// Emit event for the burnt
OS
tokens
// Emit event for the burnt
OToken
tokens
emit Withdrawal(
os
, pool, o
s
ToBurn);
emit Withdrawal(
oToken
, pool, o
Token
ToBurn);
}
}
/**
/**
* @notice Withdraw all pool LP tokens from the gauge,
* @notice Withdraw all pool LP tokens from the gauge,
Kopieren
Kopiert
Kopieren
Kopiert
* remove all
wS
and
OS
from the
SwapX
pool,
* remove all
asset
and
OToken
from the
Algebra
pool,
* burn all the O
S t
oken
s
,
* burn all the O
T
oken
,
* 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);
}
}
Kopieren
Kopiert
Kopieren
Kopiert
// Burn all
OS
in this strategy contract
// Burn all
OToken
in this strategy contract
uint256 o
s
ToBurn = IERC20(
os
).balanceOf(address(this));
uint256 o
Token
ToBurn = IERC20(
oToken
).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(o
s
ToBurn);
IVault(vaultAddress).burnForStrategy(o
Token
ToBurn);
Kopieren
Kopiert
Kopieren
Kopiert
// 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.
Kopieren
Kopiert
Kopieren
Kopiert
uint256
ws
Balance = IERC20(
ws
).balanceOf(address(this));
uint256
asset
Balance = IERC20(
asset
).balanceOf(address(this));
IERC20(
ws
).safeTransfer(vaultAddress,
ws
Balance);
IERC20(
asset
).safeTransfer(vaultAddress,
asset
Balance);
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the withdrawn
wS
tokens
// Emit event for the withdrawn
asset
tokens
emit Withdrawal(
ws
, pool,
ws
Balance);
emit Withdrawal(
asset
, pool,
asset
Balance);
// Emit event for the burnt
OS
tokens
// Emit event for the burnt
OToken
tokens
emit Withdrawal(
os
, pool, o
s
ToBurn);
emit Withdrawal(
oToken
, pool, o
Token
ToBurn);
}
}
/***************************************
/***************************************
Pool Rebalancing
Pool Rebalancing
****************************************/
****************************************/
Kopieren
Kopiert
Kopieren
Kopiert
/** @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 _
ws
Amount Amount of
Wrapped S (wS)
to swap into the pool.
* @param _
asset
Amount Amount of
asset tokens
to swap into the pool.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function swapAssetsToPool(uint256 _
ws
Amount)
function swapAssetsToPool(uint256 _
asset
Amount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
Kopieren
Kopiert
Kopieren
Kopiert
require(_
ws
Amount > 0, "Must swap something");
require(_
asset
Amount > 0, "Must swap something");
Kopieren
Kopiert
Kopieren
Kopiert
// 1. Partially remove liquidity so there’s enough
wS
for the swap
// 1. Partially remove liquidity so there’s enough
asset
for the swap
Kopieren
Kopiert
Kopieren
Kopiert
// 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(_
ws
Amount);
uint256 lpTokens = _calcTokensToBurn(_
asset
Amount);
require(lpTokens > 0, "No LP tokens to burn");
require(lpTokens > 0, "No LP tokens to burn");
_withdrawFromGaugeAndPool(lpTokens);
_withdrawFromGaugeAndPool(lpTokens);
Kopieren
Kopiert
Kopieren
Kopiert
// 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(_
ws
Amount,
ws, os
);
_swapExactTokensForTokens(_
asset
Amount,
asset, oToken
);
Kopieren
Kopiert
Kopieren
Kopiert
// 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 o
s
ToBurn = IERC20(
os
).balanceOf(address(this));
uint256 o
Token
ToBurn = IERC20(
oToken
).balanceOf(address(this));
IVault(vaultAddress).burnForStrategy(o
s
ToBurn);
IVault(vaultAddress).burnForStrategy(o
Token
ToBurn);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the burnt
OS
tokens
// Emit event for the burnt
OToken
tokens
emit Withdrawal(
os
, pool, o
s
ToBurn);
emit Withdrawal(
oToken
, pool, o
Token
ToBurn);
// Emit event for the swap
// Emit event for the swap
Kopieren
Kopiert
Kopieren
Kopiert
emit SwapAssetsToPool(_
ws
Amount, lpTokens, o
s
ToBurn);
emit SwapAssetsToPool(_
asset
Amount, lpTokens, o
Token
ToBurn);
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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 _o
s
Amount Amount of
OS
to swap into the pool.
* @param _o
Token
Amount Amount of
OToken
to swap into the pool.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function swapOTokensToPool(uint256 _o
s
Amount)
function swapOTokensToPool(uint256 _o
Token
Amount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
Kopieren
Kopiert
Kopieren
Kopiert
require(_o
s
Amount > 0, "Must swap something");
require(_o
Token
Amount > 0, "Must swap something");
Kopieren
Kopiert
Kopieren
Kopiert
// 1. Mint
OS
so it can be swapped into the pool
// 1. Mint
OToken
so it can be swapped into the pool
Kopieren
Kopiert
Kopieren
Kopiert
// There can be
OS
in the strategy from skimming the pool
// There can be
OToken
in the strategy from skimming the pool
uint256 o
s
InStrategy = IERC20(
os
).balanceOf(address(this));
uint256 o
Token
InStrategy = IERC20(
oToken
).balanceOf(address(this));
require(
_os
Amount >= o
s
InStrategy,
"Too much
OS
in strategy"
);
require(
uint256 o
s
ToMint = _o
s
Amount - o
s
InStrategy;
_oToken
Amount >= o
Token
InStrategy,
"Too much
OToken
in strategy"
);
uint256 o
Token
ToMint = _o
Token
Amount - o
Token
InStrategy;
Kopieren
Kopiert
Kopieren
Kopiert
// Mint the required
OS
tokens to this strategy
// Mint the required
OToken
tokens to this strategy
IVault(vaultAddress).mintForStrategy(o
s
ToMint);
IVault(vaultAddress).mintForStrategy(o
Token
ToMint);
Kopieren
Kopiert
Kopieren
Kopiert
// 2. Swap
OS
for
wS
against the pool
// 2. Swap
OToken
for
asset
against the pool
_swapExactTokensForTokens(_o
s
Amount,
os, ws
);
_swapExactTokensForTokens(_o
Token
Amount,
oToken, asset
);
Kopieren
Kopiert
Kopieren
Kopiert
// 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
ws
DepositAmount = IERC20(
ws
).balanceOf(address(this));
uint256
asset
DepositAmount = IERC20(
asset
).balanceOf(address(this));
Kopieren
Kopiert
Kopieren
Kopiert
// 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 o
s
DepositAmount, uint256 lpTokens) = _deposit(
ws
DepositAmount
);
(uint256 o
Token
DepositAmount, uint256 lpTokens) = _deposit(
asset
DepositAmount
);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
Kopieren
Kopiert
Kopieren
Kopiert
// Emit event for the minted
OS
tokens
// Emit event for the minted
OToken
tokens
emit Deposit(
os
, pool, o
s
ToMint + o
s
DepositAmount);
emit Deposit(
oToken
, pool, o
Token
ToMint + o
Token
DepositAmount);
// Emit event for the swap
// Emit event for the swap
emit SwapOTokensToPool(
emit SwapOTokensToPool(
Kopieren
Kopiert
Kopieren
Kopiert
o
s
ToMint,
o
Token
ToMint,
ws
DepositAmount,
asset
DepositAmount,
o
s
DepositAmount,
o
Token
DepositAmount,
lpTokens
lpTokens
);
);
}
}
/***************************************
/***************************************
Assets and Rewards
Assets and Rewards
****************************************/
****************************************/
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @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)
{
{
Kopieren
Kopiert
Kopieren
Kopiert
require(_asset ==
ws
, "Unsupported asset");
require(_asset ==
asset
, "Unsupported asset");
Kopieren
Kopiert
Kopieren
Kopiert
//
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;
Kopieren
Kopiert
Kopieren
Kopiert
// 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) {
Kopieren
Kopiert
Kopieren
Kopiert
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();
}
}
/***************************************
/***************************************
Kopieren
Kopiert
Kopieren
Kopiert
Internal
SwapX
Pool and Gauge Functions
Internal
Algebra
Pool and Gauge Functions
****************************************/
****************************************/
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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 _
ws
Amount Amount of
Wrapped S (wS)
to be added to the pool.
* @param _
asset
Amount Amount of
asset tokens
to be added to the pool.
* @return o
s
Amount Amount of
OS
to be minted and added to the pool.
* @return o
Token
Amount Amount of
OToken tokens
to be minted and added to the pool.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function _calcTokensToMint(uint256 _
ws
Amount)
function _calcTokensToMint(uint256 _
asset
Amount)
internal
internal
view
view
Kopieren
Kopiert
Kopieren
Kopiert
returns (uint256 o
s
Amount)
returns (uint256 o
Token
Amount)
{
{
Kopieren
Kopiert
Kopieren
Kopiert
(uint256
ws
Reserves, uint256 o
s
Reserves
,
) =
IPair(pool).getReserves
();
(uint256
asset
Reserves, uint256 o
Token
Reserves
) =
_getPoolReserves
();
require(
ws
Reserves > 0, "Empty pool");
require(
asset
Reserves > 0, "Empty pool");
Kopieren
Kopiert
Kopieren
Kopiert
//
OS
to add = (
wS
being added *
OS
in pool) /
wS
in pool
//
OToken
to add = (
asset
being added *
OToken
in pool) /
asset
in pool
o
s
Amount = (_
ws
Amount * o
s
Reserves) /
ws
Reserves;
o
Token
Amount = (_
asset
Amount * o
Token
Reserves) /
asset
Reserves;
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @param _
ws
Amount Amount of
Wrapped S (wS)
to be removed from the pool.
* @param _
asset
Amount 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.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function _calcTokensToBurn(uint256 _
ws
Amount)
function _calcTokensToBurn(uint256 _
asset
Amount)
internal
internal
view
view
returns (uint256 lpTokens)
returns (uint256 lpTokens)
{
{
Kopieren
Kopiert
Kopieren
Kopiert
/* 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.
Kopieren
Kopiert
Kopieren
Kopiert
* 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.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
(uint256
ws
Reserves,
,
) =
IPair(pool).getReserves
();
(uint256
asset
Reserves,
) =
_getPoolReserves
();
require(
ws
Reserves > 0, "Empty pool");
require(
asset
Reserves > 0, "Empty pool");
Kopieren
Kopiert
Kopieren
Kopiert
lpTokens = (_
ws
Amount * IPair(pool).totalSupply()) /
ws
Reserves;
lpTokens = (_
asset
Amount * IPair(pool).totalSupply()) /
asset
Reserves;
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
}
}
/**
/**
Kopieren
Kopiert
Kopieren
Kopiert
* @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.
Kopieren
Kopiert
Kopieren
Kopiert
* @param _
ws
Amount Amount of
Wrapped S (wS)
to deposit.
* @param _
asset
Amount Amount of
asset
to deposit.
* @param _o
s
Amount Amount of
OS
to deposit.
* @param _o
Token
Amount Amount of
OToken
to deposit.
* @return lpTokens Amount of
SwapX
pool LP tokens minted.
* @return lpTokens Amount of
Algebra
pool LP tokens minted.
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
function _depositToPoolAndGauge(uint256 _
ws
Amount, uint256 _o
s
Amount)
function _depositToPoolAndGauge(uint256 _
asset
Amount, uint256 _o
Token
Amount)
internal
internal
returns (uint256 lpTokens)
returns (uint256 lpTokens)
{
{
Kopieren
Kopiert
Kopieren
Kopiert
// Transfer
wS
to the pool
// Transfer
asset
to the pool
IERC20(
ws
).safeTransfer(pool, _
ws
Amount);
IERC20(
asset
).safeTransfer(pool, _
asset
Amount);
// Transfer
OS
to the pool
// Transfer
OToken
to the pool
IERC20(
os
).safeTransfer(pool, _o
s
Amount);
IERC20(
oToken
).safeTransfer(pool, _o
Token
Amount);
// 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
Kopieren
Kopiert
Kopieren
Kopiert
IGauge(gauge).depos
it(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
Gespeicherte Diffs
Originaltext
Datei öffnen
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; /** * @title SwapX Algorithmic Market Maker (AMO) Strategy * @notice AMO strategy for the SwapX OS/wS stable pool * @author Origin Protocol Inc */ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; import { StableMath } from "../../utils/StableMath.sol"; import { sqrt } from "../../utils/PRBMath.sol"; import { IBasicToken } from "../../interfaces/IBasicToken.sol"; import { IPair } from "../../interfaces/sonic/ISwapXPair.sol"; import { IGauge } from "../../interfaces/sonic/ISwapXGauge.sol"; import { IVault } from "../../interfaces/IVault.sol"; contract SonicSwapXAMOStrategy is InitializableAbstractStrategy { using SafeERC20 for IERC20; using StableMath for uint256; using SafeCast for uint256; /** * @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 * draining the protocol funds. */ uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether; /// @notice Precision for the SwapX Stable AMM (sAMM) invariant k. uint256 public constant PRECISION = 1e18; /// @notice Address of the Wrapped S (wS) token. address public immutable ws; /// @notice Address of the OS token contract. address public immutable os; /// @notice Address of the SwapX Stable pool contract. address public immutable pool; /// @notice Address of the SwapX Gauge contract. address public immutable gauge; /// @notice The max amount the OS/wS price can deviate from peg (1e18) /// before deposits are reverted scaled to 18 decimals. /// 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) /// allows a price range from 0.995 to 1.005. uint256 public maxDepeg; event SwapOTokensToPool( uint256 osMinted, uint256 wsDepositAmount, uint256 osDepositAmount, uint256 lpTokens ); event SwapAssetsToPool( uint256 wsSwapped, uint256 lpTokens, uint256 osBurnt ); event MaxDepegUpdated(uint256 maxDepeg); /** * @dev Verifies that the caller is the Strategist of the Vault. */ modifier onlyStrategist() { require( msg.sender == IVault(vaultAddress).strategistAddr(), "Caller is not the Strategist" ); _; } /** * @dev Skim the SwapX pool in case any extra wS or OS tokens were added */ modifier skimPool() { IPair(pool).skim(address(this)); _; } /** * @dev Checks the pool is balanced enough to allow deposits. */ modifier nearBalancedPool() { // OS/wS price = wS / OS // Get the OS/wS price for selling 1 OS for wS // As OS is 1, the wS amount is the OS/wS price uint256 sellPrice = IPair(pool).getAmountOut(1e18, os); // Get the amount of OS received from selling 1 wS. This is buying OS. uint256 osAmount = IPair(pool).getAmountOut(1e18, ws); // Convert to a OS/wS price = wS / OS uint256 buyPrice = 1e36 / osAmount; uint256 pegPrice = 1e18; require( sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg, "price out of range" ); _; } /** * @dev Checks the pool's balances have improved and the balances * have not tipped to the other side. * 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. */ modifier improvePoolBalance() { // Get the asset and OToken balances in the pool (uint256 wsReservesBefore, uint256 osReservesBefore, ) = IPair(pool) .getReserves(); // diff = wS balance - OS balance int256 diffBefore = wsReservesBefore.toInt256() - osReservesBefore.toInt256(); _; // Get the asset and OToken balances in the pool (uint256 wsReservesAfter, uint256 osReservesAfter, ) = IPair(pool) .getReserves(); // diff = wS balance - OS balance int256 diffAfter = wsReservesAfter.toInt256() - osReservesAfter.toInt256(); if (diffBefore == 0) { require(diffAfter == 0, "Position balance is worsened"); } else if (diffBefore < 0) { // If the pool was originally imbalanced in favor of OS, then // we want to check that the pool is now more balanced require(diffAfter <= 0, "Assets overshot peg"); require(diffBefore < diffAfter, "OTokens balance worse"); } else if (diffBefore > 0) { // If the pool was originally imbalanced in favor of wS, then // we want to check that the pool is now more balanced require(diffAfter >= 0, "OTokens overshot peg"); require(diffAfter < diffBefore, "Assets balance worse"); } } /** * @param _baseConfig The `platformAddress` is the address of the SwapX pool. * The `vaultAddress` is the address of the Origin Sonic Vault. * @param _os Address of the OS token. * @param _ws Address of the Wrapped S (wS) token. * @param _gauge Address of the SwapX gauge for the pool. */ constructor( BaseStrategyConfig memory _baseConfig, address _os, address _ws, address _gauge ) 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 require( IBasicToken(_ws).decimals() == 18 && IBasicToken(_os).decimals() == 18, "Incorrect token decimals" ); // Check the SwapX pool is a Stable AMM (sAMM) require( IPair(_baseConfig.platformAddress).isStable() == true, "Pool not stable" ); // Check the gauge is for the pool require( IGauge(_gauge).TOKEN() == _baseConfig.platformAddress, "Incorrect gauge" ); // Set the immutable variables os = _os; ws = _ws; pool = _baseConfig.platformAddress; gauge = _gauge; // This is an implementation contract. The governor is set in the proxy contract. _setGovernor(address(0)); } /** * Initializer for setting up strategy internal state. This overrides the * InitializableAbstractStrategy initializer as SwapX strategies don't fit * well within that abstraction. * @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. */ function initialize( address[] calldata _rewardTokenAddresses, uint256 _maxDepeg ) external onlyGovernor initializer { address[] memory pTokens = new address[](1); pTokens[0] = pool; address[] memory _assets = new address[](1); _assets[0] = ws; InitializableAbstractStrategy._initialize( _rewardTokenAddresses, _assets, pTokens ); maxDepeg = _maxDepeg; _approveBase(); } /*************************************** Deposit ****************************************/ /** * @notice Deposit an amount of Wrapped S (wS) into the SwapX pool. * Mint OS in proportion to the pool's wS and OS reserves, * transfer Wrapped S (wS) and OS to the pool, * mint the pool's LP token and deposit in the gauge. * @dev This tx must be wrapped by the VaultValueChecker. * To minimize loses, the pool should be rebalanced before depositing. * The pool's OS/wS price must be within the maxDepeg range. * @param _asset Address of Wrapped S (wS) token. * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit. */ function deposit(address _asset, uint256 _wsAmount) external override onlyVault nonReentrant skimPool nearBalancedPool { require(_asset == ws, "Unsupported asset"); require(_wsAmount > 0, "Must deposit something"); (uint256 osDepositAmount, ) = _deposit(_wsAmount); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the deposited wS tokens emit Deposit(ws, pool, _wsAmount); // Emit event for the minted OS tokens emit Deposit(os, pool, osDepositAmount); } /** * @notice Deposit all the strategy's Wrapped S (wS) tokens into the SwapX pool. * Mint OS in proportion to the pool's wS and OS reserves, * transfer Wrapped S (wS) and OS to the pool, * mint the pool's LP token and deposit in the gauge. * @dev This tx must be wrapped by the VaultValueChecker. * To minimize loses, the pool should be rebalanced before depositing. * The pool's OS/wS price must be within the maxDepeg range. */ function depositAll() external override onlyVault nonReentrant skimPool nearBalancedPool { uint256 wsBalance = IERC20(ws).balanceOf(address(this)); if (wsBalance > 0) { (uint256 osDepositAmount, ) = _deposit(wsBalance); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the deposited wS tokens emit Deposit(ws, pool, wsBalance); // Emit event for the minted OS tokens emit Deposit(os, pool, osDepositAmount); } } /** * @dev Mint OS in proportion to the pool's wS and OS reserves, * transfer Wrapped S (wS) and OS to the pool, * mint the pool's LP token and deposit in the gauge. * @param _wsAmount Amount of Wrapped S (wS) tokens to deposit. * @return osDepositAmount Amount of OS tokens minted and deposited into the pool. * @return lpTokens Amount of SwapX pool LP tokens minted and deposited into the gauge. */ function _deposit(uint256 _wsAmount) internal returns (uint256 osDepositAmount, uint256 lpTokens) { // Calculate the required amount of OS to mint based on the wS amount. osDepositAmount = _calcTokensToMint(_wsAmount); // Mint the required OS tokens to this strategy IVault(vaultAddress).mintForStrategy(osDepositAmount); // Add wS and OS liquidity to the pool and stake in gauge lpTokens = _depositToPoolAndGauge(_wsAmount, osDepositAmount); } /*************************************** Withdraw ****************************************/ /** * @notice Withdraw wS and OS from the SwapX pool, burn the OS, * and transfer the wS to the recipient. * @param _recipient Address of the Vault. * @param _asset Address of the Wrapped S (wS) contract. * @param _wsAmount Amount of Wrapped S (wS) to withdraw. */ function withdraw( address _recipient, address _asset, uint256 _wsAmount ) external override onlyVault nonReentrant skimPool { require(_wsAmount > 0, "Must withdraw something"); require(_asset == ws, "Unsupported asset"); // This strategy can't be set as a default strategy for wS in the Vault. // This means the recipient must always be the Vault. 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 uint256 lpTokens = _calcTokensToBurn(_wsAmount); // Withdraw pool LP tokens from the gauge and remove assets from from the pool _withdrawFromGaugeAndPool(lpTokens); // Burn all the removed OS and any that was left in the strategy uint256 osToBurn = IERC20(os).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(osToBurn); // Transfer wS to the recipient // Note there can be a dust amount of wS left in the strategy as // the burn of the pool's LP tokens is rounded up require( IERC20(ws).balanceOf(address(this)) >= _wsAmount, "Not enough wS removed from pool" ); IERC20(ws).safeTransfer(_recipient, _wsAmount); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the withdrawn wS tokens emit Withdrawal(ws, pool, _wsAmount); // Emit event for the burnt OS tokens emit Withdrawal(os, pool, osToBurn); } /** * @notice Withdraw all pool LP tokens from the gauge, * remove all wS and OS from the SwapX pool, * burn all the OS tokens, * and transfer all the wS to the Vault contract. * @dev There is no solvency check here as withdrawAll can be called to * quickly secure assets to the Vault in emergencies. */ function withdrawAll() external override onlyVaultOrGovernor nonReentrant skimPool { // Get all the pool LP tokens the strategy has staked in the gauge uint256 lpTokens = IGauge(gauge).balanceOf(address(this)); // Can not withdraw zero LP tokens from the gauge if (lpTokens == 0) return; if (IGauge(gauge).emergency()) { // The gauge is in emergency mode _emergencyWithdrawFromGaugeAndPool(); } else { // Withdraw pool LP tokens from the gauge and remove assets from from the pool _withdrawFromGaugeAndPool(lpTokens); } // Burn all OS in this strategy contract uint256 osToBurn = IERC20(os).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(osToBurn); // Get the strategy contract's wS balance. // This includes all that was removed from the SwapX pool and // any that was sitting in the strategy contract before the removal. uint256 wsBalance = IERC20(ws).balanceOf(address(this)); IERC20(ws).safeTransfer(vaultAddress, wsBalance); // Emit event for the withdrawn wS tokens emit Withdrawal(ws, pool, wsBalance); // Emit event for the burnt OS tokens emit Withdrawal(os, pool, osToBurn); } /*************************************** Pool Rebalancing ****************************************/ /** @notice Used when there is more OS than wS in the pool. * wS and OS is removed from the pool, the received wS is swapped for OS * and the left over OS in the strategy is burnt. * The OS/wS price is < 1.0 so OS is being bought at a discount. * @param _wsAmount Amount of Wrapped S (wS) to swap into the pool. */ function swapAssetsToPool(uint256 _wsAmount) external onlyStrategist nonReentrant improvePoolBalance skimPool { require(_wsAmount > 0, "Must swap something"); // 1. Partially remove liquidity so there’s enough wS for the swap // Calculate how much pool LP tokens to burn to get the required amount of wS tokens back uint256 lpTokens = _calcTokensToBurn(_wsAmount); require(lpTokens > 0, "No LP tokens to burn"); _withdrawFromGaugeAndPool(lpTokens); // 2. Swap wS for OS against the pool // Swap exact amount of wS for OS 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 _swapExactTokensForTokens(_wsAmount, ws, os); // 3. Burn all the OS left in the strategy from the remove liquidity and swap uint256 osToBurn = IERC20(os).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(osToBurn); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the burnt OS tokens emit Withdrawal(os, pool, osToBurn); // Emit event for the swap emit SwapAssetsToPool(_wsAmount, lpTokens, osToBurn); } /** * @notice Used when there is more wS than OS in the pool. * OS is minted and swapped for wS against the pool, * more OS is minted and added back into the pool with the swapped out wS. * The OS/wS price is > 1.0 so OS is being sold at a premium. * @param _osAmount Amount of OS to swap into the pool. */ function swapOTokensToPool(uint256 _osAmount) external onlyStrategist nonReentrant improvePoolBalance skimPool { require(_osAmount > 0, "Must swap something"); // 1. Mint OS so it can be swapped into the pool // There can be OS in the strategy from skimming the pool uint256 osInStrategy = IERC20(os).balanceOf(address(this)); require(_osAmount >= osInStrategy, "Too much OS in strategy"); uint256 osToMint = _osAmount - osInStrategy; // Mint the required OS tokens to this strategy IVault(vaultAddress).mintForStrategy(osToMint); // 2. Swap OS for wS against the pool _swapExactTokensForTokens(_osAmount, os, ws); // The wS is from the swap and any wS that was sitting in the strategy uint256 wsDepositAmount = IERC20(ws).balanceOf(address(this)); // 3. Add wS and OS back to the pool in proportion to the pool's reserves (uint256 osDepositAmount, uint256 lpTokens) = _deposit(wsDepositAmount); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the minted OS tokens emit Deposit(os, pool, osToMint + osDepositAmount); // Emit event for the swap emit SwapOTokensToPool( osToMint, wsDepositAmount, osDepositAmount, lpTokens ); } /*************************************** Assets and Rewards ****************************************/ /** * @notice Get the wS value of assets in the strategy and SwapX pool. * 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. * @param _asset Address of the Wrapped S (wS) token * @return balance Total value in wS. */ function checkBalance(address _asset) external view override returns (uint256 balance) { require(_asset == ws, "Unsupported asset"); // wS balance needed here for the balance check that happens from vault during depositing. balance = IERC20(ws).balanceOf(address(this)); // This assumes 1 gauge LP token = 1 pool LP token uint256 lpTokens = IGauge(gauge).balanceOf(address(this)); 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. balance += _lpValue(lpTokens); } /** * @notice Returns bool indicating whether asset is supported by strategy * @param _asset Address of the asset */ function supportsAsset(address _asset) public view override returns (bool) { return _asset == ws; } /** * @notice Collect accumulated SWPx (and other) rewards and send to the Harvester. */ function collectRewardTokens() external override onlyHarvester nonReentrant { // Collect SWPx rewards from the gauge IGauge(gauge).getReward(); _collectRewardTokens(); } /*************************************** Internal SwapX Pool and Gauge Functions ****************************************/ /** * @dev Calculate the required amount of OS to mint based on the wS amount. * This ensures the proportion of OS tokens being added to the pool matches the proportion of wS tokens. * For example, if the added wS tokens is 10% of existing wS tokens in the pool, * then the OS tokens being added should also be 10% of the OS tokens in the pool. * @param _wsAmount Amount of Wrapped S (wS) to be added to the pool. * @return osAmount Amount of OS to be minted and added to the pool. */ function _calcTokensToMint(uint256 _wsAmount) internal view returns (uint256 osAmount) { (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves(); require(wsReserves > 0, "Empty pool"); // OS to add = (wS being added * OS in pool) / wS in pool osAmount = (_wsAmount * osReserves) / wsReserves; } /** * @dev Calculate how much pool LP tokens to burn to get the required amount of wS tokens back * from the pool. * @param _wsAmount Amount of Wrapped S (wS) to be removed from the pool. * @return lpTokens Amount of SwapX pool LP tokens to burn. */ function _calcTokensToBurn(uint256 _wsAmount) internal view returns (uint256 lpTokens) { /* The SwapX pool proportionally returns the reserve tokens when removing liquidity. * First, calculate the proportion of required wS tokens against the pools wS reserves. * 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, * 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 * pool tilted to either side. * * 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 * is being voted on, the pool tilt might change so much that the proposal that has been valid while * created is no longer valid. */ (uint256 wsReserves, , ) = IPair(pool).getReserves(); require(wsReserves > 0, "Empty pool"); lpTokens = (_wsAmount * IPair(pool).totalSupply()) / wsReserves; 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 * and stake the pool's LP token in the gauge. * @param _wsAmount Amount of Wrapped S (wS) to deposit. * @param _osAmount Amount of OS to deposit. * @return lpTokens Amount of SwapX pool LP tokens minted. */ function _depositToPoolAndGauge(uint256 _wsAmount, uint256 _osAmount) internal returns (uint256 lpTokens) { // Transfer wS to the pool IERC20(ws).safeTransfer(pool, _wsAmount); // Transfer OS to the pool IERC20(os).safeTransfer(pool, _osAmount); // Mint LP tokens from the pool lpTokens = IPair(pool).mint(address(this)); // Deposit the pool's LP tokens into the gauge IGauge(gauge).deposit(lpTokens); } /** * @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 IPair(pool).burn(address(this)); } /** * @dev Swap exact amount of tokens for another token against the pool. * @param _amountIn Amount of tokens to swap into the pool. * @param _tokenIn Address of the token going into the pool. * @param _tokenOut Address of the token being swapped out of the pool. */ function _swapExactTokensForTokens( uint256 _amountIn, address _tokenIn, address _tokenOut ) internal { // Transfer in tokens to the pool IERC20(_tokenIn).safeTransfer(pool, _amountIn); // Calculate how much out tokens we get from the swap uint256 amountOut = IPair(pool).getAmountOut(_amountIn, _tokenIn); // Safety check that we are dealing with the correct pool tokens require( (_tokenIn == ws && _tokenOut == os) || (_tokenIn == os && _tokenOut == ws), "Unsupported swap" ); // Work out the correct order of the amounts for the pool (uint256 amount0, uint256 amount1) = _tokenIn == ws ? (uint256(0), amountOut) : (amountOut, 0); // Perform the swap on the pool IPair(pool).swap(amount0, amount1, address(this), new bytes(0)); // The slippage protection against the amount out is indirectly done // via the improvePoolBalance } /// @dev Calculate the value of a LP position in a SwapX stable pool /// if the pool was balanced. /// @param _lpTokens Amount of LP tokens in the SwapX pool /// @return value The wS value of the LP tokens when the pool is balanced function _lpValue(uint256 _lpTokens) internal view returns (uint256 value) { // Get total supply of LP tokens uint256 totalSupply = IPair(pool).totalSupply(); if (totalSupply == 0) return 0; // Get the current reserves of the pool (uint256 wsReserves, uint256 osReserves, ) = IPair(pool).getReserves(); // Calculate the invariant of the pool assuming both tokens have 18 decimals. // k is scaled to 18 decimals. uint256 k = _invariant(wsReserves, osReserves); // If x = y, let’s denote x = y = z (where z is the common reserve value) // Substitute z into the invariant: // k = z^3 * z + z * z^3 // k = 2 * z^4 // Going back the other way to calculate the common reserve value z // z = (k / 2) ^ (1/4) // the total value of the pool when x = y is 2 * z, which is 2 * (k / 2) ^ (1/4) uint256 zSquared = sqrt((k * 1e18) / 2); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt uint256 z = sqrt(zSquared * 1e18); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt uint256 totalValueOfPool = 2 * z; // lp value = lp tokens * value of pool / total supply value = (_lpTokens * totalValueOfPool) / totalSupply; } /** * @dev Compute the invariant for a SwapX stable pool. * This assumed both x and y tokens are to 18 decimals which is checked in the constructor. * invariant: k = x^3 * y + x * y^3 * @dev This implementation is copied from SwapX's Pair contract. * @param _x The amount of Wrapped S (wS) tokens in the pool * @param _y The amount of the OS tokens in the pool * @return k The invariant of the SwapX stable pool */ function _invariant(uint256 _x, uint256 _y) internal pure returns (uint256 k) { uint256 _a = (_x * _y) / PRECISION; uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); // slither-disable-next-line divide-before-multiply k = (_a * _b) / PRECISION; } /** * @dev Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can * keep rebalancing the pool in both directions making the protocol lose a tiny amount of * funds each time. * * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to * function. */ function _solvencyAssert() internal view { uint256 _totalVaultValue = IVault(vaultAddress).totalValue(); uint256 _totalSupply = IERC20(os).totalSupply(); if ( _totalSupply > 0 && _totalVaultValue.divPrecisely(_totalSupply) < SOLVENCY_THRESHOLD ) { revert("Protocol insolvent"); } } /*************************************** Setters ****************************************/ /** * @notice Set the maximum deviation from the OS/wS peg (1e18) before deposits are reverted. * @param _maxDepeg the OS/wS price from peg (1e18) in 18 decimals. * eg 0.01e18 or 1e16 is 1% which is 100 basis points. */ function setMaxDepeg(uint256 _maxDepeg) external onlyGovernor { maxDepeg = _maxDepeg; emit MaxDepegUpdated(_maxDepeg); } /*************************************** Approvals ****************************************/ /** * @notice Approve the spending of all assets by their corresponding pool tokens, * if for some reason is it necessary. */ function safeApproveAllTokens() external override onlyGovernor nonReentrant { _approveBase(); } // solhint-disable-next-line no-unused-vars function _abstractSetPToken(address _asset, address _pToken) internal override {} function _approveBase() internal { // Approve SwapX gauge contract to transfer SwapX pool LP tokens // This is needed for deposits of SwapX pool LP tokens into the gauge. // slither-disable-next-line unused-return IPair(pool).approve(address(gauge), type(uint256).max); } }
Bearbeitung
Datei öffnen
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; /** * @title Algebra Algorithmic Market Maker (AMO) Strategy * @notice AMO strategy for the Algebra stable swap pool * @author Origin Protocol Inc */ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; import { StableMath } from "../../utils/StableMath.sol"; import { sqrt } from "../../utils/PRBMath.sol"; import { IBasicToken } from "../../interfaces/IBasicToken.sol"; import { IPair } from "../../interfaces/algebra/IAlgebraPair.sol"; import { IGauge } from "../../interfaces/algebra/IAlgebraGauge.sol"; import { IVault } from "../../interfaces/IVault.sol"; contract StableSwapAMMStrategy is InitializableAbstractStrategy { using SafeERC20 for IERC20; using StableMath for uint256; using SafeCast for uint256; /** * @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 * draining the protocol funds. */ uint256 public constant SOLVENCY_THRESHOLD = 0.998 ether; /// @notice Precision for the Algebra Stable AMM (sAMM) invariant k. uint256 public constant PRECISION = 1e18; /// @notice Address of the asset (non OToken) token contract address public immutable asset; /// @notice Address of the OToken token contract. address public immutable oToken; /// @notice Address of the Algebra Stable pool contract. address public immutable pool; /// @notice Address of the Algebra Gauge contract. address public immutable gauge; /// @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. /// 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) /// allows a price range from 0.995 to 1.005. uint256 public maxDepeg; event SwapOTokensToPool( uint256 oTokenMinted, uint256 assetDepositAmount, uint256 oTokenDepositAmount, uint256 lpTokens ); event SwapAssetsToPool( uint256 assetSwapped, uint256 lpTokens, uint256 oTokenBurnt ); event MaxDepegUpdated(uint256 maxDepeg); /** * @dev Verifies that the caller is the Strategist of the Vault. */ modifier onlyStrategist() { require( msg.sender == IVault(vaultAddress).strategistAddr(), "Caller is not the Strategist" ); _; } /** * @dev Skim the Algebra pool in case any extra asset or OToken tokens were added */ modifier skimPool() { IPair(pool).skim(address(this)); _; } /** * @dev Checks the pool is balanced enough to allow deposits. */ modifier nearBalancedPool() { // OToken/asset price = asset / OToken // Get the OToken/asset price for selling 1 OToken for asset // As OToken is 1, the asset amount is the OToken/asset price uint256 sellPrice = IPair(pool).getAmountOut(1e18, oToken); // Get the amount of OToken received from selling 1 asset. This is buying OToken. uint256 oTokenAmount = IPair(pool).getAmountOut(1e18, asset); // Convert to a OToken/asset price = asset / OToken uint256 buyPrice = 1e36 / oTokenAmount; uint256 pegPrice = 1e18; require( sellPrice >= pegPrice - maxDepeg && buyPrice <= pegPrice + maxDepeg, "price out of range" ); _; } /** * @dev Checks the pool's balances have improved and the balances * have not tipped to the other side. * 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. */ modifier improvePoolBalance() { // Get the asset and OToken balances in the pool ( uint256 assetReserveBefore, uint256 oTokenReserveBefore ) = _getPoolReserves(); // diff = asset balance - OToken balance int256 diffBefore = assetReserveBefore.toInt256() - oTokenReserveBefore.toInt256(); _; // Get the asset and OToken balances in the pool ( uint256 assetReserveAfter, uint256 oTokenReserveAfter ) = _getPoolReserves(); // diff = asset balance - OToken balance int256 diffAfter = assetReserveAfter.toInt256() - oTokenReserveAfter.toInt256(); if (diffBefore == 0) { require(diffAfter == 0, "Position balance is worsened"); } else if (diffBefore < 0) { // If the pool was originally imbalanced in favor of OToken, then // we want to check that the pool is now more balanced require(diffAfter <= 0, "Assets overshot peg"); require(diffBefore < diffAfter, "OTokens balance worse"); } else if (diffBefore > 0) { // If the pool was originally imbalanced in favor of asset, then // we want to check that the pool is now more balanced require(diffAfter >= 0, "OTokens overshot peg"); require(diffAfter < diffBefore, "Assets balance worse"); } } /** * @param _baseConfig The `platformAddress` is the address of the Algebra pool. * The `vaultAddress` is the address of the Origin Sonic Vault. * @param _oToken Address of the OToken. * @param _asset Address of the asset token. * @param _gauge Address of the Algebra gauge for the pool. */ constructor( BaseStrategyConfig memory _baseConfig, address _oToken, address _asset, address _gauge ) InitializableAbstractStrategy(_baseConfig) { // Checked both tokens are to 18 decimals require( IBasicToken(_asset).decimals() == 18 && IBasicToken(_oToken).decimals() == 18, "Incorrect token decimals" ); // Check the Algebra pool is a Stable AMM (sAMM) require( IPair(_baseConfig.platformAddress).isStable() == true, "Pool not stable" ); // Check the gauge is for the pool require( IGauge(_gauge).TOKEN() == _baseConfig.platformAddress, "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 oToken = _oToken; asset = _asset; pool = _baseConfig.platformAddress; gauge = _gauge; // This is an implementation contract. The governor is set in the proxy contract. _setGovernor(address(0)); } /** * Initializer for setting up strategy internal state. This overrides the * InitializableAbstractStrategy initializer as Algebra strategies don't fit * well within that abstraction. * @param _rewardTokenAddresses Array containing SWPx token address * @param _maxDepeg The max amount the OToken/asset price can deviate from peg (1e18) before deposits are reverted. */ function initialize( address[] calldata _rewardTokenAddresses, uint256 _maxDepeg ) external onlyGovernor initializer { address[] memory pTokens = new address[](1); pTokens[0] = pool; address[] memory _assets = new address[](1); _assets[0] = asset; InitializableAbstractStrategy._initialize( _rewardTokenAddresses, _assets, pTokens ); maxDepeg = _maxDepeg; _approveBase(); } /*************************************** Deposit ****************************************/ /** * @notice Deposit an amount of asset into the Algebra pool. * Mint OToken in proportion to the pool's asset and OToken reserves, * transfer asset and OToken to the pool, * mint the pool's LP token and deposit in the gauge. * @dev This tx must be wrapped by the VaultValueChecker. * To minimize loses, the pool should be rebalanced before depositing. * The pool's oToken/asset price must be within the maxDepeg range. * @param _asset Address of asset token. * @param _assetAmount Amount of asset tokens to deposit. */ function deposit(address _asset, uint256 _assetAmount) external override onlyVault nonReentrant skimPool nearBalancedPool { require(_asset == asset, "Unsupported asset"); require(_assetAmount > 0, "Must deposit something"); (uint256 oTokenDepositAmount, ) = _deposit(_assetAmount); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the deposited asset tokens emit Deposit(asset, pool, _assetAmount); // Emit event for the minted OToken tokens emit Deposit(oToken, pool, oTokenDepositAmount); } /** * @notice Deposit all the strategy's asset tokens into the Algebra pool. * Mint OToken in proportion to the pool's asset and OToken reserves, * transfer asset and OToken to the pool, * mint the pool's LP token and deposit in the gauge. * @dev This tx must be wrapped by the VaultValueChecker. * To minimize loses, the pool should be rebalanced before depositing. * The pool's oToken/asset price must be within the maxDepeg range. */ function depositAll() external override onlyVault nonReentrant skimPool nearBalancedPool { uint256 assetBalance = IERC20(asset).balanceOf(address(this)); if (assetBalance > 0) { (uint256 oTokenDepositAmount, ) = _deposit(assetBalance); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the deposited asset tokens emit Deposit(asset, pool, assetBalance); // Emit event for the minted OToken tokens emit Deposit(oToken, pool, oTokenDepositAmount); } } /** * @dev Mint OToken in proportion to the pool's asset and OToken reserves, * transfer asset and OToken to the pool, * mint the pool's LP token and deposit in the gauge. * @param _assetAmount Amount of asset tokens to deposit. * @return oTokenDepositAmount Amount of OToken tokens minted and deposited into the pool. * @return lpTokens Amount of Algebra pool LP tokens minted and deposited into the gauge. */ function _deposit(uint256 _assetAmount) internal returns (uint256 oTokenDepositAmount, uint256 lpTokens) { // Calculate the required amount of OToken to mint based on the asset amount. oTokenDepositAmount = _calcTokensToMint(_assetAmount); // Mint the required OToken tokens to this strategy IVault(vaultAddress).mintForStrategy(oTokenDepositAmount); // Add asset and OToken liquidity to the pool and stake in gauge lpTokens = _depositToPoolAndGauge(_assetAmount, oTokenDepositAmount); } /*************************************** Withdraw ****************************************/ /** * @notice Withdraw asset and OToken from the Algebra pool, burn the OToken, * and transfer the asset to the recipient. * @param _recipient Address of the Vault. * @param _asset Address of the asset token. * @param _assetAmount Amount of asset tokens to withdraw. */ function withdraw( address _recipient, address _asset, uint256 _assetAmount ) external override onlyVault nonReentrant skimPool { require(_assetAmount > 0, "Must withdraw something"); require(_asset == asset, "Unsupported asset"); // This strategy can't be set as a default strategy for asset in the Vault. // This means the recipient must always be the Vault. require(_recipient == vaultAddress, "Only withdraw to vault allowed"); // Calculate how much pool LP tokens to burn to get the required amount of asset tokens back uint256 lpTokens = _calcTokensToBurn(_assetAmount); // Withdraw pool LP tokens from the gauge and remove assets from from the pool _withdrawFromGaugeAndPool(lpTokens); // Burn all the removed OToken and any that was left in the strategy uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(oTokenToBurn); // Transfer asset to the recipient // 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 require( IERC20(asset).balanceOf(address(this)) >= _assetAmount, "Not enough asset removed" ); IERC20(asset).safeTransfer(_recipient, _assetAmount); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the withdrawn asset tokens emit Withdrawal(asset, pool, _assetAmount); // Emit event for the burnt OToken tokens emit Withdrawal(oToken, pool, oTokenToBurn); } /** * @notice Withdraw all pool LP tokens from the gauge, * remove all asset and OToken from the Algebra pool, * burn all the OToken, * and transfer all the asset to the Vault contract. * @dev There is no solvency check here as withdrawAll can be called to * quickly secure assets to the Vault in emergencies. */ function withdrawAll() external override onlyVaultOrGovernor nonReentrant skimPool { // Get all the pool LP tokens the strategy has staked in the gauge uint256 lpTokens = IGauge(gauge).balanceOf(address(this)); // Can not withdraw zero LP tokens from the gauge if (lpTokens == 0) return; if (IGauge(gauge).emergency()) { // The gauge is in emergency mode _emergencyWithdrawFromGaugeAndPool(); } else { // Withdraw pool LP tokens from the gauge and remove assets from from the pool _withdrawFromGaugeAndPool(lpTokens); } // Burn all OToken in this strategy contract uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(oTokenToBurn); // Get the strategy contract's asset balance. // This includes all that was removed from the Algebra pool and // any that was sitting in the strategy contract before the removal. uint256 assetBalance = IERC20(asset).balanceOf(address(this)); IERC20(asset).safeTransfer(vaultAddress, assetBalance); // Emit event for the withdrawn asset tokens emit Withdrawal(asset, pool, assetBalance); // Emit event for the burnt OToken tokens emit Withdrawal(oToken, pool, oTokenToBurn); } /*************************************** Pool Rebalancing ****************************************/ /** @notice Used when there is more OToken than asset in the pool. * asset and OToken is removed from the pool, the received asset is swapped for OToken * and the left over OToken in the strategy is burnt. * The OToken/asset price is < 1.0 so OToken is being bought at a discount. * @param _assetAmount Amount of asset tokens to swap into the pool. */ function swapAssetsToPool(uint256 _assetAmount) external onlyStrategist nonReentrant improvePoolBalance skimPool { require(_assetAmount > 0, "Must swap something"); // 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 asset tokens back uint256 lpTokens = _calcTokensToBurn(_assetAmount); require(lpTokens > 0, "No LP tokens to burn"); _withdrawFromGaugeAndPool(lpTokens); // 2. Swap asset for OToken against the pool // Swap exact amount of asset for OToken against the pool // 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(_assetAmount, asset, oToken); // 3. Burn all the OToken left in the strategy from the remove liquidity and swap uint256 oTokenToBurn = IERC20(oToken).balanceOf(address(this)); IVault(vaultAddress).burnForStrategy(oTokenToBurn); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the burnt OToken tokens emit Withdrawal(oToken, pool, oTokenToBurn); // Emit event for the swap emit SwapAssetsToPool(_assetAmount, lpTokens, oTokenToBurn); } /** * @notice Used when there is more asset than OToken in the pool. * OToken is minted and swapped for asset against the pool, * more OToken is minted and added back into the pool with the swapped out asset. * The OToken/asset price is > 1.0 so OToken is being sold at a premium. * @param _oTokenAmount Amount of OToken to swap into the pool. */ function swapOTokensToPool(uint256 _oTokenAmount) external onlyStrategist nonReentrant improvePoolBalance skimPool { require(_oTokenAmount > 0, "Must swap something"); // 1. Mint OToken so it can be swapped into the pool // There can be OToken in the strategy from skimming the pool uint256 oTokenInStrategy = IERC20(oToken).balanceOf(address(this)); require( _oTokenAmount >= oTokenInStrategy, "Too much OToken in strategy" ); uint256 oTokenToMint = _oTokenAmount - oTokenInStrategy; // Mint the required OToken tokens to this strategy IVault(vaultAddress).mintForStrategy(oTokenToMint); // 2. Swap OToken for asset against the pool _swapExactTokensForTokens(_oTokenAmount, oToken, asset); // The asset is from the swap and any asset that was sitting in the strategy uint256 assetDepositAmount = IERC20(asset).balanceOf(address(this)); // 3. Add asset and OToken back to the pool in proportion to the pool's reserves (uint256 oTokenDepositAmount, uint256 lpTokens) = _deposit( assetDepositAmount ); // Ensure solvency of the vault _solvencyAssert(); // Emit event for the minted OToken tokens emit Deposit(oToken, pool, oTokenToMint + oTokenDepositAmount); // Emit event for the swap emit SwapOTokensToPool( oTokenToMint, assetDepositAmount, oTokenDepositAmount, lpTokens ); } /*************************************** Assets and Rewards ****************************************/ /** * @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. * This way the value can not be manipulated by changing the pool's token balances. * @param _asset Address of the asset token * @return balance Total value in asset. */ function checkBalance(address _asset) external view override returns (uint256 balance) { require(_asset == asset, "Unsupported asset"); // asset balance needed here for the balance check that happens from vault during depositing. balance = IERC20(asset).balanceOf(address(this)); // This assumes 1 gauge LP token = 1 pool LP token uint256 lpTokens = IGauge(gauge).balanceOf(address(this)); if (lpTokens == 0) return balance; // Add the strategy’s share of the asset and OToken tokens in the Algebra pool if the pool was balanced. balance += _lpValue(lpTokens); } /** * @notice Returns bool indicating whether asset is supported by strategy * @param _asset Address of the asset */ function supportsAsset(address _asset) public view override returns (bool) { return _asset == asset; } /** * @notice Collect accumulated SWPx (and other) rewards and send to the Harvester. */ function collectRewardTokens() external override onlyHarvester nonReentrant { // Collect SWPx rewards from the gauge IGauge(gauge).getReward(); _collectRewardTokens(); } /*************************************** Internal Algebra Pool and Gauge Functions ****************************************/ /** * @dev Calculate the required amount of OToken to mint based on the asset amount. * This ensures the proportion of OToken tokens being added to the pool matches the proportion of asset tokens. * For example, if the added asset tokens is 10% of existing asset tokens in the pool, * then the OToken tokens being added should also be 10% of the OToken tokens in the pool. * @param _assetAmount Amount of asset tokens to be added to the pool. * @return oTokenAmount Amount of OToken tokens to be minted and added to the pool. */ function _calcTokensToMint(uint256 _assetAmount) internal view returns (uint256 oTokenAmount) { (uint256 assetReserves, uint256 oTokenReserves) = _getPoolReserves(); require(assetReserves > 0, "Empty pool"); // OToken to add = (asset being added * OToken in pool) / asset in pool oTokenAmount = (_assetAmount * oTokenReserves) / assetReserves; } /** * @dev Calculate how much pool LP tokens to burn to get the required amount of asset tokens back * from the pool. * @param _assetAmount Amount of asset tokens to be removed from the pool. * @return lpTokens Amount of Algebra pool LP tokens to burn. */ function _calcTokensToBurn(uint256 _assetAmount) internal view returns (uint256 lpTokens) { /* The Algebra pool proportionally returns the reserve tokens when removing liquidity. * 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. * 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. * * Because we are doing balanced removal we should be making profit when removing liquidity in a * pool tilted to either side. * * 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 * is being voted on, the pool tilt might change so much that the proposal that has been valid while * created is no longer valid. */ (uint256 assetReserves, ) = _getPoolReserves(); require(assetReserves > 0, "Empty pool"); lpTokens = (_assetAmount * IPair(pool).totalSupply()) / assetReserves; lpTokens += 1; // Add 1 to ensure we get enough LP tokens with rounding } /** * @dev Deposit asset and OToken liquidity to the Algebra pool * and stake the pool's LP token in the gauge. * @param _assetAmount Amount of asset to deposit. * @param _oTokenAmount Amount of OToken to deposit. * @return lpTokens Amount of Algebra pool LP tokens minted. */ function _depositToPoolAndGauge(uint256 _assetAmount, uint256 _oTokenAmount) internal returns (uint256 lpTokens) { // Transfer asset to the pool IERC20(asset).safeTransfer(pool, _assetAmount); // Transfer OToken to the pool IERC20(oToken).safeTransfer(pool, _oTokenAmount); // Mint LP tokens from the pool lpTokens = IPair(pool).mint(address(this)); // Deposit the pool's LP tokens into the gauge IGauge(gauge).deposit(lpTokens); } /** * @dev Withdraw pool LP tokens from the gauge and remove asset and OToken from the pool. * @param _lpTokens Amount of Algebra 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 asset and OToken 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 asset and OToken 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 asset and OToken back to the strategy IPair(pool).burn(address(this)); } /** * @dev Swap exact amount of tokens for another token against the pool. * @param _amountIn Amount of tokens to swap into the pool. * @param _tokenIn Address of the token going into the pool. * @param _tokenOut Address of the token being swapped out of the pool. */ function _swapExactTokensForTokens( uint256 _amountIn, address _tokenIn, address _tokenOut ) internal { // Transfer in tokens to the pool IERC20(_tokenIn).safeTransfer(pool, _amountIn); // Calculate how much out tokens we get from the swap uint256 amountOut = IPair(pool).getAmountOut(_amountIn, _tokenIn); // Safety check that we are dealing with the correct pool tokens require( (_tokenIn == asset && _tokenOut == oToken) || (_tokenIn == oToken && _tokenOut == asset), "Unsupported swap" ); uint256 amount0; uint256 amount1; // Work out the correct order of the amounts for the pool if (_tokenIn == asset) { if (oTokenPoolIndex == 0) { amount0 = amountOut; amount1 = 0; } else { amount0 = 0; amount1 = amountOut; } } else { if (oTokenPoolIndex == 0) { amount0 = 0; amount1 = amountOut; } else { amount0 = amountOut; amount1 = 0; } } // Perform the swap on the pool IPair(pool).swap(amount0, amount1, address(this), new bytes(0)); // The slippage protection against the amount out is indirectly done // via the improvePoolBalance } /// @dev Calculate the value of a LP position in a Algebra stable pool /// if the pool was balanced. /// @param _lpTokens Amount of LP tokens in the Algebra pool /// @return value The asset value of the LP tokens when the pool is balanced function _lpValue(uint256 _lpTokens) internal view returns (uint256 value) { // Get total supply of LP tokens uint256 totalSupply = IPair(pool).totalSupply(); if (totalSupply == 0) return 0; // Get the current reserves of the pool (uint256 assetReserves, uint256 oTokenReserves) = _getPoolReserves(); // Calculate the invariant of the pool assuming both tokens have 18 decimals. // k is scaled to 18 decimals. uint256 k = _invariant(assetReserves, oTokenReserves); // If x = y, let’s denote x = y = z (where z is the common reserve value) // Substitute z into the invariant: // k = z^3 * z + z * z^3 // k = 2 * z^4 // Going back the other way to calculate the common reserve value z // z = (k / 2) ^ (1/4) // the total value of the pool when x = y is 2 * z, which is 2 * (k / 2) ^ (1/4) uint256 zSquared = sqrt((k * 1e18) / 2); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt uint256 z = sqrt(zSquared * 1e18); // 18 + 18 = 36 decimals becomes 18 decimals after sqrt uint256 totalValueOfPool = 2 * z; // lp value = lp tokens * value of pool / total supply value = (_lpTokens * totalValueOfPool) / totalSupply; } /** * @dev Compute the invariant for a Algebra stable pool. * This assumed both x and y tokens are to 18 decimals which is checked in the constructor. * invariant: k = x^3 * y + x * y^3 * @dev This implementation is copied from Algebra's Pair contract. * @param _x The amount of asset tokens in the pool * @param _y The amount of the OToken tokens in the pool * @return k The invariant of the Algebra stable pool */ function _invariant(uint256 _x, uint256 _y) internal pure returns (uint256 k) { uint256 _a = (_x * _y) / PRECISION; uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); // slither-disable-next-line divide-before-multiply k = (_a * _b) / PRECISION; } /** * @dev Checks that the protocol is solvent, protecting from a rogue Strategist / Guardian that can * keep rebalancing the pool in both directions making the protocol lose a tiny amount of * funds each time. * * Protocol must be at least SOLVENCY_THRESHOLD (99,8 %) backed in order for the rebalances to * function. */ function _solvencyAssert() internal view { uint256 _totalVaultValue = IVault(vaultAddress).totalValue(); uint256 _totalSupply = IERC20(oToken).totalSupply(); if ( _totalSupply > 0 && _totalVaultValue.divPrecisely(_totalSupply) < SOLVENCY_THRESHOLD ) { revert("Protocol insolvent"); } } /** * @dev Get the reserves of the pool no matter the order of tokens in the underlying * Algebra pool. * @return assetReserves The reserves of the asset token in the pool. * @return oTokenReserves The reserves of the OToken token in the pool. */ function _getPoolReserves() internal view returns (uint256 assetReserves, uint256 oTokenReserves) { (uint256 reserve0, uint256 reserve1, ) = IPair(pool).getReserves(); assetReserves = oTokenPoolIndex == 0 ? reserve1 : reserve0; oTokenReserves = oTokenPoolIndex == 0 ? reserve0 : reserve1; } /*************************************** Setters ****************************************/ /** * @notice Set the maximum deviation from the OToken/asset peg (1e18) before deposits are reverted. * @param _maxDepeg the OToken/asset price from peg (1e18) in 18 decimals. * eg 0.01e18 or 1e16 is 1% which is 100 basis points. */ function setMaxDepeg(uint256 _maxDepeg) external onlyGovernor { maxDepeg = _maxDepeg; emit MaxDepegUpdated(_maxDepeg); } /*************************************** Approvals ****************************************/ /** * @notice Approve the spending of all assets by their corresponding pool tokens, * if for some reason is it necessary. */ function safeApproveAllTokens() external override onlyGovernor nonReentrant { _approveBase(); } // solhint-disable-next-line no-unused-vars function _abstractSetPToken(address _asset, address _pToken) internal override {} function _approveBase() internal { // Approve Algebra gauge contract to transfer Algebra pool LP tokens // This is needed for deposits of Algebra pool LP tokens into the gauge. // slither-disable-next-line unused-return IPair(pool).approve(address(gauge), type(uint256).max); } }
Unterschied finden