Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,047 changes: 483 additions & 564 deletions src/core/LiquidTokenManager.sol

Large diffs are not rendered by default.

49 changes: 1 addition & 48 deletions src/interfaces/ILiquidTokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface ILiquidTokenManager {
IStakerNodeCoordinator stakerNodeCoordinator;
ITokenRegistryOracle tokenRegistryOracle;
IWithdrawalManager withdrawalManager;
ILSTSwapRouter lstSwapRouter;
address initialOwner;
address strategyController;
address priceUpdater;
Expand Down Expand Up @@ -145,28 +144,6 @@ interface ILiquidTokenManager {
/// @notice Emitted when a token is removed from the registry
event TokenRemoved(IERC20 indexed token, address indexed remover);

/// @notice Emitted when LSTSwapRouter contract is updated
event LSTSwapRouterUpdated(address indexed oldLsr, address indexed newLsr, address updatedBy);

/// @notice Emitted when assets are swapped and staked to a node
event AssetsSwappedAndStakedToNode(
uint256 indexed nodeId,
IERC20[] assetsSwapped,
uint256[] amountsSwapped,
IERC20[] assetsStaked,
uint256[] amountsStaked,
address indexed initiator
);

/// @notice Emitted when a swap is executed
event SwapExecuted(
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 indexed nodeId
);

/// @notice Emitted when a redemption is created due to node undelegation
/// @dev `assets` is a 1D array since each withdrawal will have only 1 corresponding asset
event RedemptionCreatedForNodeUndelegation(
Expand Down Expand Up @@ -290,10 +267,6 @@ interface ILiquidTokenManager {
/// @param init Initialization parameters
function initialize(Init memory init) external;

/// @notice Updates the LSTSwapRouter contract address
/// @param newLSTSwapRouter The new LSR contract address
function updateLSTSwapRouter(address newLSTSwapRouter) external;

/// @notice Adds a new token to the registry and configures its price sources
/// @param token Address of the token to add
/// @param decimals Number of decimals for the token
Expand Down Expand Up @@ -352,22 +325,6 @@ interface ILiquidTokenManager {
/// @param allocations Array of NodeAllocation structs containing staking information
function stakeAssetsToNodes(NodeAllocation[] calldata allocations) external;

/// @notice Swaps multiple assets and stakes them to multiple nodes
/// @param allocationsWithSwaps Array of node allocations with swap instructions
function swapAndStakeAssetsToNodes(NodeAllocationWithSwap[] calldata allocationsWithSwaps) external;

/// @notice Swaps assets and stakes them to a single node
/// @param nodeId The node ID to stake to
/// @param assetsToSwap Array of input tokens to swap from
/// @param amountsToSwap Array of amounts to swap
/// @param assetsToStake Array of output tokens to receive and stake
function swapAndStakeAssetsToNode(
uint256 nodeId,
IERC20[] memory assetsToSwap,
uint256[] memory amountsToSwap,
IERC20[] memory assetsToStake
) external;

/// @notice Undelegates a set of staker nodes from their operators and creates a set of redemptions
/// @dev A separate redemption is created for each node, since undelegating a node on EL queues one withdrawal per strategy
/// @dev On completing a redemption created from undelegation, the funds are transferred to `LiquidToken`
Expand Down Expand Up @@ -530,8 +487,4 @@ interface ILiquidTokenManager {
/// @notice Returns the LiquidToken contract
/// @return The ILiquidToken interface
function liquidToken() external view returns (ILiquidToken);

/// @notice Returns the LSTSwapRouter contract
/// @return The ILSTSwapRouter interface
function lstSwapRouter() external view returns (ILSTSwapRouter);
}
}
135 changes: 135 additions & 0 deletions src/libraries/LTMBalances.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import {IStakerNode} from "../interfaces/IStakerNode.sol";
import {IStakerNodeCoordinator} from "../interfaces/IStakerNodeCoordinator.sol";

library LTMBalances {
using Math for uint256;

function getDepositBalance(
IERC20 asset,
IStakerNode node,
bool inElShares,
mapping(IERC20 => IStrategy) storage tokenStrategies
) external view returns (uint256) {
IStrategy strategy = tokenStrategies[asset];
require(address(strategy) != address(0), "StrategyNotFound");

return inElShares ? strategy.shares(address(node)) : strategy.userUnderlyingView(address(node));
}

function getWithdrawableBalance(
IERC20 asset,
IStakerNode node,
bool inElShares,
mapping(IERC20 => IStrategy) storage tokenStrategies,
IDelegationManager delegationManager
) external view returns (uint256) {
IStrategy strategy = tokenStrategies[asset];
require(address(strategy) != address(0), "StrategyNotFound");

IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategy;

(uint256[] memory withdrawableShares, ) = delegationManager.getWithdrawableShares(address(node), strategies);

if (withdrawableShares[0] == 0) return 0;

return inElShares ? withdrawableShares[0] : strategy.sharesToUnderlyingView(withdrawableShares[0]);
}

function getAllDepositBalances(
IERC20 asset,
bool inElShares,
mapping(IERC20 => IStrategy) storage tokenStrategies,
IStakerNodeCoordinator stakerNodeCoordinator
) external view returns (uint256 totalBalance) {
IStrategy strategy = tokenStrategies[asset];
require(address(strategy) != address(0), "StrategyNotFound");

IStakerNode[] memory nodes = stakerNodeCoordinator.getAllNodes();
totalBalance = 0;

for (uint256 i = 0; i < nodes.length; i++) {
totalBalance += inElShares
? strategy.shares(address(nodes[i]))
: strategy.userUnderlyingView(address(nodes[i]));
}
}

function getAllWithdrawableBalances(
IERC20 asset,
bool inElShares,
mapping(IERC20 => IStrategy) storage tokenStrategies,
IStakerNodeCoordinator stakerNodeCoordinator,
IDelegationManager delegationManager
) external view returns (uint256 totalBalance) {
IStrategy strategy = tokenStrategies[asset];
require(address(strategy) != address(0), "StrategyNotFound");

IStakerNode[] memory nodes = stakerNodeCoordinator.getAllNodes();
totalBalance = 0;

IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategy;

for (uint256 i = 0; i < nodes.length; i++) {
(uint256[] memory withdrawableShares, ) = delegationManager.getWithdrawableShares(
address(nodes[i]),
strategies
);

if (withdrawableShares[0] > 0) {
totalBalance += inElShares
? withdrawableShares[0]
: strategy.sharesToUnderlyingView(withdrawableShares[0]);
}
}
}

function getWithdrawableAmount(
IERC20 asset,
uint256 amount,
bool inElShares,
mapping(IERC20 => IStrategy) storage tokenStrategies,
IStakerNodeCoordinator stakerNodeCoordinator,
IDelegationManager delegationManager
) external view returns (uint256) {
// Direct implementation instead of calling other functions
IStrategy strategy = tokenStrategies[asset];
require(address(strategy) != address(0), "StrategyNotFound");

IStakerNode[] memory nodes = stakerNodeCoordinator.getAllNodes();
uint256 totalDepositBalance = 0;
uint256 totalWithdrawableBalance = 0;

IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategy;

// Calculate total deposit balance and withdrawable balance in one loop
for (uint256 i = 0; i < nodes.length; i++) {
address nodeAddress = address(nodes[i]);

// Add deposit balance
totalDepositBalance += inElShares ? strategy.shares(nodeAddress) : strategy.userUnderlyingView(nodeAddress);

// Add withdrawable balance
(uint256[] memory withdrawableShares, ) = delegationManager.getWithdrawableShares(nodeAddress, strategies);

if (withdrawableShares[0] > 0) {
totalWithdrawableBalance += inElShares
? withdrawableShares[0]
: strategy.sharesToUnderlyingView(withdrawableShares[0]);
}
}

if (totalDepositBalance == 0 || totalWithdrawableBalance == 0) return 0;

return amount.mulDiv(totalWithdrawableBalance, totalDepositBalance);
}
}
Loading