@@ -98,3 +98,105 @@ pub fn getPendingBalanceToWithdraw(comptime fork: ForkSeq, state: *BeaconState(f
9898 }
9999 return total ;
100100}
101+
102+ test "isActiveValidator" {
103+ // Active: activation_epoch <= epoch < exit_epoch
104+ var v : Validator.Type = std .mem .zeroes (Validator .Type );
105+ v .activation_epoch = 5 ;
106+ v .exit_epoch = 10 ;
107+
108+ try std .testing .expect (! isActiveValidator (& v , 4 )); // before activation
109+ try std .testing .expect (isActiveValidator (& v , 5 )); // at activation
110+ try std .testing .expect (isActiveValidator (& v , 7 )); // mid-range
111+ try std .testing .expect (isActiveValidator (& v , 9 )); // last active epoch
112+ try std .testing .expect (! isActiveValidator (& v , 10 )); // at exit
113+ try std .testing .expect (! isActiveValidator (& v , 11 )); // after exit
114+
115+ // FAR_FUTURE_EPOCH means never exits
116+ v .exit_epoch = std .math .maxInt (Epoch );
117+ try std .testing .expect (isActiveValidator (& v , 100 ));
118+ }
119+
120+ test "isSlashableValidator" {
121+ var v : Validator.Type = std .mem .zeroes (Validator .Type );
122+ v .activation_epoch = 5 ;
123+ v .withdrawable_epoch = 20 ;
124+ v .slashed = false ;
125+
126+ // Slashable: !slashed AND activation_epoch <= epoch < withdrawable_epoch
127+ try std .testing .expect (! isSlashableValidator (& v , 4 )); // before activation
128+ try std .testing .expect (isSlashableValidator (& v , 5 )); // at activation
129+ try std .testing .expect (isSlashableValidator (& v , 15 )); // mid-range
130+ try std .testing .expect (isSlashableValidator (& v , 19 )); // last slashable epoch
131+ try std .testing .expect (! isSlashableValidator (& v , 20 )); // at withdrawable
132+ try std .testing .expect (! isSlashableValidator (& v , 25 )); // after withdrawable
133+
134+ // Already slashed
135+ v .slashed = true ;
136+ try std .testing .expect (! isSlashableValidator (& v , 10 ));
137+ }
138+
139+ test "getChurnLimit" {
140+ const config = &@import ("config" ).mainnet .config ;
141+
142+ // With small validator count, should return MIN_PER_EPOCH_CHURN_LIMIT
143+ const min_churn = getChurnLimit (config , 10 );
144+ try std .testing .expectEqual (config .chain .MIN_PER_EPOCH_CHURN_LIMIT , min_churn );
145+
146+ // With large validator count, should return active_count / CHURN_LIMIT_QUOTIENT
147+ const large_count : usize = config .chain .CHURN_LIMIT_QUOTIENT * 100 ;
148+ const large_churn = getChurnLimit (config , large_count );
149+ try std .testing .expectEqual (100 , large_churn );
150+ }
151+
152+ test "getActivationChurnLimit" {
153+ const config = &@import ("config" ).mainnet .config ;
154+
155+ // Pre-deneb: same as getChurnLimit
156+ const pre_deneb = getActivationChurnLimit (config , .capella , 1_000_000 );
157+ const churn = getChurnLimit (config , 1_000_000 );
158+ try std .testing .expectEqual (churn , pre_deneb );
159+
160+ // Deneb+: min(MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, getChurnLimit)
161+ const post_deneb = getActivationChurnLimit (config , .deneb , 1_000_000 );
162+ try std .testing .expectEqual (@min (config .chain .MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT , churn ), post_deneb );
163+ }
164+
165+ test "getBalanceChurnLimit" {
166+ // Large total balance: calculated churn > min churn
167+ // 10_000_000 / 65536 * 1e9 = 152 * 1e9 = 152_000_000_000
168+ // vs min 128_000_000_000 → calculated wins
169+ const result = getBalanceChurnLimit (
170+ 10_000_000 , // total_active_balance_increments (~10M validators)
171+ 65536 , // churn_limit_quotient
172+ 128_000_000_000 , // min_per_epoch_churn_limit (128 ETH in Gwei)
173+ );
174+ const expected_raw : u64 = (10_000_000 / 65536 ) * preset .EFFECTIVE_BALANCE_INCREMENT ;
175+ const expected = expected_raw - (expected_raw % preset .EFFECTIVE_BALANCE_INCREMENT );
176+ try std .testing .expectEqual (expected , result );
177+
178+ // Small total balance: min churn wins
179+ // 100 / 65536 * 1e9 = 0 → min 128_000_000_000 wins
180+ const small_result = getBalanceChurnLimit (
181+ 100 , // tiny
182+ 65536 ,
183+ 128_000_000_000 ,
184+ );
185+ const min_churn : u64 = 128_000_000_000 ;
186+ const expected_small = min_churn - (min_churn % preset .EFFECTIVE_BALANCE_INCREMENT );
187+ try std .testing .expectEqual (expected_small , small_result );
188+ }
189+
190+ test "getMaxEffectiveBalance" {
191+ // ETH1 withdrawal credentials → MIN_ACTIVATION_BALANCE
192+ var eth1_creds : WithdrawalCredentials = [_ ]u8 {0x01 } ++ [_ ]u8 {0 } ** 31 ;
193+ try std .testing .expectEqual (preset .MIN_ACTIVATION_BALANCE , getMaxEffectiveBalance (& eth1_creds ));
194+
195+ // Compounding withdrawal credentials → MAX_EFFECTIVE_BALANCE_ELECTRA
196+ var compounding_creds : WithdrawalCredentials = [_ ]u8 {0x02 } ++ [_ ]u8 {0 } ** 31 ;
197+ try std .testing .expectEqual (preset .MAX_EFFECTIVE_BALANCE_ELECTRA , getMaxEffectiveBalance (& compounding_creds ));
198+
199+ // BLS withdrawal credentials → MIN_ACTIVATION_BALANCE
200+ var bls_creds : WithdrawalCredentials = [_ ]u8 {0x00 } ++ [_ ]u8 {0 } ** 31 ;
201+ try std .testing .expectEqual (preset .MIN_ACTIVATION_BALANCE , getMaxEffectiveBalance (& bls_creds ));
202+ }
0 commit comments