Skip to content

Commit bb68ca9

Browse files
committed
refactor: read the Request through syncRepaidStatus and store the payload digest as a uint
The value gate derived the effective repaid state from the mirrored deadline plus the stale repaid flag; syncRepaidStatus returns exactly that disjunction and folds a past-deadline Request into the flag, the same idiom resolve uses. The payload digest now goes through the existing tStoreUint/tLoadUint, so LibTransientSlot is untouched by this PR.
1 parent 4979873 commit bb68ca9

4 files changed

Lines changed: 26 additions & 38 deletions

File tree

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,11 @@ interface IRetargetter {
251251
/// one balance snapshot taken before any leg executes and do not compose: every leg
252252
/// resolves against the same pre-call balances, never the state left by earlier legs,
253253
/// so REPAY sentinels across several modules can together commit more than the shared
254-
/// balance and revert. While the operation's Request is unrepaid and short of its
255-
/// deadline, the position manager's totalAssets must not grow across the call (every
256-
/// caller, owner included): Request capital may enter the position only against
257-
/// equivalent value flowing back out in the same call.
254+
/// balance and revert. While the operation's Request is unrepaid, the position's net
255+
/// value (quoted collateral minus debt over every module, underwater ones included)
256+
/// must not grow across the call, for every caller including the owner: Request
257+
/// capital may enter the position only against equivalent value flowing back out in
258+
/// the same call.
258259
/// @param data The rebalancing data forwarded to the position manager
259260
function rebalance(RebalancingData calldata data) external;
260261

‎src/libs/common/LibTransientSlot.sol‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@ library LibTransientSlot {
2121
}
2222
}
2323

24-
/// @dev Reads a bytes32 from a transient slot.
25-
function tLoadBytes32(bytes32 slot) internal view returns (bytes32 value) {
26-
assembly ("memory-safe") {
27-
value := tload(slot)
28-
}
29-
}
30-
3124
/// @dev Writes an address to a transient slot.
3225
function tStoreAddress(bytes32 slot, address value) internal {
3326
assembly ("memory-safe") {
@@ -41,11 +34,4 @@ library LibTransientSlot {
4134
tstore(slot, value)
4235
}
4336
}
44-
45-
/// @dev Writes a bytes32 to a transient slot.
46-
function tStoreBytes32(bytes32 slot, bytes32 value) internal {
47-
assembly ("memory-safe") {
48-
tstore(slot, value)
49-
}
50-
}
5137
}

‎src/manager/rebalancer/Retargetter.sol‎

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -567,15 +567,15 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
567567
bytes memory payload = abi.encode(data);
568568
MODULE_TSLOT.tStoreAddress(flashLoanModule);
569569
AMOUNT_TSLOT.tStoreUint(flashLoanAmount);
570-
PAYLOAD_TSLOT.tStoreBytes32(keccak256(payload));
570+
PAYLOAD_TSLOT.tStoreUint(uint256(keccak256(payload)));
571571
BALANCE_TSLOT.tStoreUint(debtAsset.balanceOf(address(this)));
572572

573573
IFlashLoanModule(flashLoanModule).flashLoan(debtAsset, flashLoanAmount, payload);
574574

575575
// The module has pulled its repayment; close the window and scrub any leftover approval
576576
MODULE_TSLOT.tStoreAddress(address(0));
577577
AMOUNT_TSLOT.tStoreUint(0);
578-
PAYLOAD_TSLOT.tStoreBytes32(bytes32(0));
578+
PAYLOAD_TSLOT.tStoreUint(0);
579579
BALANCE_TSLOT.tStoreUint(0);
580580
debtAsset.safeApprove(flashLoanModule, 0);
581581

@@ -605,12 +605,12 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
605605
address module = MODULE_TSLOT.tLoadAddress();
606606
if (
607607
module == address(0) || msg.sender != module || amount != AMOUNT_TSLOT.tLoadUint()
608-
|| keccak256(data) != PAYLOAD_TSLOT.tLoadBytes32()
608+
|| uint256(keccak256(data)) != PAYLOAD_TSLOT.tLoadUint()
609609
) {
610610
revert LibRetargetterErrors.UnauthorizedFlashLoanCallback();
611611
}
612612
MODULE_TSLOT.tStoreAddress(address(0));
613-
PAYLOAD_TSLOT.tStoreBytes32(bytes32(0));
613+
PAYLOAD_TSLOT.tStoreUint(0);
614614
address debtAsset = LibStorage.assetsStorage().debtAsset;
615615
if (debtAsset.balanceOf(address(this)) < BALANCE_TSLOT.tLoadUint() + amount) {
616616
revert LibRetargetterErrors.PrincipalNotDelivered();
@@ -910,20 +910,17 @@ contract Retargetter is IRetargetter, IFlashLoanReceiver, OwnableRoles, Initiali
910910
}
911911

912912
/// @dev Arms the rebalance value-conservation gate: returns the position manager's current
913-
/// net value while the operation's Request is unrepaid and short of its deadline, and
914-
/// the max sentinel (gate disarmed) otherwise. Once the Request is repaid, or
915-
/// auto-expired past its deadline with holders redeeming its remaining balance,
916-
/// folding residual value back into the position must reopen, which is why the gate
917-
/// keys on the effective repaid state rather than the operation being active.
918-
function _bridgeValueSnapshot(address positionManager) internal view returns (int256) {
919-
RetargetterOperation storage operation_ = LibStorage.operationStorage();
920-
address request = operation_.request;
921-
if (
922-
request == address(0) || block.timestamp >= operation_.repaymentDeadline
923-
|| IRequestInteractions(request).isRepaid()
924-
) {
925-
return type(int256).max;
926-
}
913+
/// net value while the operation's Request is unrepaid, and the max sentinel (gate
914+
/// disarmed) otherwise. Once the Request is repaid, or auto-expired past its deadline
915+
/// with holders redeeming its remaining balance, folding residual value back into the
916+
/// position must reopen, which is why the gate keys on the effective repaid state
917+
/// rather than the operation being active. Read through the state-mutating sync (the
918+
/// same one resolve gates on) so a past-deadline Request reads repaid here instead of
919+
/// wedging the gate on a stale flag; that makes this the only non-view step of the
920+
/// rebalance guardrails.
921+
function _bridgeValueSnapshot(address positionManager) internal returns (int256) {
922+
address request = LibStorage.operationStorage().request;
923+
if (request == address(0) || IRequest(request).syncRepaidStatus()) return type(int256).max;
927924
return _positionValue(positionManager);
928925
}
929926

‎test/manager/rebalancer/RetargetterAsync.t.sol‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,10 +1045,14 @@ contract RetargetterAsyncTest is RetargetterBaseTest {
10451045
vm.expectRevert(LibRequestErrors.AlreadyRepaid.selector);
10461046
retargetter.repay();
10471047

1048-
// Fold the pulled funds back into the position debt (below target is always allowed),
1049-
// then resolve through the state-mutating repaid sync
1048+
// The fold grows the position's net value, so it only lands because the value gate reads
1049+
// the Request through the same state-mutating sync resolve uses: past the deadline the
1050+
// Request reads repaid (and its stale flag is synced here) and the gate disarms
1051+
assertFalse(IRequest(request).isRepaid(), "repaid flag still stale before the rebalance");
10501052
vm.prank(rebalancer);
10511053
retargetter.rebalance(_rebalancingData(0, MAX_SENTINEL, RebalancingOperationType.REPAY, MAX_SENTINEL));
1054+
assertTrue(IRequest(request).isRepaid(), "rebalance synced the auto-expired Request");
1055+
10521056
vm.prank(rebalancer);
10531057
retargetter.resolve();
10541058
assertFalse(retargetter.isActive(), "resolved");

0 commit comments

Comments
 (0)