-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathRebalance.s.sol
More file actions
92 lines (82 loc) · 4.25 KB
/
Copy pathRebalance.s.sol
File metadata and controls
92 lines (82 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {FCMVault} from "../src/FCMVault.sol";
import {FCMHelpers} from "../src/libraries/FCMHelpers.sol";
import {IMorpho, Market, MarketParams, Position} from "@morpho-blue/interfaces/IMorpho.sol";
import {IOracle} from "@morpho-blue/interfaces/IOracle.sol";
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";
import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol";
import {Script, console} from "forge-std/Script.sol";
/// @title Rebalance
/// @notice Drives a LIVE FCMVault's leveraged Morpho position back inside its
/// configured health-factor band, rebalancing to the re-entry target.
/// `rebalance` is permissionless, so any account with FLOW for gas can
/// run this.
///
/// The market oracle (Pyth) must be fresh — push an update with
/// `make mainnet-update-oracle` first, otherwise the oracle read
/// inside `rebalance` reverts with StalePrice.
///
/// Everything is read from the vault itself, so this works against any
/// FCMVault address with no config file. Spends real funds (swap fees
/// + price impact) when it actually rebalances, so dry-run first by
/// dropping --broadcast — the dry-run fork-simulates the exact same
/// sequence for free.
///
/// Env:
/// VAULT (required) FCMVault address
///
/// Usage (dry-run first by dropping --broadcast):
/// VAULT=0x... forge script script/Rebalance.s.sol \
/// --rpc-url flow_mainnet --broadcast --account "$ACCOUNT"
contract Rebalance is Script {
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;
using FCMHelpers for FCMVault;
function run() public {
FCMVault vault = FCMVault(vm.envAddress("VAULT"));
uint256 hfBefore = _healthFactor(vault);
uint256 debtBefore = _debt(vault);
vm.startBroadcast();
vault.rebalance();
vm.stopBroadcast();
uint256 hfAfter = _healthFactor(vault);
uint256 debtAfter = _debt(vault);
console.log("=== rebalance complete ===");
console.log("vault: %s", address(vault));
console.log("HF min: %s", vault.HEALTH_FACTOR_MIN());
console.log("HF max: %s", vault.HEALTH_FACTOR_MAX());
console.log("HF before: %s", hfBefore);
console.log("HF after: %s", hfAfter);
console.log("debt before: %s", debtBefore);
console.log("debt after: %s", debtAfter);
if (debtAfter == debtBefore) {
console.log("no-op: HF was inside [min, max]");
}
}
/// @dev Reads the vault's Morpho market params and computes the position's
/// health factor the same way the contract does (WAD-scaled). Returns
/// `type(uint256).max` when there is no debt.
function _healthFactor(FCMVault vault) internal view returns (uint256) {
MarketParams memory mp = vault.market();
Position memory pos = IMorpho(0x9a094eA4AbE343D908E1bDE9fC478D71b41D665f).position(mp.id(), address(vault));
if (pos.borrowShares == 0) return type(uint256).max;
uint256 debt = _debtFromPosition(mp, pos);
uint256 maxBorrow = (uint256(pos.collateral) * ((IOracle(mp.oracle).price() * mp.lltv) / 1e36)) / 1e18;
return (maxBorrow * 1e18) / debt;
}
/// @dev The vault's outstanding debt in loan-token units.
function _debt(FCMVault vault) internal view returns (uint256) {
MarketParams memory mp = vault.market();
Position memory pos = IMorpho(0x9a094eA4AbE343D908E1bDE9fC478D71b41D665f).position(mp.id(), address(vault));
if (pos.borrowShares == 0) return 0;
return _debtFromPosition(mp, pos);
}
/// @dev Converts borrow shares to loan-token debt with Morpho's own
/// `SharesMathLib.toAssetsUp`, matching how Morpho charges debt (and
/// the contract's `MorphoLib.debt`).
function _debtFromPosition(MarketParams memory mp, Position memory pos) internal view returns (uint256) {
Market memory mkt = IMorpho(0x9a094eA4AbE343D908E1bDE9fC478D71b41D665f).market(mp.id());
return uint256(pos.borrowShares).toAssetsUp(uint256(mkt.totalBorrowAssets), uint256(mkt.totalBorrowShares));
}
}