Skip to content

Commit

Permalink
Merge pull request #228 from lidofinance/feature/increase-min-transfe…
Browse files Browse the repository at this point in the history
…rable-steth-amount

Increase Escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT
  • Loading branch information
Psirex authored Dec 5, 2024
2 parents 4b1120c + 88a133f commit 8409ce8
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 30 deletions.
4 changes: 2 additions & 2 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract Escrow is IEscrow {
/// @dev The lower limit for stETH transfers when requesting a withdrawal batch
/// during the Rage Quit phase. For more details, see https://github.com/lidofinance/lido-dao/issues/442.
/// The current value is chosen to ensure functionality over an extended period, spanning several decades.
uint256 private constant _MIN_TRANSFERRABLE_ST_ETH_AMOUNT = 8 wei;
uint256 public constant MIN_TRANSFERRABLE_ST_ETH_AMOUNT = 100 wei;

// ---
// Sanity Check Parameters & Immutables
Expand Down Expand Up @@ -303,7 +303,7 @@ contract Escrow is IEscrow {
/// Using only `minStETHWithdrawalRequestAmount` is insufficient because it is an external variable
/// that could be decreased independently. Introducing `minWithdrawableStETHAmount` provides
/// an internal safeguard, enforcing a minimum threshold within the contract.
uint256 minWithdrawableStETHAmount = Math.max(_MIN_TRANSFERRABLE_ST_ETH_AMOUNT, minStETHWithdrawalRequestAmount);
uint256 minWithdrawableStETHAmount = Math.max(MIN_TRANSFERRABLE_ST_ETH_AMOUNT, minStETHWithdrawalRequestAmount);

if (stETHRemaining < minWithdrawableStETHAmount) {
return _batchesQueue.close();
Expand Down
17 changes: 16 additions & 1 deletion test/mocks/WithdrawalQueueMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.26;

// import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; /*, ERC721("test", "test")*/
import {IWithdrawalQueue} from "contracts/interfaces/IWithdrawalQueue.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/* solhint-disable no-unused-vars,custom-errors */
contract WithdrawalQueueMock is IWithdrawalQueue {
Expand All @@ -11,8 +12,11 @@ contract WithdrawalQueueMock is IWithdrawalQueue {
uint256 private _minStETHWithdrawalAmount;
uint256 private _maxStETHWithdrawalAmount;
uint256[] private _requestWithdrawalsResult;
IERC20 private _stETH;

constructor() {}
constructor(IERC20 stETH) {
_stETH = stETH;
}

function MIN_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256) {
return _minStETHWithdrawalAmount;
Expand Down Expand Up @@ -70,6 +74,17 @@ contract WithdrawalQueueMock is IWithdrawalQueue {
uint256[] calldata _amounts,
address _owner
) external returns (uint256[] memory requestIds) {
uint256 totalAmount = 0;
for (uint256 i = 0; i < _amounts.length; i++) {
if (_amounts[i] < _minStETHWithdrawalAmount) {
revert("Amount is less than MIN_STETH_WITHDRAWAL_AMOUNT");
}
if (_amounts[i] > _maxStETHWithdrawalAmount) {
revert("Amount is more than MAX_STETH_WITHDRAWAL_AMOUNT");
}
totalAmount += _amounts[i];
}
IERC20(_stETH).transferFrom(_owner, address(this), totalAmount);
return _requestWithdrawalsResult;
}

Expand Down
2 changes: 1 addition & 1 deletion test/unit/DualGovernance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ contract DualGovernanceUnitTests is UnitTest {
address private proposalsCanceller = makeAddr("proposalsCanceller");

StETHMock private immutable _STETH_MOCK = new StETHMock();
IWithdrawalQueue private immutable _WITHDRAWAL_QUEUE_MOCK = new WithdrawalQueueMock();
IWithdrawalQueue private immutable _WITHDRAWAL_QUEUE_MOCK = new WithdrawalQueueMock(_STETH_MOCK);

// TODO: Replace with mocks
IWstETH private immutable _WSTETH_STUB = IWstETH(makeAddr("WSTETH_STUB"));
Expand Down
159 changes: 133 additions & 26 deletions test/unit/Escrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract EscrowUnitTests is UnitTest {
StETHMock private _stETH;
IWstETH private _wstETH;

address private _withdrawalQueue;
WithdrawalQueueMock private _withdrawalQueue;

Duration private _minLockAssetDuration = Durations.from(1 days);
uint256 private stethAmount = 100 ether;
Expand All @@ -42,11 +42,15 @@ contract EscrowUnitTests is UnitTest {
_stETH = new StETHMock();
_stETH.__setShareRate(1);
_wstETH = IWstETH(address(new ERC20Mock()));
_withdrawalQueue = address(new WithdrawalQueueMock());
_withdrawalQueue = new WithdrawalQueueMock(_stETH);
_withdrawalQueue.setMaxStETHWithdrawalAmount(1_000 ether);
_masterCopy =
new Escrow(_stETH, _wstETH, WithdrawalQueueMock(_withdrawalQueue), IDualGovernance(_dualGovernance), 100);
_escrow = Escrow(payable(Clones.clone(address(_masterCopy))));

vm.prank(address(_escrow));
_stETH.approve(address(_withdrawalQueue), type(uint256).max);

vm.prank(_dualGovernance);
_escrow.initialize(_minLockAssetDuration);

Expand Down Expand Up @@ -141,6 +145,129 @@ contract EscrowUnitTests is UnitTest {
_masterCopy.isWithdrawalsBatchesClosed();
}

// ---
// MIN_TRANSFERRABLE_ST_ETH_AMOUNT
// ---

function test_MIN_TRANSFERRABLE_ST_ETH_AMOUNT() external {
assertEq(_escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT(), 100);
}

function test_MIN_TRANSFERRABLE_ST_ETH_AMOUNT_gt_minWithdrawableStETHAmountWei_HappyPath() external {
uint256 amountToLock = 100;

uint256 minWithdrawableStETHAmountWei = 99;
assertEq(_escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT(), 100);
_withdrawalQueue.setMinStETHWithdrawalAmount(minWithdrawableStETHAmountWei);

// Lock stETH
_stETH.mint(_vetoer, amountToLock);
vm.prank(_vetoer);
_escrow.lockStETH(amountToLock);
assertEq(_stETH.balanceOf(address(_escrow)), amountToLock);

// Request withdrawal
vm.prank(_dualGovernance);
_escrow.startRageQuit(Durations.ZERO, Durations.ZERO);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), false);

uint256[] memory unstEthIds = new uint256[](1);
unstEthIds[0] = 1;
_withdrawalQueue.setRequestWithdrawalsResult(unstEthIds);
_escrow.requestNextWithdrawalsBatch(100);

assertEq(_stETH.balanceOf(address(_escrow)), 0);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 1);
assertEq(_escrow.isWithdrawalsBatchesClosed(), true);
}

function test_MIN_TRANSFERRABLE_ST_ETH_AMOUNT_gt_minWithdrawableStETHAmountWei_HappyPath_closes_queue() external {
uint256 amountToLock = 99;

uint256 minWithdrawableStETHAmountWei = 99;
assertEq(_escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT(), 100);
_withdrawalQueue.setMinStETHWithdrawalAmount(minWithdrawableStETHAmountWei);

// Lock stETH
_stETH.mint(_vetoer, amountToLock);
vm.prank(_vetoer);
_escrow.lockStETH(amountToLock);
assertEq(_stETH.balanceOf(address(_escrow)), amountToLock);

// Request withdrawal
vm.prank(_dualGovernance);
_escrow.startRageQuit(Durations.ZERO, Durations.ZERO);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), false);

uint256[] memory unstEthIds = new uint256[](0);
_withdrawalQueue.setRequestWithdrawalsResult(unstEthIds);
_escrow.requestNextWithdrawalsBatch(100);

assertEq(_stETH.balanceOf(address(_escrow)), 99);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), true);
}

function test_MIN_TRANSFERRABLE_ST_ETH_AMOUNT_lt_minWithdrawableStETHAmountWei_HappyPath() external {
uint256 amountToLock = 101;

uint256 minWithdrawableStETHAmountWei = 101;
assertEq(_escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT(), 100);
_withdrawalQueue.setMinStETHWithdrawalAmount(minWithdrawableStETHAmountWei);

// Lock stETH
_stETH.mint(_vetoer, amountToLock);
vm.prank(_vetoer);
_escrow.lockStETH(amountToLock);
assertEq(_stETH.balanceOf(address(_escrow)), amountToLock);

// Request withdrawal
vm.prank(_dualGovernance);
_escrow.startRageQuit(Durations.ZERO, Durations.ZERO);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), false);

uint256[] memory unstEthIds = new uint256[](1);
unstEthIds[0] = 1;
_withdrawalQueue.setRequestWithdrawalsResult(unstEthIds);
_escrow.requestNextWithdrawalsBatch(100);

assertEq(_stETH.balanceOf(address(_escrow)), 0);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 1);
assertEq(_escrow.isWithdrawalsBatchesClosed(), true);
}

function test_MIN_TRANSFERRABLE_ST_ETH_AMOUNT_lt_minWithdrawableStETHAmountWei_HappyPath_closes_queue() external {
uint256 amountToLock = 100;

uint256 minWithdrawableStETHAmountWei = 101;
assertEq(_escrow.MIN_TRANSFERRABLE_ST_ETH_AMOUNT(), 100);
_withdrawalQueue.setMinStETHWithdrawalAmount(minWithdrawableStETHAmountWei);

// Lock stETH
_stETH.mint(_vetoer, amountToLock);
vm.prank(_vetoer);
_escrow.lockStETH(amountToLock);
assertEq(_stETH.balanceOf(address(_escrow)), amountToLock);

// Request withdrawal
vm.prank(_dualGovernance);
_escrow.startRageQuit(Durations.ZERO, Durations.ZERO);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), false);

uint256[] memory unstEthIds = new uint256[](1);
unstEthIds[0] = 1;
_withdrawalQueue.setRequestWithdrawalsResult(unstEthIds);
_escrow.requestNextWithdrawalsBatch(100);

assertEq(_stETH.balanceOf(address(_escrow)), 100);
assertEq(_escrow.getNextWithdrawalBatch(100).length, 0);
assertEq(_escrow.isWithdrawalsBatchesClosed(), true);
}

function vetoerLockedUnstEth(uint256[] memory amounts) internal returns (uint256[] memory unstethIds) {
unstethIds = new uint256[](amounts.length);
IWithdrawalQueue.WithdrawalRequestStatus[] memory statuses =
Expand All @@ -153,36 +280,16 @@ contract EscrowUnitTests is UnitTest {
}

vm.mockCall(
_withdrawalQueue,
address(_withdrawalQueue),
abi.encodeWithSelector(IWithdrawalQueue.getWithdrawalStatus.selector, unstethIds),
abi.encode(statuses)
);
vm.mockCall(_withdrawalQueue, abi.encodeWithSelector(IWithdrawalQueue.transferFrom.selector), abi.encode(true));
vm.mockCall(
address(_withdrawalQueue), abi.encodeWithSelector(IWithdrawalQueue.transferFrom.selector), abi.encode(true)
);

vm.startPrank(_vetoer);
_escrow.lockUnstETH(unstethIds);
vm.stopPrank();
}

function createEscrow(uint256 size) internal returns (Escrow) {
return
new Escrow(_stETH, _wstETH, WithdrawalQueueMock(_withdrawalQueue), IDualGovernance(_dualGovernance), size);
}

function createEscrowProxy(uint256 minWithdrawalsBatchSize) internal returns (Escrow) {
Escrow masterCopy = createEscrow(minWithdrawalsBatchSize);
return Escrow(payable(Clones.clone(address(masterCopy))));
}

function createInitializedEscrowProxy(
uint256 minWithdrawalsBatchSize,
Duration minAssetsLockDuration
) internal returns (Escrow) {
Escrow instance = createEscrowProxy(minWithdrawalsBatchSize);

vm.startPrank(_dualGovernance);
instance.initialize(minAssetsLockDuration);
vm.stopPrank();
return instance;
}
}

0 comments on commit 8409ce8

Please sign in to comment.