-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
alcueca
committed
Feb 24, 2023
1 parent
bfaffde
commit 8d3e078
Showing
2 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity >=0.8.13; | ||
|
||
import "../interfaces/ICauldronGov.sol"; | ||
import "../interfaces/ICauldron.sol"; | ||
import "../interfaces/ILadleGov.sol"; | ||
import "../interfaces/ILadle.sol"; | ||
import "../interfaces/IJoin.sol"; | ||
import "../oracles/yieldspace/YieldSpaceMultiOracle.sol"; | ||
import "../oracles/composite/CompositeMultiOracle.sol"; | ||
import "@yield-protocol/yieldspace-tv/src/interfaces/IPool.sol"; | ||
import "@yield-protocol/utils-v2/src/access/AccessControl.sol"; | ||
|
||
/// @title A contract that allows configuring the cauldron and ladle within bounds | ||
contract ContangoWand is AccessControl { | ||
ICauldronGov public immutable cauldron; | ||
ICauldron public immutable masterCauldron; | ||
ILadleGov public immutable ladle; | ||
ILadle public immutable masterLadle; | ||
YieldSpaceMultiOracle public immutable yieldSpaceOracle; | ||
CompositeMultiOracle public immutable compositeOracle; | ||
|
||
mapping (bytes6 => mapping(bytes6 => uint32)) public ratio; | ||
mapping (bytes6 => mapping(bytes6 => DataTypes.Debt)) public debt; | ||
|
||
DataTypes.Debt public defaultDebtLimits; | ||
uint32 public defaultRatio; | ||
|
||
constructor( | ||
ICauldronGov cauldron_, | ||
ICauldron masterCauldron_, | ||
ILadleGov ladle_, | ||
ILadle masterLadle_, | ||
YieldSpaceMultiOracle yieldSpaceOracle_, | ||
CompositeMultiOracle compositeOracle_ | ||
) { | ||
cauldron = cauldron_; | ||
masterCauldron = masterCauldron_; | ||
ladle = ladle_; | ||
masterLadle = masterLadle_; | ||
yieldSpaceOracle = yieldSpaceOracle_; | ||
compositeOracle = compositeOracle_; | ||
} | ||
|
||
/// ----------------- Cauldron Governance ----------------- | ||
|
||
/// @notice Copy the spotOracle and ratio from the master cauldron | ||
function copySpotOracle(bytes6 baseId, bytes6 ilkId) external auth { | ||
DataTypes.SpotOracle memory spotOracle_ = masterCauldron.spotOracles(baseId, ilkId); | ||
cauldron.setSpotOracle(baseId, ilkId, spotOracle_.oracle, spotOracle_.ratio); | ||
} | ||
|
||
/// @notice Copy the lending oracle from the master cauldron | ||
function copyLendingOracle(bytes6 baseId) external auth { | ||
IOracle lendingOracle_ = masterCauldron.lendingOracles(baseId); | ||
cauldron.setLendingOracle(baseId, lendingOracle_); | ||
} | ||
|
||
/// @notice Copy the debt limits from the master cauldron | ||
function copyDebtLimits(bytes6 baseId, bytes6 ilkId) external auth { | ||
DataTypes.Debt memory debt_ = masterCauldron.debt(baseId, ilkId); | ||
cauldron.setDebtLimits(baseId, ilkId, debt_.max, debt_.min, debt_.dec); | ||
} | ||
|
||
/// @notice Add a new asset in the Cauldron, as long as it is an asset or fyToken known to the Yield Cauldron | ||
function addAsset(bytes6 assetId) external auth { | ||
address asset_ = masterCauldron.assets(assetId); | ||
require( | ||
asset_ != address(0) || | ||
address(masterCauldron.series(assetId).fyToken) != address(0), | ||
"Asset not known to the Yield Cauldron"); | ||
cauldron.addAsset(assetId, asset_); | ||
} | ||
|
||
/// @notice Add a new series, if it exists in the Yield Cauldron | ||
function addSeries(bytes6 seriesId) external auth { | ||
DataTypes.Series memory series_ = masterCauldron.series(seriesId); | ||
require(address(series_.fyToken) != address(0), "Series not known to the Yield Cauldron"); | ||
cauldron.addSeries(seriesId, series_.baseId, series_.fyToken); | ||
} | ||
|
||
/// @notice Add ilks to series | ||
function addIlks(bytes6 seriesId, bytes6[] calldata ilkIds) external auth { | ||
cauldron.addIlks(seriesId, ilkIds); | ||
} | ||
|
||
/// @notice Bound ratio for a given asset pair | ||
function boundRatio(bytes6 baseId, bytes6 ilkId, uint32 ratio_) external auth { | ||
ratio[baseId][ilkId] = ratio_; | ||
} | ||
|
||
/// @notice Set the default ratio | ||
function setDefaultRatio(uint32 ratio_) external auth { | ||
defaultRatio = ratio_; | ||
} | ||
|
||
/// @notice Set the ratio for a given asset pair in the Cauldron, within bounds. Set the spot oracle always to the composite oracle. | ||
function setRatio(bytes6 baseId, bytes6 ilkId, uint32 ratio_) external auth { | ||
// If the ilkId is a series and boundaries are not set, set ratio to the default | ||
uint32 bound_ = ratio[baseId][ilkId]; | ||
if (bound_ == 0 && cauldron.series(ilkId).fyToken != IFYToken(address(0))) { | ||
ratio[baseId][ilkId] = bound_ = defaultRatio; | ||
} | ||
require(ratio_ >= bound_, "Ratio out of bounds"); | ||
cauldron.setSpotOracle(baseId, ilkId, compositeOracle, ratio_); | ||
} | ||
|
||
function _getDebtDecimals(bytes6 baseId, bytes6 ilkId) internal view returns (uint8 dec) { | ||
// If the debt is already set in the cauldron, we use the decimals from there | ||
// Otherwise, we use the decimals of the base | ||
DataTypes.Debt memory cauldronDebt_ = ICauldron(address(cauldron)).debt(baseId, ilkId); | ||
if (cauldronDebt_.sum != 0) { | ||
dec = cauldronDebt_.dec; | ||
} else { | ||
dec = IERC20Metadata(cauldron.assets(baseId)).decimals(); | ||
} | ||
} | ||
|
||
/// @notice Bound debt limits for a given asset pair | ||
function boundDebtLimits(bytes6 baseId, bytes6 ilkId, uint96 max, uint24 min) external auth { | ||
debt[baseId][ilkId] = DataTypes.Debt({ | ||
max: max, | ||
min: min, | ||
dec: _getDebtDecimals(baseId, ilkId), | ||
sum: 0 | ||
}); | ||
} | ||
|
||
/// @notice Set the default debt limits | ||
function setDefaultDebtLimits(uint96 max, uint24 min) external auth { | ||
defaultDebtLimits = DataTypes.Debt({ | ||
max: max, | ||
min: min, | ||
dec: 0, | ||
sum: 0 | ||
}); | ||
} | ||
|
||
/// @notice Set the debt limits for a given asset pair in the Cauldron, within bounds | ||
function setDebtLimits(bytes6 baseId, bytes6 ilkId, uint96 max, uint24 min) external auth { | ||
// If the ilkId is a series and boundaries are not set, set them to default values | ||
DataTypes.Debt memory bounds_ = debt[baseId][ilkId]; | ||
|
||
if (bounds_.max == 0 && bounds_.min == 0) { | ||
bounds_ = defaultDebtLimits; | ||
bounds_.dec = _getDebtDecimals(baseId, ilkId); | ||
debt[baseId][ilkId] = bounds_; | ||
} | ||
require(max <= bounds_.max, "Max debt out of bounds"); | ||
require(min >= bounds_.min, "Min debt out of bounds"); | ||
|
||
cauldron.setDebtLimits(baseId, ilkId, max, min, bounds_.dec); | ||
} | ||
|
||
/// ----------------- Oracle Governance ----------------- | ||
|
||
/// @notice Set a pool as a source in the YieldSpace oracle, as long as: | ||
/// - It is a pool known to the Yield Ladle | ||
/// - The baseId matches the pool's baseId | ||
/// - The quoteId matches the pool's seriesId | ||
function setYieldSpaceOracleSource(bytes6 seriesId) external auth { | ||
IPool pool_ = IPool(masterLadle.pools(seriesId)); | ||
require(address(pool_) != address(0), "Pool not known to the Yield Ladle"); | ||
DataTypes.Series memory series_ = masterCauldron.series(seriesId); | ||
require(address(series_.fyToken) != address(0), "Series not known to the Yield Cauldron"); | ||
require(address(series_.fyToken) == address(pool_.fyToken()), "fyToken mismatch"); // Sanity check | ||
|
||
yieldSpaceOracle.setSource(series_.baseId, seriesId, pool_); | ||
} | ||
|
||
/// @notice Set the YieldSpace oracle as the source for a given asset pair in the Composite oracle, provided the source is set in the YieldSpace oracle | ||
function setCompositeOracleSource(bytes6 baseId, bytes6 ilkId) external auth { | ||
(IPool pool_, ) = yieldSpaceOracle.sources(baseId, ilkId); | ||
require(address(pool_) != address(0), "YieldSpace oracle not set"); | ||
compositeOracle.setSource(baseId, ilkId, yieldSpaceOracle); | ||
} | ||
|
||
/// @notice Set a path in the Composite oracle, as long as the path is not overwriting anything | ||
function setCompositeOraclePath(bytes6 baseId, bytes6 quoteId, bytes6[] calldata path) external auth { | ||
require(compositeOracle.paths(baseId, quoteId, 0) == bytes6(0), "Path already set"); // We check that the first element in the path is empty | ||
compositeOracle.setPath(baseId, quoteId, path); | ||
} | ||
|
||
/// ----------------- Ladle Governance ----------------- | ||
|
||
/// @notice Propagate a pool to the Ladle from the Yield Ladle | ||
function addPool(bytes6 seriesId) external auth { | ||
address pool_ = masterLadle.pools(seriesId); | ||
require(pool_ != address(0), "Pool not known to the Yield Ladle"); | ||
ladle.addPool(seriesId, pool_); | ||
} | ||
|
||
/// @notice Propagate an integration to the Ladle from the Yield Ladle | ||
function addIntegration(address integration) external auth { | ||
ladle.addIntegration(integration, masterLadle.integrations(integration)); | ||
} | ||
|
||
/// @notice Propagate a token to the Ladle from the Yield Ladle | ||
function addToken(address token) external auth { | ||
ladle.addToken(token, masterLadle.tokens(token)); | ||
} | ||
|
||
/// @notice Add join to the Ladle. | ||
/// @dev These will often be used to hold fyToken, so it doesn't seem possible to put boundaries. However, it seems low risk. Famous last words. | ||
function addJoin(bytes6 assetId, address join) external auth { | ||
ladle.addJoin(assetId, join); | ||
} | ||
} |