Skip to content

Commit 9ff8d08

Browse files
Merge pull request #359 from etherfi-protocol/pankaj/test/update-fixes-for-test-suite
Pankaj/test/update fixes for test suite
2 parents 93f1d81 + adb96c9 commit 9ff8d08

10 files changed

Lines changed: 197 additions & 39 deletions

File tree

.github/workflows/run-forge-tests.yaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ jobs:
3737
if: always()
3838
shell: bash
3939
run: |
40-
set -o pipefail
41-
if forge coverage --report summary --no-match-coverage '(script/|test/|src/helpers/|src/interfaces/|src/eigenlayer|src/libraries/|src/archive/)' --color never 2>&1 | tee full_output.txt | sed -n '/^[|╭╰+]/p' > coverage.txt; then
40+
# Capture full output; || true so the step always continues
41+
forge coverage --report summary \
42+
--no-match-coverage '(script/|test/|src/helpers/|src/interfaces/|src/eigenlayer|src/libraries/|src/archive/)' \
43+
--color never > full_output.txt 2>&1 || true
44+
45+
# Extract coverage table lines
46+
grep -E '^\|' full_output.txt > coverage.txt || true
47+
48+
if [ -s coverage.txt ]; then
4249
echo "" >> coverage.txt
4350
echo "---" >> coverage.txt
4451
grep -E "^(Ran|Suite result:|Test result:)" full_output.txt >> coverage.txt || true
4552
else
46-
echo "forge coverage failed; see logs above (tests step is the gate)." > coverage.txt
53+
echo "Coverage report could not be generated. Check workflow logs for details." > coverage.txt
4754
fi
48-
# Debug output
49-
echo "=== DEBUG: coverage.txt contents (first 20 lines) ==="
50-
head -20 coverage.txt || echo "(empty or missing)"
51-
echo "=== DEBUG: coverage.txt size ==="
52-
wc -l coverage.txt || echo "(file missing)"
53-
echo "=== DEBUG: full_output.txt table lines ==="
54-
grep -cE '^[|╭╰+]' full_output.txt || echo "0 lines match table pattern"
5555
env:
5656
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
5757
SCROLL_RPC_URL: ${{ secrets.SCROLL_RPC_URL }}

CLAUDE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# EtherFi Smart Contracts
2+
3+
## Build & Test
4+
5+
```bash
6+
forge build # compile
7+
forge test --match-test <name> # unit tests (no RPC needed)
8+
forge test --match-test <name> --fork-url $MAINNET_RPC_URL # mainnet fork tests
9+
```
10+
11+
- Solidity 0.8.27, Foundry with 1500 optimizer runs
12+
- Env vars: `MAINNET_RPC_URL`, `FORK_RPC_URL` (optional override), `VALIDATOR_DB`, `BEACON_NODE_URL`
13+
14+
## Project Layout
15+
16+
```
17+
src/ # Core contracts
18+
EtherFiNode.sol # Per-validator-group contract, owns an EigenPod
19+
EtherFiNodesManager.sol # Entry point for pod operations (0x8B71...6F)
20+
LiquidityPool.sol # Main ETH pool (0x3088...16)
21+
EtherFiRestaker.sol # Manages stETH restaking via EigenLayer (0x1B7a...Ff)
22+
EtherFiRedemptionManager.sol # Instant redemptions with rate limiting (0xDadE...e0)
23+
StakingManager.sol # Validator lifecycle
24+
WeETH.sol / EETH.sol # Token contracts
25+
eigenlayer-interfaces/ # EigenLayer interface definitions (no implementations)
26+
test/
27+
TestSetup.sol # Base test with initializeRealisticFork() / initializeTestingFork()
28+
behaviour-tests/ # PreludeTest - validator lifecycle on mainnet fork
29+
integration-tests/ # Cross-contract integration tests on mainnet fork
30+
fork-tests/ # Additional fork-based tests
31+
script/
32+
operations/ # Operational tooling (Python + Solidity for Gnosis Safe txns)
33+
deploys/Deployed.s.sol # All mainnet deployed addresses as constants
34+
```
35+
36+
## Architecture
37+
38+
- Validator pubkey -> `etherFiNodeFromPubkeyHash` -> EtherFiNode -> `getEigenPod()` -> EigenPod
39+
- `calculateValidatorPubkeyHash`: `sha256(pubkey + bytes16(0))`
40+
- Legacy validators use integer IDs; new validators use pubkey hashes. `etherfiNodeAddress(id)` resolves both via a heuristic on upper bits.
41+
- UUPS proxy pattern throughout. Upgrades go through timelocks.
42+
43+
## Key Addresses (Mainnet)
44+
45+
| Role | Address |
46+
|------|---------|
47+
| OPERATING_TIMELOCK | `0xcD425f44758a08BaAB3C4908f3e3dE5776e45d7a` |
48+
| UPGRADE_TIMELOCK | `0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761` |
49+
| ROLE_REGISTRY | `0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9` |
50+
| EIGENLAYER_DELEGATION_MANAGER | `0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A` |
51+
| EIGENLAYER_POD_MANAGER | `0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338` |
52+
| LIDO_WITHDRAWAL_QUEUE | `0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1` |
53+
54+
Full list in `script/deploys/Deployed.s.sol`.
55+
56+
## Access Control Roles
57+
58+
- `ETHERFI_NODES_MANAGER_POD_PROVER_ROLE` -> startCheckpoint, verifyCheckpointProofs
59+
- `ETHERFI_NODES_MANAGER_EIGENLAYER_ADMIN_ROLE` -> queueETHWithdrawal, completeQueuedETHWithdrawals
60+
- `ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE` -> setCapacity, setRefillRate, setLowWatermark, setExitFee
61+
- `OPERATING_TIMELOCK` holds the redemption manager admin role
62+
63+
## Test Setup Patterns
64+
65+
Two fork modes in `TestSetup.sol`:
66+
- `initializeRealisticFork(MAINNET_FORK)` — uses real mainnet contracts at their deployed addresses. Forks at **latest block** (no pinned block), so mainnet state drifts.
67+
- `initializeTestingFork(MAINNET_FORK)` — deploys fresh contracts on a mainnet fork.
68+
69+
`PreludeTest` (behaviour-tests) has its own setup: forks mainnet, upgrades contracts in-place, deploys fresh RateLimiter, grants roles to test addresses (`admin`, `eigenlayerAdmin`, `podProver`, `elExiter`).
70+
71+
## Mainnet Fork Test Decisions
72+
73+
These are hard-won lessons. Follow them when writing or fixing fork tests:
74+
75+
1. **Never assume zero baselines.** Mainnet contracts have live state (pending withdrawals, balances, queued operations). Always capture initial values and assert deltas relative to them.
76+
77+
2. **EigenPod storage slot 52** = `withdrawableRestakedExecutionLayerGwei` (uint64, packed with `proofSubmitter` address in same slot). When poking this with `vm.store`:
78+
- Set it BEFORE any `queueETHWithdrawal` / `completeQueuedETHWithdrawals` calls
79+
- Use a large value (10000+ ETH in gwei) to cover pre-existing queued withdrawals from mainnet state
80+
- Also `vm.deal` ETH to the EigenPod so it can actually transfer funds during withdrawal completion
81+
- `completeQueuedETHWithdrawals` iterates ALL eligible queued withdrawals, not just the one you queued in the test
82+
83+
3. **RedemptionManager lowWatermark blocks redemptions on fork.** `lowWatermarkInETH = totalPooledEther * lowWatermarkInBpsOfTvl / 10000`. On mainnet, TVL is millions of ETH, so even a 1% watermark = tens of thousands ETH. Test deposits of a few thousand ETH can never exceed this. Fix: `setLowWatermarkInBpsOfTvl(0, token)` via `OPERATING_TIMELOCK` at start of test.
84+
85+
4. **Rate limiter (BucketLimiter)** must also be configured in fork tests: `setCapacity()` + `setRefillRatePerSecond()` + `vm.warp(block.timestamp + 1)` to refill.
86+
87+
## EigenLayer Integration
88+
89+
- EigenPod key function selectors: `currentCheckpointTimestamp()` = `0x42ecff2a`, `lastCheckpointTimestamp()` = `0xee94d67c`, `activeValidatorCount()` = `0x2340e8d3`
90+
- EigenPod storage slots used in tests: slot 52 = `withdrawableRestakedExecutionLayerGwei`, slot 57 = `activeValidatorCount`
91+
- Beacon ETH strategy address: `0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0`
92+
- Withdrawal delay: `EIGENLAYER_WITHDRAWAL_DELAY_BLOCKS = 100800` blocks (~14 days)
93+
94+
## Operations Tooling
95+
96+
- Python scripts in `script/operations/` use `validator_utils.py` for shared DB/beacon utilities
97+
- Solidity scripts in same dirs generate Gnosis Safe transactions
98+
- DB tables: `etherfi_validators` (pubkey, id, phase, status, node_address), `MainnetValidators` (pubkey, eigen_pod_contract, etherfi_node_contract)
99+
- Withdrawal credentials format: `0x01 + 22_zero_chars + 40_char_eigenpod_address`

script/operations/steth-management/AutomateStEthWithdrawals.s.sol

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@ import "forge-std/Script.sol";
55
import "forge-std/console2.sol";
66
import {Deployed} from "../../deploys/Deployed.s.sol";
77
import {EtherFiRestaker} from "../../../src/EtherFiRestaker.sol";
8-
import {ILido, ILidoWithdrawalQueue} from "../../../src/interfaces/ILiquifier.sol";
8+
import {ILido} from "../../../src/interfaces/ILiquifier.sol";
9+
10+
11+
interface ILidoWithdrawalQueue {
12+
function getLastFinalizedRequestId() external view returns (uint256);
13+
function getLastCheckpointIndex() external view returns (uint256);
14+
function findCheckpointHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex) external view returns (uint256[] memory hintIds);
15+
function prefinalize(uint256[] calldata _batches, uint256 _maxShareRate) external view returns (uint256 ethToLock, uint256 sharesToBurn);
16+
function finalize(uint256 _lastRequestIdToBeFinalized, uint256 _maxShareRate) external payable;
17+
function FINALIZE_ROLE() external view returns (bytes32);
18+
function getRoleMember(bytes32 _role, uint256 _index) external view returns (address);
19+
}
920

1021
// Full withdrawal:
1122
// FULL_WITHDRAWAL=true forge script script/operations/steth-claim-withdrawals/AutomateStEthWithdrawals.s.sol --fork-url $MAINNET_RPC_URL -vvvv
@@ -18,7 +29,7 @@ contract AutomateStEthWithdrawals is Script, Deployed {
1829
EtherFiRestaker constant etherFiRestaker = EtherFiRestaker(payable(ETHERFI_RESTAKER));
1930

2031
function run() external {
21-
ILidoWithdrawalQueue lidoWithdrawalQueue = etherFiRestaker.lidoWithdrawalQueue();
32+
ILidoWithdrawalQueue lidoWithdrawalQueue = ILidoWithdrawalQueue(address(etherFiRestaker.lidoWithdrawalQueue()));
2233
ILido lido = etherFiRestaker.lido();
2334

2435
bool fullWithdrawal = vm.envOr("FULL_WITHDRAWAL", false);
@@ -100,8 +111,8 @@ contract AutomateStEthWithdrawals is Script, Deployed {
100111
}
101112

102113
// Get checkpoint hints
103-
uint256 lastCheckpointIndex = lidoWithdrawalQueue.getLastCheckpointIndex();
104-
uint256[] memory hints = lidoWithdrawalQueue.findCheckpointHints(claimableIds, 1, lastCheckpointIndex);
114+
uint256 lastCheckpointIndex = ILidoWithdrawalQueue(address(lidoWithdrawalQueue)).getLastCheckpointIndex();
115+
uint256[] memory hints = ILidoWithdrawalQueue(address(lidoWithdrawalQueue)).findCheckpointHints(claimableIds, 1, lastCheckpointIndex);
105116

106117
// Log claim calldata
107118
bytes memory claimCalldata = abi.encodeWithSelector(

script/operations/steth-management/ClaimStEthWithdrawals.s.sol

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import "forge-std/Script.sol";
55
import "forge-std/console2.sol";
66
import {Deployed} from "../../deploys/Deployed.s.sol";
77
import {EtherFiRestaker} from "../../../src/EtherFiRestaker.sol";
8-
import {ILidoWithdrawalQueue} from "../../../src/interfaces/ILiquifier.sol";
8+
// import {ILidoWithdrawalQueue} from "../../../src/interfaces/ILiquifier.sol";
9+
10+
interface ILidoWithdrawalQueue {
11+
function getLastFinalizedRequestId() external view returns (uint256);
12+
function getLastCheckpointIndex() external view returns (uint256);
13+
function findCheckpointHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex) external view returns (uint256[] memory hintIds);
14+
}
915

1016
// forge script script/operations/steth-claim-withdrawals/ClaimStEthWithdrawals.s.sol --fork-url $MAINNET_RPC_URL -vvvv
1117

@@ -17,14 +23,14 @@ contract ClaimStEthWithdrawals is Script, Deployed {
1723
uint256 startId = 113785; // Set this to the first request you want to claim
1824
uint256 endId = 113863; // Set this to the last request you want to claim
1925

20-
ILidoWithdrawalQueue lidoWithdrawalQueue = etherFiRestaker.lidoWithdrawalQueue();
26+
ILidoWithdrawalQueue lidoWithdrawalQueue = ILidoWithdrawalQueue(address(etherFiRestaker.lidoWithdrawalQueue()));
2127
console2.log("LidoWithdrawalQueue:", address(lidoWithdrawalQueue));
2228

2329
// Cap endId to the last finalized request
24-
uint256 lastFinalizedId = lidoWithdrawalQueue.getLastFinalizedRequestId();
30+
uint256 lastFinalizedId = ILidoWithdrawalQueue(address(lidoWithdrawalQueue)).getLastFinalizedRequestId();
2531
console2.log("Last finalized request ID:", lastFinalizedId);
2632

27-
if (endId > lastFinalizedId) {
33+
if (endId > lastFinalizedId) {
2834
console2.log("WARNING: endId", endId, "exceeds last finalized ID, capping to", lastFinalizedId);
2935
endId = lastFinalizedId;
3036
}
@@ -40,10 +46,10 @@ contract ClaimStEthWithdrawals is Script, Deployed {
4046
}
4147

4248
// Get checkpoint hints
43-
uint256 lastCheckpointIndex = lidoWithdrawalQueue.getLastCheckpointIndex();
49+
uint256 lastCheckpointIndex = ILidoWithdrawalQueue(address(lidoWithdrawalQueue)).getLastCheckpointIndex();
4450
console2.log("Last checkpoint index:", lastCheckpointIndex);
4551

46-
uint256[] memory hints = lidoWithdrawalQueue.findCheckpointHints(requestIds, 1, lastCheckpointIndex);
52+
uint256[] memory hints = ILidoWithdrawalQueue(address(lidoWithdrawalQueue)).findCheckpointHints(requestIds, 1, lastCheckpointIndex);
4753

4854
console2.log("Hints found for", hints.length, "requests");
4955
for (uint256 i = 0; i < hints.length; i++) {

script/upgrades/CrossPodApproval/deploy.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "forge-std/Script.sol";
55
import {LiquidityPool} from "../../../src/LiquidityPool.sol";
66
import {EtherFiNodesManager} from "../../../src/EtherFiNodesManager.sol";
77
import {Deployed} from "../../deploys/Deployed.s.sol";
8-
import {Utils, ICreate2Factory} from "../../utils/Utils.sol";
8+
import {Utils, ICreate2Factory} from "../../utils/utils.sol";
99

1010
/**
1111
command:

script/upgrades/CrossPodApproval/transactions.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {EtherFiRateLimiter} from "../../../src/EtherFiRateLimiter.sol";
1212
import {IEtherFiNodesManager} from "../../../src/interfaces/IEtherFiNodesManager.sol";
1313
import {ContractCodeChecker} from "../../ContractCodeChecker.sol";
1414
import {Deployed} from "../../deploys/Deployed.s.sol";
15-
import {Utils} from "../../utils/Utils.sol";
15+
import {Utils} from "../../utils/utils.sol";
1616
import {IEigenPodTypes} from "../../../src/eigenlayer-interfaces/IEigenPod.sol";
1717

1818
// forge script script/upgrades/CrossPodApproval/transactions.s.sol:CrossPodApprovalScript --fork-url $MAINNET_RPC_URL -vvvv

test/EtherFiRestaker.t.sol

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,19 @@ contract EtherFiRestakerTest is TestSetup {
6262
uint256 lpBalance = address(liquidityPoolInstance).balance;
6363
uint256 currentEtherFiRestakerTotalPooledEther = etherFiRestakerInstance.getTotalPooledEther();
6464
uint256 currentStEthBalance = stEth.balanceOf(address(etherFiRestakerInstance));
65+
uint256 initialPendingRedemption = etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth));
6566
uint256 amount = 10 ether;
6667

6768
_deposit_stEth(amount);
6869

69-
assertEq(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), 0);
70+
assertEq(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), initialPendingRedemption);
7071

7172
vm.startPrank(owner);
7273
uint256 stEthBalance = stEth.balanceOf(address(etherFiRestakerInstance)) - currentStEthBalance;
7374
uint256[] memory reqIds = etherFiRestakerInstance.stEthRequestWithdrawal(stEthBalance);
7475
vm.stopPrank();
7576

76-
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), amount, 2 wei);
77+
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), initialPendingRedemption + amount, 2 wei);
7778
assertApproxEqAbs(etherFiRestakerInstance.getTotalPooledEther(), currentEtherFiRestakerTotalPooledEther + amount, 2 wei);
7879
assertApproxEqAbs(liquidityPoolInstance.getTotalPooledEther(), lpTvl + amount, 2 wei);
7980

@@ -87,7 +88,7 @@ contract EtherFiRestakerTest is TestSetup {
8788
etherFiRestakerInstance.lidoWithdrawalQueue().finalize(reqIds[reqIds.length-1], currentRate);
8889
vm.stopPrank();
8990

90-
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), amount, 2 wei);
91+
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), initialPendingRedemption + amount, 2 wei);
9192
assertApproxEqAbs(etherFiRestakerInstance.getTotalPooledEther(), currentEtherFiRestakerTotalPooledEther + amount, 2 wei);
9293
assertApproxEqAbs(liquidityPoolInstance.getTotalPooledEther(), lpTvl + amount, 2 wei);
9394

@@ -97,8 +98,8 @@ contract EtherFiRestakerTest is TestSetup {
9798
uint256[] memory hints = etherFiRestakerInstance.lidoWithdrawalQueue().findCheckpointHints(reqIds, 1, lastCheckPointIndex);
9899
etherFiRestakerInstance.stEthClaimWithdrawals(reqIds, hints);
99100

100-
// the cycle completes
101-
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), 0, 2 wei);
101+
// the cycle completes - only the newly requested amount should be redeemed, initial pending remains
102+
assertApproxEqAbs(etherFiRestakerInstance.getAmountPendingForRedemption(address(stEth)), initialPendingRedemption, 2 wei);
102103
assertApproxEqAbs(etherFiRestakerInstance.getTotalPooledEther(), currentEtherFiRestakerTotalPooledEther, 2 wei);
103104
assertApproxEqAbs(address(etherFiRestakerInstance).balance, 0, 2);
104105
assertApproxEqAbs(liquidityPoolInstance.getTotalPooledEther(), lpTvl + amount, 2 wei);

test/behaviour-tests/prelude.t.sol

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -628,13 +628,16 @@ contract PreludeTest is Test, ArrayTestHelper {
628628
vm.stopPrank();
629629
}
630630

631+
// poke withdrawable funds into the restakedExecutionLayerGwei storage slot of the eigenpod
632+
// Must be done before queueing to ensure the EigenPod has sufficient state.
633+
// Use a large value to also cover any pre-existing queued withdrawals on mainnet.
634+
address eigenpod = etherFiNodesManager.getEigenPod(uint256(pubkeyHash));
635+
vm.store(eigenpod, bytes32(uint256(52)) /*slot*/, bytes32(uint256(10000 ether / 1 gwei)));
636+
vm.deal(eigenpod, 10000 ether);
637+
631638
vm.prank(eigenlayerAdmin);
632639
etherFiNodesManager.queueETHWithdrawal(uint256(pubkeyHash), 1 ether);
633640

634-
// poke some withdrawable funds into the restakedExecutionLayerGwei storage slot of the eigenpod
635-
address eigenpod = etherFiNodesManager.getEigenPod(uint256(pubkeyHash));
636-
vm.store(eigenpod, bytes32(uint256(52)) /*slot*/, bytes32(uint256(50 ether / 1 gwei)));
637-
638641
uint256 startingBalance = address(liquidityPool).balance;
639642

640643
vm.roll(block.number + (7200 * 15));

test/integration-tests/Validator-Flows.t.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ import "../../src/libraries/DepositDataRootGenerator.sol";
1212
contract ValidatorFlowsIntegrationTest is TestSetup, Deployed {
1313
function setUp() public {
1414
initializeRealisticFork(MAINNET_FORK);
15+
16+
// Handle any pending oracle report that hasn't been processed yet
17+
_syncOracleReportState();
18+
}
19+
20+
/// @dev Advances the admin's lastHandledReportRefSlot to match the oracle's lastPublishedReportRefSlot.
21+
function _syncOracleReportState() internal {
22+
uint32 lastPublished = etherFiOracleInstance.lastPublishedReportRefSlot();
23+
uint32 lastHandled = etherFiAdminInstance.lastHandledReportRefSlot();
24+
25+
if (lastPublished != lastHandled) {
26+
uint32 lastPublishedBlock = etherFiOracleInstance.lastPublishedReportRefBlock();
27+
28+
// EtherFiAdmin slot 209 packs: lastHandledReportRefSlot (4B @ offset 0) +
29+
// lastHandledReportRefBlock (4B @ offset 4) + other fields in higher bytes
30+
bytes32 slot209 = vm.load(address(etherFiAdminInstance), bytes32(uint256(209)));
31+
uint256 val = uint256(slot209);
32+
val &= ~uint256(0xFFFFFFFFFFFFFFFF); // clear low 64 bits (both uint32 fields)
33+
val |= uint256(lastPublished);
34+
val |= uint256(lastPublishedBlock) << 32;
35+
vm.store(address(etherFiAdminInstance), bytes32(uint256(209)), bytes32(val));
36+
}
1537
}
1638

1739
function _toArray(IStakingManager.DepositData memory d) internal pure returns (IStakingManager.DepositData[] memory arr) {

0 commit comments

Comments
 (0)