@@ -46,12 +46,127 @@ contract StakingLensTest is Test {
4646 assertEq (validators[1 ].apyBps, expected);
4747 }
4848
49+ function test_getDelegationsIncludesPositionsWithoutActiveDelegations () public {
50+ address delegator = address (0xc08A759F868Ab179F1259b2A7b1B81b0B968710E );
51+ uint64 withdrawValidatorId = 7 ;
52+ uint64 rewardsValidatorId = 9 ;
53+ uint64 currentEpoch = 3 ;
54+ uint64 withdrawEpoch = 4 ;
55+ uint256 withdrawAmount = 2 ether ;
56+ uint256 rewardAmount = 1 ether ;
57+
58+ uint64 [] memory validators = new uint64 [](2 );
59+ validators[0 ] = withdrawValidatorId;
60+ validators[1 ] = rewardsValidatorId;
61+ _mockConsensusSet (validators);
62+
63+ _mockEpoch (currentEpoch);
64+ _mockDelegations (delegator, new uint64 [](0 ));
65+ _mockDelegator (delegator, withdrawValidatorId, 0 , 0 , 0 , 0 );
66+ _mockDelegator (delegator, rewardsValidatorId, 0 , rewardAmount, 0 , 0 );
67+
68+ _mockWithdrawalRequest (withdrawValidatorId, delegator, 0 , withdrawAmount, withdrawEpoch);
69+ for (uint8 withdrawId = 1 ; withdrawId < lens.MAX_WITHDRAW_IDS (); ++ withdrawId) {
70+ _mockWithdrawalRequest (withdrawValidatorId, delegator, withdrawId, 0 , 0 );
71+ }
72+ for (uint8 withdrawId = 0 ; withdrawId < lens.MAX_WITHDRAW_IDS (); ++ withdrawId) {
73+ _mockWithdrawalRequest (rewardsValidatorId, delegator, withdrawId, 0 , 0 );
74+ }
75+
76+ StakingLens.Delegation[] memory positions = lens.getDelegations (delegator);
77+
78+ assertEq (positions.length , 2 );
79+
80+ assertEq (positions[0 ].validatorId, withdrawValidatorId);
81+ assertEq (positions[0 ].withdrawId, 0 );
82+ assertEq (uint8 (positions[0 ].state), uint8 (StakingLens.DelegationState.Deactivating));
83+ assertEq (positions[0 ].amount, withdrawAmount);
84+ assertEq (positions[0 ].rewards, 0 );
85+ assertEq (positions[0 ].withdrawEpoch, withdrawEpoch);
86+ assertGt (positions[0 ].completionTimestamp, 0 );
87+
88+ assertEq (positions[1 ].validatorId, rewardsValidatorId);
89+ assertEq (uint8 (positions[1 ].state), uint8 (StakingLens.DelegationState.Active));
90+ assertEq (positions[1 ].amount, 0 );
91+ assertEq (positions[1 ].rewards, rewardAmount);
92+ assertEq (positions[1 ].withdrawEpoch, 0 );
93+ assertEq (positions[1 ].completionTimestamp, 0 );
94+ }
95+
96+ function test_getDelegationsIncludesWithdrawalsWhenActiveDelegationsExist () public {
97+ address delegator = address (0xc08A759F868Ab179F1259b2A7b1B81b0B968710E );
98+ uint64 withdrawValidatorId = 7 ;
99+ uint64 activeValidatorId = 9 ;
100+ uint64 currentEpoch = 3 ;
101+ uint64 withdrawEpoch = 4 ;
102+ uint256 withdrawAmount = 2 ether ;
103+ uint256 activeStake = 5 ether ;
104+
105+ uint64 [] memory validators = new uint64 [](2 );
106+ validators[0 ] = withdrawValidatorId;
107+ validators[1 ] = activeValidatorId;
108+ _mockConsensusSet (validators);
109+
110+ uint64 [] memory activeDelegations = new uint64 [](1 );
111+ activeDelegations[0 ] = activeValidatorId;
112+
113+ _mockEpoch (currentEpoch);
114+ _mockDelegations (delegator, activeDelegations);
115+ _mockDelegator (delegator, activeValidatorId, activeStake, 0 , 0 , 0 );
116+ _mockDelegator (delegator, withdrawValidatorId, 0 , 0 , 0 , 0 );
117+
118+ for (uint8 withdrawId = 0 ; withdrawId < lens.MAX_WITHDRAW_IDS (); ++ withdrawId) {
119+ _mockWithdrawalRequest (activeValidatorId, delegator, withdrawId, 0 , 0 );
120+ }
121+ _mockWithdrawalRequest (withdrawValidatorId, delegator, 1 , withdrawAmount, withdrawEpoch);
122+ for (uint8 withdrawId = 0 ; withdrawId < lens.MAX_WITHDRAW_IDS (); ++ withdrawId) {
123+ if (withdrawId != 1 ) {
124+ _mockWithdrawalRequest (withdrawValidatorId, delegator, withdrawId, 0 , 0 );
125+ }
126+ }
127+
128+ StakingLens.Delegation[] memory positions = lens.getDelegations (delegator);
129+
130+ assertEq (positions.length , 2 );
131+
132+ bool foundActive;
133+ bool foundWithdraw;
134+ for (uint256 i = 0 ; i < positions.length ; ++ i) {
135+ StakingLens.Delegation memory position = positions[i];
136+
137+ if (position.validatorId == activeValidatorId && position.state == StakingLens.DelegationState.Active) {
138+ foundActive = true ;
139+ assertEq (position.amount, activeStake);
140+ assertEq (position.rewards, 0 );
141+ }
142+
143+ if (
144+ position.validatorId == withdrawValidatorId
145+ && position.state == StakingLens.DelegationState.Deactivating && position.withdrawId == 1
146+ ) {
147+ foundWithdraw = true ;
148+ assertEq (position.amount, withdrawAmount);
149+ assertEq (position.rewards, 0 );
150+ assertEq (position.withdrawEpoch, withdrawEpoch);
151+ }
152+ }
153+
154+ assertTrue (foundActive);
155+ assertTrue (foundWithdraw);
156+ }
157+
49158 function _mockConsensusSet () internal {
50159 bytes memory data = abi.encodeCall (IStaking.getConsensusValidatorSet, (0 ));
51160 bytes memory result = abi.encode (true , uint32 (0 ), validatorIds);
52161 vm.mockCall (STAKING_PRECOMPILE, data, result);
53162 }
54163
164+ function _mockConsensusSet (uint64 [] memory ids ) internal {
165+ bytes memory data = abi.encodeCall (IStaking.getConsensusValidatorSet, (0 ));
166+ bytes memory result = abi.encode (true , uint32 (0 ), ids);
167+ vm.mockCall (STAKING_PRECOMPILE, data, result);
168+ }
169+
55170 function _mockValidator (uint64 validatorId , uint256 stake , uint256 commission ) internal {
56171 bytes memory data = abi.encodeCall (IStaking.getValidator, (validatorId));
57172 bytes memory result = abi.encode (
@@ -71,6 +186,43 @@ contract StakingLensTest is Test {
71186 vm.mockCall (STAKING_PRECOMPILE, data, result);
72187 }
73188
189+ function _mockEpoch (uint64 epoch ) internal {
190+ bytes memory data = abi.encodeCall (IStaking.getEpoch, ());
191+ bytes memory result = abi.encode (epoch, false );
192+ vm.mockCall (STAKING_PRECOMPILE, data, result);
193+ }
194+
195+ function _mockDelegations (address delegator , uint64 [] memory valIds ) internal {
196+ bytes memory data = abi.encodeCall (IStaking.getDelegations, (delegator, uint64 (0 )));
197+ bytes memory result = abi.encode (true , uint64 (0 ), valIds);
198+ vm.mockCall (STAKING_PRECOMPILE, data, result);
199+ }
200+
201+ function _mockDelegator (
202+ address delegator ,
203+ uint64 validatorId ,
204+ uint256 stake ,
205+ uint256 rewards ,
206+ uint256 deltaStake ,
207+ uint256 nextDeltaStake
208+ ) internal {
209+ bytes memory data = abi.encodeCall (IStaking.getDelegator, (validatorId, delegator));
210+ bytes memory result = abi.encode (stake, uint256 (0 ), rewards, deltaStake, nextDeltaStake, uint64 (0 ), uint64 (0 ));
211+ vm.mockCall (STAKING_PRECOMPILE, data, result);
212+ }
213+
214+ function _mockWithdrawalRequest (
215+ uint64 validatorId ,
216+ address delegator ,
217+ uint8 withdrawId ,
218+ uint256 amount ,
219+ uint64 withdrawEpoch
220+ ) internal {
221+ bytes memory data = abi.encodeCall (IStaking.getWithdrawalRequest, (validatorId, delegator, withdrawId));
222+ bytes memory result = abi.encode (amount, uint256 (0 ), withdrawEpoch);
223+ vm.mockCall (STAKING_PRECOMPILE, data, result);
224+ }
225+
74226 function _expectedNetworkApy () internal view returns (uint64 ) {
75227 uint256 annualRewards = lens.MONAD_BLOCK_REWARD () * lens.MONAD_BLOCKS_PER_YEAR ();
76228 uint256 apy = (annualRewards * lens.APY_BPS_PRECISION ()) / TOTAL_STAKE;
0 commit comments