datahaven/contracts
Ahmad Kaouk eaf55fb414
feat: implement weighted top-32 validator selection (#443)
## Overview

Implements deterministic weighted-stake-based validator selection in
`DataHavenServiceManager`, building on the era-targeting submitter model
from PR #433. Previously, `buildNewValidatorSetMessage()` forwarded all
registered operators in arbitrary membership order with no stake-based
ranking, meaning high-stake operators could be displaced by lower-stake
ones when downstream caps applied. This PR fixes that by computing a
weighted stake score per operator and selecting the top-32 candidates
before bridging the set to DataHaven.

Spec: `specs/validator-set-selection/validator-set-selection.md`

## Contract Changes (`DataHavenServiceManager.sol`)

**New state:**
- `MAX_ACTIVE_VALIDATORS = 32` — cap on the outbound validator set
- `mapping(IStrategy => uint96) public strategiesAndMultipliers` —
per-strategy weight used in the selection formula

**Updated `buildNewValidatorSetMessage()`:**
1. Fetches allocated stake for all operators × strategies from
`AllocationManager`
2. Computes `weightedStake(op) = Σ(allocatedStake[op][j] ×
multiplier[j])` across all strategies
3. Filters operators with no solochain address mapping or zero weighted
stake
4. Runs a partial selection sort to pick the top `min(candidateCount,
32)` by descending weighted stake; ties broken by lower operator address
(deterministic)
5. Reverts with `EmptyValidatorSet()` if no eligible candidates remain

**Admin API changes:**
- `addStrategiesToValidatorsSupportedStrategies()` signature changed
from `IStrategy[]` to `IRewardsCoordinatorTypes.StrategyAndMultiplier[]`
— strategy and multiplier are stored atomically in one call, eliminating
the risk of a strategy being registered without a multiplier
- New `setStrategiesAndMultipliers(StrategyAndMultiplier[])` — updates
multiplier weights for existing strategies without touching the
EigenLayer strategy set
- New `getStrategiesAndMultipliers()` — returns all strategies with
their current multipliers
- `removeStrategiesFromValidatorsSupportedStrategies()` now cleans up
multiplier entries on removal

**New error / event:**
- `EmptyValidatorSet()` — reverts when no eligible candidates exist
- `StrategiesAndMultipliersSet(StrategyAndMultiplier[])` — emitted on
add or update of multipliers

## Tests (`ValidatorSetSelection.t.sol`)

New 552-line Foundry test suite covering all cases from the spec:

| Case |
|------|
| `addStrategies` stores multiplier atomically |
| `removeStrategies` deletes multiplier |
| `setStrategiesAndMultipliers` updates without touching the strategy
set |
| `getStrategiesAndMultipliers` returns correct pairs |
| Weighted stake computed correctly across multiple strategies |
| Operators with zero weighted stake are excluded |
| Unset multiplier treated as 0 |
| Top-32 selection when candidate count > 32 |
| All candidates included when count < 32 |
| Tie-breaking by lower operator address |
| `EmptyValidatorSet` revert when no eligible operators |

## Deploy Scripts

- **`DeployBase.s.sol`**: Sets a default multiplier of `1` for all
configured validator strategies after AVS registration via
`setStrategiesAndMultipliers`
- **New `AllocateOperatorStake.s.sol`**: Forge script that allocates
full magnitude (`1e18`) to the validator operator set for a given
operator. Must be run at least one block after `SignUpValidator` to
respect EigenLayer's allocation configuration delay.

## E2E Framework

- **`validators.ts` — `registerOperator()`**: Extended to deposit tokens
into each deployed strategy and allocate full magnitude to the DataHaven
operator set after registration. Previously operators registered without
staking, producing zero weighted stake and getting filtered out by the
new selection logic.
- **`setup-validators.ts`**: Added a stake allocation pass after the
registration loop, invoking `AllocateOperatorStake.s.sol` per validator.
- **`validator-set-update.test.ts`**: Added debug logging for
transaction receipts and the `OutboundMessageAccepted` /
`ExternalValidatorsSet` events.
- **`generated.ts`**: Regenerated contract bindings to include new
functions, events, and the `EmptyValidatorSet` error.

## ⚠️ Breaking Changes ⚠️

- `addStrategiesToValidatorsSupportedStrategies(IStrategy[])` →
`addStrategiesToValidatorsSupportedStrategies(StrategyAndMultiplier[])`:
callers must supply multipliers alongside strategies.
- Operators with zero weighted stake are no longer included in the
bridged validator set.

## Rollout Notes

1. PR #433 (era-targeting + submitter role) must be deployed first
2. Deploy this `ServiceManager` upgrade
3. Confirm `strategiesAndMultipliers` is set for all active strategies
(default multiplier `1` applied automatically by `DeployBase`)
4. Deploy the runtime cap-enforcement changes (spec section 10.2)
5. Submitter daemon requires no changes — continues submitting
`targetEra = ActiveEra + 1`
2026-02-24 09:23:57 +01:00
..
config feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
deployments feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
lib chore: update snowbridge submodule to latest solochain (#361) 2025-12-19 11:31:45 +01:00
resources docs: 📝 Update contracts diagram (#45) 2025-04-17 12:26:25 -03:00
script feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
scripts feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
src feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
storage-snapshots feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
test feat: implement weighted top-32 validator selection (#443) 2026-02-24 09:23:57 +01:00
.gitignore Fix: command cli deploy contracts (#319) 2025-11-27 15:06:04 +01:00
foundry.lock feat: automated validator set submission with era targeting (#433) 2026-02-20 10:31:44 +01:00
foundry.toml fix: resolve forge build warnings (#398) 2026-01-22 09:48:27 -03:00
README.md refactor: clean old veto committee (#434) 2026-02-09 14:28:34 +01:00

DataHaven AVS Smart Contracts

Implements the Actively Validated Service (AVS) logic for DataHaven, secured by EigenLayer. These contracts manage operator registration, handle cross-chain rewards via Snowbridge, and enforce slashing.

Project Structure

contracts/
├── src/
│   ├── DataHavenServiceManager.sol   # Core AVS service manager
│   ├── middleware/                   # RewardsRegistry, Snowbridge helpers
│   ├── interfaces/                   # Contract interfaces
│   └── libraries/                    # Utility libraries
├── script/                           # Deployment & setup scripts
├── lib/                              # External dependencies (EigenLayer, Snowbridge, OpenZeppelin)
└── test/                             # Foundry test suites

Key Components

  • DataHavenServiceManager (src/DataHavenServiceManager.sol): Core contract for operator lifecycle; inherits ServiceManagerBase.
  • RewardsRegistry (src/middleware/RewardsRegistry.sol): Tracks validator performance and distributes rewards via Snowbridge.

Development

Requires Foundry.

# Build and Test
forge build
forge test

# Regenerate TS bindings (after contract changes)
cd ../test && bun generate:wagmi

Configuration

Deployment parameters (EigenLayer addresses, initial validators, owners) are defined in contracts/config/<network>.json.

  • Do not edit Config.sol or DeployParams.s.sol directly; they only load the JSON.
  • Ensure contracts/config/hoodi.json matches your target environment before deploying.

Deployment

Two deployment paths exist: Local (Anvil) and Testnet (Hoodi). Both install the DataHaven AVS contracts (ServiceManager, RewardsRegistry) and Snowbridge (BeefyClient, Gateway, Agent). They differ in EigenLayer setup:

Local (Anvil)

DeployLocal.s.sol bootstraps a full EigenLayer core deployment (DelegationManager, StrategyManager, AVSDirectory, etc.) alongside DataHaven AVS and Snowbridge.

anvil
forge script script/deploy/DeployLocal.s.sol --rpc-url anvil --broadcast

Testnet (Hoodi)

DeployTestnet.s.sol references existing EigenLayer contracts (addresses from contracts/config/<network>.json) and only deploys DataHaven AVS + Snowbridge.

NETWORK=hoodi forge script script/deploy/DeployTestnet.s.sol \
  --rpc-url hoodi \
  --private-key $PRIVATE_KEY \
  --broadcast

Supported networks: hoodi (no mainnet config yet). Artifacts → contracts/deployments/<network>.json.

How It Works

  1. Registration: Validators register with EigenLayer via DataHavenServiceManager.
  2. Performance Tracking: DataHaven computes reward points and sends a Merkle root to RewardsRegistry on Ethereum via Snowbridge.
  3. Rewards Claims: Validators claim rewards on Ethereum from RewardsRegistry using Merkle proofs.
  4. Slashing: Misbehavior triggers slashing.

See test/README.md for full network integration tests.