Skip to content

Commit

Permalink
Merge pull request #145 from lidofinance/feat/frontrun-escrow-batch-c…
Browse files Browse the repository at this point in the history
…laim

tests: add test case to forbid front-running claim of unStETH from batch
  • Loading branch information
Psirex authored Oct 27, 2024
2 parents 2d7b26d + 71ffd13 commit efbfe44
Showing 1 changed file with 75 additions and 0 deletions.
75 changes: 75 additions & 0 deletions test/scenario/escrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {WithdrawalRequestStatus} from "contracts/interfaces/IWithdrawalQueue.sol
import {EscrowState, State} from "contracts/libraries/EscrowState.sol";

import {Escrow, VetoerState, LockedAssetsTotals, WithdrawalsBatchesQueue} from "contracts/Escrow.sol";
import {AssetsAccounting, UnstETHRecordStatus} from "contracts/libraries/AssetsAccounting.sol";

import {ScenarioTestBlueprint, LidoUtils, console} from "../utils/scenario-test-blueprint.sol";

Expand Down Expand Up @@ -544,6 +545,80 @@ contract EscrowHappyPath is ScenarioTestBlueprint {
this.externalUnlockStETH(_VETOER_1);
}

function testFork_EdgeCase_frontRunningClaimUnStethFromBatchIsForbidden() external {
// Prepare vetoer1 unstETH nft to lock in Escrow
uint256 requestAmount = 10 * 1e18;
uint256[] memory amounts = new uint256[](1);
amounts[0] = requestAmount;
vm.prank(_VETOER_1);
uint256[] memory unstETHIdsVetoer1 = _lido.withdrawalQueue.requestWithdrawals(amounts, _VETOER_1);

// Should be the same as vetoer1 unstETH nft
uint256 lastRequestIdBeforeBatch = _lido.withdrawalQueue.getLastRequestId();

// Lock unstETH nfts
_lockUnstETH(_VETOER_1, unstETHIdsVetoer1);
// Lock stETH to generate batch
_lockStETH(_VETOER_1, 20 * requestAmount);

vm.prank(address(_dualGovernance));
escrow.startRageQuit(_RAGE_QUIT_EXTRA_TIMELOCK, _RAGE_QUIT_WITHDRAWALS_TIMELOCK);

uint256 batchSizeLimit = 16;
// Generate batch with stETH locked in Escrow
escrow.requestNextWithdrawalsBatch(batchSizeLimit);

uint256[] memory nextWithdrawalBatch = escrow.getNextWithdrawalBatch(batchSizeLimit);
assertEq(nextWithdrawalBatch.length, 1);
assertEq(nextWithdrawalBatch[0], _lido.withdrawalQueue.getLastRequestId());

// Should be the id of unstETH nft in the batch
uint256 requestIdFromBatch = nextWithdrawalBatch[0];

// validate that the new unstEth nft is created
assertEq(requestIdFromBatch, lastRequestIdBeforeBatch + 1);

_finalizeWithdrawalQueue();

// Check that unstETH nft of vetoer1 could be claimed
uint256[] memory unstETHIdsToClaim = new uint256[](1);
unstETHIdsToClaim[0] = lastRequestIdBeforeBatch;
uint256[] memory hints = _lido.withdrawalQueue.findCheckpointHints(
unstETHIdsToClaim, 1, _lido.withdrawalQueue.getLastCheckpointIndex()
);
escrow.claimUnstETH(unstETHIdsToClaim, hints);

// The attempt to claim funds of untEth from Escrow generated batch will fail
unstETHIdsToClaim[0] = requestIdFromBatch;
hints = _lido.withdrawalQueue.findCheckpointHints(
unstETHIdsToClaim, 1, _lido.withdrawalQueue.getLastCheckpointIndex()
);
vm.expectRevert(
abi.encodeWithSelector(
AssetsAccounting.InvalidUnstETHStatus.selector, requestIdFromBatch, UnstETHRecordStatus.NotLocked
)
);
escrow.claimUnstETH(unstETHIdsToClaim, hints);

// The rage quit process can be successfully finished
while (escrow.getUnclaimedUnstETHIdsCount() > 0) {
escrow.claimNextWithdrawalsBatch(batchSizeLimit);
}

escrow.startRageQuitExtensionPeriod();
assertEq(escrow.isRageQuitFinalized(), false);

_wait(_RAGE_QUIT_EXTRA_TIMELOCK.plusSeconds(1));
assertEq(escrow.isRageQuitFinalized(), true);

_wait(_RAGE_QUIT_WITHDRAWALS_TIMELOCK.plusSeconds(1));

vm.startPrank(_VETOER_1);
escrow.withdrawETH();
escrow.withdrawETH(unstETHIdsVetoer1);
vm.stopPrank();
}

// ---
// Helper external methods to test reverts
// ---
Expand Down

0 comments on commit efbfe44

Please sign in to comment.