mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
### 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>
88 lines
2.7 KiB
TOML
88 lines
2.7 KiB
TOML
[package]
|
|
authors = { workspace = true }
|
|
description = "Common code used through the DataHaven network"
|
|
edition = { workspace = true }
|
|
name = "datahaven-runtime-common"
|
|
version = { workspace = true }
|
|
|
|
[dependencies]
|
|
alloy-core = { workspace = true, features = ["sol-types"] }
|
|
codec = { workspace = true }
|
|
fp-account = { workspace = true, features = ["serde"] }
|
|
frame-support = { workspace = true }
|
|
frame-system = { workspace = true }
|
|
log = { workspace = true }
|
|
pallet-authorship = { workspace = true }
|
|
pallet-balances = { workspace = true }
|
|
pallet-external-validators-rewards = { workspace = true }
|
|
pallet-timestamp = { workspace = true }
|
|
pallet-evm = { workspace = true }
|
|
pallet-evm-chain-id = { workspace = true }
|
|
pallet-evm-precompile-proxy = { workspace = true }
|
|
pallet-migrations = { workspace = true }
|
|
pallet-safe-mode = { workspace = true }
|
|
pallet-tx-pause = { workspace = true }
|
|
pallet-treasury = { workspace = true }
|
|
polkadot-primitives = { workspace = true }
|
|
polkadot-runtime-common = { workspace = true }
|
|
precompile-utils = { workspace = true }
|
|
scale-info = { workspace = true }
|
|
snowbridge-outbound-queue-primitives = { workspace = true }
|
|
sp-core = { workspace = true, features = ["serde"] }
|
|
sp-io = { workspace = true }
|
|
sp-runtime = { workspace = true, features = ["serde"] }
|
|
sp-std = { workspace = true }
|
|
xcm = { workspace = true }
|
|
|
|
[features]
|
|
default = ["std"]
|
|
std = [
|
|
"alloy-core/std",
|
|
"codec/std",
|
|
"frame-support/std",
|
|
"log/std",
|
|
"pallet-authorship/std",
|
|
"pallet-balances/std",
|
|
"pallet-external-validators-rewards/std",
|
|
"pallet-timestamp/std",
|
|
"pallet-evm/std",
|
|
"pallet-evm-chain-id/std",
|
|
"pallet-evm-precompile-proxy/std",
|
|
"pallet-migrations/std",
|
|
"pallet-safe-mode/std",
|
|
"pallet-tx-pause/std",
|
|
"pallet-treasury/std",
|
|
"polkadot-primitives/std",
|
|
"polkadot-runtime-common/std",
|
|
"precompile-utils/std",
|
|
"scale-info/std",
|
|
"snowbridge-outbound-queue-primitives/std",
|
|
"sp-core/std",
|
|
"sp-io/std",
|
|
"sp-runtime/std",
|
|
"sp-std/std",
|
|
"xcm/std",
|
|
]
|
|
|
|
runtime-benchmarks = [
|
|
"frame-support/runtime-benchmarks",
|
|
"pallet-migrations/runtime-benchmarks",
|
|
"pallet-safe-mode/runtime-benchmarks",
|
|
"pallet-tx-pause/runtime-benchmarks",
|
|
"polkadot-primitives/runtime-benchmarks",
|
|
"polkadot-runtime-common/runtime-benchmarks",
|
|
"sp-runtime/runtime-benchmarks",
|
|
]
|
|
|
|
try-runtime = [
|
|
"frame-support/try-runtime",
|
|
"pallet-migrations/try-runtime",
|
|
"pallet-safe-mode/try-runtime",
|
|
"pallet-tx-pause/try-runtime",
|
|
"pallet-timestamp/try-runtime",
|
|
"polkadot-runtime-common/try-runtime",
|
|
"sp-runtime/try-runtime",
|
|
]
|
|
|
|
# Set timing constants (e.g. session period) to faster versions to speed up testing.
|
|
fast-runtime = []
|