Skip to content

Commit 5404017

Browse files
committed
Remove forecast assumptions from retarget sizing
1 parent 6998ffd commit 5404017

11 files changed

Lines changed: 139 additions & 413 deletions

File tree

‎src/interfaces/manager/rebalancer/IRetargetter.sol‎

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {Order} from "../../../libs/funds/Order.sol";
88
/// @notice Owner-set yield and duration estimates feeding the principal-cap formulas.
99
/// @param requestYieldRate The expected bridge-loan yield rate (WAD per 365 days)
1010
/// @param borrowRate The expected venue borrow rate on existing debt (WAD per 365 days)
11-
/// @param collateralYieldRate The expected collateral yield rate (WAD per 365 days)
11+
/// @param collateralYieldRate Reserved for storage and ABI compatibility; ignored
1212
/// @param subscriptionDuration The expected DEPOSIT settlement duration in seconds
1313
/// @param redemptionDuration The expected REDEEM settlement duration in seconds
1414
struct YieldEstimates {
1515
uint64 requestYieldRate;
1616
uint64 borrowRate;
17+
// Retained to preserve the deployed proxy storage layout.
1718
uint64 collateralYieldRate;
1819
uint32 subscriptionDuration;
1920
uint32 redemptionDuration;
@@ -26,7 +27,7 @@ struct YieldEstimates {
2627
/// tickDuration; doubles as the consumption window length (capital entry closes this
2728
/// long after the loan clock origin)
2829
/// @param maxYieldBps The owner ceiling on per-operation yield caps, at most 5000
29-
/// @param principalBufferBps The headroom over the computed principal cap, at most 2000
30+
/// @param principalBufferBps Reserved for storage and ABI compatibility; ignored
3031
/// @param collateralResidualExponent The settlement tolerance for the collateral asset:
3132
/// balances strictly below 2^exponent pass the residual gate, so dust donations
3233
/// cannot grief settlement (zero keeps the gate exact)
@@ -419,15 +420,9 @@ interface IRetargetter {
419420
function bridgeOutstanding() external view returns (bool);
420421

421422
/// @notice Computes the current principal cap for the bound position manager.
422-
/// @dev Quoter formula on live state with the owner estimates, auto-detected direction,
423-
/// times one plus the principal buffer. Below target the cap is further bounded by
424-
/// the one-trip repayment bound, so a cap-sized, fully deployed operation can always
425-
/// borrow its worst permitted repayment back at target in a single settlement trip.
426-
/// This view sizes that bound on the config yield-cap ceiling, a safe floor for
427-
/// every permissible operation; the operation gates size it on the effective
428-
/// per-operation cap (zero for a SYNC window, whose flash repayment carries no
429-
/// yield) and can admit up to that looser bound. Reverts if no position manager is
430-
/// bound.
423+
/// @dev Uses the quoter formula on live state with the owner estimates and auto-detected
424+
/// direction. Auction yield limits govern accepted offers separately. Reverts if no
425+
/// position manager is bound.
431426
/// @return The principal cap in debt-asset units
432427
function maxPrincipal() external view returns (uint256);
433428

‎src/interfaces/manager/rebalancer/IRetargetterQuoter.sol‎

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface IRetargetterQuoter {
2424
/// @param targetLtv The target loan-to-value ratio (WAD)
2525
/// @param requestYieldRate The bridge-loan yield rate (WAD per 365 days)
2626
/// @param borrowRate The venue borrow rate on existing debt (WAD per 365 days)
27-
/// @param collateralYieldRate The collateral yield rate (WAD per 365 days)
27+
/// @param collateralYieldRate Deprecated compatibility parameter; ignored
2828
/// @param subscriptionDuration The expected subscription settlement duration in seconds
2929
/// @param redemptionDuration The expected redemption settlement duration in seconds
3030
/// @return principal The bridge-loan principal in debt-asset units (rounded down)
@@ -41,16 +41,16 @@ interface IRetargetterQuoter {
4141

4242
/// @notice Computes the borrow principal that brings an under-leveraged position to target.
4343
/// @dev Sized so the position sits exactly at target once the subscription settles and the
44-
/// bridge repayment is drawn as new debt, accounting for debt drift, collateral yield
45-
/// and bridge yield over the window. Returns 0 when the position is at or above target
46-
/// once those accruals are applied. Reverts with `InvalidParameters` only at a full
44+
/// bridge repayment is drawn as new debt, accounting for debt drift and bridge yield
45+
/// over the window. Returns 0 when the position is at or above target once debt drift
46+
/// is applied. Reverts with `InvalidParameters` only at a full
4747
/// target LTV combined with a zero bridge yield estimate.
4848
/// @param collateralQuoted The aggregate collateral value in debt-asset units
4949
/// @param debt The aggregate debt in debt-asset units
5050
/// @param targetLtv The target loan-to-value ratio (WAD)
5151
/// @param requestYieldRate The bridge-loan yield rate (WAD per 365 days)
5252
/// @param borrowRate The venue borrow rate on existing debt (WAD per 365 days)
53-
/// @param collateralYieldRate The collateral yield rate (WAD per 365 days)
53+
/// @param collateralYieldRate Deprecated compatibility parameter; ignored
5454
/// @param duration The expected subscription settlement duration in seconds
5555
/// @return principal The bridge-loan principal in debt-asset units (rounded down)
5656
function ltvUpPrincipal(
@@ -66,16 +66,16 @@ interface IRetargetterQuoter {
6666
/// @notice Computes the borrow principal that brings an over-leveraged position to target.
6767
/// @dev Sized so the position sits exactly at target once the bridge principal has repaid
6868
/// debt and the freed collateral's redemption has covered the bridge repayment,
69-
/// accounting for debt drift, collateral yield and bridge yield over the window.
70-
/// Returns (0, 0) when the position is at or below target once those accruals are
69+
/// accounting for debt drift and bridge yield over the window.
70+
/// Returns (0, 0) when the position is at or below target once debt drift is
7171
/// applied. Reverts with `InvalidParameters` on economically unreasonable rate inputs
7272
/// (a non-positive sizing denominator).
7373
/// @param collateralQuoted The aggregate collateral value in debt-asset units
7474
/// @param debt The aggregate debt in debt-asset units
7575
/// @param targetLtv The target loan-to-value ratio (WAD)
7676
/// @param requestYieldRate The bridge-loan yield rate (WAD per 365 days)
7777
/// @param borrowRate The venue borrow rate on existing debt (WAD per 365 days)
78-
/// @param collateralYieldRate The collateral yield rate (WAD per 365 days)
78+
/// @param collateralYieldRate Deprecated compatibility parameter; ignored
7979
/// @param duration The expected redemption settlement duration in seconds
8080
/// @return principal The bridge-loan principal in debt-asset units (rounded down)
8181
/// @return collateralToFreeQuoted The collateral value to withdraw and redeem so proceeds
@@ -93,22 +93,18 @@ interface IRetargetterQuoter {
9393
/// @notice Computes the one-trip repayment bound of an LTV-up operation.
9494
/// @dev The largest principal whose worst permitted repayment (the principal plus the full
9595
/// flat yield cap) can still be borrowed at target once the subscription has landed as
96-
/// collateral: `P * (1 + yieldCap) <= targetLtv * (K * (1 + Yc) + P) - D * (1 + Rb)`.
97-
/// Same accrual terms as `ltvUpPrincipal`, with the bridge yield estimate replaced
98-
/// by the flat cap because repayment prices actual YT supply, not the estimate.
99-
/// The bound is exact in integers: the collateral growth floors before the target
100-
/// applies (matching the settlement LTV check's own arithmetic) and the drifted debt
101-
/// rounds up, so the worst repayment of a bound-sized principal is always borrowable
102-
/// at target in one settlement trip.
103-
/// Returns 0 when the position is at or above target once the accruals are applied.
96+
/// collateral: `P * (1 + yieldCap) <= targetLtv * (K + P) - D * (1 + Rb)`.
97+
/// This compatibility quote is not part of the Retargetter principal cap; auction
98+
/// yield limits govern accepted offers without changing the requested principal.
99+
/// Returns 0 when the position is at or above target once debt drift is applied.
104100
/// Reverts with `InvalidParameters` only at a full target LTV combined with a zero
105101
/// yield cap.
106102
/// @param collateralQuoted The aggregate collateral value in debt-asset units
107103
/// @param debt The aggregate debt in debt-asset units
108104
/// @param targetLtv The target loan-to-value ratio (WAD)
109105
/// @param maxYieldBps The flat yield cap bounding the operation's YT supply (basis points)
110106
/// @param borrowRate The venue borrow rate on existing debt (WAD per 365 days)
111-
/// @param collateralYieldRate The collateral yield rate (WAD per 365 days)
107+
/// @param collateralYieldRate Deprecated compatibility parameter; ignored
112108
/// @param duration The expected subscription settlement duration in seconds
113109
/// @return principal The one-trip repayment bound in debt-asset units (rounded down)
114110
function ltvUpOneTripPrincipal(
@@ -157,15 +153,13 @@ interface IRetargetterQuoter {
157153

158154
/// @notice Computes the settlement-time cash mismatch of an LTV-down operation under price drift.
159155
/// @dev The bridge repayment is fixed when the operation is sized while the redemption
160-
/// settles at the realized price; the delta is that cash mismatch. The freed collateral
161-
/// was sized net of its own expected yield, so the drift is measured against the
162-
/// expected growth `1 + Yc` rather than unity: a price that moves exactly as forecast
163-
/// yields a zero delta. Positive means surplus (fold into position debt), negative
164-
/// means shortfall (owner-only borrow top-up). Shortfalls round up so a remediation
165-
/// sized from this value always covers the gap.
156+
/// settles at the realized price; the delta is that cash mismatch measured from the
157+
/// start price. Positive means surplus (fold into position debt), negative means
158+
/// shortfall (owner-only borrow top-up). Shortfalls round up so a remediation sized
159+
/// from this value always covers the gap.
166160
/// @param principal The bridge-loan principal in debt-asset units
167161
/// @param requestYieldRate The bridge-loan yield rate (WAD per 365 days)
168-
/// @param collateralYieldRate The collateral yield rate used when sizing (WAD per 365 days)
162+
/// @param collateralYieldRate Deprecated compatibility parameter; ignored
169163
/// @param duration The realized settlement duration in seconds
170164
/// @param priceDriftWad The settlement price ratio `rho = p1 / p0` (WAD)
171165
/// @return delta The signed cash mismatch in debt-asset units

‎src/libs/manager/rebalancer/LibRetargetterConstants.sol‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ uint24 constant MAX_TICK_DURATION = 30 days;
4242
/// @custom:value 5,000
4343
uint16 constant MAX_YIELD_CAP_BPS = 5000;
4444

45-
/// @dev Maximum headroom over the computed principal cap: 20% (2000 basis points).
46-
/// @custom:value 2,000
47-
uint16 constant MAX_PRINCIPAL_BUFFER_BPS = 2000;
48-
4945
/// @dev Maximum number of accounts holding a registered mint authorization at once. Bounds
5046
/// every loop over the authorization set (pruning, revocation at the first pull of
5147
/// funds, clearing at resolve), so a consumer handing out dust authorizations cannot

‎src/manager/rebalancer/Retargetter.sol‎

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
MAX_HORIZON,
2626
MAX_TICK_DURATION,
2727
MAX_YIELD_CAP_BPS,
28-
MAX_PRINCIPAL_BUFFER_BPS,
2928
MAX_AUTHORIZED_ACCOUNTS,
3029
REPAYMENT_DEADLINE_OFFSET,
3130
FULL_BALANCE_SENTINEL,
@@ -36,7 +35,6 @@ import {
3635
BALANCE_TSLOT,
3736
REENTRANCY_TSLOT
3837
} from "../../libs/manager/rebalancer/LibRetargetterConstants.sol";
39-
import {BPS} from "../../libs/Constants.sol";
4038
import {LibChecks} from "../../libs/common/LibChecks.sol";
4139
import {LibTransientSlot} from "../../libs/common/LibTransientSlot.sol";
4240
import {EnumerableSetLib} from "lib/solady/src/utils/EnumerableSetLib.sol";
@@ -217,7 +215,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
217215
}
218216

219217
RetargetterOperation storage operation_ = LibStorage.operationStorage();
220-
address positionManager = _checkStart(operation_, fund, principal, effectiveYieldCap);
218+
address positionManager = _checkStart(operation_, fund, principal);
221219

222220
// Safe: block.timestamp + 90 days fits in uint40 for ~35,000 years
223221
// forge-lint: disable-next-line(unsafe-typecast)
@@ -280,9 +278,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
280278
operation_.checkYield(offer.amount, offer.expectedReturn);
281279
operation_.checkConsumptionWindow();
282280
ytAmount = IRequest(request).consume(offer, signature, ptAmount);
283-
operation_.checkPrincipalCap(
284-
request, 0, _maxPrincipal(LibStorage.assetsStorage().positionManager, operation_.operationMaxYieldBps)
285-
);
281+
operation_.checkPrincipalCap(request, 0, _maxPrincipal(LibStorage.assetsStorage().positionManager));
286282
emit OfferConsumed(request, offer.maker, ptAmount, ytAmount);
287283
}
288284

@@ -309,11 +305,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
309305
accounts.remove(to);
310306
if (ptAmount != 0 || ytAmount != 0) {
311307
operation_.checkOffer(
312-
request,
313-
ptAmount,
314-
ytAmount,
315-
ptAmount,
316-
_maxPrincipal(LibStorage.assetsStorage().positionManager, operation_.operationMaxYieldBps)
308+
request, ptAmount, ytAmount, ptAmount, _maxPrincipal(LibStorage.assetsStorage().positionManager)
317309
);
318310
// The capacity bound keeps every loop over the set within gas reach; see the constant
319311
accounts.add(to, MAX_AUTHORIZED_ACCOUNTS);
@@ -612,9 +604,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
612604
onlyOwnerOrRebalancer
613605
{
614606
RetargetterOperation storage operation_ = LibStorage.operationStorage();
615-
// The flash repayment carries no yield, so the window's principal cap is sized on a
616-
// zero yield cap (the pure one-trip headroom bound)
617-
address positionManager = _checkStart(operation_, fund, flashLoanAmount, 0);
607+
address positionManager = _checkStart(operation_, fund, flashLoanAmount);
618608
if (!LibStorage.whitelistsStorage().flashLoanModules[flashLoanModule]) {
619609
revert LibRetargetterErrors.ModuleNotWhitelisted();
620610
}
@@ -815,13 +805,9 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
815805
}
816806

817807
/// @inheritdoc IRetargetter
818-
/// @dev Sized on the config ceiling maxYieldBps, the largest yield cap any operation may
819-
/// carry, so this view is a safe floor for every permissible operation; the operation
820-
/// gates size on the effective per-operation cap instead (zero for a SYNC window,
821-
/// whose flash repayment carries no yield) and can admit up to that looser bound.
822-
/// See {_maxPrincipal} for the formula. Reverts if no position manager is bound.
808+
/// @dev See {_maxPrincipal} for the formula. Reverts if no position manager is bound.
823809
function maxPrincipal() public view returns (uint256) {
824-
return _maxPrincipal(LibStorage.assetsStorage().positionManager, LibStorage.configStorage().maxYieldBps);
810+
return _maxPrincipal(LibStorage.assetsStorage().positionManager);
825811
}
826812

827813
/// @inheritdoc IRetargetter
@@ -885,7 +871,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
885871
if (
886872
config_.horizon < MIN_HORIZON || config_.horizon > MAX_HORIZON || config_.tickDuration == 0
887873
|| config_.tickDuration > MAX_TICK_DURATION || config_.tickThreshold >= config_.tickDuration
888-
|| config_.maxYieldBps > MAX_YIELD_CAP_BPS || config_.principalBufferBps > MAX_PRINCIPAL_BUFFER_BPS
874+
|| config_.maxYieldBps > MAX_YIELD_CAP_BPS
889875
) {
890876
revert LibRetargetterErrors.InvalidParameters();
891877
}
@@ -906,9 +892,8 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
906892
/// and a SYNC window keeps it populated, so this also locks out starts smuggled into a
907893
/// window payload. Requires a bound position manager (unbound means the instance was
908894
/// abandoned), a whitelisted fund, and the requested amount within the live principal
909-
/// cap sized on the operation's yield cap (fail-fast for ASYNC, where the cap re-runs
910-
/// at every consume; zero for SYNC, whose flash repayment carries no yield).
911-
function _checkStart(RetargetterOperation storage operation_, address fund, uint256 amount, uint16 yieldCapBps)
895+
/// cap (fail-fast for ASYNC, where the cap re-runs at every consume).
896+
function _checkStart(RetargetterOperation storage operation_, address fund, uint256 amount)
912897
internal
913898
view
914899
returns (address positionManager)
@@ -918,7 +903,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
918903
positionManager = LibStorage.assetsStorage().positionManager;
919904
if (positionManager == address(0)) revert LibRetargetterErrors.PositionManagerNotBound();
920905
if (!LibStorage.whitelistsStorage().funds[fund]) revert LibRetargetterErrors.FundNotWhitelisted();
921-
if (amount > _maxPrincipal(positionManager, yieldCapBps)) revert LibRetargetterErrors.PrincipalCapExceeded();
906+
if (amount > _maxPrincipal(positionManager)) revert LibRetargetterErrors.PrincipalCapExceeded();
922907
}
923908

924909
/// @dev Shared preamble of the wrappers acting on the live order: requires an active
@@ -962,12 +947,9 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
962947
/// from live position manager state, so the cap self-corrects: once a rebalance moves
963948
/// the position to target the cap collapses toward zero and further consumption is
964949
/// blocked. An empty position reverts EmptyPosition; debt against zero collateral
965-
/// reverts BadDebtPosition. Below target the buffered cap is further bounded by the
966-
/// quoter's one-trip repayment bound: repayment prices actual YT supply, which the
967-
/// flat yield cap bounds rather than the yield estimate, so the buffer alone would
968-
/// let a cap-sized operation owe more than the post-supply target-LTV borrow
969-
/// capacity can fund in one settlement trip.
970-
function _maxPrincipal(address positionManager, uint16 yieldCapBps) internal view returns (uint256) {
950+
/// reverts BadDebtPosition. Auction yield limits govern accepted offers separately and
951+
/// do not change this position-sizing result.
952+
function _maxPrincipal(address positionManager) internal view returns (uint256) {
971953
uint256 collateralQuoted = IPositionManager(positionManager).collateralAmountQuoted();
972954
uint256 debt = IPositionManager(positionManager).debtAmount();
973955
if (collateralQuoted == 0) {
@@ -990,19 +972,7 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
990972
estimates.subscriptionDuration,
991973
estimates.redemptionDuration
992974
);
993-
uint256 buffered = principal * (BPS + config_.principalBufferBps) / BPS;
994-
if (debt.divWad(collateralQuoted) >= target) return buffered;
995-
uint256 oneTrip = IRetargetterQuoter(_QUOTER)
996-
.ltvUpOneTripPrincipal(
997-
collateralQuoted,
998-
debt,
999-
target,
1000-
yieldCapBps,
1001-
estimates.borrowRate,
1002-
estimates.collateralYieldRate,
1003-
estimates.subscriptionDuration
1004-
);
1005-
return buffered.min(oneTrip);
975+
return principal;
1006976
}
1007977

1008978
/// @dev Current owed amount on the operation's Request, priced on the repayment terms

0 commit comments

Comments
 (0)