Diff
checker
テキスト
テキスト
画像
ドキュメント
Excel
フォルダ
Legal
Enterprise
デスクトップ
料金
ログイン
Diffchecker デスクトップのダウンロード
テキスト比較
2 つのテキスト ファイルの違いを見つける
ツール
履歴
ライブエディター
空白の変更を非表示
未変更行を折りたたむ
折り返しなし
レイアウト
分割
統合
比較精度
スマート
単語
文字
テキストスタイル
外観を変更
シンタックスハイライト
構文を選択
無視
テキスト変換
最初の差分へ移動
入力を編集
Diffchecker Desktop
Diffcheckerを実行する最も安全な方法。Diffchecker Desktopアプリを入手:あなたの差分はコンピューターから出ることはありません!
Desktopを入手
SonicSwapXAMOStrategy vs StableSwapAMMStrategy
作成日
5 か月前
差分は期限切れになりません
クリア
エクスポート
共有
説明
394 削除
行
合計
削除
文字
合計
削除
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
667 行
すべてコピー
369 追加
行
合計
追加
文字
合計
追加
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
646 行
すべてコピー
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
/**
/**
コピー
コピー済み
コピー
コピー済み
* @title
SwapX
Algorithmic Market Maker (AMO) Strategy
* @title
Algebra
Algorithmic Market Maker (AMO) Strategy
* @notice AMO strategy for the
SwapX OS/wS
stable
pool
* @notice AMO strategy for the
Algebra
stable
swap
pool
* @author Origin Protocol Inc
* @author Origin Protocol Inc
*/
*/
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { StableMath } from "../../utils/StableMath.sol";
import { StableMath } from "../../utils/StableMath.sol";
import { sqrt } from "../../utils/PRBMath.sol";
import { sqrt } from "../../utils/PRBMath.sol";
import { IBasicToken } from "../../interfaces/IBasicToken.sol";
import { IBasicToken } from "../../interfaces/IBasicToken.sol";
コピー
コピー済み
コピー
コピー済み
import { IPair } from "../../interfaces/
sonic/ISwapXPair
.sol";
import { IPair } from "../../interfaces/
algebra/IAlgebraPair
.sol";
import { IGauge } from "../../interfaces/
sonic/ISwapXGauge
.sol";
import { IGauge } from "../../interfaces/
algebra/IAlgebraGauge
.sol";
import { IVault } from "../../interfaces/IVault.sol";
import { IVault } from "../../interfaces/IVault.sol";
コピー
コピー済み
コピー
コピー済み
contract 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;
コピー
コピー済み
コピー
コピー済み
/// @notice Precision for the
SwapX
Stable AMM (sAMM) invariant k.
/// @notice Precision for the
Algebra
Stable AMM (sAMM) invariant k.
uint256 public constant PRECISION = 1e18;
uint256 public constant PRECISION = 1e18;
コピー
コピー済み
コピー
コピー済み
/// @notice Address of the
Wrapped S (wS
) token
.
/// @notice Address of the
asset (non OToken
) token
contract
address public immutable
ws
;
address public immutable
asset
;
コピー
コピー済み
コピー
コピー済み
/// @notice Address of the
OS
token contract.
/// @notice Address of the
OToken
token contract.
address public immutable
os
;
address public immutable
oToken
;
コピー
コピー済み
コピー
コピー済み
/// @notice Address of the
SwapX
Stable pool contract.
/// @notice Address of the
Algebra
Stable pool contract.
address public immutable pool;
address public immutable pool;
コピー
コピー済み
コピー
コピー済み
/// @notice Address of the
SwapX
Gauge contract.
/// @notice Address of the
Algebra
Gauge contract.
address public immutable gauge;
address public immutable gauge;
コピー
コピー済み
コピー
コピー済み
/// @notice The max amount the
OS/wS
price can deviate from peg (1e18)
/// @notice Index of the OToken in the Algebra pool.
uint256 public immutable oTokenPoolIndex;
/// @notice The max amount the
OToken/asset
price can deviate from peg (1e18)
/// before deposits are reverted scaled to 18 decimals.
/// before deposits are reverted scaled to 18 decimals.
/// eg 0.01e18 or 1e16 is 1% which is 100 basis points.
/// eg 0.01e18 or 1e16 is 1% which is 100 basis points.
/// This is the amount below and above peg so a 50 basis point deviation (0.005e18)
/// This is the amount below and above peg so a 50 basis point deviation (0.005e18)
/// allows a price range from 0.995 to 1.005.
/// allows a price range from 0.995 to 1.005.
uint256 public maxDepeg;
uint256 public maxDepeg;
event SwapOTokensToPool(
event SwapOTokensToPool(
コピー
コピー済み
コピー
コピー済み
uint256 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(
コピー
コピー済み
コピー
コピー済み
uint256
ws
Swapped,
uint256
asset
Swapped,
uint256 lpTokens,
uint256 lpTokens,
コピー
コピー済み
コピー
コピー済み
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"
);
);
_;
_;
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @dev Skim the
SwapX
pool in case any extra
wS
or
OS
tokens were added
* @dev Skim the
Algebra
pool in case any extra
asset
or
OToken
tokens were added
*/
*/
modifier skimPool() {
modifier skimPool() {
IPair(pool).skim(address(this));
IPair(pool).skim(address(this));
_;
_;
}
}
/**
/**
* @dev Checks the pool is balanced enough to allow deposits.
* @dev Checks the pool is balanced enough to allow deposits.
*/
*/
modifier nearBalancedPool() {
modifier nearBalancedPool() {
コピー
コピー済み
コピー
コピー済み
//
OS/wS
price =
wS
/
OS
//
OToken/asset
price =
asset
/
OToken
// Get the
OS/wS
price for selling 1
OS
for
wS
// Get the
OToken/asset
price for selling 1
OToken
for
asset
// As
OS
is 1, the
wS
amount is the
OS/wS
price
// As
OToken
is 1, the
asset
amount is the
OToken/asset
price
uint256 sellPrice = IPair(pool).getAmountOut(1e18,
os
);
uint256 sellPrice = IPair(pool).getAmountOut(1e18,
oToken
);
コピー
コピー済み
コピー
コピー済み
// Get the amount of
OS
received from selling 1
wS
. This is buying
OS
.
// Get the amount of
OToken
received from selling 1
asset
. This is buying
OToken
.
uint256 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
コピー
コピー済み
コピー
コピー済み
(
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
コピー
コピー済み
コピー
コピー済み
(
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) {
コピー
コピー済み
コピー
コピー済み
// If the pool was originally imbalanced in favor of
OS
, then
// If the pool was originally imbalanced in favor of
OToken
, then
// we want to check that the pool is now more balanced
// we want to check that the pool is now more balanced
require(diffAfter <= 0, "Assets overshot peg");
require(diffAfter <= 0, "Assets overshot peg");
require(diffBefore < diffAfter, "OTokens balance worse");
require(diffBefore < diffAfter, "OTokens balance worse");
} else if (diffBefore > 0) {
} else if (diffBefore > 0) {
コピー
コピー済み
コピー
コピー済み
// If the pool was originally imbalanced in favor of
wS
, then
// If the pool was originally imbalanced in favor of
asset
, then
// we want to check that the pool is now more balanced
// we want to check that the pool is now more balanced
require(diffAfter >= 0, "OTokens overshot peg");
require(diffAfter >= 0, "OTokens overshot peg");
require(diffAfter < diffBefore, "Assets balance worse");
require(diffAfter < diffBefore, "Assets balance worse");
}
}
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @param _baseConfig The `platformAddress` is the address of the
SwapX
pool.
* @param _baseConfig The `platformAddress` is the address of the
Algebra
pool.
* The `vaultAddress` is the address of the Origin Sonic Vault.
* The `vaultAddress` is the address of the Origin Sonic Vault.
コピー
コピー済み
コピー
コピー済み
* @param _
os
Address of the 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,
コピー
コピー済み
コピー
コピー済み
address _
os
,
address _
oToken
,
address _
ws
,
address _
asset
,
address _gauge
address _gauge
) InitializableAbstractStrategy(_baseConfig) {
) InitializableAbstractStrategy(_baseConfig) {
コピー
コピー済み
コピー
コピー済み
// Check the pool tokens are correct
require(
IPair(_baseConfig.platformAddress).token0() == _ws &&
IPair(_baseConfig.platformAddress).token1() == _os,
"Incorrect pool tokens"
);
// Checked both tokens are to 18 decimals
// Checked both tokens are to 18 decimals
require(
require(
コピー
コピー済み
コピー
コピー済み
IBasicToken(_
ws
).decimals() == 18 &&
IBasicToken(_
asset
).decimals() == 18 &&
IBasicToken(_
os
).decimals() == 18,
IBasicToken(_
oToken
).decimals() == 18,
"Incorrect token decimals"
"Incorrect token decimals"
);
);
コピー
コピー済み
コピー
コピー済み
// Check the
SwapX
pool is a Stable AMM (sAMM)
// Check the
Algebra
pool is a Stable AMM (sAMM)
require(
require(
IPair(_baseConfig.platformAddress).isStable() == true,
IPair(_baseConfig.platformAddress).isStable() == true,
"Pool not stable"
"Pool not stable"
);
);
// Check the gauge is for the pool
// Check the gauge is for the pool
require(
require(
IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,
IGauge(_gauge).TOKEN() == _baseConfig.platformAddress,
"Incorrect gauge"
"Incorrect gauge"
);
);
コピー
コピー済み
コピー
コピー済み
oTokenPoolIndex = IPair(_baseConfig.platformAddress).token0() == _oToken
? 0
: 1;
// Check the pool tokens are correct
require(
IPair(_baseConfig.platformAddress).token0() ==
(oTokenPoolIndex == 0 ? _oToken : _asset) &&
IPair(_baseConfig.platformAddress).token1() ==
(oTokenPoolIndex == 0 ? _asset : _oToken),
"Incorrect pool tokens"
);
// Set the immutable variables
// Set the immutable variables
コピー
コピー済み
コピー
コピー済み
os
= _
os
;
oToken
= _
oToken
;
ws
= _
ws
;
asset
= _
asset
;
pool = _baseConfig.platformAddress;
pool = _baseConfig.platformAddress;
gauge = _gauge;
gauge = _gauge;
// This is an implementation contract. The governor is set in the proxy contract.
// This is an implementation contract. The governor is set in the proxy contract.
_setGovernor(address(0));
_setGovernor(address(0));
}
}
/**
/**
* Initializer for setting up strategy internal state. This overrides the
* Initializer for setting up strategy internal state. This overrides the
コピー
コピー済み
コピー
コピー済み
* InitializableAbstractStrategy initializer as
SwapX
strategies don't fit
* InitializableAbstractStrategy initializer as
Algebra
strategies don't fit
* well within that abstraction.
* well within that abstraction.
* @param _rewardTokenAddresses Array containing SWPx token address
* @param _rewardTokenAddresses Array containing SWPx token address
コピー
コピー済み
コピー
コピー済み
* @param _maxDepeg The max amount the
OS/wS
price can deviate from peg (1e18) before deposits are reverted.
* @param _maxDepeg The max amount the
OToken/asset
price can deviate from peg (1e18) before deposits are reverted.
*/
*/
function initialize(
function initialize(
address[] calldata _rewardTokenAddresses,
address[] calldata _rewardTokenAddresses,
uint256 _maxDepeg
uint256 _maxDepeg
) external onlyGovernor initializer {
) external onlyGovernor initializer {
address[] memory pTokens = new address[](1);
address[] memory pTokens = new address[](1);
pTokens[0] = pool;
pTokens[0] = pool;
address[] memory _assets = new address[](1);
address[] memory _assets = new address[](1);
コピー
コピー済み
コピー
コピー済み
_assets[0] =
ws
;
_assets[0] =
asset
;
InitializableAbstractStrategy._initialize(
InitializableAbstractStrategy._initialize(
_rewardTokenAddresses,
_rewardTokenAddresses,
_assets,
_assets,
pTokens
pTokens
);
);
maxDepeg = _maxDepeg;
maxDepeg = _maxDepeg;
_approveBase();
_approveBase();
}
}
/***************************************
/***************************************
Deposit
Deposit
****************************************/
****************************************/
/**
/**
コピー
コピー済み
コピー
コピー済み
* @notice Deposit an amount of
Wrapped S (wS)
into the
SwapX
pool.
* @notice Deposit an amount of
asset
into the
Algebra
pool.
* Mint
OS
in proportion to the pool's
wS
and
OS
reserves,
* Mint
OToken
in proportion to the pool's
asset
and
OToken
reserves,
* transfer
Wrapped S (wS)
and
OS
to the pool,
* transfer
asset
and
OToken
to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
* @dev This tx must be wrapped by the VaultValueChecker.
* @dev This tx must be wrapped by the VaultValueChecker.
* To minimize loses, the pool should be rebalanced before depositing.
* To minimize loses, the pool should be rebalanced before depositing.
コピー
コピー済み
コピー
コピー済み
* The pool's
OS/wS
price must be within the maxDepeg range.
* The pool's
oToken/asset
price must be within the maxDepeg range.
* @param _asset Address of
Wrapped S (wS)
token.
* @param _asset Address of
asset
token.
* @param _
ws
Amount Amount of
Wrapped S (wS)
tokens to deposit.
* @param _
asset
Amount Amount of
asset
tokens to deposit.
*/
*/
コピー
コピー済み
コピー
コピー済み
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
{
{
コピー
コピー済み
コピー
コピー済み
require(_asset ==
ws
, "Unsupported asset");
require(_asset ==
asset
, "Unsupported asset");
require(_
ws
Amount > 0, "Must deposit something");
require(_
asset
Amount > 0, "Must deposit something");
コピー
コピー済み
コピー
コピー済み
(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();
コピー
コピー済み
コピー
コピー済み
// 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);
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @notice Deposit all the strategy's
Wrapped S (wS)
tokens into the
SwapX
pool.
* @notice Deposit all the strategy's
asset
tokens into the
Algebra
pool.
* Mint
OS
in proportion to the pool's
wS
and
OS
reserves,
* Mint
OToken
in proportion to the pool's
asset
and
OToken
reserves,
* transfer
Wrapped S (wS)
and
OS
to the pool,
* transfer
asset
and
OToken
to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
* @dev This tx must be wrapped by the VaultValueChecker.
* @dev This tx must be wrapped by the VaultValueChecker.
* To minimize loses, the pool should be rebalanced before depositing.
* To minimize loses, the pool should be rebalanced before depositing.
コピー
コピー済み
コピー
コピー済み
* The pool's
OS/wS
price must be within the maxDepeg range.
* The pool's
oToken/asset
price must be within the maxDepeg range.
*/
*/
function depositAll()
function depositAll()
external
external
override
override
onlyVault
onlyVault
nonReentrant
nonReentrant
skimPool
skimPool
nearBalancedPool
nearBalancedPool
{
{
コピー
コピー済み
コピー
コピー済み
uint256
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();
コピー
コピー済み
コピー
コピー済み
// 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);
}
}
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @dev Mint
OS
in proportion to the pool's
wS
and
OS
reserves,
* @dev Mint
OToken
in proportion to the pool's
asset
and
OToken
reserves,
* transfer
Wrapped S (wS)
and
OS
to the pool,
* transfer
asset
and
OToken
to the pool,
* mint the pool's LP token and deposit in the gauge.
* mint the pool's LP token and deposit in the gauge.
コピー
コピー済み
コピー
コピー済み
* @param _
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.
*/
*/
コピー
コピー済み
コピー
コピー済み
function _deposit(uint256 _
ws
Amount)
function _deposit(uint256 _
asset
Amount)
internal
internal
コピー
コピー済み
コピー
コピー済み
returns (uint256 o
s
DepositAmount, uint256 lpTokens)
returns (uint256 o
Token
DepositAmount, uint256 lpTokens)
{
{
コピー
コピー済み
コピー
コピー済み
// Calculate the required amount of
OS
to mint based on the
wS
amount.
// Calculate the required amount of
OToken
to mint based on the
asset
amount.
o
s
DepositAmount = _calcTokensToMint(_
ws
Amount);
o
Token
DepositAmount = _calcTokensToMint(_
asset
Amount);
コピー
コピー済み
コピー
コピー済み
// 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);
コピー
コピー済み
コピー
コピー済み
// 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
****************************************/
****************************************/
/**
/**
コピー
コピー済み
コピー
コピー済み
* @notice Withdraw
wS
and
OS
from the
SwapX
pool, burn the
OS
,
* @notice Withdraw
asset
and
OToken
from the
Algebra
pool, burn the
OToken
,
* and transfer the
wS
to the recipient.
* and transfer the
asset
to the recipient.
* @param _recipient Address of the Vault.
* @param _recipient Address of the Vault.
コピー
コピー済み
コピー
コピー済み
* @param _asset Address of the
Wrapped S (wS) contract
.
* @param _asset Address of the
asset token
.
* @param _
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,
コピー
コピー済み
コピー
コピー済み
uint256 _
ws
Amount
uint256 _
asset
Amount
) external override onlyVault nonReentrant skimPool {
) external override onlyVault nonReentrant skimPool {
コピー
コピー済み
コピー
コピー済み
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");
コピー
コピー済み
コピー
コピー済み
// 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);
コピー
コピー済み
コピー
コピー済み
// 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);
コピー
コピー済み
コピー
コピー済み
// Transfer
wS
to the recipient
// Transfer
asset
to the recipient
// Note there can be a dust amount of
wS
left in the strategy as
// Note there can be a dust amount of
asset
left in the strategy as
// the burn of the pool's LP tokens is rounded up
// the burn of the pool's LP tokens is rounded up
require(
require(
コピー
コピー済み
コピー
コピー済み
IERC20(
ws
).balanceOf(address(this)) >= _
ws
Amount,
IERC20(
asset
).balanceOf(address(this)) >= _
asset
Amount,
"Not enough
wS
removed
from pool
"
"Not enough
asset
removed
"
);
);
コピー
コピー済み
コピー
コピー済み
IERC20(
ws
).safeTransfer(_recipient, _
ws
Amount);
IERC20(
asset
).safeTransfer(_recipient, _
asset
Amount);
// Ensure solvency of the vault
// Ensure solvency of the vault
_solvencyAssert();
_solvencyAssert();
コピー
コピー済み
コピー
コピー済み
// Emit event for the withdrawn
wS
tokens
// Emit event for the withdrawn
asset
tokens
emit Withdrawal(
ws
, pool, _
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,
コピー
コピー済み
コピー
コピー済み
* 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);
}
}
コピー
コピー済み
コピー
コピー済み
// 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);
コピー
コピー済み
コピー
コピー済み
// Get the strategy contract's
wS
balance.
// Get the strategy contract's
asset
balance.
// This includes all that was removed from the
SwapX
pool and
// This includes all that was removed from the
Algebra
pool and
// any that was sitting in the strategy contract before the removal.
// any that was sitting in the strategy contract before the removal.
コピー
コピー済み
コピー
コピー済み
uint256
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);
コピー
コピー済み
コピー
コピー済み
// 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
****************************************/
****************************************/
コピー
コピー済み
コピー
コピー済み
/** @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.
*/
*/
コピー
コピー済み
コピー
コピー済み
function swapAssetsToPool(uint256 _
ws
Amount)
function swapAssetsToPool(uint256 _
asset
Amount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
コピー
コピー済み
コピー
コピー済み
require(_
ws
Amount > 0, "Must swap something");
require(_
asset
Amount > 0, "Must swap something");
コピー
コピー済み
コピー
コピー済み
// 1. Partially remove liquidity so there’s enough
wS
for the swap
// 1. Partially remove liquidity so there’s enough
asset
for the swap
コピー
コピー済み
コピー
コピー済み
// Calculate how much pool LP tokens to burn to get the required amount of
wS
tokens back
// Calculate how much pool LP tokens to burn to get the required amount of
asset
tokens back
uint256 lpTokens = _calcTokensToBurn(_
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);
コピー
コピー済み
コピー
コピー済み
// 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
);
コピー
コピー済み
コピー
コピー済み
// 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();
コピー
コピー済み
コピー
コピー済み
// 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
コピー
コピー済み
コピー
コピー済み
emit SwapAssetsToPool(_
ws
Amount, lpTokens, o
s
ToBurn);
emit SwapAssetsToPool(_
asset
Amount, lpTokens, o
Token
ToBurn);
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @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.
*/
*/
コピー
コピー済み
コピー
コピー済み
function swapOTokensToPool(uint256 _o
s
Amount)
function swapOTokensToPool(uint256 _o
Token
Amount)
external
external
onlyStrategist
onlyStrategist
nonReentrant
nonReentrant
improvePoolBalance
improvePoolBalance
skimPool
skimPool
{
{
コピー
コピー済み
コピー
コピー済み
require(_o
s
Amount > 0, "Must swap something");
require(_o
Token
Amount > 0, "Must swap something");
コピー
コピー済み
コピー
コピー済み
// 1. Mint
OS
so it can be swapped into the pool
// 1. Mint
OToken
so it can be swapped into the pool
コピー
コピー済み
コピー
コピー済み
// There can be
OS
in the strategy from skimming the pool
// There can be
OToken
in the strategy from skimming the pool
uint256 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;
コピー
コピー済み
コピー
コピー済み
// 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);
コピー
コピー済み
コピー
コピー済み
// 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
);
コピー
コピー済み
コピー
コピー済み
// 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));
コピー
コピー済み
コピー
コピー済み
// 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();
コピー
コピー済み
コピー
コピー済み
// 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(
コピー
コピー済み
コピー
コピー済み
o
s
ToMint,
o
Token
ToMint,
ws
DepositAmount,
asset
DepositAmount,
o
s
DepositAmount,
o
Token
DepositAmount,
lpTokens
lpTokens
);
);
}
}
/***************************************
/***************************************
Assets and Rewards
Assets and Rewards
****************************************/
****************************************/
/**
/**
コピー
コピー済み
コピー
コピー済み
* @notice Get the
wS
value of assets in the strategy and
SwapX
pool.
* @notice Get the
asset
value of assets in the strategy and
Algebra
pool.
* The value of the assets in the pool is calculated assuming the pool is balanced.
* The value of the assets in the pool is calculated assuming the pool is balanced.
* This way the value can not be manipulated by changing the pool's token balances.
* This way the value can not be manipulated by changing the pool's token balances.
コピー
コピー済み
コピー
コピー済み
* @param _asset Address of the
Wrapped S (wS)
token
* @param _asset Address of the
asset
token
* @return balance Total value in
wS
.
* @return balance Total value in
asset
.
*/
*/
function checkBalance(address _asset)
function checkBalance(address _asset)
external
external
view
view
override
override
returns (uint256 balance)
returns (uint256 balance)
{
{
コピー
コピー済み
コピー
コピー済み
require(_asset ==
ws
, "Unsupported asset");
require(_asset ==
asset
, "Unsupported asset");
コピー
コピー済み
コピー
コピー済み
//
wS
balance needed here for the balance check that happens from vault during depositing.
//
asset
balance needed here for the balance check that happens from vault during depositing.
balance = IERC20(
ws
).balanceOf(address(this));
balance = IERC20(
asset
).balanceOf(address(this));
// This assumes 1 gauge LP token = 1 pool LP token
// This assumes 1 gauge LP token = 1 pool LP token
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
uint256 lpTokens = IGauge(gauge).balanceOf(address(this));
if (lpTokens == 0) return balance;
if (lpTokens == 0) return balance;
コピー
コピー済み
コピー
コピー済み
// Add the strategy’s share of the
wS
and
OS
tokens in the
SwapX
pool if the pool was balanced.
// Add the strategy’s share of the
asset
and
OToken
tokens in the
Algebra
pool if the pool was balanced.
balance += _lpValue(lpTokens);
balance += _lpValue(lpTokens);
}
}
/**
/**
* @notice Returns bool indicating whether asset is supported by strategy
* @notice Returns bool indicating whether asset is supported by strategy
* @param _asset Address of the asset
* @param _asset Address of the asset
*/
*/
function supportsAsset(address _asset) public view override returns (bool) {
function supportsAsset(address _asset) public view override returns (bool) {
コピー
コピー済み
コピー
コピー済み
return _asset ==
ws
;
return _asset ==
asset
;
}
}
/**
/**
* @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.
* @notice Collect accumulated SWPx (and other) rewards and send to the Harvester.
*/
*/
function collectRewardTokens()
function collectRewardTokens()
external
external
override
override
onlyHarvester
onlyHarvester
nonReentrant
nonReentrant
{
{
// Collect SWPx rewards from the gauge
// Collect SWPx rewards from the gauge
IGauge(gauge).getReward();
IGauge(gauge).getReward();
_collectRewardTokens();
_collectRewardTokens();
}
}
/***************************************
/***************************************
コピー
コピー済み
コピー
コピー済み
Internal
SwapX
Pool and Gauge Functions
Internal
Algebra
Pool and Gauge Functions
****************************************/
****************************************/
/**
/**
コピー
コピー済み
コピー
コピー済み
* @dev Calculate the required amount of
OS
to mint based on the
wS
amount.
* @dev Calculate the required amount of
OToken
to mint based on the
asset
amount.
* This ensures the proportion of
OS
tokens being added to the pool matches the proportion of
wS
tokens.
* This ensures the proportion of
OToken
tokens being added to the pool matches the proportion of
asset
tokens.
* For example, if the added
wS
tokens is 10% of existing
wS
tokens in the pool,
* For example, if the added
asset
tokens is 10% of existing
asset
tokens in the pool,
* then the
OS
tokens being added should also be 10% of the
OS
tokens in the pool.
* then the
OToken
tokens being added should also be 10% of the
OToken
tokens in the pool.
* @param _
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.
*/
*/
コピー
コピー済み
コピー
コピー済み
function _calcTokensToMint(uint256 _
ws
Amount)
function _calcTokensToMint(uint256 _
asset
Amount)
internal
internal
view
view
コピー
コピー済み
コピー
コピー済み
returns (uint256 o
s
Amount)
returns (uint256 o
Token
Amount)
{
{
コピー
コピー済み
コピー
コピー済み
(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");
コピー
コピー済み
コピー
コピー済み
//
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;
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @dev Calculate how much pool LP tokens to burn to get the required amount of
wS
tokens back
* @dev Calculate how much pool LP tokens to burn to get the required amount of
asset
tokens back
* from the pool.
* from the pool.
コピー
コピー済み
コピー
コピー済み
* @param _
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.
*/
*/
コピー
コピー済み
コピー
コピー済み
function _calcTokensToBurn(uint256 _
ws
Amount)
function _calcTokensToBurn(uint256 _
asset
Amount)
internal
internal
view
view
returns (uint256 lpTokens)
returns (uint256 lpTokens)
{
{
コピー
コピー済み
コピー
コピー済み
/* The
SwapX
pool proportionally returns the reserve tokens when removing liquidity.
/* The
Algebra
pool proportionally returns the reserve tokens when removing liquidity.
* First, calculate the proportion of required
wS
tokens against the pools
wS
reserves.
* First, calculate the proportion of required
asset
tokens against the pools
asset
reserves.
* That same proportion is used to calculate the required amount of pool LP tokens.
* That same proportion is used to calculate the required amount of pool LP tokens.
コピー
コピー済み
コピー
コピー済み
* For example, if the required
wS
tokens is 10% of the pool's
wS
reserves,
* For example, if the required
asset
tokens is 10% of the pool's
asset
reserves,
* then 10% of the pool's LP supply needs to be burned.
* then 10% of the pool's LP supply needs to be burned.
*
*
* Because we are doing balanced removal we should be making profit when removing liquidity in a
* Because we are doing balanced removal we should be making profit when removing liquidity in a
* pool tilted to either side.
* pool tilted to either side.
*
*
* Important: A downside is that the Strategist / Governor needs to be
* Important: A downside is that the Strategist / Governor needs to be
* cognizant of not removing too much liquidity. And while the proposal to remove liquidity
* cognizant of not removing too much liquidity. And while the proposal to remove liquidity
* is being voted on, the pool tilt might change so much that the proposal that has been valid while
* is being voted on, the pool tilt might change so much that the proposal that has been valid while
* created is no longer valid.
* created is no longer valid.
*/
*/
コピー
コピー済み
コピー
コピー済み
(uint256
ws
Reserves,
,
) =
IPair(pool).getReserves
();
(uint256
asset
Reserves,
) =
_getPoolReserves
();
require(
ws
Reserves > 0, "Empty pool");
require(
asset
Reserves > 0, "Empty pool");
コピー
コピー済み
コピー
コピー済み
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
}
}
/**
/**
コピー
コピー済み
コピー
コピー済み
* @dev Deposit
Wrapped S (wS)
and
OS
liquidity to the
SwapX
pool
* @dev Deposit
asset
and
OToken
liquidity to the
Algebra
pool
* and stake the pool's LP token in the gauge.
* and stake the pool's LP token in the gauge.
コピー
コピー済み
コピー
コピー済み
* @param _
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.
*/
*/
コピー
コピー済み
コピー
コピー済み
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)
{
{
コピー
コピー済み
コピー
コピー済み
// 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
コピー
コピー済み
コピー
コピー済み
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
保存された差分
原文
ファイルを開く
// 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); } }
変更されたテキスト
ファイルを開く
// 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); } }
違いを見つける