KYB-Gated Consensus: How KumplyValidatorSetManager Works, and the Bug That Almost Broke It
Updated September 30, 2026: clarified that validator removal is permissionless but not automatic, that the deployed manager accepts Tier 4 or higher (a fix restricting it to exactly Tier 4 is in the code and ships with the redeploy), and that the L1 is not activated yet.
Avalanche's own pitch for institutional adoption keeps coming back to one idea: a chain where every validator is known and KYC'd. Evergreen Subnets embed that at the chain level through permissioning and allow-lists. KUMPLY's Compliance L1 takes the same idea further and enforces it in contract code, not policy: KumplyValidatorSetManager (ACP-99) requires a live attestation of Tier 4 (KYB) or higher to join the validator set, checked on registration; after that, anyone can trigger removal once it lapses.
How the gate actually works
The rule hangs on one immutable constant, REQUIRED_VALIDATOR_TIER == 4. Registering as a validator means the contract calls into AttestationStore and confirms the candidate address holds a valid attestation at that tier or higher. No credential, no seat. After that, if a validator's attestation expires or is revoked while it's active, anyone, not just an admin, can call disableExpiredValidator() to start removing it from the set. That's permissionless, but not automatic: someone has to make the call. A fix that restricts registration to exactly Tier 4 is already in the code and ships with the manager's redeploy before the L1 is activated. The same fix lets anyone purge a validator whose attestation was re-issued below Tier 4.
Two more mechanics keep the set stable while that's happening: churn is capped at MAX_CHURN_PER_PERIOD == 20 validator changes per rolling CHURN_PERIOD == 1 day, and no single validator can hold more than MAX_VALIDATOR_WEIGHT_BPS == 2000 (20%) of total stake weight. And the contract is Pausable in a specific, deliberate way: pausing blocks new initiate operations (registrations, removals starting) but never blocks complete operations, because those are settlement, and settlement should never get stuck mid-flight just because something else triggered a pause.
Tested, not just written
KumplyValidatorSetManager.test.ts has grown to 50 dedicated tests today, covering the full two-phase registration and removal lifecycle, weight updates, the 20-per-day churn cap, and pausable settlement behavior. Across the whole contracts package, that's 110 tests passing (43 for AttestationStore, 17 for ComplianceGate, 50 here), verified live before writing this.
The bug we found comparing against the real thing
ACP-99 validator sets activate through a P-Chain conversion message. Building the hash for that message means packing several fields together in an exact byte layout, and Avalanche's reference implementation, icm-contracts, defines what that layout has to be. Comparing KUMPLY's computeConversionID function against that reference directly (not against its own docs) found a real mismatch: KUMPLY's version packed the validator manager's address as raw 20 bytes with no length prefix. The reference implementation packs a uint32(20) length prefix immediately before that same address. Four bytes, in the wrong place, and the resulting hash would never match a genuine P-Chain conversionID. Not a cosmetic bug: initializeValidatorSet would have reverted against every real conversion message, forever, and the L1 could never have activated.
Why 27/27 passing tests didn't catch it
At the time this was found, KumplyValidatorSetManager.test.ts had 27 tests, and all 27 passed, every time. That wasn't reassuring once we understood why: the test suite's mock Warp messenger lets a test inject any payload directly, so a test that builds its mock conversion message using KUMPLY's own (buggy) packing function will always agree with itself. Nothing in the suite ever called the real icm-contracts packing function to build the injected message, so the mismatch against reality was invisible from the inside. The fix went in two places at once: the pre-image in ValidatorMessages.sol, and the test suite's own off-chain re-implementation of that same packing, which had the identical gap.
Fixed, redeployed to Fuji, Snowtrace-verified: 0x935114966Ac6CB6Ec569c8C6959aDF5Ceb9E6f64. Full writeup, including a second, lower-severity finding from the same pass: docs/audits.
Where this actually stands, September 2026
Honestly: the chain is registered on Fuji and its ACP-99 validator manager is deployed and verified, but the manager is not initialized and the L1 has not been activated. There are no validators yet, institutional or otherwise. What's real today is the mechanism and the demand pattern behind it, not a live customer list. Avalanche is already positioning Evergreen Subnets around exactly this idea for institutions moving into tokenized funds, and KUMPLY's validator gate is that same idea enforced as code instead of chain-level policy. Founding validator slots are open to any KYB-verified institution; none are confirmed and named publicly yet, and this post won't pretend otherwise.