@@ -31,6 +31,7 @@ contract TestRebalancerInitiateClosePosition is
3131 PositionId internal prevPosId;
3232 Position internal protocolPosition;
3333 uint128 internal wstEthPrice;
34+ uint128 internal securityDeposit;
3435
3536 function setUp () public {
3637 (, amountInRebalancer,,) = _setUpImbalanced (15 ether);
@@ -39,15 +40,7 @@ contract TestRebalancerInitiateClosePosition is
3940 rebalancer.setPositionMaxLeverage (maxLeverage);
4041 skip (5 minutes);
4142
42- {
43- wstEthPrice = 1490 ether ;
44- uint128 ethPrice = uint128 (wstETH.getWstETHByStETH (wstEthPrice)) / 1e10 ;
45- mockPyth.setPrice (int64 (uint64 (ethPrice)));
46- mockPyth.setLastPublishTime (block .timestamp );
47- wstEthPrice = uint128 (wstETH.getStETHByWstETH (ethPrice * 1e10 ));
48- mockChainlinkOnChain.setLastPublishTime (block .timestamp );
49- mockChainlinkOnChain.setLastPrice (int256 (uint256 (ethPrice)));
50- }
43+ wstEthPrice = _setOraclePrices (1490 ether);
5144
5245 uint256 oracleFee = oracleMiddleware.validationCost (MOCK_PYTH_DATA, ProtocolAction.Liquidation);
5346 protocol.liquidate { value: oracleFee }(MOCK_PYTH_DATA);
@@ -60,6 +53,7 @@ contract TestRebalancerInitiateClosePosition is
6053 index: previousPositionData.index
6154 });
6255 (protocolPosition,) = protocol.getLongPosition (prevPosId);
56+ securityDeposit = protocol.getSecurityDepositValue ();
6357 }
6458
6559 function test_setUp () public view {
@@ -74,9 +68,8 @@ contract TestRebalancerInitiateClosePosition is
7468 * @custom:then The call reverts because of the imbalance
7569 */
7670 function test_rebalancerNoWithdrawalAfterRebalancerTrigger () public {
77- uint256 securityDepositValue = protocol.getSecurityDepositValue ();
7871 vm.expectPartialRevert (UsdnProtocolImbalanceLimitReached.selector );
79- rebalancer.initiateClosePosition { value: securityDepositValue }(
72+ rebalancer.initiateClosePosition { value: securityDeposit }(
8073 amountInRebalancer, address (this ), DISABLE_MIN_PRICE, type (uint256 ).max, "" , EMPTY_PREVIOUS_DATA
8174 );
8275 }
@@ -110,7 +103,7 @@ contract TestRebalancerInitiateClosePosition is
110103
111104 vm.expectEmit ();
112105 emit ClosePositionInitiated (address (this ), amount, amountToClose, amountInRebalancer - amount);
113- (bool success ) = rebalancer.initiateClosePosition { value: protocol. getSecurityDepositValue () }(
106+ (bool success ) = rebalancer.initiateClosePosition { value: securityDeposit }(
114107 amount, address (this ), DISABLE_MIN_PRICE, type (uint256 ).max, "" , EMPTY_PREVIOUS_DATA
115108 );
116109
@@ -151,7 +144,6 @@ contract TestRebalancerInitiateClosePosition is
151144 function test_RevertWhen_rebalancerInitiateClosePositionPartialTriggerImbalanceLimit () public {
152145 // choose an amount big enough to trigger imbalance limits
153146 uint88 amount = amountInRebalancer / 10 ;
154- uint256 securityDeposit = protocol.getSecurityDepositValue ();
155147
156148 vm.expectPartialRevert (UsdnProtocolImbalanceLimitReached.selector );
157149 rebalancer.initiateClosePosition { value: securityDeposit }(
@@ -184,7 +176,7 @@ contract TestRebalancerInitiateClosePosition is
184176
185177 vm.expectEmit ();
186178 emit ClosePositionInitiated (address (this ), amountInRebalancer, amountToClose, 0 );
187- (bool success ) = rebalancer.initiateClosePosition { value: protocol. getSecurityDepositValue () }(
179+ (bool success ) = rebalancer.initiateClosePosition { value: securityDeposit }(
188180 amountInRebalancer, address (this ), DISABLE_MIN_PRICE, type (uint256 ).max, "" , EMPTY_PREVIOUS_DATA
189181 );
190182
@@ -207,6 +199,89 @@ contract TestRebalancerInitiateClosePosition is
207199 );
208200 }
209201
202+ /**
203+ * @custom:scenario A user closing its position through the rebalancer can also liquidate ticks
204+ * @custom:given A tick can be liquidated in the USDN protocol
205+ * @custom:when The user calls the rebalancer's `initiateClosePosition` function
206+ * @custom:then A ClosePositionInitiated event is emitted
207+ * @custom:and The user depositData is deleted
208+ * @custom:and The position data is updated
209+ * @custom:and The user initiate close position is pending in protocol
210+ * @custom:and The user receives the liquidation rewards
211+ */
212+ function test_rebalancerInitiateClosePositionLiquidatesAPosition () public {
213+ vm.prank (SET_PROTOCOL_PARAMS_MANAGER);
214+ protocol.setExpoImbalanceLimits (0 , 0 , 0 , 0 , 0 , 0 );
215+
216+ skip (1 hours);
217+ // put the eth price a bit higher to avoid liquidating existing position
218+ wstEthPrice = _setOraclePrices (wstEthPrice * 15 / 10 );
219+
220+ // open a position to liquidate during the initiateClose call
221+ (, PositionId memory posId ) = protocol.initiateOpenPosition { value: securityDeposit }(
222+ 2 ether,
223+ wstEthPrice * 9 / 10 ,
224+ type (uint128 ).max,
225+ protocol.getMaxLeverage (),
226+ payable (address (this )),
227+ payable (address (this )),
228+ type (uint256 ).max,
229+ "" ,
230+ EMPTY_PREVIOUS_DATA
231+ );
232+
233+ skip (1 hours);
234+ // put the price below the above position's liquidation price
235+ wstEthPrice = _setOraclePrices (wstEthPrice * 8 / 10 );
236+
237+ uint256 amountToCloseWithoutBonus = FixedPointMathLib.fullMulDiv (
238+ amountInRebalancer,
239+ rebalancer.getPositionData (rebalancer.getPositionVersion ()).entryAccMultiplier,
240+ rebalancer.getPositionData (rebalancer.getUserDepositData (address (this )).entryPositionVersion)
241+ .entryAccMultiplier
242+ );
243+
244+ uint256 amountToClose = amountToCloseWithoutBonus
245+ + amountToCloseWithoutBonus * (protocolPosition.amount - previousPositionData.amount)
246+ / previousPositionData.amount;
247+
248+ uint256 balanceOfRebalancerBefore = wstETH.balanceOf (address (rebalancer));
249+ LiqTickInfo[] memory liqTickInfoArray;
250+
251+ // snapshot and liquidate to get the liquidated ticks data
252+ uint256 snapshotId = vm.snapshot ();
253+ liqTickInfoArray = protocol.liquidate {
254+ value: oracleMiddleware.validationCost (MOCK_PYTH_DATA, ProtocolAction.Liquidation)
255+ }(MOCK_PYTH_DATA);
256+ vm.revertTo (snapshotId);
257+
258+ uint256 liquidationRewards = liquidationRewardsManager.getLiquidationRewards (
259+ liqTickInfoArray, wstEthPrice, false , RebalancerAction.None, ProtocolAction.InitiateClosePosition, "" , ""
260+ );
261+
262+ vm.expectEmit (false , true , false , false );
263+ emit LiquidatedTick (posId.tick, 0 , 0 , 0 , 0 );
264+ vm.expectEmit ();
265+ emit ClosePositionInitiated (address (this ), amountInRebalancer, amountToClose, 0 );
266+ vm.expectEmit ();
267+ emit Transfer (address (rebalancer), address (this ), liquidationRewards);
268+ (bool success ) = rebalancer.initiateClosePosition { value: securityDeposit }(
269+ amountInRebalancer, address (this ), DISABLE_MIN_PRICE, type (uint256 ).max, "" , EMPTY_PREVIOUS_DATA
270+ );
271+
272+ UserDeposit memory depositData = rebalancer.getUserDepositData (address (this ));
273+
274+ assertTrue (success, "The rebalancer close should be successful " );
275+ assertEq (depositData.amount, 0 , "The user's deposited amount in rebalancer should be zero " );
276+ assertEq (depositData.entryPositionVersion, 0 , "The user's entry position version should be zero " );
277+
278+ assertEq (
279+ balanceOfRebalancerBefore,
280+ wstETH.balanceOf (address (rebalancer)),
281+ "The wstETH balance of the rebalancer should not have changed "
282+ );
283+ }
284+
210285 /**
211286 * @custom:scenario The user sends too much ether when closing its position
212287 * @custom:when The user calls the rebalancer's {initiateClosePosition} function with too much ether
@@ -216,7 +291,6 @@ contract TestRebalancerInitiateClosePosition is
216291 vm.prank (SET_PROTOCOL_PARAMS_MANAGER);
217292 protocol.setExpoImbalanceLimits (0 , 0 , 0 , 0 , 0 , 0 );
218293
219- uint256 securityDeposit = protocol.getSecurityDepositValue ();
220294 uint256 userBalanceBefore = address (this ).balance;
221295 uint256 excessAmount = 1 ether ;
222296
@@ -240,7 +314,6 @@ contract TestRebalancerInitiateClosePosition is
240314 * @custom:then It should revert with `RebalancerUserLiquidated` error
241315 */
242316 function test_RevertWhen_rebalancerUserLiquidated () public {
243- uint256 securityDeposit = protocol.getSecurityDepositValue ();
244317 // compensate imbalance to allow rebalancer users to close
245318 (, PositionId memory newPosId ) = protocol.initiateOpenPosition { value: securityDeposit }(
246319 10 ether,
@@ -355,7 +428,6 @@ contract TestRebalancerInitiateClosePosition is
355428
356429 // deposit assets in the protocol to imbalance it
357430 uint256 oracleFee = oracleMiddleware.validationCost (MOCK_PYTH_DATA, ProtocolAction.ValidateDeposit);
358- uint256 securityDeposit = protocol.getSecurityDepositValue ();
359431 protocol.initiateDeposit { value: securityDeposit }(
360432 100 ether,
361433 DISABLE_SHARES_OUT_MIN,
0 commit comments