Untitled Diff
492 Zeilen
/*
/*
Copyright 2021 Set Labs Inc.
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
*/
*/
pragma solidity 0.6.10;
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { BaseAdapter } from "../lib/BaseAdapter.sol";
import { BaseAdapter } from "../lib/BaseAdapter.sol";
import { ICErc20 } from "../interfaces/ICErc20.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IChainlinkAggregatorV3 } from "../interfaces/IChainlinkAggregatorV3.sol";
import { IChainlinkAggregatorV3 } from "../interfaces/IChainlinkAggregatorV3.sol";
import { IComptroller } from "../interfaces/IComptroller.sol";
import { ILeverageModule } from "../interfaces/ILeverageModule.sol";
import { ICompoundLeverageModule } from "../interfaces/ICompoundLeverageModule.sol";
import { IProtocolDataProvider } from "../interfaces/IProtocolDataProvider.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
/**
/**
* @title FlexibleLeverageStrategyExtension
* @title AaveFLIStrategyExtension
* @author Set Protocol
* @author Set Protocol
*
*
* Smart contract that enables trustless leverage tokens using the flexible leverage methodology. This extension is paired with the CompoundLeverageModule from Set
* Smart contract that enables trustless leverage tokens using the flexible leverage methodology. This extension is paired with the AaveLeverageModule from Set
* protocol where module interactions are invoked via the IBaseManager contract. Any leveraged token can be constructed as long as the collateral and borrow
* protocol where module interactions are invoked via the IBaseManager contract. Any leveraged token can be constructed as long as the collateral and borrow
* asset is available on Compound. This extension contract also allows the operator to set an ETH reward to incentivize keepers calling the rebalance function at
* asset is available on Aave. This extension contract also allows the operator to set an ETH reward to incentivize keepers calling the rebalance function at
* different leverage thresholds.
* different leverage thresholds.
*
*
* CHANGELOG 4/14/2021:
* - Update ExecutionSettings struct to split exchangeData into leverExchangeData and deleverExchangeData
* - Update _lever and _delever internal functions with struct changes
* - Update setExecutionSettings to account for leverExchangeData and deleverExchangeData
*
* CHANGELOG 5/24/2021:
* - Update _calculateActionInfo to add chainlink prices
* - Update _calculateBorrowUnits and _calculateMinRepayUnits to use chainlink as an oracle in
*
* CHANGELOG 6/29/2021: c55bd3cdb0fd43c03da9904493dcc23771ef0f71
* - Add ExchangeSettings struct that contains exchange specific information
* - Update ExecutionSettings struct to not include exchange information
* - Add mapping of exchange names to ExchangeSettings structs and a list of enabled exchange names
* - Update constructor to take an array of exchange names and an array of ExchangeSettings
* - Add _exchangeName parameter to rebalancing functions to select which exchange to use
* - Add permissioned addEnabledExchange, updateEnabledExchange, and removeEnabledExchange functions
* - Add getChunkRebalanceNotional function
* - Update shouldRebalance and shouldRebalanceWithBounds to return an array of ShouldRebalance enums and an array of exchange names
* - Update _shouldRebalance to use exchange specific last trade timestamps
* - Update _validateRipcord and _validateNormalRebalance to take in a timestamp parameter (so we can pass either global or exchange specific timestamp)
* - Add _updateLastTradeTimestamp function to update global and exchange specific timestamp
* - Change contract name to FlexibleLeverageStrategyExtension
*/
*/
contract FlexibleLeverageStrategyExtension is BaseAdapter {
contract AaveFLIStrategyExtension is BaseAdapter {
using Address for address;
using Address for address;
using PreciseUnitMath for uint256;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using SafeCast for int256;
using StringArrayUtils for string[];
using StringArrayUtils for string[];
/* ============ Enums ============ */
/* ============ Enums ============ */
enum ShouldRebalance {
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
}
/* ============ Structs ============ */
/* ============ Structs ============ */
struct ActionInfo {
struct ActionInfo {
uint256 collateralBalance; // Balance of underlying held in Compound in base units (e.g. USDC 10e6)
uint256 collateralBalance; // Balance of underlying held in Aave in base units (e.g. USDC 10e6)
uint256 borrowBalance; // Balance of underlying borrowed from Compound in base units
uint256 borrowBalance; // Balance of underlying borrowed from Aave in base units
uint256 collateralValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 collateralValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 borrowValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 borrowValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 collateralPrice; // Price of collateral in precise units (10e18) from Chainlink
uint256 collateralPrice; // Price of collateral in precise units (10e18) from Chainlink
uint256 borrowPrice; // Price of borrow asset in precise units (10e18) from Chainlink
uint256 borrowPrice; // Price of borrow asset in precise units (10e18) from Chainlink
uint256 setTotalSupply; // Total supply of SetToken
uint256 setTotalSupply; // Total supply of SetToken
}
}
struct LeverageInfo {
struct LeverageInfo {
ActionInfo action;
ActionInfo action;
uint256 currentLeverageRatio; // Current leverage ratio of Set
uint256 currentLeverageRatio; // Current leverage ratio of Set
uint256 slippageTolerance; // Allowable percent trade slippage in preciseUnits (1% = 10^16)
uint256 slippageTolerance; // Allowable percent trade slippage in preciseUnits (1% = 10^16)
uint256 twapMaxTradeSize; // Max trade size in collateral units allowed for rebalance action
uint256 twapMaxTradeSize; // Max trade size in collateral units allowed for rebalance action
string exchangeName; // Exchange to use for trade
string exchangeName; // Exchange to use for trade
}
}
struct ContractSettings {
struct ContractSettings {
ISetToken setToken; // Instance of leverage token
ISetToken setToken; // Instance of leverage token
ICompoundLeverageModule leverageModule; // Instance of Compound leverage module
ILeverageModule leverageModule; // Instance of Aave leverage module
IComptroller comptroller; // Instance of Compound Comptroller
IProtocolDataProvider aaveProtocolDataProvider; // Instance of Aave protocol data provider
IChainlinkAggregatorV3 collateralPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for collateral asset
IChainlinkAggregatorV3 collateralPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for collateral asset
IChainlinkAggregatorV3 borrowPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for borrow asset
IChainlinkAggregatorV3 borrowPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for borrow asset
ICErc20 targetCollateralCToken; // Instance of target collateral cToken asset
IERC20 targetCollateralAToken; // Instance of target collateral aToken asset
ICErc20 targetBorrowCToken; // Instance of target borrow cToken asset
IERC20 targetBorrowDebtToken; // Instance of target borrow variable debt token asset
address collateralAsset; // Address of underlying collateral
address collateralAsset; // Address of underlying collateral
address borrowAsset; // Address of underlying borrow asset
address borrowAsset; // Address of underlying borrow asset
uint256 collateralDecimalAdjustment; // Decimal adjustment for chainlink oracle of the collateral asset. Equal to 28-collateralDecimals (10^18 * 10^18 / 10^decimals / 10^8)
uint256 collateralDecimals; // Decimals of collateral asset
uint256 borrowDecimalAdjustment; // Decimal adjustment for chainlink oracle of the borrowing asset. Equal to 28-borrowDecimals (10^18 * 10^18 / 10^decimals / 10^8)
uint256 borrowDecimals; // Decimals of borrow asset
}
}
struct MethodologySettings {
struct MethodologySettings {
uint256 targetLeverageRatio; // Long term target ratio in precise units (10e18)
uint256 targetLeverageRatio; // Long term target ratio in precise units (10e18)
uint256 minLeverageRatio; // In precise units (10e18). If current leverage is below, rebalance target is this ratio
uint256 minLeverageRatio; // In precise units (10e18). If current leverage is below, rebalance target is this ratio
uint256 maxLeverageRatio; // In precise units (10e18). If current leverage is above, rebalance target is this ratio
uint256 maxLeverageRatio; // In precise units (10e18). If current leverage is above, rebalance target is this ratio
uint256 recenteringSpeed; // % at which to rebalance back to target leverage in precise units (10e18)
uint256 recenteringSpeed; // % at which to rebalance back to target leverage in precise units (10e18)
uint256 rebalanceInterval; // Period of time required since last rebalance timestamp in seconds
uint256 rebalanceInterval; // Period of time required since last rebalance timestamp in seconds
}
}
struct ExecutionSettings {
struct ExecutionSettings {
uint256 unutilizedLeveragePercentage; // Percent of max borrow left unutilized in precise units (1% = 10e16)
uint256 unutilizedLeveragePercentage; // Percent of max borrow left unutilized in precise units (1% = 10e16)
uint256 slippageTolerance; // % in precise units to price min token receive amount from trade quantities
uint256 slippageTolerance; // % in precise units to price min token receive amount from trade quantities
uint256 twapCooldownPeriod; // Cooldown period required since last trade timestamp in seconds
uint256 twapCooldownPeriod; // Cooldown period required since last trade timestamp in seconds
}
}
struct ExchangeSettings {
struct ExchangeSettings {
uint256 twapMaxTradeSize; // Max trade size in collateral base units
uint256 twapMaxTradeSize; // Max trade size in collateral base units
uint256 exchangeLastTradeTimestamp; // Timestamp of last trade made with this exchange
uint256 exchangeLastTradeTimestamp; // Timestamp of last trade made with this exchange
uint256 incentivizedTwapMaxTradeSize; // Max trade size for incentivized rebalances in collateral base units
uint256 incentivizedTwapMaxTradeSize; // Max trade size for incentivized rebalances in collateral base units
bytes leverExchangeData; // Arbitrary exchange data passed into rebalance function for levering up
bytes leverExchangeData; // Arbitrary exchange data passed into rebalance function for levering up
bytes deleverExchangeData; // Arbitrary exchange data passed into rebalance function for delevering
bytes deleverExchangeData; // Arbitrary exchange data passed into rebalance function for delevering
}
}
struct IncentiveSettings {
struct IncentiveSettings {
uint256 etherReward; // ETH reward for incentivized rebalances
uint256 etherReward; // ETH reward for incentivized rebalances
uint256 incentivizedLeverageRatio; // Leverage ratio for incentivized rebalances
uint256 incentivizedLeverageRatio; // Leverage ratio for incentivized rebalances
uint256 incentivizedSlippageTolerance; // Slippage tolerance percentage for incentivized rebalances
uint256 incentivizedSlippageTolerance; // Slippage tolerance percentage for incentivized rebalances
uint256 incentivizedTwapCooldownPeriod; // TWAP cooldown in seconds for incentivized rebalances
uint256 incentivizedTwapCooldownPeriod; // TWAP cooldown in seconds for incentivized rebalances
}
}
/* ============ Events ============ */
/* ============ Events ============ */
event Engaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event Engaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event Rebalanced(
event Rebalanced(
uint256 _currentLeverageRatio,
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
uint256 _totalRebalanceNotional
);
);
event RebalanceIterated(
event RebalanceIterated(
uint256 _currentLeverageRatio,
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
uint256 _totalRebalanceNotional
);
);
event RipcordCalled(
event RipcordCalled(
uint256 _currentLeverageRatio,
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _newLeverageRatio,
uint256 _rebalanceNotional,
uint256 _rebalanceNotional,
uint256 _etherIncentive
uint256 _etherIncentive
);
);
event Disengaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event Disengaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event MethodologySettingsUpdated(
event MethodologySettingsUpdated(
uint256 _targetLeverageRatio,
uint256 _targetLeverageRatio,
uint256 _minLeverageRatio,
uint256 _minLeverageRatio,
uint256 _maxLeverageRatio,
uint256 _maxLeverageRatio,
uint256 _recenteringSpeed,
uint256 _recenteringSpeed,
uint256 _rebalanceInterval
uint256 _rebalanceInterval
);
);
event ExecutionSettingsUpdated(
event ExecutionSettingsUpdated(
uint256 _unutilizedLeveragePercentage,
uint256 _unutilizedLeveragePercentage,
uint256 _twapCooldownPeriod,
uint256 _twapCooldownPeriod,
uint256 _slippageTolerance
uint256 _slippageTolerance
);
);
event IncentiveSettingsUpdated(
event IncentiveSettingsUpdated(
uint256 _etherReward,
uint256 _etherReward,
uint256 _incentivizedLeverageRatio,
uint256 _incentivizedLeverageRatio,
uint256 _incentivizedSlippageTolerance,
uint256 _incentivizedSlippageTolerance,
uint256 _incentivizedTwapCooldownPeriod
uint256 _incentivizedTwapCooldownPeriod
);
);
event ExchangeUpdated(
event ExchangeUpdated(
string _exchangeName,
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes leverExchangeData,
bytes deleverExchangeData
bytes deleverExchangeData
);
);
event ExchangeAdded(
event ExchangeAdded(
string _exchangeName,
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes leverExchangeData,
bytes deleverExchangeData
bytes deleverExchangeData
);
);
event ExchangeRemoved(
event ExchangeRemoved(
string _exchangeName
string _exchangeName
);
);
/* ============ Modifiers ============ */
/* ============ Modifiers ============ */
/**
/**
* Throws if rebalance is currently in TWAP`
* Throws if rebalance is currently in TWAP`
*/
*/
modifier noRebalanceInProgress() {
modifier noRebalanceInProgress() {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
_;
_;
}
}
/* ============ State Variables ============ */
/* ============ State Variables ============ */
ContractSettings internal strategy; // Struct of contracts used in the strategy (SetToken, price oracles, leverage module etc)
ContractSettings internal strategy; // Struct of contracts used in the strategy (SetToken, price oracles, leverage module etc)
MethodologySettings internal methodology; // Struct containing methodology parameters
MethodologySettings internal methodology; // Struct containing methodology parameters
ExecutionSettings internal execution; // Struct containing execution parameters
ExecutionSettings internal execution; // Struct containing execution parameters
mapping(string => ExchangeSettings) internal exchangeSettings; // Mapping from exchange name to exchange settings
mapping(string => ExchangeSettings) internal exchangeSettings; // Mapping from exchange name to exchange settings
IncentiveSettings internal incentive; // Struct containing incentive parameters for ripcord
IncentiveSettings internal incentive; // Struct containing incentive parameters for ripcord
string[] public enabledExchanges; // Array containing enabled exchanges
string[] public enabledExchanges; // Array containing enabled exchanges
uint256 public twapLeverageRatio; // Stored leverage ratio to keep track of target between TWAP rebalances
uint256 public twapLeverageRatio; // Stored leverage ratio to keep track of target between TWAP rebalances
uint256 public globalLastTradeTimestamp; // Last rebalance timestamp. Current timestamp must be greater than this variable + rebalance interval to rebalance
uint256 public globalLastTradeTimestamp; // Last rebalance timestamp. Current timestamp must be greater than this variable + rebalance interval to rebalance
/* ============ Constructor ============ */
/* ============ Constructor ============ */
/**
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
*
* @param _manager Address of IBaseManager contract
* @param _manager Address of IBaseManager contract
* @param _strategy Struct of contract addresses
* @param _strategy Struct of contract addresses
* @param _methodology Struct containing methodology parameters
* @param _methodology Struct containing methodology parameters
* @param _execution Struct containing execution parameters
* @param _execution Struct containing execution parameters
* @param _incentive Struct containing incentive parameters for ripcord
* @param _incentive Struct containing incentive parameters for ripcord
* @param _exchangeNames List of initial exchange names
* @param _exchangeNames List of initial exchange names
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
*/
*/
constructor(
constructor(
IBaseManager _manager,
IBaseManager _manager,
ContractSettings memory _strategy,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings
ExchangeSettings[] memory _exchangeSettings
)
)
public
public
BaseAdapter(_manager)
BaseAdapter(_manager)
{
{
strategy = _strategy;
strategy = _strategy;
methodology = _methodology;
methodology = _methodology;
execution = _execution;
execution = _execution;
incentive = _incentive;
incentive = _incentive;
for (uint256 i = 0; i < _exchangeNames.length; i++) {
for (uint256 i = 0; i < _exchangeNames.length; i++) {
_validateExchangeSettings(_exchangeSettings[i]);
_validateExchangeSettings(_exchangeSettings[i]);
exchangeSettings[_exchangeNames[i]] = _exchangeSettings[i];
exchangeSettings[_exchangeNames[i]] = _exchangeSettings[i];
enabledExchanges.push(_exchangeNames[i]);
enabledExchanges.push(_exchangeNames[i]);
}
}
_validateNonExchangeSettings(methodology, execution, incentive);
_validateNonExchangeSettings(methodology, execution, incentive);
}
}
/* ============ External Functions ============ */
/* ============ External Functions ============ */
/**
/**
* OPERATOR ONLY: Engage to target leverage ratio for the first time. SetToken will borrow debt position from Compound and trade for collateral asset. If target
* OPERATOR ONLY: Engage to target leverage ratio for the first time. SetToken will borrow debt position from Aave and trade for collateral asset. If target
* leverage ratio is above max borrow or max trade size, then TWAP is kicked off. To complete engage if TWAP, any valid caller must call iterateRebalance until target
* leverage ratio is above max borrow or max trade size, then TWAP is kicked off. To complete engage if TWAP, any valid caller must call iterateRebalance until target
* is met.
* is met.
*
*
* @param _exchangeName the exchange used for trading
* @param _exchangeName the exchange used for trading
*/
*/
function engage(string memory _exchangeName) external onlyOperator {
function engage(string memory _exchangeName) external onlyOperator {
ActionInfo memory engageInfo = _createActionInfo();
ActionInfo memory engageInfo = _createActionInfo();
require(engageInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(engageInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(engageInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(engageInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(engageInfo.borrowBalance == 0, "Debt must be 0");
require(engageInfo.borrowBalance == 0, "Debt must be 0");
LeverageInfo memory leverageInfo = LeverageInfo({
LeverageInfo memory leverageInfo = LeverageInfo({
action: engageInfo,
action: engageInfo,
currentLeverageRatio: PreciseUnitMath.preciseUnit(), // 1x leverage in precise units
currentLeverageRatio: PreciseUnitMath.preciseUnit(), // 1x leverage in precise units
slippageTolerance: execution.slippageTolerance,
slippageTolerance: execution.slippageTolerance,
twapMaxTradeSize: exchangeSettings[_exchangeName].twapMaxTradeSize,
twapMaxTradeSize: exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeName: _exchangeName
exchangeName: _exchangeName
});
});
// Calculate total rebalance units and kick off TWAP if above max borrow or max trade size
// Calculate total rebalance units and kick off TWAP if above max borrow or max trade size
(
(
uint256 chunkRebalanceNotional,
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, methodology.targetLeverageRatio, true);
) = _calculateChunkRebalanceNotional(leverageInfo, methodology.targetLeverageRatio, true);
_lever(leverageInfo, chunkRebalanceNotional);
_lever(leverageInfo, chunkRebalanceNotional);
_updateRebalanceState(
_updateRebalanceState(
chunkRebalanceNotional,
chunkRebalanceNotional,
totalRebalanceNotional,
totalRebalanceNotional,
methodology.targetLeverageRatio,
methodology.targetLeverageRatio,
_exchangeName
_exchangeName
);
);
emit Engaged(
emit Engaged(
leverageInfo.currentLeverageRatio,
leverageInfo.currentLeverageRatio,
methodology.targetLeverageRatio,
methodology.targetLeverageRatio,
chunkRebalanceNotional,
chunkRebalanceNotional,
totalRebalanceNotional
totalRebalanceNotional
);
);
}
}
/**
/**
* ONLY EOA AND ALLOWED CALLER: Rebalance according to flexible leverage methodology. If current leverage ratio is between the max and min bounds, then rebalance
* ONLY EOA AND ALLOWED CALLER: Rebalance according to flexible leverage methodology. If current leverage ratio is between the max and min bounds, then rebalance
* can only be called once the rebalance interval has elapsed since last timestamp. If outside the max and min, rebalance can be called anytime to bring leverage
* can only be called once the rebalance interval has elapsed since last timestamp. If outside the max and min, rebalance can be called anytime to bring leverage
* ratio back to the max or min bounds. The methodology will determine whether to delever or lever.
* ratio back to the max or min bounds. The methodology will determine whether to delever or lever.
*
*
* Note: If the calculated current leverage ratio is above the incentivized leverage ratio or in TWAP then rebalance cannot be called. Instead, you must call
* Note: If the calculated current leverage ratio is above the incentivized leverage ratio or in TWAP then rebalance cannot be called. Instead, you must call
* ripcord() which is incentivized with a reward in Ether or iterateRebalance().
* ripcord() which is incentivized with a reward in Ether or iterateRebalance().
*
*
* @param _exchangeName the exchange used for trading
* @param _exchangeName the exchange used for trading
*/
*/
function rebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
function rebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
_exchangeName
);
);
// use globalLastTradeTimestamps to prevent multiple rebalances being called with different exchanges during the epoch rebalance
// use globalLastTradeTimestamps to prevent multiple rebalances being called with different exchanges during the epoch rebalance
_validateNormalRebalance(leverageInfo, methodology.rebalanceInterval, globalLastTradeTimestamp);
_validateNormalRebalance(leverageInfo, methodology.rebalanceInterval, globalLastTradeTimestamp);
_validateNonTWAP();
_validateNonTWAP();
uint256 newLeverageRatio = _calculateNewLeverageRatio(leverageInfo.currentLeverageRatio);
uint256 newLeverageRatio = _calculateNewLeverageRatio(leverageInfo.currentLeverageRatio);
(
(
uint256 chunkRebalanceNotional,
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
uint256 totalRebalanceNotional
) = _handleRebalance(leverageInfo, newLeverageRatio);
) = _handleRebalance(leverageInfo, newLeverageRatio);
_updateRebalanceState(chunkRebalanceNotional, totalRebalanceNotional, newLeverageRatio, _exchangeName);
_updateRebalanceState(chunkRebalanceNotional, totalRebalanceNotional, newLeverageRatio, _exchangeName);
emit Rebalanced(
emit Rebalanced(
leverageInfo.currentLeverageRatio,
leverageInfo.currentLeverageRatio,
newLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
chunkRebalanceNotional,
totalRebalanceNotional
totalRebalanceNotional
);
);
}
}
/**
/**
* ONLY EOA AND ALLOWED CALLER: Iterate a rebalance when in TWAP. TWAP cooldown period must have elapsed. If price moves advantageously, then exit without rebalancing
* ONLY EOA AND ALLOWED CALLER: Iterate a rebalance when in TWAP. TWAP cooldown period must have elapsed. If price moves advantageously, then exit without rebalancing
* and clear TWAP state. This function can only be called when below incentivized leverage ratio and in TWAP state.
* and clear TWAP state. This function can only be called when below incentivized leverage ratio and in TWAP state.
*
*
* @param _exchangeName the exchange used for trading
* @param _exchangeName the exchange used for trading
*/
*/
function iterateRebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
function iterateRebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
_exchangeName
);
);
// Use the exchangeLastTradeTimestamp since cooldown periods are measured on a per-exchange basis, allowing it to rebalance multiple time in quick
// Use the exchangeLastTradeTimestamp since cooldown periods are measured on a per-exchange basis, allowing it to rebalance multiple time in quick
// succession with different exchanges
// succession with different exchanges
_validateNormalRebalance(leverageInfo, execution.twapCooldownPeriod, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateNormalRebalance(leverageInfo, execution.twapCooldownPeriod, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateTWAP();
_validateTWAP();
uint256 chunkRebalanceNotional;
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
uint256 totalRebalanceNotional;
if (!_isAdvantageousTWAP(leverageInfo.currentLeverageRatio)) {
if (!_isAdvantageousTWAP(leverageInfo.currentLeverageRatio)) {
(chunkRebalanceNotional, totalRebalanceNotional) = _handleRebalance(leverageInfo, twapLeverageRatio);
(chunkRebalanceNotional, totalRebalanceNotional) = _handleRebalance(leverageInfo, twapLeverageRatio);
}
}
// If not advantageous, then rebalance is skipped and chunk and total rebalance notional are both 0, which means TWAP state is
// If not advantageous, then rebalance is skipped and chunk and total rebalance notional are both 0, which means TWAP state is
// cleared
// cleared
_updateIterateState(chunkRebalanceNotional, totalRebalanceNotional, _exchangeName);
_updateIterateState(chunkRebalanceNotional, totalRebalanceNotional, _exchangeName);
emit RebalanceIterated(
emit RebalanceIterated(
leverageInfo.currentLeverageRatio,
leverageInfo.currentLeverageRatio,
twapLeverageRatio,
twapLeverageRatio,
chunkRebalanceNotional,
chunkRebalanceNotional,
totalRebalanceNotional
totalRebalanceNotional
);
);
}
}
/**
/**
* ONLY EOA: In case the current leverage ratio exceeds the incentivized leverage threshold, the ripcord function can be called by anyone to return leverage ratio
* ONLY EOA: In case the current leverage ratio exceeds the incentivized leverage threshold, the ripcord function can be called by anyone to return leverage ratio
* back to the max leverage ratio. This function typically would only be called during times of high downside volatility and / or normal keeper malfunctions. The caller
* back to the max leverage ratio. This function typically would only be called during times of high downside volatility and / or normal keeper malfunctions. The caller
* of ripcord() will receive a reward in Ether. The ripcord function uses it's own TWAP cooldown period, slippage tolerance and TWAP max trade size which are typically
* of ripcord() will receive a reward in Ether. The ripcord function uses it's own TWAP cooldown period, slippage tolerance and TWAP max trade size which are typically
* looser than in regular rebalances.
* looser than in regular rebalances.
*
*
* @param _exchangeName the exchange used for trading
* @param _exchangeName the exchange used for trading
*/
*/
function ripcord(string memory _exchangeName) external onlyEOA {
function ripcord(string memory _exchangeName) external onlyEOA {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
incentive.incentivizedSlippageTolerance,
incentive.incentivizedSlippageTolerance,
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize,
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize,
_exchangeName
_exchangeName
);
);
// Use the exchangeLastTradeTimestamp so it can ripcord quickly with multiple exchanges
// Use the exchangeLastTradeTimestamp so it can ripcord quickly with multiple exchanges
_validateRipcord(leverageInfo, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateRipcord(leverageInfo, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
( uint256 chunkRebalanceNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, methodology.maxLeverageRatio, false);
( uint256 chunkRebalanceNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, methodology.maxLeverageRatio, false);
_delever(leverageInfo, chunkRebalanceNotional);
_delever(leverageInfo, chunkRebalanceNotional);
_updateRipcordState(_exchangeName);
_updateRipcordState(_exchangeName);
uint256 etherTransferred = _transferEtherRewardToCaller(incentive.etherReward);
uint256 etherTransferred = _transferEtherRewardToCaller(incentive.etherReward);
emit RipcordCalled(
emit RipcordCalled(
leverageInfo.currentLeverageRatio,
leverageInfo.currentLeverageRatio,
methodology.maxLeverageRatio,
methodology.maxLeverageRatio,
chunkRebalanceNotional,
chunkRebalanceNotional,
etherTransferred
etherTransferred
);
);
}
}
/**
/**
* OPERATOR ONLY: Return leverage ratio to 1x and delever to repay loan. This can be used for upgrading or shutting down the strategy. SetToken will redeem
* OPERATOR ONLY: Return leverage ratio to 1x and delever to repay loan. This can be used for upgrading or shutting down the strategy. SetToken will redeem
* collateral position and trade for debt position to repay Compound. If the chunk rebalance size is less than the total notional size, then this function will
* collateral position and trade for debt position to repay Aave. If the chunk rebalance size is less than the total notional size, then this function will
* delever and repay entire borrow balance on Compound. If chunk rebalance size is above max borrow or max trade size, then operator must
* delever and repay entire borrow balance on Aave. If chunk rebalance size is above max borrow or max trade size, then operator must
* continue to call this function to complete repayment of loan. The function iterateRebalance will not work.
* continue to call this function to complete repayment of loan. The function iterateRebalance will not work.
*
*
* Note: Delever to 0 will likely result in additional units of the borrow asset added as equity on the SetToken due to oracle price / market price mismatch
* Note: Delever to 0 will likely result in additional units of the borrow asset added as equity on the SetToken due to oracle price / market price mismatch
*
*
* @param _exchangeName the exchange used for trading
* @param _exchangeName the exchange used for trading
*/
*/
function disengage(string memory _exchangeName) external onlyOperator {
function disengage(string memory _exchangeName) external onlyOperator {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
_exchangeName
);
);
uint256 newLeverageRatio = PreciseUnitMath.preciseUnit();
uint256 newLeverageRatio = PreciseUnitMath.preciseUnit();
(
(
uint256 chunkRebalanceNotional,
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, false);
) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, false);
if (totalRebalanceNotional > chunkRebalanceNotional) {
if (totalRebalanceNotional > chunkRebalanceNotional) {
_delever(leverageInfo, chunkRebalanceNotional);
_delever(leverageInfo, chunkRebalanceNotional);
} else {
} else {
_deleverToZeroBorrowBalance(leverageInfo, totalRebalanceNotional);
_deleverToZeroBorrowBalance(leverageInfo, totalRebalanceNotional);
}
}
emit Disengaged(
emit Disengaged(
leverageInfo.currentLeverageRatio,
leverageInfo.currentLeverageRatio,
newLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
chunkRebalanceNotional,
totalRebalanceNotional
totalRebalanceNotional
);
);
}
}
/**
/**
* OPERATOR ONLY: Set methodology settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* OPERATOR ONLY: Set methodology settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
* in a rebalance.
*
*
* @param _newMethodologySettings Struct containing methodology parameters
* @param _newMethodologySettings Struct containing methodology parameters
*/
*/
function setMethodologySettings(MethodologySettings memory _newMethodologySettings) external onlyOperator noRebalanceInProgress {
function setMethodologySettings(MethodologySettings memory _newMethodologySettings) external onlyOperator noRebalanceInProgress {
methodology = _newMethodologySettings;
methodology = _newMethodologySettings;
_validateNonExchangeSettings(methodology, execution, incentive);
_validateNonExchangeSettings(methodology, execution, incentive);
emit MethodologySettingsUpdated(
emit MethodologySettingsUpdated(
methodology.targetLeverageRatio,
methodology.targetLeverageRatio,
methodology.minLeverageRatio,
methodology.minLeverageRatio,
methodology.maxLeverageRatio,
methodology.maxLeverageRatio,
methodology.recenteringSpeed,
methodology.recenteringSpeed,
methodology.rebalanceInterval
methodology.rebalanceInterval
);
);
}
}
/**
/**
* OPERATOR ONLY: Set execution settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* OPERATOR ONLY: Set execution settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
* in a rebalance.
*
*
* @param _newExecutionSettings Struct containing execution parameters
* @param _newExecutionSettings Struct containing execution parameters
*/
*/
function setExecutionSettings(ExecutionSettings memory _newExecutionSettings) external onlyOperator noRebalanceInProgress {
function setExecutionSettings(ExecutionSettings memory _newExecutionSettings) external onlyOperator noRebalanceInProgress {
execution = _newExecutionSettings;
execution = _newExecutionSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
_validateNonExchangeSettings(methodology, execution, incentive);
emit ExecutionSe
emit ExecutionSettingsUpdated(
execution.unutilizedLeveragePercentage,
execution.twapCooldownPeriod,
execution.slippageTolerance
);
}
/**
* OPERATOR ONLY: Set incentive settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newIncentiveSettings Struct containing incentive parameters
*/
function setIncentiveSettings(IncentiveSettings memory _newIncentiveSettings) external onlyOperator noRebalanceInProgress {
incentive = _newIncentiveSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit IncentiveSettingsUpdated(
incentive.etherReward,
incentive.incentivizedLeverageRatio,
incentive.incentivizedSlippageTolerance,
incentive.incentivizedTwapCooldownPeriod
);
}
/**
* OPERATOR ONLY: Add a new enabled exchange for trading during rebalances. New exchanges will have their exchangeLastTradeTimestamp set to 0. Adding
* exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function addEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize == 0, "Exchange already enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;