Commit graph

20 commits

Author SHA1 Message Date
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
Ahmad Kaouk
401f646286
feat: automated validator set submission with era targeting (#433)
## 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>
2026-02-20 10:31:44 +01:00
Gonza Montiel
ddbc9bdd8b
fix: 🩹 map validator address to operator address for rewards & slashes (#441)
## 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>
2026-02-18 21:38:13 +02:00
Ahmad Kaouk
4a16de1061
fix: resolve forge build warnings (#398)
## 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
2026-01-22 09:48:27 -03:00
Ahmad Kaouk
5313089659
refactor(contracts): Harden DataHavenServiceManager with input validation and code cleanup (#395)
## Summary

- Add zero address validation across all functions that accept address
parameters to prevent misconfiguration
- Fix race condition in `buildNewValidatorSetMessage()` that could cause
reverts during validator deregistration
- Refactor contract for improved readability and reduced code
duplication
- Update AVS metadata URL to point to the correct hosted JSON file

## Changes

### Security & Validation
- Add `ZeroAddress` error and validate all address inputs in
`initialize`, `setRewardsInitiator`, `setSnowbridgeGateway`,
`addValidatorToAllowlist`, `registerOperator`, and
`updateSolochainAddressForValidator`
- Fix race condition: filter out zero solochain addresses in
`buildNewValidatorSetMessage()` to prevent reverts when a validator is
mid-deregistration

### Refactoring
- Replace verbose `if/revert` patterns with `require` statements for
consistency
- Inline single-use internal functions (`_createDataHavenOperatorSets`,
`_setRewardsInitiator`)
- Consolidate duplicate error types into single `ZeroAddress` error
- Rename `initialise` → `initialize` to maintain consistency with the
transparent upgradability pattern
- Optimize validator set message encoding by removing redundant wrapper
function

### Observability
- Add `SolochainAddressUpdated` event for tracking validator address
changes

### Cleanup
- Remove unused remappings from `foundry.toml`
- Fix typo in metadata description

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-01-20 10:32:32 +00:00
undercover-cactus
ac28323e7d
feat : Slashing integration in EigenLayer and Datahaven AVS (#345)
## Summary

This PR integrate the slashing feature with EigenLayer. With this PR,
slashing can now be relayed to our Datahaven AVS and then executed
within EigenLayer. In addition some refactoring of the original slashing
pallet has been done.

## Motivation 

To avoid misbehaving actor in the network, Datahaven has implemented a
slashing pallet in which offenses can be reported and then if adequate
can lead to a sanction on the misbehaving node. It incentive nodes to
only follow good behavior in addition to the reward incentive. The
rewards flow is managed directly into EigenLayer (see
https://github.com/datahaven-xyz/datahaven/pull/351).

## Slashing flow


<img width="2355" height="946" alt="Slashing Flow"
src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246"
/>

## What changes

* Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It
received all the slashing requests batched (every new era the queued
slashing are being relayed from substrate to Ethereum). It handle the
slashing of the operators reported into the Validator set.
* Added a `slashes_adapter.rs` utility file to remove the duplication
for each runtime. In addition, we made use of the `sol!` macro from
alloy to encode the calldata for the Ethereum call. This avoid rewriting
encoding logic and allow to remove the hardcoded selector value used to
call the slashing function.
* Added some tests in solidity to test the registering and slashing of
an operator in Ethereum via Eigen Layer.
* Added e2e tests that test the injection of a slash request, it being
relayed via the snowbridge relayer and executed by our Datahaven AVS.

## What could be better

* We are only deploying one strategy for now so it is hardcoded in the
slashing flow. We should be able to update the pallet in case we are
adding a new strategy. So communication from Ethereum should be relayed.
* We don't have error being return in case the slashing fail. Which
could happen if we don't have the right number of strategy or the
validator is not registered... etc.
* More tests for the unhappy path
2026-01-16 20:49:45 +01:00
Ahmad Kaouk
a0ab11afec
refactor: Remove eigenlayer-middleware and flatten ServiceManagerBase (#389)
## Summary

- Flatten `ServiceManagerBase` middleware layer directly into
`DataHavenServiceManager`
- Remove all unused EigenLayer integration code to keep the contract
minimal
- Fix access control on `deregisterOperatorFromOperatorSets` (was
missing `onlyOwner`)

  ## Motivation

The `ServiceManagerBase` from eigenlayer-middleware was designed for the
old `AVSDirectory` model and included many generic functions DataHaven
doesn't use. This refactor:

  - Reduces code complexity and contract size
  - Removes ~200 lines of unused code
  - Makes the codebase easier to audit and maintain
  - Keeps only what DataHaven actually needs

  ## Changes

  ### Architecture
Before: DataHavenServiceManager → ServiceManagerBase →
ServiceManagerBaseStorage → OwnableUpgradeable
After: DataHavenServiceManager → OwnableUpgradeable, IAVSRegistrar,
IDataHavenServiceManager

  ### Removed (unused)
- `IServiceManager` and `IServiceManagerUI` interfaces (old AVSDirectory
model)
  - `ServiceManagerBase` and `ServiceManagerBaseStorage` middleware
  - `PermissionController` integration (5 proxy functions)
  - `createOperatorSets()` - only needed at initialization
  - `avs()` - never called

  ### Kept (with fixes)
- `deregisterOperatorFromOperatorSets()` - added `onlyOwner` modifier
(security fix)
  - `updateAVSMetadataURI()` - needed for EigenLayer registration

  ### Files Deleted
  - `src/interfaces/IServiceManager.sol`
  - `src/interfaces/IServiceManagerUI.sol`
  - `src/middleware/ServiceManagerBase.sol`
  - `src/middleware/ServiceManagerBaseStorage.sol`
  - `test/mocks/ServiceManagerMock.sol`
  - `test/ServiceManagerBase.t.sol`

  ## Test Plan

  - [x] `forge build` passes
  - [x] `forge test` - all 10 tests pass
  - [x] Contract bindings regenerated
  - [x] State diff regenerated
2026-01-13 15:03:10 +01:00
Ahmad Kaouk
9be1acc97e
refactor: cleanup old rewards model (#383)
## Summary

This PR removes the old merkle root-based rewards model and completes
the migration to EigenLayer Rewards V2 distribution. The old model
required operators to claim rewards by providing merkle proofs, while
the new model uses `submitRewards` to send rewards directly to
EigenLayer's `RewardsCoordinator`.

### Key Changes

- **Smart Contracts**: Removed `RewardsRegistry`,
`RewardsRegistryStorage`, `IRewardsRegistry`, and `SortedMerkleProof`
contracts along with all merkle claim functions from
`ServiceManagerBase`
- **Substrate Pallets**: Removed merkle proof generation from
`external-validators-rewards` pallet and deleted the entire
`runtime-api` crate (no longer needed)
- **Test Framework**: Removed all RewardsRegistry-related code from
deployment scripts, CLI handlers, and TypeScript bindings
- **Runtimes**: Cleaned up all three runtimes (testnet, stagenet,
mainnet) to remove runtime API implementations and unused imports

### Files Removed

**Contracts:**
- `contracts/src/middleware/RewardsRegistry.sol`
- `contracts/src/middleware/RewardsRegistryStorage.sol`
- `contracts/src/interfaces/IRewardsRegistry.sol`
- `contracts/src/libraries/SortedMerkleProof.sol`
- `contracts/test/RewardsRegistry.t.sol`
- `contracts/test/ServiceManagerRewardsRegistry.t.sol`

**Substrate:**
- `operator/pallets/external-validators-rewards/runtime-api/` (entire
crate)

**Test Framework:**
- `test/suites/rewards-message.test.ts`

### Files Modified

**Contracts:**
- `ServiceManagerBase.sol` - Removed merkle claim functions
- `ServiceManagerBaseStorage.sol` - Removed
`operatorSetToRewardsRegistry` mapping
- `IServiceManager.sol` - Removed interface members

**Substrate:**
- `external-validators-rewards` pallet - Removed merkle proof
generation, simplified `EraRewardsUtils` struct
- All runtime configs - Removed `ExternalValidatorsRewardsApi`
implementations

**Test Framework:**
- Updated deployment scripts, CLI handlers, relayer configs, and
TypeScript bindings

### Stats

```
50 files changed, 966 insertions(+), 4453 deletions(-)
```

## Test plan

- [x] All Rust tests pass (`cargo test`)
- [x] All contract tests pass (`forge test`)
- [x] TypeScript type checking passes (`bun typecheck`)
- [x] Contracts build successfully (`forge build`)
- [x] Operator builds successfully (`cargo build --release --features
fast-runtime`)
- [ ] E2E tests pass (`bun test:e2e`)
2026-01-09 15:25:49 +01:00
Ahmad Kaouk
268427be8d
feat: Implement EigenLayer Rewards V2 distribution (#351)
### Summary

This PR implements the EigenLayer Rewards Distribution V2 model for
DataHaven, replacing the previous merkle-root-based rewards registry
approach with EigenLayer's native `OperatorDirectedRewardsSubmission`
API. This enables direct integration with EigenLayer's
RewardsCoordinator for validator rewards distribution.

### Motivation

EigenLayer's V2 rewards model provides several advantages:
- **Direct integration**: Uses EigenLayer's native
`createOperatorDirectedOperatorSetRewardsSubmission` API
- **Per-operator rewards**: Distributes rewards proportionally to
individual operators based on their earned points
- **Simplified architecture**: Removes the need for a separate
RewardsRegistry contract
- **Better UX**: Operators receive rewards directly through EigenLayer's
established claiming mechanism


### Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                       DataHaven Substrate                       │
├─────────────────────────────────────────────────────────────────┤
│  Era End                                                        │
│    │                                                            │
│    ▼                                                            │
│  external-validators-rewards pallet                             │
│    │ generate_era_rewards_utils()                               │
│    │   • Calculate individual points per validator              │
│    │   • Compute total inflation amount                         │
│    │                                                            │
│    ▼                                                            │
│  RewardsSubmissionAdapter (runtime_common)                      │
│    │ build() → points_to_rewards() → encode_rewards_calldata()  │
│    │                                                            │
│    ▼                                                            │
│  Snowbridge Outbound Queue                                      │
│    │ CallContract(ServiceManager.submitRewards(...))            │
└────│────────────────────────────────────────────────────────────┘
     │
     ▼ Cross-chain message via Snowbridge
┌─────────────────────────────────────────────────────────────────┐
│                         Ethereum                                │
├─────────────────────────────────────────────────────────────────┤
│  DataHavenServiceManager                                        │
│    │ submitRewards(OperatorDirectedRewardsSubmission)           │
│    │   • Approve wHAVE tokens to RewardsCoordinator             │
│    │                                                            │
│    ▼                                                            │
│  EigenLayer RewardsCoordinator                                  │
│    │ createOperatorDirectedOperatorSetRewardsSubmission()       │
│    │                                                            │
│    ▼                                                            │
│  Operators claim rewards via EigenLayer                         │
└─────────────────────────────────────────────────────────────────┘
```

### Changes Overview

#### Smart Contracts (`contracts/`)

**DataHavenServiceManager.sol**
- Added `submitRewards(OperatorDirectedRewardsSubmission)` function to
submit rewards to EigenLayer's RewardsCoordinator
- Implements `SafeERC20` for secure token approvals
- Uses `onlyRewardsInitiator` modifier for access control (Snowbridge
Agent)
- Emits `RewardsSubmitted` and `RewardsInitiatorSet` events for tracking

**IDataHavenServiceManager.sol**
- Added `submitRewards()` interface for EigenLayer rewards submission
- Added `setRewardsInitiator()` interface for configuring the authorized
caller
- Added new events: `RewardsSubmitted`, `RewardsInitiatorSet`

**New Test: RewardsSubmitter.t.sol**
- Comprehensive test suite covering:
  - Access control (only rewards initiator can submit)
  - Single and multiple operator rewards
  - Multiple consecutive submissions
  - Custom descriptions and different tokens

#### Substrate Runtime (`operator/`)

**New: `runtime/common/src/rewards_adapter.rs` (934 lines)**

A generic, configurable adapter for building EigenLayer rewards
messages:

- **`RewardsSubmissionConfig` trait**: Runtime-agnostic configuration
interface
  - `OutboundQueue`: Snowbridge outbound queue type
  - `rewards_duration()`: Reward period duration (typically 86400s)
  - `whave_token_address()`: wHAVE ERC20 token on Ethereum
  - `service_manager_address()`: ServiceManager contract address
  - `rewards_agent_origin()`: Snowbridge agent origin

- **`RewardsSubmissionAdapter<C>`**: Generic implementation of
`SendMessage` trait

- **`points_to_rewards()`**: Converts validator points to token amounts
  - Proportional distribution based on total points
  - Returns remainder (dust) from integer division
  - Arithmetic overflow/underflow protection

- **`encode_rewards_calldata()`**: ABI-encodes the `submitRewards` call
  - Uses `alloy-core` for type-safe Solidity ABI encoding
  - Validates `uint96` multiplier bounds

- **Comprehensive test suite** covering:
  - Basic and edge-case reward calculations
  - Remainder/dust handling
  - Overflow/underflow protection
  - ABI encoding round-trip verification
  - Message building with various configurations

**Modified: `pallets/external-validators-rewards/`**

- **`types.rs`**: Extended `EraRewardsUtils` struct:
  ```rust
  pub struct EraRewardsUtils {
      pub era_index: u32,                    // NEW
      pub rewards_merkle_root: H256,
      pub leaves: Vec<H256>,
      pub leaf_index: Option<u64>,
      pub total_points: u128,
      pub individual_points: Vec<(H160, u32)>, // NEW
      pub inflation_amount: u128,             // NEW
      pub era_start_timestamp: u32       // NEW
  }
  ```

- **`lib.rs`**: Updated `generate_era_rewards_utils()`:
  - Now accepts `inflation_amount` parameter
  - Extracts `individual_points` as `(H160, u32)` tuples for EigenLayer
- Returns `None` when `total_points` is zero (prevents inflation with no
distribution)

- **`mock.rs`**: Updated test mock to use `H160` as `AccountId`
(matching DataHaven's EVM-compatible account model)

**Modified: Runtime Configurations**

All three runtimes (mainnet, stagenet, testnet) updated:

1. **New runtime parameters** (`runtime_params.rs`):
- `ServiceManagerAddress`: DataHaven ServiceManager contract on Ethereum
   - `WHAVETokenAddress`: wHAVE ERC20 token address
   - `RewardsGenesisTimestamp`: EigenLayer-aligned genesis timestamp
   - `RewardsDuration`: Rewards period (default: 86400 = 1 day)

2. **Refactored `RewardsSendAdapter`**:
- Replaced inline implementation with `RewardsSubmissionAdapter<Config>`
   - Each runtime implements `RewardsSubmissionConfig` trait
   - Cleaner, DRY configuration

## ⚠️ Breaking Changes ⚠️

- **Runtime Parameters**: New parameters must be configured via
governance before rewards submission will work:
  - `ServiceManagerAddress` (replaces `RewardsRegistryAddress`)
  - `WHAVETokenAddress`
  - `RewardsGenesisTimestamp`
  
- **Contract Interface**: `submitRewards()` now accepts a full
`OperatorDirectedRewardsSubmission` struct instead of a merkle root

---------

Co-authored-by: Gonza Montiel <gonzamontiel@users.noreply.github.com>
2026-01-06 23:53:03 +00:00
undercover-cactus
863250d555
misc: remove slasher middleware solidity contracts (#366)
## Summary

This PR remove the middlewares contracts from eigen layer. Instead we
are planning to use the eigne layer contract directly. It also removes
the tests related to the middleware slasher code and the mock contract
used in it.

## Motivation

When slashing an operator in the Dathaven we are going through the
substrate slashing pallet already implemented. It already allow to
configure a slashing window and/or to cancel a slashing. In the future
it will also be compatible with a government pallet. This part of code
is therefore redundant. For the same reason we remove the tests because
we are not using the slashing middleware contracts.

## What changed

* Remove the slasher middleware files
* Remove the tests related to the middleware slasher file
2025-12-29 14:55:21 +01:00
Ahmad Kaouk
41788d56bb
test: refactor e2e tests (#365)
This PR significantly refactors and improves the end-to-end testing
framework and infrastructure. The primary focus was on simplifying the
test suites, improving reliability through better resource management,
and hardening the relayer infrastructure.

All E2E tests are now passing on the CI and demonstrate consistent
reliability when run locally.

### Key Changes

#### 1. E2E Test Suite Refactor & Cleanup
* **Simplified Test Logic**: Heavily refactored the core test suites
(`native-token-transfer.test.ts`, `rewards-message.test.ts`, and
`validator-set-update.test.ts`). The new implementation is much cleaner,
utilizing shared helpers to reduce boilerplate.
* **Utility Consolidation**: Removed redundant utility files
(`storage.ts`, `rewards-helpers.ts`) and simplified `events.ts`. Event
waiting now uses `rxjs` for Substrate and native `viem` watchers for
Ethereum, which is more robust and easier to maintain.
* **Better Connector Management**: Unified the creation and cleanup of
test clients in `ConnectorFactory`. It now handles the lifecycle of
WebSocket connections more gracefully, including clearing the
`socketClientCache` to prevent reconnection noise during teardown.

#### 2. Infrastructure & Stability
* **Relayer Relaunch Policy**: Added a restart policy for Snowbridge
relayer containers. They are now configured with `--restart
on-failure:5`, ensuring that relayers automatically relaunch if they
crash during the sensitive initialization phase.
*   **WebSocket Integration**: 
* Updated the `ConnectorFactory` to prefer **WebSockets** for the
Ethereum public client, which is essential for efficient, event-heavy
E2E testing.
* Enhanced `launchKurtosisNetwork` to correctly identify and register
the Execution Layer's WebSocket endpoint from Kurtosis.
* **Disabled Contract Injection**: This PR temporarily disables the
automatic injection of contracts into the genesis state by default.
* *Reason*: I encountered issues generating a valid `state-diff.json`
for the latest contract versions. Even after applying several
workarounds, the injected state remained unstable. As a result, I've
reverted to manual contract deployment during the launch sequence for
better reliability for now.

#### 3. Documentation & Maintenance
* Removed obsolete documentation (`event-utilities-guide.md`) that no
longer reflects the simplified event-handling API.
* Cleaned up `test/launcher/validators.ts` and moved logic into more
appropriate helpers.

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2025-12-24 13:31:40 +01:00
Ahmad Kaouk
ffc4d43847
chore: update snowbridge submodule to latest solochain (#361) 2025-12-19 11:31:45 +01:00
Ahmad Kaouk
b737bc03ba
refactor: remove BSP and MSP operator sets (#323) 2025-11-28 14:01:28 +01:00
Ahmad Kaouk
470f5fc916
feat: update eigenlayer contracts to v1.8.0 (#270)
## Summary
- sync `contracts/lib/eigenlayer-contracts` to tag
`v1.8.0-testnet-final` and refresh `EIGENLAYER.md` with the new commit
reference
- update local/test deployment flows to deploy the upstream
`EigenStrategy`, feed it into `AllocationManager`/`StrategyManager`, and
adopt the revised `EigenPod` constructor
- drop the obsolete `AllocationManagerMock` stub and replace its usage
with targeted `vm.mockCall` stubs that return `slashOperator` share data
- adjust slasher unit tests to match the new ABI so DataHaven stays
aligned with EigenLayer 1.8 semantics

## Testing
- forge build
- forge test
2025-11-04 16:30:18 +01:00
Ahmad Kaouk
3815b4cda7
test: Rewards distribution end to end Tests (#132)
### PR Description

Add a comprehensive end-to-end test that validates rewards distribution
across the full system (chain → bridge → execution environment).

### Use cases covered
- Verify the rewards infrastructure is correctly deployed and reachable.
- Detect the end-of-era rewards emission and capture its essential data.
- Confirm the cross-chain delivery and execution of the rewards message.
- Ensure the rewards registry updates with the new root and can be
queried.
- Generate per-validator proofs for claiming rewards.
- Successfully claim rewards for a validator and validate the payout is
reflected.
- Prevent a second (double) claim for the same index with a proper
rejection.

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2025-09-17 09:10:54 +00:00
Gonza Montiel
e9fc4f271f
Fix: 🏗️ Message encoding / decoding (#113)
## Summary of changes
- We decided to remove the topics and nonce from the massage encoding
since we don't use them (original commit:
ee2a3f2fd4).
- Besides, we already have a nonce at the Snowbridge message level
f4ab5c2b2e/operator/primitives/snowbridge/inbound-queue/src/v2/message.rs (L105)

- I had to recreate the static test for _encoding_ (happens in
[DataHavenSnowbridgeMessages.sol](d12d40634f/contracts/src/libraries/DataHavenSnowbridgeMessages.sol)
) / _decoding_ (happens in
[operator/primitives/bridge/src/lib.rs)](f9f9cc65fe/operator/primitives/bridge/src/lib.rs).
Now it matches the current structure. The idea is that now we can test
that we don't break the decoding in followup refactoring.
- Fixes a problem with EigenLayer validator addresses. In all our
contracts we were using `bytes32` to refer to a Solochain validator
address. But on our Substrate change we actually expect AccountId20, so
only 20 bytes. This was causing the decoding to fail.
- I opted for the minimal change that would be to take the right-most 20
bytes to send that to our chain. But we might want aswell to limit our
EigenLayer contracts to be only 20 bytes long. @ahmadkaouk showcase this
[here](92a34c273c)
- Adds a bash script to run the static test. The test will compile the
contracts, run the encoding test, compile the operator, and run the
decoding test. This saves a huge amount of time since we don't need to
run the full e2e setup. The way of running it is the following:
```bash
cd operator/test/scripts
./test_message_encoding.sh
```
- As a consequence of this PR, the execution relayer now works properly.

EDIT:

> [!IMPORTANT]
**We decided to use 20-byte addresses in our contracts**. So what is
stated above is not valid anymore.

The change implies that the mapping from Ethereum addresses to bytes32
addresses now it's a mapping as follows:


dd3ba99ac0/contracts/src/DataHavenServiceManager.sol (L51-L52)

I've updated helper functions, tests, etc to be compliant with this
change. The execution relayer and beefy relayer look stable now.

---------

Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
2025-07-16 07:38:58 +00:00
Tobi Demeco
e8970a2b5f
feat: make RewardsRegistry keep a history of roots and claim status (#106)
# Description
This PR implements a comprehensive overhaul of the `RewardsRegistry`
contract to maintain complete history of reward merkle roots while
providing index-based claim tracking for operators. The new architecture
enables operators to claim rewards from any historical merkle root
instead of only the latest one. To do so, it:
- Adds the `merkleRootHistory` storage array to the contract, in which
we keep all rewards roots that ever came from the DataHaven side.
- Adds the `operatorClaimedByIndex` storage map to the contract, in
which we keep track, for each validator and root index, if it has
claimed it or not.
- This works even for new validators, since theoretically with this
system you could argue they could claim older roots that they were not a
part of which would be catastrophic, but they could never draft a
correct proof for those to claim them.
- Keeps some of the interface from before the overhaul, to have quick
access to the latest rewards merkle root through `getLatestMerkleRoot()`
and to claim rewards for it with `claimRewards()`. This is because the
expected behaviour is for validators to claim their rewards every era.
- Adds a way to batch claim rewards with `claimRewardsBatch()`. This
function allows a validator to claim rewards for multiple root indices
in one call by providing multiple proofs, useful if the validator has
fallen behind claims and has to catch up, although special care will
have to be taken by it to avoid reaching the gas limit of a transaction.

## Storage Efficiency Analysis
One might think this solution is not as storage-efficient as other
solutions that we can think of (I even had two other alternatives in
mind as well), but a simple back-of-the-envelope calculation gives us
peace of mind that the impact of this solution on the overal state size
of the chain is negligible:

### Assumptions (Worst Case Scenario):
- 1,000 validators (actual estimate for DataHaven: ~50/100 validators)
- 6-hour eras (most-likely scenario, following what Polkadot does:
~24-hour eras)
  - Which means 4 merkle root updates per day

### Annual Storage Requirements:
- Merkle Root History: **46,720 bytes/year**
  - 4 roots/day × 32 bytes/root × 365 days/year = 46,720 bytes/year
- Operator Claim Tracking: **~1.46 MB/year**
- 1,000 operators × 1 boolean/(operator * root index) × 1 byte/boolean ×
4 root indices/day × 365 days/year = 1,460,000 bytes/year
- **Total: ~1.5 MB/year**

This represents negligible storage overhead compared to the significant
operational benefits gained.

## TODO
Since we want to allow the operators/validators to only have to interact
with the AVS contract (that's why the `claimRewards` functions have the
`onlyAVS` modifier), we still have to:
- [x] Add the required functions to the AVS to allow operators to claim
their rewards.
- [x] Adds comprehensive unit tests for them.

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2025-07-10 08:47:39 +02:00
Ahmad Kaouk
b6e8661dea
chore: update snowbridge submodule (#109)
This PR updates the Snowbridge submodule and regenerates contract
bindings to incorporate the latest changes from upstream.
2025-07-01 13:52:17 +00:00
Facundo Farall
a86791ec1c
perf(CLI): Add option to use local Docker build in CLI for faster iteration (#77)
In this PR:
1. Add new `datahaven-node-local.dockerfile` for building a local image
with the locally built DataHaven Node binary. This severely improves
iteration speed on running `bun cli` if there are changes in the DH
node. Previously, it relied on the published dockerfile, which builds
the Cargo project inside of it, taking +20m even with no changes.
2. Building this local dockerfile is integrated to the CLI, which now
also asks if the user wants to rebuild the local docker image of the DH
node.
3. A new script `cargo-crossbuild` is added, to be able to build the
DataHaven node Cargo project both from Mac and Linux, with the target
being `x86_64-unknown-linux-gnu`. For building from Mac, it uses `cargo
zigbuild`, so `zig` is now a dependency. Building for this target is
needed because the docker image is an Ubuntu image, so it will need to
run a linux binary.
4. Added `zig` as dependency in docs.
5. CI still uses the docker image built by the CI itself, which builds
the Cargo project inside of it. The CI can take advantage of caching for
this.
2025-05-16 18:04:40 -03:00
Tim B
fa4d3b8391
test: 🧙 Generate Type Bindings for Contracts (#58)
## Summary
This PR adds statically typed bindings for contracts. This allows you to
write E2E tests with full completions in TS.

## Additions

- `ts-build.yml` New CI, this will make sure that if there's changes
made to the contracts that the contract-bindings are up to date.
- `package.json` script changes
- `start:e2e:ci` - Designed to be run with all options specified since
CIs are famously bad with iteractive CLI prompts
  - `test:e2e` - added timeout
- `generate:wagmi` - This generates the smart contract bindings for our
tests
- New Function Helpers:
- `generateRandomAccount()` Returns a viem account type object for a
random account. Useful for tests where you want idempotency on a long
lived network since the state is probabilistically fresh
- `getContractInstance()` Returns a viem contract instance that allows
you to read/write to the deployed contract. You should get full type
inference here for the methods available and parameters required.

### Example

```ts
 it("avs() can be read from contract instance", async () => {
    const value = await instance.read.avs();
    expect(isAddress(value), "AVS getter should return an address").toBeTrue();
  });
```

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
2025-05-01 11:14:19 +01:00