Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ on:
pull_request:

env:
FOUNDRY_PROFILE: ci
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}

jobs:
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ on:
- develop
pull_request:

env:
FOUNDRY_PROFILE: ci

jobs:
test:
strategy:
Expand Down
16 changes: 0 additions & 16 deletions script/DeploySavingsAf.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ import "forge-std/Script.sol";

contract DeploySavingsAf is Script {

// @todo -- set oracles!

address[] public strategies;

uint256 private collateralChainlinkPriceOracleHeartbeat = 86_400; // 24 hours // @todo -- set correct heartbeat!

string private constant NAME = "Savings USDaf";
string private constant SYMBOL = "sUSDaf";

Expand All @@ -43,53 +39,41 @@ contract DeploySavingsAf is Script {
address _ysyBOLDStrategy = STRATEGY_FACTORY.newStrategy(
address(0x3414bd84dfF0900a9046a987f4dF2e0eF08Fa1ce), // ysyBOLD Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf ysyBOLD Stability Pool"
);
strategies.push(_ysyBOLDStrategy);

address _scrvUSDStrategy = STRATEGY_FACTORY.newStrategy(
address(0x0C7B6C6a60ae2016199d393695667c1482719C82), // scrvUSD Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf scrvUSD Stability Pool"
);
strategies.push(_scrvUSDStrategy);

address _sUSDSStrategy = STRATEGY_FACTORY.newStrategy(
address(0x330A0fDfc1818Be022FEDCE96A041293E16dc6d1), // sUSDS Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf sUSDS Stability Pool"
);
strategies.push(_sUSDSStrategy);

address _sfrxUSDStrategy = STRATEGY_FACTORY.newStrategy(
address(0x0ad1C302203F0fbB6Ca34641BDFeF0Bf4182377c), // sfrxUSD Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf sfrxUSD Stability Pool"
);
strategies.push(_sfrxUSDStrategy);

address _tBTCStrategy = STRATEGY_FACTORY.newStrategy(
address(0xbd9f75471990041A3e7C22872c814A273485E999), // tBTC Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf tBTC Stability Pool"
);
strategies.push(_tBTCStrategy);

address _WBTC18Strategy = STRATEGY_FACTORY.newStrategy(
address(0x2C5A85a3fd181857D02baff169D1e1cB220ead6d), // WBTC18 Address Registry
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"USDaf WBTC18 Stability Pool"
);
strategies.push(_WBTC18Strategy);
Expand Down
31 changes: 6 additions & 25 deletions script/DeployStrategies.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import "forge-std/Script.sol";

contract DeployStrategies is Script {

// @todo -- set oracles!

uint256 public collateralChainlinkPriceOracleHeartbeat = 86_400; // 24 hours // @todo -- set correct heartbeat!

address public constant ASSET = 0x6440f144b7e50D6a8439336510312d2F54beB01D; // BOLD

StrategyFactory public constant STRATEGY_FACTORY = StrategyFactory(0x73dfCc4fB90E6e252E5D41f6588534a8043dBa58);
Expand All @@ -22,31 +18,16 @@ contract DeployStrategies is Script {
vm.startBroadcast(vm.envUint("DEPLOYER_PRIVATE_KEY"));

address _wethAddressesRegistry = address(0x20F7C9ad66983F6523a0881d0f82406541417526); // WETH Address Registry
address _wethStrategy = STRATEGY_FACTORY.newStrategy(
_wethAddressesRegistry,
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"Liquity V2 WETH Stability Pool"
);
address _wethStrategy =
STRATEGY_FACTORY.newStrategy(_wethAddressesRegistry, ASSET, "Liquity V2 WETH Stability Pool");

address _wstethAddressesRegistry = address(0x8d733F7ea7c23Cbea7C613B6eBd845d46d3aAc54); // wstETH Address Registry
address _wstethStrategy = STRATEGY_FACTORY.newStrategy(
_wstethAddressesRegistry,
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"Liquity V2 wstETH Stability Pool"
);
address _wstethStrategy =
STRATEGY_FACTORY.newStrategy(_wstethAddressesRegistry, ASSET, "Liquity V2 wstETH Stability Pool");

address _rethAddressesRegistry = address(0x6106046F031a22713697e04C08B330dDaf3e8789); // rETH Address Registry
address _rethStrategy = STRATEGY_FACTORY.newStrategy(
_rethAddressesRegistry,
ASSET,
address(0),
collateralChainlinkPriceOracleHeartbeat,
"Liquity V2 rETH Stability Pool"
);
address _rethStrategy =
STRATEGY_FACTORY.newStrategy(_rethAddressesRegistry, ASSET, "Liquity V2 rETH Stability Pool");

vm.stopBroadcast();

Expand Down
88 changes: 6 additions & 82 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {BaseHealthCheck, BaseStrategy, ERC20} from "@periphery/Bases/HealthCheck
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {AggregatorV3Interface} from "./interfaces/AggregatorV3Interface.sol";
import {IPriceFeed} from "./interfaces/IPriceFeed.sol";
import {IStabilityPool} from "./interfaces/IStabilityPool.sol";
import {IAuction} from "./interfaces/IAuction.sol";
Expand Down Expand Up @@ -49,15 +48,9 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
/// @notice WAD constant
uint256 private constant WAD = 1e18;

/// @notice Multiplier to scale 8-decimal Chainlink price to 18 decimals
uint256 private constant CHAINLINK_TO_WAD = 1e10;

/// @notice Auction starting price buffer percentage increase when the oracle is down
uint256 public constant ORACLE_DOWN_BUFFER_PCT_MULTIPLIER = 200; // 200x

/// @notice Auction starting price buffer percentage increase when the auction price is too low
uint256 public constant AUCTION_PRICE_TOO_LOW_BUFFER_PCT_MULTIPLIER = 50; // 50x

/// @notice Minimum buffer percentage for the auction starting price
uint256 public constant MIN_BUFFER_PERCENTAGE = WAD + 15e16; // 15%

Expand All @@ -71,10 +64,6 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
/// considered worth depositing during harvests
uint256 public constant MIN_DUST_THRESHOLD = 1e15;

/// @notice Heartbeat of the `CHAINLINK_ORACLE`
/// @dev We will skip the `_isAuctionPriceTooLow` check if the oracle is stale
uint256 public immutable CHAINLINK_ORACLE_HEARTBEAT;

/// @notice Asset dust threshold. We will not bother depositing amounts below this value on harvests
uint256 public constant ASSET_DUST_THRESHOLD = MIN_DUST_THRESHOLD;

Expand All @@ -85,11 +74,6 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
/// @dev Assumes the price feed is using 18 decimals
IPriceFeed public immutable COLL_PRICE_ORACLE;

/// @notice Direct reference to the underlying Chainlink price feed used by `COLL_PRICE_ORACLE`
/// @dev This feed provides a read-only price for the collateral asset without
/// performing Liquity's internal validity or heartbeat checks
AggregatorV3Interface public immutable CHAINLINK_ORACLE;

/// @notice Stability Pool contract
IStabilityPool public immutable SP;

Expand All @@ -103,30 +87,23 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
/// @param _addressesRegistry Address of the AddressesRegistry
/// @param _asset Address of the strategy's underlying asset
/// @param _auctionFactory Address of the AuctionFactory
/// @param _oracle Address of the COLL/USD _read_ price oracle
/// @param _oracleHeartbeat Heartbeat of the `_oracle`
/// @param _name Name of the strategy
constructor(
address _addressesRegistry,
address _asset,
address _auctionFactory,
address _oracle,
uint256 _oracleHeartbeat,
string memory _name
) BaseHealthCheck(_asset, _name) {
COLL_PRICE_ORACLE = IAddressesRegistry(_addressesRegistry).priceFeed();
(, bool _isOracleDown) = COLL_PRICE_ORACLE.fetchPrice();
require(!_isOracleDown && COLL_PRICE_ORACLE.priceSource() == IPriceFeed.PriceSource.primary, "!oracle");

CHAINLINK_ORACLE = AggregatorV3Interface(_oracle);
require(CHAINLINK_ORACLE.decimals() == 8, "!decimals");
CHAINLINK_ORACLE_HEARTBEAT = _oracleHeartbeat;

SP = IAddressesRegistry(_addressesRegistry).stabilityPool();
require(SP.boldToken() == _asset, "!sp");
COLL = ERC20(SP.collToken());

AUCTION = IAuctionFactory(_auctionFactory).createNewAuction(_asset);
AUCTION.setGovernanceOnlyKick(true);
AUCTION.enable(address(COLL));

minAuctionPriceBps = 9_500; // 95%
Expand Down Expand Up @@ -274,6 +251,9 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
/// @inheritdoc BaseStrategy
function _harvestAndReport() internal override returns (uint256 _totalAssets) {
if (!TokenizedStrategy.isShutdown()) {
uint256 _toClaim = SP.getDepositorYieldGain(address(this));
if (_toClaim > ASSET_DUST_THRESHOLD) claim();

uint256 _toDeploy = asset.balanceOf(address(this));
if (_toDeploy > ASSET_DUST_THRESHOLD) _deployFunds(_toDeploy);
}
Expand All @@ -294,12 +274,6 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
) internal override {
if (isCollateralGainToClaim()) claim();

// If auction price is below our acceptable threshold, we will start a new one with an increased starting price
// such that there's a larger delay before we reach our minimum acceptable price again
bool _isPriceTooLow = false;
if (_isAuctionPriceTooLow()) _isPriceTooLow = true;

// If there's an active auction, sweep if needed, and settle
if (AUCTION.isActive(address(COLL))) {
if (AUCTION.available(address(COLL)) > 0) AUCTION.sweep(address(COLL));
AUCTION.settle(address(COLL));
Expand All @@ -312,12 +286,11 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
uint256 _bufferPercentage = bufferPercentage;
if (_isOracleDown || COLL_PRICE_ORACLE.priceSource() != IPriceFeed.PriceSource.primary) {
_bufferPercentage *= ORACLE_DOWN_BUFFER_PCT_MULTIPLIER;
} else if (_isPriceTooLow) {
_bufferPercentage *= AUCTION_PRICE_TOO_LOW_BUFFER_PCT_MULTIPLIER;
}

// slither-disable-next-line divide-before-multiply
AUCTION.setStartingPrice(_available * _price / WAD * _bufferPercentage / WAD / WAD); // Reverts if there's an active auction
AUCTION.setStartingPrice(_available * _price / WAD * _bufferPercentage / WAD / WAD);
AUCTION.setMinimumPrice(_price * minAuctionPriceBps / MAX_BPS);

COLL.safeTransfer(address(AUCTION), _toAuction);
AUCTION.kick(address(COLL));
Expand All @@ -329,15 +302,9 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
// ===============================================================

/// @inheritdoc BaseStrategy
/// @dev Tend is triggered if either:
/// - we need to stop an unhealthy auction, OR
/// - basefee <= maxGasPriceToTend and we have gains or collateral to auction
function _tendTrigger() internal view override returns (bool) {
if (TokenizedStrategy.totalAssets() == 0) return false;

// Check if active auction price is below our acceptable threshold
if (_isAuctionPriceTooLow()) return true;

// If active auction, wait
if (AUCTION.available(address(COLL)) > dustThreshold) return false;

Expand All @@ -349,47 +316,4 @@ contract LiquityV2SPStrategy is BaseHealthCheck {
return block.basefee <= maxGasPriceToTend && (isCollateralGainToClaim() || _available > dustThreshold);
}

/// @notice Used to trigger emergency stopping of unhealthy auctions
/// @dev Returns true if there is an active auction and its price is
/// below our minimum acceptable price
function _isAuctionPriceTooLow() internal view returns (bool) {
uint256 _minAuctionPriceBps = minAuctionPriceBps;

// If zero, the check is disabled
if (_minAuctionPriceBps == 0) return false;

// If no active auction, return false
if (AUCTION.available(address(COLL)) <= dustThreshold) return false;

// Get the current market price of the collateral from Chainlink
(, int256 _answer,, uint256 _updatedAt,) = CHAINLINK_ORACLE.latestRoundData();

// If the oracle is stale, return false
if (_isStale(_answer, _updatedAt)) return false;

// Scale the answer to 18 decimals
uint256 _marketPrice = uint256(_answer) * CHAINLINK_TO_WAD;

// Our minimum acceptable price (some % below market price)
uint256 _minPrice = _marketPrice * _minAuctionPriceBps / MAX_BPS;

// Price per unit of collateral required by the auction
uint256 _auctionPrice = AUCTION.price(address(COLL));

// Return true if auction price is below our minimum acceptable price
return _auctionPrice < _minPrice;
}

/// @notice Check if the Chainlink oracle is stale
/// @param _answer Latest answer from the oracle
/// @param _updatedAt Timestamp of the latest answer from the oracle
/// @return True if the oracle is stale
function _isStale(
int256 _answer,
uint256 _updatedAt
) internal view virtual returns (bool) {
bool _stale = _updatedAt + CHAINLINK_ORACLE_HEARTBEAT <= block.timestamp;
return _stale || _answer <= 0;
}

}
9 changes: 2 additions & 7 deletions src/StrategyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,16 @@ contract StrategyFactory {
* @notice Deploy a new Strategy.
* @param _addressesRegistry The address of the AddressesRegistry.
* @param _asset The underlying asset for the strategy to use.
* @param _oracle Address of the collateral's COLL/USD _read_ price oracle.
* @param _oracleHeartbeat Heartbeat of the `_oracle`
* @return . The address of the new strategy.
*/
function newStrategy(
address _addressesRegistry,
address _asset,
address _oracle,
uint256 _oracleHeartbeat,
string calldata _name
) external virtual onlyManagement returns (address) {
// tokenized strategies available setters.
IStrategyInterface _newStrategy = IStrategyInterface(
address(new Strategy(_addressesRegistry, _asset, auctionFactory, _oracle, _oracleHeartbeat, _name))
);
IStrategyInterface _newStrategy =
IStrategyInterface(address(new Strategy(_addressesRegistry, _asset, auctionFactory, _name)));

_newStrategy.setPerformanceFeeRecipient(performanceFeeRecipient);

Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/IAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ interface IAuction {
function price(
address _from
) external view returns (uint256);
function minimumPrice() external view returns (uint256);
function startingPrice() external view returns (uint256);
function kick(
address _token
) external returns (uint256);
function setGovernanceOnlyKick(
bool _governanceOnlyKick
) external;
function setMinimumPrice(
uint256 _minimumPrice
) external;
function setStartingPrice(
uint256 _startingPrice
) external;
Expand Down
2 changes: 0 additions & 2 deletions src/interfaces/IPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.23;

import {AggregatorV3Interface} from "./AggregatorV3Interface.sol";

interface IPriceFeed {

enum PriceSource {
Expand Down
2 changes: 0 additions & 2 deletions src/interfaces/IStrategyInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ interface IStrategyInterface is IBaseHealthCheck {
address _address
) external view returns (bool);
function ORACLE_DOWN_BUFFER_PCT_MULTIPLIER() external view returns (uint256);
function AUCTION_PRICE_TOO_LOW_BUFFER_PCT_MULTIPLIER() external view returns (uint256);
function MIN_BUFFER_PERCENTAGE() external view returns (uint256);
function MIN_MAX_GAS_PRICE_TO_TEND() external view returns (uint256);
function MIN_DUST_THRESHOLD() external view returns (uint256);
function COLL() external view returns (address);
function COLL_PRICE_ORACLE() external view returns (address);
function CHAINLINK_ORACLE() external view returns (address);
function SP() external view returns (address);
function AUCTION() external view returns (address);

Expand Down
9 changes: 0 additions & 9 deletions src/interfaces/IWSTETH.sol

This file was deleted.

Loading