Skip to content

Commit b389ae1

Browse files
committed
test(utils): add unit tests for validator utility functions
Add tests for pure validator utility functions: - isActiveValidator: boundary conditions (before/at activation, at/after exit, FAR_FUTURE) - isSlashableValidator: boundary conditions, already-slashed case - getChurnLimit: min churn floor, large validator set - getActivationChurnLimit: pre-Deneb vs Deneb+ behavior - getBalanceChurnLimit: calculated vs min churn, EFFECTIVE_BALANCE_INCREMENT rounding - getMaxEffectiveBalance: ETH1, compounding, and BLS credential types 🤖 Generated with AI assistance
1 parent a351e0e commit b389ae1

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

src/state_transition/utils/validator.zig

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)