Skip to content

Commit

Permalink
Gas saving when claiming Lido withdrawals (#50)
Browse files Browse the repository at this point in the history
* Moved calculating the Lido claim withdrawal hints off-chain

* Changed amounts in requestLidoWithdrawals to use calldata

* Fix merge

* Add LidoARM contract to gas report
  • Loading branch information
naddison36 authored Dec 16, 2024
1 parent 48fd1ff commit e05ecf2
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 15 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ verbosity = 3
sender = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322"
tx_origin = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322"
auto_detect_remappings = false
gas_reports = ["OethARM", "Proxy"]
gas_reports = ["OethARM", "Proxy", "LidoARM"]
fs_permissions = [{ access = "read-write", path = "./build" }]
extra_output_files = ["metadata"]
ignored_warnings_from = ["src/contracts/Proxy.sol"]
Expand Down
7 changes: 4 additions & 3 deletions src/contracts/LidoARM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ contract LidoARM is Initializable, AbstractARM {
/**
* @notice Claim the ETH owed from the redemption requests and convert it to WETH.
* Before calling this method, caller should check on the request NFTs to ensure the withdrawal was processed.
* @param requestIds The request IDs of the withdrawal requests.
* @param hintIds The hint IDs of the withdrawal requests.
* Call `findCheckpointHints` on the Lido withdrawal queue contract to get the hint IDs.
*/
function claimLidoWithdrawals(uint256[] calldata requestIds) external {
function claimLidoWithdrawals(uint256[] calldata requestIds, uint256[] calldata hintIds) external {
// Claim the NFTs for ETH.
uint256 lastIndex = lidoWithdrawalQueue.getLastCheckpointIndex();
uint256[] memory hintIds = lidoWithdrawalQueue.findCheckpointHints(requestIds, 1, lastIndex);
lidoWithdrawalQueue.claimWithdrawals(requestIds, hintIds);

// Reduce the amount outstanding from the Lido Withdrawal Queue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_
emit LidoARM.ClaimLidoWithdrawals(emptyList);

// Main call
lidoARM.claimLidoWithdrawals(new uint256[](0));
lidoARM.claimLidoWithdrawals(emptyList, emptyList);

assertEq(address(lidoARM).balance, 0);
assertEq(lidoARM.lidoWithdrawalQueueAmount(), 0);
Expand All @@ -72,12 +72,15 @@ contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_
uint256[] memory requests = new uint256[](1);
requests[0] = stETHWithdrawal.getLastRequestId();

uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requests, 1, lastIndex);

// Expected events
vm.expectEmit({emitter: address(lidoARM)});
emit LidoARM.ClaimLidoWithdrawals(requests);

// Main call
lidoARM.claimLidoWithdrawals(requests);
lidoARM.claimLidoWithdrawals(requests, hintIds);

// Assertions after
assertEq(lidoARM.lidoWithdrawalQueueAmount(), 0);
Expand All @@ -100,12 +103,15 @@ contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_
requests[0] = stETHWithdrawal.getLastRequestId() - 1;
requests[1] = stETHWithdrawal.getLastRequestId();

uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requests, 1, lastIndex);

// Expected events
vm.expectEmit({emitter: address(lidoARM)});
emit LidoARM.ClaimLidoWithdrawals(requests);

// Main call
lidoARM.claimLidoWithdrawals(requests);
lidoARM.claimLidoWithdrawals(requests, hintIds);

// Assertions after
assertEq(lidoARM.lidoWithdrawalQueueAmount(), 0);
Expand Down
12 changes: 9 additions & 3 deletions test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,10 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
_mockFunctionClaimWithdrawOnLidoARM(DEFAULT_AMOUNT);

// 4. Operator claim withdrawal on lido
uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requests, 1, lastIndex);
lidoARM.totalAssets();
lidoARM.claimLidoWithdrawals(requests);
lidoARM.claimLidoWithdrawals(requests, hintIds);

// 5. User burn shares
(, uint256 receivedAssets) = lidoARM.requestRedeem(shares);
Expand Down Expand Up @@ -532,7 +534,9 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
uint256[] memory requests = new uint256[](1);
requests[0] = requestId;
// 4. Operator claim the withdrawal on lido
lidoARM.claimLidoWithdrawals(requests);
uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requests, 1, lastIndex);
lidoARM.claimLidoWithdrawals(requests, hintIds);
// 5. User burn shares
(, uint256 receivedAssets) = lidoARM.requestRedeem(shares);

Expand Down Expand Up @@ -592,7 +596,9 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
requests[0] = requestId;

// 4. Operator claim the withdrawal on lido
lidoARM.claimLidoWithdrawals(requests);
uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requests, 1, lastIndex);
lidoARM.claimLidoWithdrawals(requests, hintIds);

// 5. User burn shares
(, uint256 receivedAssets) = lidoARM.requestRedeem(shares);
Expand Down
19 changes: 19 additions & 0 deletions test/fork/utils/MockCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ contract MockLidoWithdraw {
function claimWithdrawals(uint256[] memory, uint256[] memory) external {
ethSender.sendETH(lidoARM);
}

/// @notice Mock the call to the Lido contract's `getLastCheckpointIndex` function.
function getLastCheckpointIndex() public pure returns (uint256) {
// hardcoded as this is not used by the Lido ARM
return 300;
}

/// @notice Mock the call to the Lido contract's `findCheckpointHints` function.
function findCheckpointHints(uint256[] calldata _requestIds, uint256, uint256)
external
pure
returns (uint256[] memory hintIds)
{
hintIds = new uint256[](_requestIds.length);
for (uint256 i = 0; i < _requestIds.length; ++i) {
// hardcoded as this is not used by the Lido ARM
hintIds[i] = 300;
}
}
}

contract ETHSender {
Expand Down
2 changes: 1 addition & 1 deletion test/invariants/BasicInvariants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ contract Invariant_Basic_Test_ is Invariant_Base_Test_ {
swapHandler = new SwapHandler(address(lidoARM), address(weth), address(steth), swaps);
ownerHandler =
new OwnerHandler(address(lidoARM), address(weth), address(steth), MIN_BUY_T1, MAX_SELL_T1, MAX_FEES);
llmHandler = new LLMHandler(address(lidoARM), address(steth));
llmHandler = new LLMHandler(address(lidoARM), address(steth), address(lidoWithdraw));
donationHandler = new DonationHandler(address(lidoARM), address(weth), address(steth));

lpHandler.setSelectorWeight(lpHandler.deposit.selector, 5_000); // 50%
Expand Down
15 changes: 11 additions & 4 deletions test/invariants/handlers/LLMHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import {BaseHandler} from "./BaseHandler.sol";
// Contracts
import {IERC20} from "contracts/Interfaces.sol";
import {LidoARM} from "contracts/LidoARM.sol";
import {IStETHWithdrawal} from "contracts/Interfaces.sol";

/// @notice LidoLiquidityManager Handler contract
/// @dev This contract is used to handle all functionnalities that are related to the Lido Liquidity Manager.
/// @dev This contract is used to handle all functionalities that are related to the Lido Liquidity Manager.
contract LLMHandler is BaseHandler {
////////////////////////////////////////////////////
/// --- CONSTANTS && IMMUTABLES
////////////////////////////////////////////////////
IERC20 public immutable steth;
LidoARM public immutable arm;
address public immutable owner;
IStETHWithdrawal public stETHWithdrawal;
uint256 public constant MAX_AMOUNT = 1_000 ether;

////////////////////////////////////////////////////
Expand All @@ -36,9 +38,10 @@ contract LLMHandler is BaseHandler {
////////////////////////////////////////////////////
/// --- CONSTRUCTOR
////////////////////////////////////////////////////
constructor(address _arm, address _steth) {
constructor(address _arm, address _steth, address _lidoWithdrawalQueue) {
arm = LidoARM(payable(_arm));
owner = arm.owner();
stETHWithdrawal = IStETHWithdrawal(_lidoWithdrawalQueue);
steth = IERC20(_steth);
}

Expand Down Expand Up @@ -111,7 +114,9 @@ contract LLMHandler is BaseHandler {
vm.startPrank(owner);

// Claim stETH withdrawal for WETH
arm.claimLidoWithdrawals(requestIds_);
uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requestIds_, 1, lastIndex);
arm.claimLidoWithdrawals(requestIds_, hintIds);

// Stop Prank
vm.stopPrank();
Expand All @@ -137,7 +142,9 @@ contract LLMHandler is BaseHandler {
vm.startPrank(owner);

// Claim stETH withdrawal for WETH
arm.claimLidoWithdrawals(requestIds);
uint256 lastIndex = stETHWithdrawal.getLastCheckpointIndex();
uint256[] memory hintIds = stETHWithdrawal.findCheckpointHints(requestIds, 1, lastIndex);
arm.claimLidoWithdrawals(requestIds, hintIds);

// Stop Prank
vm.stopPrank();
Expand Down

0 comments on commit e05ecf2

Please sign in to comment.