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
497 changes: 497 additions & 0 deletions broadcast/DeployStrategy.s.sol/1/run-1787527737485.json

Large diffs are not rendered by default.

508 changes: 508 additions & 0 deletions broadcast/DeployStrategy.s.sol/1/run-1789572435382.json

Large diffs are not rendered by default.

387 changes: 199 additions & 188 deletions broadcast/DeployStrategy.s.sol/1/run-latest.json

Large diffs are not rendered by default.

473 changes: 473 additions & 0 deletions broadcast/DeployVault.s.sol/1/run-1789572134483.json

Large diffs are not rendered by default.

473 changes: 473 additions & 0 deletions broadcast/DeployVault.s.sol/1/run-latest.json

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions script/DeployLockedYvusdStrategy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {IStrategy} from "../src/interfaces/IStrategy.sol";
import {ICentralAPROracle} from "./interfaces/ICentralAPROracle.sol";
import {ICommonReportTrigger} from "./interfaces/ICommonReportTrigger.sol";

import {LockedYvusdFlexLenderStrategy} from "../src/integrations/LockedYvusdStrategy.sol";

import "forge-std/Script.sol";

// ---- Usage ----

// deploy:
// forge script script/DeployLockedYvusdStrategy.s.sol:DeployLockedYvusdStrategy --verify --slow --etherscan-api-key $KEY --rpc-url $RPC_URL --broadcast

contract DeployLockedYvusdStrategy is Script {

// Market params
string public constant NAME = "Flex Locked yvUSD/USDC Lender";
address public constant LENDER = address(0); // TODO: Flex v2 Locked yvUSD/USDC Lender

// Yearn addresses
address public constant SMS = 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7; // sms mainnet
address public constant VAULT = 0x863687e4E9751b57F38b4B0ebA04744C72d0f7B8; // yvFlexUSDC mainnet
address public constant KEEPER = 0x604e586F17cE106B64185A7a0d2c1Da5bAce711E; // yHaaS mainnet
address public constant ACCOUNTANT = 0x5A74Cb32D36f2f517DB6f7b0A0591e09b22cDE69; // accountant mainnet

// Deployed contracts
address public constant EXIT_ROUTER = 0xe8a511403B0C83e7b85513e00Ed39996B48c2aeD;
address public constant FIXED_REPORT_TRIGGER = 0xb9F57B62Cbe9463da16E5b75e3B809321a0eA871;
address public constant STRATEGY_APR_ORACLE = 0xcB5A60AB76F3741204C87744ECFe0445C74Eb171;
ICentralAPROracle public constant CENTRAL_APR_ORACLE = ICentralAPROracle(0x1981AD9F44F2EA9aDd2dC4AD7D075c102C70aF92);
ICommonReportTrigger public constant COMMON_REPORT_TRIGGER = ICommonReportTrigger(0xf8dF17a35c88AbB25e83C92f9D293B4368b9D52D);

function run() public {
uint256 _pk = vm.envUint("DEPLOYER_PRIVATE_KEY");

// Derive deployer address from private key
address _deployerAddress = vm.addr(_pk);

require(_deployerAddress == address(0x000005281a2b04A182085D37cC9E6dD552795caa), "!johnny.flexmeow.eth");
require(LENDER != address(0), "!lender");
console.log("Deployer address: %s", _deployerAddress);

vm.startBroadcast(_pk);

// Deploy the Strategy. The deployer is management, keeper, and fee recipient until set below
IStrategy _strategy = IStrategy(address(new LockedYvusdFlexLenderStrategy(LENDER, EXIT_ROUTER, NAME)));

// Set up the Strategy
_strategy.setKeeper(KEEPER);
_strategy.setPerformanceFeeRecipient(ACCOUNTANT);
_strategy.setProfitMaxUnlockTime(0 days);
_strategy.setAllowed(VAULT, true);
_strategy.setPerformanceFee(0);
_strategy.setPendingManagement(SMS);

// Set APR oracle for the strategy
CENTRAL_APR_ORACLE.setOracle(address(_strategy), STRATEGY_APR_ORACLE);

// Set report trigger for the strategy
COMMON_REPORT_TRIGGER.setCustomStrategyTrigger(address(_strategy), FIXED_REPORT_TRIGGER);

console2.log("---------------------------------");
console2.log("Strategy: ", address(_strategy));
console2.log("---------------------------------");

vm.stopBroadcast();
}

}
8 changes: 4 additions & 4 deletions script/DeployStrategy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import "forge-std/Script.sol";
contract DeployStrategy is Script {

// Market params
string public constant NAME = "Flex ysyBOLD/USDC Lender";
address public constant ASSET = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC
address public constant LENDER = 0xf4996Ca4190A1a3e7CF19AbE2F6eb712abd4a03C; // Flex v2 ysyBOLD/USDC Lender
string public constant NAME = "Flex yvWETH-2/WETH Lender";
address public constant ASSET = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
address public constant LENDER = 0x0d57098e501D68905fC4B0A3397f7D4Aa4889E36; // Flex v2 yvWETH-2/WETH Lender

// Yearn addresses
address public constant SMS = 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7; // sms mainnet
address public constant VAULT = 0x863687e4E9751b57F38b4B0ebA04744C72d0f7B8; // yvFlexUSDC mainnet
address public constant VAULT = 0xfaC55fAFD0b55BFb8dD41F735EfCc195adA9891F; // yvFlexWETH mainnet
address public constant KEEPER = 0x604e586F17cE106B64185A7a0d2c1Da5bAce711E; // yHaaS mainnet
address public constant ACCOUNTANT = 0x5A74Cb32D36f2f517DB6f7b0A0591e09b22cDE69; // accountant mainnet

Expand Down
12 changes: 6 additions & 6 deletions script/DeployVault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import "forge-std/Script.sol";

contract DeployVault is Script {

string private constant NAME = "Flex USDC yVault";
string private constant SYMBOL = "yvFlexUSDC";
address private constant ASSET = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
string private constant NAME = "Flex WETH yVault";
string private constant SYMBOL = "yvFlexWETH";
address private constant ASSET = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
address private constant SMS = 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7; // sms mainnet
address private constant KEEPER = 0x604e586F17cE106B64185A7a0d2c1Da5bAce711E; // yHaaS mainnet
address private constant ROLE_MANAGER = 0xb3bd6B2E61753C311EFbCF0111f75D29706D9a41;

// 3.0.4 Vault Factory
IVaultFactory private constant VAULT_FACTORY = IVaultFactory(0x770D0d1Fb036483Ed4AbB6d53c1C88fb277D812F);
// 3.1.0 Vault Factory
IVaultFactory private constant VAULT_FACTORY = IVaultFactory(0x310aC28ACF5E514abDbFF9Ab25e21f1bfe22bcAC);

function run() public {
uint256 _pk = vm.envUint("DEPLOYER_PRIVATE_KEY");
Expand All @@ -34,7 +34,7 @@ contract DeployVault is Script {

vm.startBroadcast(_pk);

IVault _vault = IVault(VAULT_FACTORY.deploy_new_vault(ASSET, NAME, SYMBOL, _deployerAddress, 7 days));
IVault _vault = IVault(VAULT_FACTORY.deploy_new_vault(ASSET, NAME, SYMBOL, _deployerAddress, 4 days));
_vault.set_role(_deployerAddress, 16383); // ADD_STRATEGY_MANAGER/DEPOSIT_LIMIT_MANAGER/MAX_DEBT_MANAGER/DEBT_MANAGER
_vault.set_role(SMS, 16383); // ADD_STRATEGY_MANAGER/DEPOSIT_LIMIT_MANAGER/MAX_DEBT_MANAGER/DEBT_MANAGER
_vault.set_role(KEEPER, 32); // REPORTING_MANAGER
Expand Down
11 changes: 7 additions & 4 deletions script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

- Allocator Strategy APR Oracle (v1): 0x84D1AeC938D4b9F492c50A1b5218E77195899471
- Allocator Strategy Factory (v1): 0x7A3B96E84156d22Cdb53CbfC0B035Ddd61805266
- Allocator Strategy APR Oracle (v2): 0xcB5A60AB76F3741204C87744ECFe0445C74Eb171
- Allocator Strategy Factory (v2): 0x1792348Fe3ebDdc0C6bB123aC24c094ac9D78252
- Allocator Strategy APR Oracle: 0xcB5A60AB76F3741204C87744ECFe0445C74Eb171
- Allocator Strategy Factory: 0x1792348Fe3ebDdc0C6bB123aC24c094ac9D78252
- Exit Router: 0xe8a511403B0C83e7b85513e00Ed39996B48c2aeD

** Yearn Allocator **

- yvFlexUSDC: 0x863687e4E9751b57F38b4B0ebA04744C72d0f7B8
- yvFlexWETH: 0xfaC55fAFD0b55BFb8dD41F735EfCc195adA9891F
- yvUSD/USDC Strategy (v1): 0x467Ce10870747372968ba98463A1d9af9Fb27751
- siUSD/USDC Strategy (v1): 0x1231310e5F68398dfE929Cc43ce65c1B871eb2C8
- yvUSD/USDC Strategy (v2): 0x7501EAE6b5C2Cb0A6EDAC908E3A679B20eDd737b
- ysyBOLD/USDC Strategy (v2): 0xDd58AeFE74585DA2C7Ad3D3dF0F3aeEAb05b5176
- yvUSD/USDC Strategy: 0x7501EAE6b5C2Cb0A6EDAC908E3A679B20eDd737b
- ysyBOLD/USDC Strategy: 0xDd58AeFE74585DA2C7Ad3D3dF0F3aeEAb05b5176
- yvcrvUSD-2/USDC Strategy: 0xC818A0Be1BB2cE820CfdefAD5f890667e885f39d
- yvWETH-2/WETH Strategy: 0x7E4a6A89583e117C641aB3ce8897209800A3F2E3
13 changes: 10 additions & 3 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,14 @@ contract FlexLenderStrategy is BaseHealthCheck {
function availableDepositLimit(
address _owner
) public view override returns (uint256) {
return Math.min(super.availableDepositLimit(_owner), LENDER.maxDeposit(address(this)));
// The Lender's deposit limit
uint256 _lenderDepositLimit = LENDER.maxDeposit(address(this));

// Subtract any idle asset we have
_lenderDepositLimit -= Math.min(asset.balanceOf(address(this)), _lenderDepositLimit);

// Cap by the base's limit
return Math.min(super.availableDepositLimit(_owner), _lenderDepositLimit);
}

/// @inheritdoc BaseStrategy
Expand All @@ -111,8 +118,8 @@ contract FlexLenderStrategy is BaseHealthCheck {
// If a `_proceedsReceiver` is set, there is no limit
if (_proceedsReceiver != address(0)) return type(uint256).max;

// Otherwise only what can be withdrawn from idle liquidity
return asset.balanceOf(address(this)) + asset.balanceOf(address(LENDER));
// Otherwise only what can be withdrawn from idle liquidity, capped by what our Lender shares are worth
return asset.balanceOf(address(this)) + Math.min(asset.balanceOf(address(LENDER)), LENDER.maxWithdraw(address(this)));
}

// ============================================================================================
Expand Down
47 changes: 36 additions & 11 deletions src/integrations/CooldownStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.30;

import {IERC20, IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {BaseStrategy, ERC20, FlexLenderStrategy} from "../Strategy.sol";

Expand All @@ -18,15 +19,21 @@ abstract contract CooldownFlexLenderStrategy is FlexLenderStrategy {
/// @notice WAD constant
uint256 internal constant _WAD = 1e18;

/// @notice Pending amount below which reports are not blocked, for rounding dust the protocol leaves behind
uint256 public immutable PENDING_DUST;

/// @notice Collateral token
IERC4626 public immutable COLLATERAL;

// ============================================================================================
// Storage
// ============================================================================================

/// @notice Asset amount queued for redemption in the collateral's protocol
uint256 public pendingRedemptions;
/// @notice Whether reports ignore the pending redemptions, an escape hatch for management
bool public ignorePending;

/// @notice Collateral taken in kind and not unwound yet. Storage var and not `balanceOf()` to avoid donations issues
uint256 public takenInKind;

// ============================================================================================
// Constructor
Expand All @@ -48,6 +55,30 @@ abstract contract CooldownFlexLenderStrategy is FlexLenderStrategy {

// Make sure the auction starting price buffer is 0
require(DUTCH_DESK.starting_price_buffer_percentage() == _WAD, "!buffer");

// Set the dust to half the asset's decimals, e.g. 0.001 USDC or 1e9 ETH
PENDING_DUST = 10 ** (asset.decimals() / 2);
}

// ============================================================================================
// Public view functions
// ============================================================================================

/// @notice Asset value taken in kind that is not back yet, loose collateral plus whatever is
/// queued in the collateral's protocol, read live
function pendingRedemptions() public view virtual returns (uint256);

// ============================================================================================
// Management functions
// ============================================================================================

/// @notice Set whether reports ignore the pending redemptions
/// @dev Only callable by management
/// @param _ignorePending Whether to ignore the pending redemptions
function setIgnorePending(
bool _ignorePending
) external onlyManagement {
ignorePending = _ignorePending;
}

// ============================================================================================
Expand All @@ -70,13 +101,7 @@ abstract contract CooldownFlexLenderStrategy is FlexLenderStrategy {

// Take the kicked auction, receiving the collateral in kind
uint256 _auctionId = pendingAuctionId;
if (AUCTION.is_active(_auctionId)) AUCTION.take(_auctionId);
}

/// @notice Zero out the pending redemptions accounting
/// @dev Only callable by management
function zeroPendingRedemptions() external onlyManagement {
pendingRedemptions = 0;
if (AUCTION.is_active(_auctionId)) takenInKind += AUCTION.take(_auctionId);
}

// ============================================================================================
Expand All @@ -100,8 +125,8 @@ abstract contract CooldownFlexLenderStrategy is FlexLenderStrategy {

/// @inheritdoc BaseStrategy
function _harvestAndReport() internal view override returns (uint256) {
// Block reports until in-flight cooldowns are claimed
require(pendingRedemptions == 0, "!cooldown");
// Block reports until everything taken in kind is back as asset, unless management says otherwise
require(ignorePending || pendingRedemptions() <= PENDING_DUST, "!cooldown");
return super._harvestAndReport();
}

Expand Down
52 changes: 44 additions & 8 deletions src/integrations/FxSaveStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {ICurveStableSwap} from "../interfaces/ICurveStableSwap.sol";
import {IFxSave} from "../interfaces/IFxSave.sol";
import {IFxUSDBasePool} from "../interfaces/IFxUSDBasePool.sol";

import {CooldownFlexLenderStrategy, ERC20} from "./CooldownStrategy.sol";
import {CooldownFlexLenderStrategy, ERC20, Math} from "./CooldownStrategy.sol";

/// @title fxSAVE Flex Lender Strategy
/// @author Flex
Expand All @@ -29,6 +29,9 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
/// @notice Scales an 18-decimals fxBASE amount valued by the 18-decimals nav to 6-decimals asset terms
uint256 internal constant _NAV_TO_ASSET_SCALE = 1e30;

/// @notice Scales an 18-decimals fxUSD amount to 6-decimals asset terms
uint256 internal constant _FXUSD_TO_ASSET_SCALE = 1e12;

/// @notice USDC token
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

Expand All @@ -41,6 +44,16 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
/// @notice Curve USDC/fxUSD pool
ICurveStableSwap public constant CURVE_POOL = ICurveStableSwap(0x5018BE882DccE5E3F2f3B0913AE2096B9b3fB61f);

// ============================================================================================
// Storage
// ============================================================================================

/// @notice fxBASE assets queued in fxBASE's redemption queue
uint256 public queuedBaseAssets;

/// @notice fxUSD claimed from the queue and not swapped yet. Storage var and not `balanceOf()` to avoid donations issues
uint256 public claimedFxusd;

// ============================================================================================
// Constructor
// ============================================================================================
Expand All @@ -61,6 +74,20 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
FXUSD.forceApprove(address(CURVE_POOL), type(uint256).max);
}

// ============================================================================================
// Public view functions
// ============================================================================================

/// @inheritdoc CooldownFlexLenderStrategy
function pendingRedemptions() public view override returns (uint256 _pending) {
// Claimed fxUSD, 1:1 with the asset
_pending = claimedFxusd / _FXUSD_TO_ASSET_SCALE;

// fxSAVE taken in kind and the queued fxBASE assets, valued via fxBASE's nav
uint256 _baseAssets = COLLATERAL.convertToAssets(takenInKind) + queuedBaseAssets;
if (_baseAssets > 0) _pending += _baseAssets * FXBASE.nav() / _NAV_TO_ASSET_SCALE;
}

// ============================================================================================
// Cooldown
// ============================================================================================
Expand All @@ -69,19 +96,22 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
/// @dev Only callable by management
/// @dev Requests accumulate, but every request resets the unlock clock for the whole queued amount
/// @param _shares The amount of fxSAVE to unwind, capped by the loose balance
/// @return _pendingAssets The amount of asset queued in fxBASE's redemption queue
/// @return The amount of asset queued in fxBASE's redemption queue
function initiateCooldown(
uint256 _shares
) external onlyManagement returns (uint256 _pendingAssets) {
) external onlyManagement returns (uint256) {
// Cap the shares by the loose collateral balance
_shares = _capToBalance(COLLATERAL, _shares);

// Consume the collateral taken in kind variable
takenInKind -= Math.min(_shares, takenInKind);

// fxSAVE --> fxBASE, queued for redemption
uint256 _baseAssets = IFxSave(address(COLLATERAL)).requestRedeem(_shares);
queuedBaseAssets += _baseAssets;

// Record the queued amount, valued in asset terms via fxBASE's nav
_pendingAssets = _baseAssets * FXBASE.nav() / _NAV_TO_ASSET_SCALE;
pendingRedemptions += _pendingAssets;
// Valued in asset terms via fxBASE's nav
return _baseAssets * FXBASE.nav() / _NAV_TO_ASSET_SCALE;
}

/// @notice Claim the queued redemption from fxBASE once the cooldown passed
Expand All @@ -90,10 +120,10 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
/// @return _assetsOut The amount of asset received
/// @return _fxusdOut The amount of fxUSD received
function claimCooldown() external onlyManagement returns (uint256 _assetsOut, uint256 _fxusdOut) {
require(pendingRedemptions > 0, "!pending");
require(queuedBaseAssets > 0, "!pending");

// The claim always empties the queue
pendingRedemptions = 0;
queuedBaseAssets = 0;

// Claim the whole queued amount
uint256 _preAssetBalance = asset.balanceOf(address(this));
Expand All @@ -102,6 +132,9 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
_assetsOut = asset.balanceOf(address(this)) - _preAssetBalance;
_fxusdOut = FXUSD.balanceOf(address(this)) - _preFxusdBalance;
require(_assetsOut + _fxusdOut > 0, "!assets");

// Record the claimed fxUSD
claimedFxusd += _fxusdOut;
}

/// @notice Swap loose fxUSD to the asset through Curve
Expand All @@ -116,6 +149,9 @@ contract FxSaveFlexLenderStrategy is CooldownFlexLenderStrategy {
// Cap the amount by the loose fxUSD balance
_amount = _capToBalance(FXUSD, _amount);

// Consume the claimed fxUSD variable
claimedFxusd -= Math.min(_amount, claimedFxusd);

// fxUSD --> asset
return CURVE_POOL.exchange(_FXUSD_INDEX, _USDC_INDEX, _amount, _minOut);
}
Expand Down
Loading
Loading