## Summary
- Replace the manual `sendNewValidatorSetForEra` contract call in the
`validator-set-update` E2E test with the **validator-set-submitter
daemon** running as a Docker container
- Add `test/e2e/framework/submitter.ts` with helpers to build the image,
launch the container on the shared Docker network, and clean up after
the test
- Remove unused imports (`getOwnerAccount`, `decodeEventLog`,
`parseEther`, `gatewayAbi`) that were only needed for manual submission
The submitter automatically detects the last session of an era and
submits the validator set via Snowbridge, matching production behavior
more closely than the previous manual call.
## Test plan
- [x] `bun test e2e/suites/validator-set-update.test.ts --timeout
900000` passes (4/4 tests, 15 assertions)
- [x] Verify submitter container starts and connects to both Ethereum
and DataHaven
- [x] Verify `ExternalValidatorsSet` event is observed on DataHaven
- [x] Verify submitter container is cleaned up after the test
- [x] Verify Charlie and Dave appear in the final validator set
## 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`
## Era-targeted validator set submission with dedicated submitter role
> **Note:** This PR includes a detailed specification at
[`specs/validator-set-submission/validator-set-submission.md`](https://github.com/datahaven-xyz/datahaven/blob/feat/validator-set-submitter/specs/validator-set-submission/validator-set-submission.md)
that covers the design rationale, submission lifecycle, era-targeting
rules, and failure modes. Reading the spec first will make the contract,
pallet, and daemon changes easier to follow.
### Summary
- Introduce a dedicated `validatorSetSubmitter` role on
`DataHavenServiceManager`, separating validator set submission authority
from the contract owner
- Replace the unscoped `sendNewValidatorSet` with
`sendNewValidatorSetForEra`, which encodes a `targetEra` into the
Snowbridge message payload
- Add server-side era validation in the `external-validators` pallet to
reject stale, duplicate, or out-of-range submissions
- Add a long-running TypeScript daemon that watches session changes and
automatically submits each era's validator set at the right time
### Contract changes (`contracts/`)
- **New `validatorSetSubmitter` storage slot** — set during `initialize`
and rotatable via `setValidatorSetSubmitter` (owner-only). The storage
gap is decremented accordingly.
- **`sendNewValidatorSet` → `sendNewValidatorSetForEra`** — accepts a
`uint64 targetEra` parameter and is restricted to
`onlyValidatorSetSubmitter` instead of `onlyOwner`.
- **`buildNewValidatorSetMessageForEra`** — the
`NewValidatorSetPayload.externalIndex` is now caller-supplied instead of
hardcoded to `0`.
- **New events** — `ValidatorSetSubmitterUpdated`,
`ValidatorSetMessageSubmitted`.
- **New error** — `OnlyValidatorSetSubmitter`.
- **New test suite** — `ValidatorSetSubmitter.t.sol` covering submitter
set/rotate, access control, era encoding, and legacy function removal.
### Pallet changes (`operator/`)
- **`validate_target_era`** in `external-validators` — enforces
`activeEra < targetEra <= activeEra + 1` and `targetEra > ExternalIndex`
(dedup guard).
- **New errors** — `TargetEraTooOld`, `TargetEraTooNew`,
`DuplicateOrStaleTargetEra`.
- **Tests** — five new test cases for era boundary conditions (next-era
acceptance, old-era rejection, too-new rejection, duplicate rejection,
genesis behavior). Existing `era_hooks_with_external_index` test updated
to use valid target eras.
- **Runtime test fixes** — `external_index: 0` → `1` in
mainnet/stagenet/testnet EigenLayer message processor tests to satisfy
the new validation.
### Validator set submitter daemon
(`test/tools/validator-set-submitter/`)
- Event-driven service that subscribes to finalized
`Session.CurrentIndex` via Polkadot-API `watchValue`.
- Submits once per era during the last session, targeting `ActiveEra +
1`.
- Tracks submitted eras to avoid duplicates; skips if `ExternalIndex`
already covers the target.
- Startup self-checks: Ethereum connectivity, DataHaven connectivity,
on-chain submitter authorization.
- Supports `--dry-run` mode and YAML configuration.
- Graceful shutdown on `SIGINT`/`SIGTERM`.
### Test & tooling updates
- **E2E test** (`validator-set-update.test.ts`) — calls
`sendNewValidatorSetForEra` with a computed `targetEra` and filters the
substrate event by `external_index`.
- **`update-validator-set.ts` script** — accepts `--target-era` flag;
defaults to era 1 for fresh networks.
- **CLI launch** — wires validator set update as an interactive step
after relayer launch.
- **`package.json`** — new `submitter` and `submitter:dry-run` scripts.
- Regenerated contract bindings, PAPI metadata, state-diff, and storage
layout snapshots.
### Test plan
- [x] `forge test` — passes, including new `ValidatorSetSubmitter.t.sol`
- [x] `cargo test` — passes, including new era-validation tests in
`external-validators`
- [x] `bun test:e2e` — validator-set-update suite passes with
era-targeted flow
- [x] Manual: run submitter daemon against local network (`bun
submitter`), verify it submits once per era at the correct session
## ⚠️ Breaking Changes ⚠️
- **`sendNewValidatorSet` removed** — replaced by
`sendNewValidatorSetForEra(uint64 targetEra, ...)`. Callers must now
supply a `targetEra` parameter.
- **Access control changed** — validator set submission is now
restricted to the `validatorSetSubmitter` role instead of the contract
`owner`. The submitter address is set during `initialize` and rotatable
via `setValidatorSetSubmitter` (owner-only).
- **`external-validators` pallet now validates `targetEra`** — messages
with a stale, duplicate, or out-of-range `external_index` are rejected
on-chain. Existing integrations sending `external_index: 0` will fail
validation.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Adds an arithmetic invariant check (`slot + size == 151`) to
`check-storage-layout.sh` that catches cases where a new state variable
is added without shrinking `__GAP` accordingly
- The existing snapshot-diff check alone could pass with a wrong gap
size if the snapshot is updated to match — this new check prevents that
- Updates the negative test to also accept the new `__GAP invariant
violated!` error message
## Test plan
- [x] `check-storage-layout.sh` passes on the current correct layout
- [x] `check-storage-layout-negative.sh` passes — the bad layout
contract (slot 107 + size 45 = 152) correctly triggers the invariant
failure
## Summary
Slashing and rewards submissions were submitted through the bridge with
their **solochain address** , while EigenLayer expects the **ethereum
operator address**, the addresses were not being translated, so the
protocol was broken.
This PR adds a **reverse mapping** (Solochain address → Eth address) and
uses it in both the slashing and rewards paths so that:
- `slashValidatorsOperator` accepts requests where `operator` is a
Solochain address and translates it to the Eth operator before calling
EigenLayer.
- `submitRewards` translates each `operatorRewards[].operator` from
Solochain to Eth before calling the RewardsCoordinator.
- Unknown or unmapped solochain addresses cause a revert
(`UnknownSolochainAddress`) instead of silently failing.
## What's changed
### DataHavenServiceManager
- **Reverse mapping**: `mapping(address => address) public
validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP`
reduced by one slot for upgradeable layout.
- **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth
operator for a Solochain address, reverts with
`UnknownSolochainAddress()` if unmapped.
- **Registration / lifecycle**:
- `registerOperator`: populates both forward and reverse mappings;
enforces uniqueness (one Solochain per operator) and clears old reverse
entry when an operator re-registers with a new Solochain.
- `deregisterOperator`: clears both forward and reverse entries.
- `updateSolochainAddressForValidator`: updates both mappings, enforces
uniqueness and clears the previous Solochain's reverse entry.
- **Slashing**: `slashValidatorsOperator` uses
`_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by
Solochain address are translated before calling EigenLayer.
- **Rewards**: `submitRewards` builds a translated copy of the
submission with each `operatorRewards[].operator` set via
`_ethOperatorFromSolochain(...)`; unmapped addresses revert.
### IDataHavenServiceManager
- New getter: `validatorSolochainAddressToEthAddress(address solochain)
external view returns (address)`.
- New errors: `UnknownSolochainAddress()`,
`SolochainAddressAlreadyAssigned()`.
### Storage and fixtures
- Storage snapshot updated for the new state variable.
- `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap)
for layout negative tests.
- Storage layout test extended to assert the reverse mapping is
preserved across proxy upgrade.
### Tests
- **Slashing.t.sol**: Slashing with Solochain address (translation and
emit of Eth operator); negative test for unmapped Solochain reverting
with `UnknownSolochainAddress()`.
- **RewardsSubmitter.t.sol**: Rewards submission with Solochain
addresses (translation to Eth in RewardsCoordinator calldata); negative
test for unmapped Solochain.
- **StorageLayout.t.sol**: Reverse mapping preserved after upgrade.
- **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already
assigned to another operator), update/deregister clearing reverse
mapping, and getter behaviour.
## Testing
- **Unit tests**: `forge test` from `contracts/` (all existing and new
tests pass).
- **Storage**:
- `./scripts/check-storage-layout.sh`
- `./scripts/check-storage-layout-negative.sh`
- **Coverage**: Slashing path (Solochain → Eth translation + revert),
rewards path (translation + revert), registration/update/deregister
(reverse mapping and uniqueness), and storage layout upgrade
preservation.
---------
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
## Summary
- Add stagenet-hoodi deployment artifacts (contract addresses, rewards
info) and update Snowbridge config with latest validator set
- Fix the `bun cli contracts verify` command to correctly verify all
deployed contracts, including proxy contracts and Snowbridge
dependencies
- Fix the `bun cli contracts update-metadata` command to use the correct
config file when `--environment` is specified
## Contract verification fixes
The verification CLI hardcoded all contract source paths as
`src/<Name>.sol`, which failed for:
- **Snowbridge contracts** (Gateway, BeefyClient, AgentExecutor) — these
live in `lib/snowbridge/contracts/src/`
- **Gateway proxy** — the `Gateway` deployment address is actually a
`GatewayProxy`, not the Gateway implementation. The implementation
address needs to be resolved from the ERC1967 storage slot
- **ServiceManager proxy** — was not being verified at all
Changes:
- Added `contractPath` field to `ContractToVerify` so each contract
specifies its source location relative to the contracts directory
- Added `guessConstructorArgs` option for proxy contracts with complex
encoded init data (uses forge's `--guess-constructor-args`)
- Gateway is now verified as two separate contracts: Gateway
Implementation (address resolved from ERC1967 proxy slot) and
GatewayProxy
- ServiceManager proxy is now verified as `TransparentUpgradeableProxy`
## Update-metadata fix
The `update-metadata` command was ignoring the `--environment` flag when
selecting the deployments file:
1. The handler received a pre-built networkId (`"stagenet-hoodi"`) as
the chain parameter, which `getChainDeploymentParams` couldn't resolve
(falling back to anvil). Now chain and environment are passed
separately.
2. Commander.js routed `--environment` to the parent contracts command,
leaving `options.environment` undefined on the subcommand. Added the
same parent-fallback logic already used for `--chain`.
## Test plan
- [x] `bun typecheck` passes
- [x] Ran `bun cli contracts verify --chain hoodi --environment
stagenet` — all contracts verified successfully on Etherscan
- [x] `bun cli contracts update-metadata --chain hoodi --environment
stagenet` now reads the correct `stagenet-hoodi.json` deployments file
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Wire the Frontier `EthPubSub` module into the node's RPC layer,
enabling WebSocket-based `eth_subscribe`/`eth_unsubscribe` support for
`newHeads`, `logs`, and `newPendingTransactions`
- Use Ethereum-style hex (`0x`-prefixed) subscription IDs via
`EthereumSubIdProvider` for client compatibility
- Add Moonwall test suites (adapted from Moonbeam) covering block header
subscriptions, log filtering (by address, topics, wildcards, conditional
parameters), and pending transaction notifications
## Changes
### `operator/node/src/rpc.rs`
- Import and merge `EthPubSub` / `EthPubSubApiServer` into the RPC
module
- Accept `subscription_task_executor` and `pubsub_notification_sinks`
parameters in `create_full()`
- Remove stale commented-out boilerplate
### `operator/node/src/service.rs`
- Clone `pubsub_notification_sinks` and forward it (along with
`subscription_executor`) into the RPC factory closure
- Set `config.rpc.id_provider` to `EthereumSubIdProvider` for
Ethereum-compatible subscription IDs
### `test/moonwall/suites/dev/stagenet/subscription/`
- `test-subscription.ts` — `newHeads`: subscription ID format, block
header field validation
- `test-subscription-logs.ts` — `logs`: basic log notification on
contract deployment
- `test-subscription-logs2.ts` — `logs`: filtering by single/multiple
addresses, topics, wildcards, conditional and combined parameters (8
cases)
- `test-subscription-pending.ts` — `newPendingTransactions`: pending tx
hash notification
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Summary
- drop the Foundry library and build artifact cache restores from the
e2e workflow
- also remove the Foundry build cache from the dedicated Foundry tests
workflow since it wasn’t providing value
Testing
- Not run (not requested)
## Summary
We are now launching the MSP backend when starting stpragehub services.
In this PR, we also fix the MSP and BSP node configuration and register
it with the correct keys.
## What changed
* Added a launch Backend MSP function that is called when launching
storage hub services
* Fix the wrong genesis error message in storagehub node by removing the
`--chain dev` flags (so it can be launch of the same network as our
local datahaven nodes).
* Use the correct keys to register MSP and BSP. We were injecting
different keys that the one we used for MSP and BSP registration leading
to the MSP and BSP node to never fully register as storage providers.
---------
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
## Summary
- Add the `mmr-gadget` to the DataHaven client for proper MMR leaf
indexing in offchain storage
- Gate the gadget on `offchain_worker.indexing_enabled` to avoid running
when indexing is disabled
- Enable efficient MMR proof queries by block number via the MMR RPC
## Problem
The DataHaven client was missing the `mmr-gadget`, which prevented MMR
leaves from being correctly indexed in the offchain database. Without
it:
- MMR proofs could only be queried by block hash, not block number
- Light clients and bridge relayers could not efficiently verify
finality
- The `mmr_generateProof` RPC had degraded functionality
## Changes
| File | Change |
|------|--------|
| `operator/Cargo.toml` | Add workspace deps for `mmr-gadget`,
`sp-mmr-primitives` |
| `operator/node/Cargo.toml` | Add node deps for `mmr-gadget`,
`sp-mmr-primitives` |
| `operator/node/src/service.rs` | Add import and spawn `MmrGadget`
after BEEFY gadget |
## Test plan
- [x] Build passes: `cd operator && cargo build --release --features
fast-runtime`
- [x] Run node with debug logging: `--log mmr-gadget=debug`
- [x] Verify `mmr-gadget` task starts in logs
- [x] Test MMR RPC by block number works:
```bash
curl -H "Content-Type: application/json" \
-d '{"id":1,"jsonrpc":"2.0","method":"mmr_generateProof","params":[[1],
null, null]}' \
http://localhost:9944
```
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Add missing weights
The aim of this PR is to complete our weights by enabling more runtime
benchmarks from the pallets used in DataHaven. I will start this effort
with Storage Hub pallets.
## What's included
- [x] `pallet_nfts`
- [x] Added signing helper for pallet nfts
- [x] Add pallet to benchmarks
- [x] Run benches and generate weights
- [x] Wire up weights to runtimes
- [x] `pallet_session`
- [x] Added a `pallet_session_benchmarking` crate
- [x] Added signing helpers
- [x] Add pallet to benchmarks
- [x] Run benches and generate weights
- [x] Wire up weights to runtimes
- [x] `pallet_payment_streams`
- [x] Add `TreasuryAccount` helper and configure properly
- [x] Add pallet to benchmarks
- [x] Run benches and generate weights
- [x] Wire up weights to runtimes
- [x] `pallet_storage_providers`
- [x] Add `TreasuryAccount` helper and configure properly
- [x] Add pallet to benchmarks
- [x] Run benches and generate weights
- [x] Wire up weights to runtimes
- [x] `pallet_file_system`
- [x] Add pallet to benchmarks and configure properly
- [ ] Run benches and generate weights
- [x] Wire up weights to runtimes
- [x] `pallet_proofs_dealer`
- [x] Add pallet to benchmarks and configure properly
- [x] Run benches and generate weights
- [x] Wire up weights to runtimes
## What's not included
- `pallet_identity` - We'll enable it once we update to `2506`, that
will allow us to have a BenchmarkHelper in the config on the pallet (see
[here](ac28323e7d/operator/runtime/mainnet/src/configs/mod.rs (L632-L643)))
- `pallet_grandpa` - the upstream pallet defines
[benchmarks](bbc435c766/substrate/frame/grandpa/src/benchmarking.rs (L25))
for `check_equivocation_proof` and `note_stalled`, but the required
weights to be wired are actually `report_equivocation`,
`report_equivocation_unsigned` and `note_stalled`. That means including
`pallet_grandpa` in the benchmarks results in an inconsistent
`WeightInfo` implementation, so further understanding in the pallet's
approach to benchmarking is needed.
- `pallet_file_system` -> Run benches and generate weights. Weights will
fail because of a hardcoded `AccountId32` on the
[benchmarks](57d2a195d5/pallets/file-system/src/benchmark_proofs.rs (L69-L71)).
I'll create a PR for SH soon.
These two are left for a follow up PR.
## Summary
- Add multi-environment deployment support (stagenet, testnet, mainnet)
to CLI and contracts
- Configure stagenet and testnet runtimes with correct genesis hashes
and Snowbridge Agent IDs
- Add CLI commands for BEEFY checkpoint updates and rewards origin
computation
- Add ETH validator strategies (native beacon chain ETH + LSTs) to all
config files
## Changes
### Runtime Configuration
**Stagenet Runtime:**
- Set `StagenetGenesisHash` to DataHaven stagenet genesis hash
- Configure `RewardsAgentOrigin` with computed Snowbridge Agent ID
- Add tests verifying rewards account derivation and agent ID
computation
**Testnet Runtime:**
- Set `TestnetGenesisHash` to DataHaven testnet genesis hash
- Configure `RewardsAgentOrigin` with computed Snowbridge Agent ID
- Add tests verifying rewards account derivation and agent ID
computation
The Rewards Agent ID is computed following Snowbridge's location
description pattern:
```
blake2_256(SCALE_ENCODE("GlobalConsensus", ByGenesis(genesis), "AccountKey20", rewards_account))
```
### CLI Enhancements
- All contracts subcommands (`status`, `deploy`, `verify`,
`update-metadata`) now accept `--environment` option
- Config and deployment files use environment-prefixed naming (e.g.,
`stagenet-hoodi.json`, `testnet-hoodi.json`)
- New `update-beefy-checkpoint` command that:
- Connects to a live DataHaven chain via WebSocket RPC
- Fetches all BEEFY data at the same finalized block for consistency
- Uses parallel queries with `Promise.all` for better performance
- Computes authority hashes (keccak256 of Ethereum addresses derived
from BEEFY public keys)
- Uses Snowbridge's quorum formula `n - floor((n-1)/3)` for strictly >
2/3 majority
- New `update-rewards-origin` command that computes the Snowbridge Agent
ID for the rewards pallet
- Centralized validation via `contractsPreActionHook` for all contract
commands
- Environment validation against allowlist (`stagenet`, `testnet`,
`mainnet`)
### Contract Changes
- Network validation uses explicit allowlist instead of suffix matching
- Added `initialValidatorSetId` and `nextValidatorSetId` fields to
`SnowbridgeConfig` struct
- `DeployBase.s.sol` now uses config values for validator set IDs
instead of hardcoded 0/1
- `DeployParams.s.sol` loads validator set IDs from config with
backwards compatibility
### Validator Strategies
Added ETH-equivalent strategies to allow validators to stake using
native ETH or LSTs:
**All Networks:**
- `0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0` - Native beacon chain ETH
(virtual strategy)
**Hoodi Testnet:**
- `0xf8a1a66130d614c7360e868576d5e59203475fe0` - stETH
- `0x24579aD4fe83aC53546E5c2D3dF5F85D6383420d` - WETH
**Ethereum Mainnet:**
- `0x93c4b944D05dfe6df7645A86cd2206016c51564D` - stETH
- `0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2` - rETH
- `0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc` - cbETH
### Config Files
- `stagenet-hoodi.json` - Hoodi testnet with stagenet EigenLayer
addresses
- `testnet-hoodi.json` - Hoodi testnet with testnet EigenLayer addresses
- `mainnet-ethereum.json` - Ethereum mainnet with mainnet EigenLayer
addresses
- Removed `hoodi.json` (replaced by environment-prefixed files)
## Usage
```bash
# Deploy to stagenet on Hoodi
bun cli contracts deploy --chain hoodi --environment stagenet
# Update BEEFY checkpoint from live chain
bun cli contracts update-beefy-checkpoint \
--chain hoodi \
--environment stagenet \
--rpc-url wss://services.datahaven-dev.network/stagenet
# Compute rewards origin for a chain
bun cli contracts update-rewards-origin \
--chain hoodi \
--environment stagenet \
--rpc-url wss://services.datahaven-dev.network/stagenet
# Check deployment status
bun cli contracts status --chain hoodi --environment stagenet
```
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Import `pallet-proxy-genesis-companion` from Moonbeam to enable proxy
account configuration at genesis time
- Configure the pallet in all runtimes (mainnet, stagenet, testnet) with
pallet index 106
- Add `Serialize`/`Deserialize` derives to `ProxyType` enum to satisfy
`MaybeSerializeDeserialize` bounds
- Include mock runtime and unit tests adapted for polkadot-stable2412-6
This pallet extends `pallet-proxy` with genesis configuration support,
allowing proxy relationships to be established at chain genesis rather
than requiring extrinsic calls after launch.
### Key adaptations from Moonbeam
The pallet was modified to work with the DataHaven SDK version
(polkadot-stable2412-6):
- Removed `BlockNumberProvider` associated type constraint (not present
in this version of pallet-proxy)
- Uses `frame_system::pallet_prelude::BlockNumberFor<T>` directly for
delay parameter
- Uses `MaybeSerializeDeserialize` trait bound for `ProxyType`
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
Upgrades StorageHub dependencies from v0.3.3 to v0.3.5. This requires a
client upgrade.
## ⚠️ Breaking Changes ⚠️
Fisherman CLI options have been added to support specifying filtering
and ordering strategies for pending file deletions with reasonable
defaults:
- `--fisherman-filtering`: The filtering strategy [**`none` (default)**,
`ttl`]
- `--fisherman-ordering`: The ordering strategy [**`chronological`
(default)**, `randomized`]
- `--fisherman-ttl-threshold-seconds`: TTL for a file to be ignored for
deletion in seconds
MSP and BSP CLI options have been added to support specifying a specific
batch response and confirm size for MSP and BSP nodes with reasonable
defaults.
- `--bsp-confirm-file-batch-size`: How many storage requests to respond
to (confirming) in a single extrinsic call **(default: 20)**
- `--msp-respond-storage-batch-size`: How many storage requests to
respond to (accepting or rejecting) in a single extrinsic call
**(default: 20)**
### Summary
Set username deletion to use a 30‑day grace period (in blocks) and added
a non‑zero username deposit, both based on the Moonbeam's runtimes. This
makes username unbinding wait before deletion and makes authority‑issued
usernames non‑free, mitigating the sybil vector while aligning with
existing identity config patterns.
### Changes
- Configures for `pallet_identity`
- `UsernameGracePeriod = 30 * DAYS`
- `UsernameGracePeriod = deposit(0, MaxUsernameLength::get())`
Use the latest v0.15.2 release of Kurtosis, that includes improved
compatibility with rootless Podman (wrt. socket detection and bind
mounting) following the merge of
https://github.com/kurtosis-tech/kurtosis/pull/2803.
Up to now, the e2e CI job was using a custom (patched) version of
Kurtosis CLI, Engine & Core images.
## Summary
- Fixed `ProxyType` enum in the Solidity Proxy precompile interface to
match the runtime definition
- Removed non-existent `AuthorMapping` variant
- Added missing `SudoOnly` variant
## Problem
The Solidity interface in `Proxy.sol` had incorrect `ProxyType` enum
values that didn't match the runtime definition:
| Index | Runtime (Correct) | Solidity (Was) |
|-------|------------------|----------------|
| 0 | Any | Any |
| 1 | NonTransfer | NonTransfer |
| 2 | Governance | Governance |
| 3 | Staking | Staking |
| 4 | CancelProxy | CancelProxy |
| 5 | Balances | Balances |
| 6 | **IdentityJudgement** | **AuthorMapping** ❌ |
| 7 | **SudoOnly** | **IdentityJudgement** ❌ |
This mismatch would cause EVM users calling the Proxy precompile with
`IdentityJudgement` (index 7 in Solidity) to actually get `SudoOnly`
behavior, and `AuthorMapping` (index 6) would fail to decode entirely
since it doesn't exist in the runtime.
## Solution
Updated the Solidity enum to match the runtime:
```solidity
enum ProxyType {
Any,
NonTransfer,
Governance,
Staking,
CancelProxy,
Balances,
IdentityJudgement,
SudoOnly
}
```
## ⚠️ Breaking Changes ⚠️
- **`ProxyType.AuthorMapping` removed**: This variant never existed in
the runtime and would fail to decode.
- **`ProxyType.IdentityJudgement` index changed**: Moved from index 7 to
index 6. Solidity code using `ProxyType.IdentityJudgement` will now work
correctly (previously it mapped to `SudoOnly` in the runtime)
- **`ProxyType.SudoOnly` added**: New variant at index 7 for proxies
that can only execute Sudo pallet calls
## Test plan
- [x] Proxy precompile tests pass (32/32)
- [x] Mainnet runtime proxy tests pass (22/22)
- [x] Governance proxy tests pass (6/6)
- [x] Verified `InstanceFilter<RuntimeCall>` implementation handles all
8 variants correctly
- [x] Verified `EvmProxyCallFilter` implementation handles all 8
variants correctly
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Upgrades to StorageHub version v0.3.3. This upgrade requires both a
runtime and client upgrade.
---------
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
### Summary
Optimizes `award_session_performance_points` by batching all validator
rewards into a single storage mutation instead of performing individual
mutations inside the loop.
### Problem
The `award_session_performance_points` function, called during session
rotation via `SessionManager::end_session`, was calling `reward_by_ids`
inside the validator loop for each validator individually:
```rust
for validator in validators.iter() {
// ... calculate points ...
Self::reward_by_ids([(validator.clone(), points)].into_iter());
}
```
Each call to `reward_by_ids` performs a `StorageMap::mutate` on
`RewardPointsForEra`, which reads and writes the entire
`EraRewardPoints` structure (a `BTreeMap` containing up to N validator
entries). With N validators, this results in N separate
read-modify-write cycles of an O(N)-sized structure, leading to O(N²)
total storage I/O.
### Solution
Collect all reward points first, then perform a single batched call to
`reward_by_ids`:
```rust
let mut rewards = Vec::new();
for validator in validators.iter() {
// ... calculate points ...
rewards.push((validator.clone(), points));
}
if !rewards.is_empty() {
Self::reward_by_ids(rewards.into_iter());
}
```
This reduces the complexity from O(N²) to O(N) by performing only one
storage mutation that processes all validators at once.
### Why This Matters
Session rotation hooks are mandatory—they execute regardless of block
weight limits. While `pallet_session::on_initialize` returns `max_block`
weight during rotation (preventing user transactions), the actual
execution time still matters. With a large validator set, O(N²) storage
operations could exceed the block time target, potentially causing block
production delays.
### Test Plan
- [x] Existing unit tests pass (`cargo test -p
pallet-external-validators-rewards`)
## Summary
### Configuration
- Remove deprecated `deny_warnings` config key from foundry.toml
- Add global `[lint]` config to suppress naming convention warnings for
AVS/EL/ERC patterns (`mixed-case-function`, `mixed-case-variable`)
### DataHavenServiceManager Refactoring
- Rename immutable variables to SCREAMING_SNAKE_CASE
(`_allocationManager` → `_ALLOCATION_MANAGER`, `_rewardsCoordinator` →
`_REWARDS_COORDINATOR`)
- Wrap modifier logic in internal functions (`_checkRewardsInitiator`,
`_checkValidator`, `_checkAllocationManager`) to reduce contract size
- Add `_toAddress` helper with assembly for safe bytes-to-address
conversion
### Safe Typecasting
- Replace direct typecasts with OpenZeppelin's SafeCast library in
deploy scripts and test utilities
- Use `.toUint32()`, `.toUint64()`, `.toUint160()` for
overflow-protected conversions
- Replace `bytes32("wrong origin")` string cast with hex literal in test
deployer
### Code Cleanup
- Remove 25+ unused imports across script and test files
- Convert plain imports to named imports for better clarity
- Use `SafeERC20.safeTransfer()` for token transfers in tests
- Change `view` to `pure` where appropriate
## Test plan
- [x] `forge build` completes with no warnings
- [x] `forge test` passes all 10 tests
## Summary
ERC20 balances precompile `withdraw()` was failing to account for gas
costs associated with storage reads. In fact the function was calling
`usable_balance` without accounting for `record_db_read`.
## Changes
- Added `116 bytes` of storage read computed like this: `Blake2128(16) +
AccountId(20) + AccountInfo ((4 * 4) + AccountData(16 * 4))`, to cover
for `usable_balance`, following the same that `balance_of` does.