datahaven/contracts/deployments/state-diff.json

644 lines
556 KiB
JSON
Raw Permalink Normal View History

{
"31": {
"address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000007a2088a1bfc9d81c55368ae168c2c02570cb814f",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"35": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"address": "0x9d4454B023096f34B160D6B654540c56A1F81688",
"code": "0x608060405260043610610021575f3560e01c8063439fab911461008a5761003f565b3661003f5760405163858d70bd60e01b815260040160405180910390fd5b5f6100687f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b9050365f5f375f5f365f845af43d5f5f3e808015610084573d5ff35b3d5ffd5b005b348015610095575f5ffd5b506100886100a43660046100bc565b6040516282b42960e81b815260040160405180910390fd5b5f5f602083850312156100cd575f5ffd5b823567ffffffffffffffff8111156100e3575f5ffd5b8301601f810185136100f3575f5ffd5b803567ffffffffffffffff811115610109575f5ffd5b85602082840101111561011a575f5ffd5b602091909101959094509250505056fea264697066735822122042eb79be1c5c1bf83d453ef1bcf09a68daefc0a952ec517c315a6e1a8ca586e264736f6c634300081c003300",
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c233": "0x0000000000000000000000000000000000000000000000000000000000000112",
"0x59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f7": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f8": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0xdf92d0c198eb2c08351629e12172b863967bc505b5d2fa9fdf58f7b97e45495f": "0x000000000000000000000000beaafda2e17fc95e69dc06878039d274e0d2b21a",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x626b8e6b0a06114fed7a662a5b224ce123b32b155eef2616324caf5d9adeb4fa": "0x000000000000000000000000beaafda2e17fc95e69dc06878039d274e0d2b21a",
"0x59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f6": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000008f86403a4de0bb5791fa46b8e795c547942fe4cf",
"0x8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22f": "0x0000000000000000000003e8df077f5f72071df6e8b0a78071e496ba17b5ee0c",
"0x173ec3ea915b0ecad49b752ec145e745446de67d464520dc696504b3980fccda": "0x000000000000000000000000ac06641381166cf085281c45292147f833c622d7",
"0x24c230e7f96dea56c14d16c737ac85f999d444fd74b5f3f00170ca4640c77b8f": "0x000000000000000000000000beaafda2e17fc95e69dc06878039d274e0d2b21a",
"0x6bd2118f0148c813209325d23233ce0b7f1042ab160c97a1c605fdedff377204": "0x000000000000000000000000df077f5f72071df6e8b0a78071e496ba17b5ee0c",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x91839d9989408fbab863f2059ae80fee5216f58ec04fa3bffb021275bf7d4f24": "0x000000000000000000000000df077f5f72071df6e8b0a78071e496ba17b5ee0c",
"0x8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c230": "0x0000000000000000000000000000000100000000000000000000000000000001",
"0x8510b5c501cdfc97210e26067e7b0bee5b5cd43d52d902454bc5e2b62167df1d": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c231": "0x0000000000000000000000000000000000000000000000000000000000000001"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"6": {
"address": "0x0165878A594ca255338adfa4d48449f69242Eb8F",
"code": "0x730165878a594ca255338adfa4d48449f69242eb8f3014608060405260043610610034575f3560e01c8063439fab9114610038575b5f5ffd5b818015610043575f5ffd5b50610057610052366004610683565b610059565b005b5f6100827f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6001600160a01b0316036100a8576040516282b42960e81b815260040160405180910390fd5b7e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab5f6100d584840185610760565b8051835491925090839060ff1916600183818111156100f6576100f6610802565b02179055505f7f03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c1113145f1b60405161012b90610676565b908152602001604051809103905ff08015801561014a573d5f5f3e3d5ffd5b507f03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c1113145f908152600285016020908152604080832080546001600160a01b0319166001600160a01b0386169081179091558151608081018352848152808401859052808301859052606081019190915260018085528089019093529220825181549495509293909291839160ff19169083818111156101ea576101ea610802565b02179055506020828101518254604080860151610100600160881b031990921661010067ffffffffffffffff9485160267ffffffffffffffff60481b191617600160481b9390921692909202178355606093840151600193840180546001600160a01b0319166001600160a01b0392831617905581516080810183525f808252818501819052818401819052918716958101959095526002815287840190925290208251815491929091839160ff199091169083818111156102ae576102ae610802565b021790555060208201518154604080850151610100600160881b031990921661010067ffffffffffffffff9485160267ffffffffffffffff60481b191617600160481b9390921692909202178255606090920151600190910180546001600160a01b0319166001600160a01b03909216919091179055515f907f81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b799061035290610676565b908152602001604051809103905ff080158015610371573d5f5f3e3d5ffd5b507f81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b795f908152600286016020908152604080832080546001600160a01b0319166001600160a01b038616908117909155815160808101835284815280840185905280830185905260608101919091528151637061726160e01b81850152607d60e31b6024820152825180820360080181526028909101835280519084012084526001808a019093529220825181549495509293909291839160ff191690838181111561043f5761043f610802565b021790555060208201518154604084015167ffffffffffffffff908116600160481b0267ffffffffffffffff60481b19919093166101000216610100600160881b031990911617178155606090910151600190910180546001600160a01b039092166001600160a01b03199092169190911790555f6104db7f59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f690565b60408501518155602080860151600180840180546fffffffffffffffffffffffffffffffff19166001600160801b0393841617905560c08801516002909401939093557f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22f80546001600160a01b03969096166001600160c01b031990961695909517607d60a31b1790945560a08601517f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23155606086015160808701518516600160801b02908516177f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c2305560e08601517f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23380546101009889015190961690970270ffffffffffffffffffffffffffffffffff1990951660ff9091161793909317909455505f80527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e9052507f8510b5c501cdfc97210e26067e7b0bee5b5cd43d52d902454bc5e2b62167df1d805460ff19169091179055505050565b61032e8061081783390190565b5f5f60208385031215610694575f5ffd5b823567ffffffffffffffff8111156106aa575f5ffd5b8301601f810185136106ba575f5ffd5b803567ffffffffffffffff8111156106d0575f5ffd5b8560208284010111156106e1575f5ffd5b6020919091019590945092505050565b604051610120810167ffffffffffffffff8111828210171561072157634e487b7160e01b5f52604160045260245ffd5b60405290565b803560028110610735575f5ffd5b919050565b80356001600160801b0381168114610735575f5ffd5b803560ff81168114610735575f5ffd5b5f610120828403128015610772575f5ffd5b5061077b6106f1565b61078483610727565b81526107926020840161073a565b6020820152604083810135908201526107ad6060840161073a565b60608201526107be6080840161073a565b608082015260a0838101359082015260c080840135908201526107e360e08401610750565b60e08201526107f5610100840161073a565b6101
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {}
},
"11": {
"address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
"code": "0x739fe46736679d2d9a65f0992f2272de9f3c7fa6e03014608060405260043610610055575f3560e01c80634a283cd91461005957806376b1d08f1461007a578063fd10ebe514610099578063fe65a388146100d2575b5f5ffd5b818015610064575f5ffd5b50610078610073366004610aed565b6100f1565b005b610082600881565b60405160ff90911681526020015b60405180910390f35b7e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96b0546040516001600160401b039091168152602001610090565b8180156100dd575f5ffd5b506100786100ec366004610b67565b610202565b5f8181527e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ad60205260409020547e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab906001600160a01b0316806101e4578260405161015990610ae0565b908152602001604051809103905ff080158015610178573d5f5f3e3d5ffd5b505f84815260028401602090815260409182902080546001600160a01b0319166001600160a01b0385169081179091558251878152918201529192507f7c96960a1ebd8cc753b10836ea25bd7c9c4f8cd43590db1e8b3648cb0ec4cc89910160405180910390a1505050565b604051630d82532d60e21b815260040160405180910390fd5b505050565b61028d336102448a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061029792505050565b61024e888a610c98565b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152508992508891506102bf9050565b5050505050505050565b6040805180820182525f80825260606020928301528251808401909352825281019190915290565b6102c761056c565b6001600160801b033411156102ef576040516330e972ad60e01b815260040160405180910390fd5b6102f98183610d8e565b6001600160801b03163410156103225760405163044044a560e21b815260040160405180910390fd5b5f61034c7f81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b796105c5565b90506103616001600160a01b0382163461061d565b8451600810156103845760405163df8153c760e01b815260040160405180910390fd5b5f85516001600160401b0381111561039e5761039e610c54565b6040519080825280602002602001820160405280156103e357816020015b604080518082019091525f8152606060208201528152602001906001900390816103bc5790505b5090505f5b86518110156104385761041387828151811061040657610406610dad565b6020026020010151610646565b82828151811061042557610425610dad565b60209081029190910101526001016103e8565b507e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96b0547e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab9061048f906001600160401b03166001610dc1565b816005015f6101000a8154816001600160401b0302191690836001600160401b031602179055505f6040518060e001604052808b6001600160a01b031681526020018481526020018a81526020018881526020018688346104f09190610de0565b6104fa9190610de0565b6001600160801b03908116825288811660208301528716604091820152600584015490519192507f550e2067494b1736ea5573f2d19cdc0ac95b410fff161bf16f11c6229655ec9c91610558916001600160401b0316908490610e56565b60405180910390a150505050505050505050565b7e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab80545f9060ff1660018111156105a4576105a4610f6b565b146105c257604051633ac4266d60e11b815260040160405180910390fd5b50565b5f8181527e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ad60205260409020546001600160a01b0316806106185760405163d3227c9b60e01b815260040160405180910390fd5b919050565b5f5f5f5f5f85875af19050806101fd57604051633d2cec6f60e21b815260040160405180910390fd5b604080518082019091525f815260606020820152602082015160ff165f819003610698575f5f848060200190518101906106809190610f7f565b925092505061068f82826106b1565b95945050505050565b604051636448d6e960e11b815260040160405180910390fd5b604080518082019091525f8152606060208201525f7f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e6001600160a01b0385165f90815260208290526040902080549192509060ff166107245760405163259ba1ad60e01b815260040160405180910390fd5b5f846001600160801b03161161074d5760405163162908e360e11b815260040160405180910390fd5b6001810154610783576001820154610770906001600160a01b0316863387610821565b61077a858561089d565b9250505061081b565b61078c81610919565b1561080257604051632770a7eb60e21b81523360048201526001600160801b03851660248201526001600160a01b03861690639dc29fac906044015f604051808303815f87803b1580156107de575f5ffd5b505af11580156107f0573d5f5f3e3d5ffd5b5050505061077a
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {}
},
"17": {
"address": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
"code": "0x608060405234801561000f575f5ffd5b506004361061026b575f3560e01c80637ecebe001161014b578063ca8aa7c7116100bf578063f2fde38b11610084578063f2fde38b1461062f578063f3b4a00014610642578063f698da251461064c578063fabc1cbc14610654578063fd98042314610667578063fe243a171461067a575f5ffd5b8063ca8aa7c71461059b578063cbc2bd62146105c2578063de44acb6146105d5578063df5cf723146105f5578063e7a050aa1461061c575f5ffd5b80638da5cb5b116101105780638da5cb5b1461052b57806394f649dd1461053c578063967fc0d21461054f5780639ac01d6114610562578063b5d8b5b814610575578063c665670214610588575f5ffd5b80637ecebe001461047f578063829fca731461049e578063886f1195146104b157806388c10299146104f05780638b8aac3c14610503575f5ffd5b806350ff7225116101e25780635de08ff2116101a75780635de08ff2146103fc578063663c1de41461040f578063715018a614610431578063724af4231461043957806376fb162b1461044c5780637def15641461045f575f5ffd5b806350ff72251461037c57806354fd4d50146103a4578063595c6a67146103b95780635ac86ab7146103c15780635c975abb146103f4575f5ffd5b806332e89ace1161023357806332e89ace146102f157806336a8c500146103045780633f292b081461031a5780633fb99ca51461032f57806348825e94146103425780634b6d5d6e14610369575f5ffd5b8063136439dd1461026f5780631794bb3c146102845780632d44def6146102975780632eae418c146102bd57806331f8fb4c146102d0575b5f5ffd5b61028261027d366004612e33565b6106a4565b005b610282610292366004612e5e565b6106de565b6102aa6102a5366004612eb2565b610804565b6040519081526020015b60405180910390f35b6102826102cb366004612ef0565b6108b6565b6102e36102de366004612f3e565b610982565b6040516102b4929190612fda565b6102aa6102ff36600461304b565b610b10565b61030c610b95565b6040516102b4929190613125565b610322610cb0565b6040516102b4919061317b565b61028261033d3660046131d8565b610d98565b6102aa7f4337f82d142e41f2a8c10547cd8c859bddb92262a61058e77842e24d9dea922481565b61028261037736600461321c565b610ee0565b61038f61038a366004612e5e565b61102d565b604080519283526020830191909152016102b4565b6103ac6110a1565b6040516102b49190613265565b6102826110d1565b6103e46103cf366004613277565b609854600160ff9092169190911b9081161490565b60405190151581526020016102b4565b6098546102aa565b61028261040a366004613297565b6110e5565b6103e461041d36600461321c565b60d16020525f908152604090205460ff1681565b610282611238565b6102aa610447366004612e5e565b611249565b6102aa61045a366004612eb2565b6112a6565b61047261046d366004613306565b6112f5565b6040516102b49190613320565b6102aa61048d36600461321c565b60ca6020525f908152604090205481565b6102aa6104ac366004612f3e565b611327565b6104d87f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b6040516001600160a01b0390911681526020016102b4565b6104726104fe366004612f3e565b611361565b6102aa61051136600461321c565b6001600160a01b03165f90815260ce602052604090205490565b6033546001600160a01b03166104d8565b6102e361054a36600461321c565b611498565b60cb546104d8906001600160a01b031681565b6102aa610570366004613332565b61160f565b610282610583366004613297565b6116a0565b61028261059636600461321c565b6117e7565b6104d87f00000000000000000000000068b1d87f95878fe05b998f19b66f4baba5de1aed81565b6104d86105d0366004613393565b61180a565b6105e86105e336600461321c565b61183e565b6040516102b491906133bd565b6104d87f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd8281565b6102aa61062a366004612e5e565b6118b1565b61028261063d36600461321c565b6118e4565b6104d8620e16e481565b6102aa61195a565b610282610662366004612e33565b611a13565b6102aa61067536600461321c565b611a80565b6102aa6106883660046133cf565b60cd60209081525f928352604080842090915290825290205481565b6106ac611a95565b60985481811681146106d15760405163c61dca5d60e01b815260040160405180910390fd5b6106da82611b38565b5050565b5f54610100900460ff16158080156106fc57505f54600160ff909116105b806107155750303b15801561071557505f5460ff166001145b61077d5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff19166001179055801561079e575f805461ff0019166101001790555b6107a782611b38565b6107b084611b75565b6107b983611bc6565b80156107fe575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020016040
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"18": {
"address": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf",
"code": "0x608060405234801561000f575f5ffd5b50600436106100f0575f3560e01c806366ae69a011610093578063a77cf3d211610063578063a77cf3d214610238578063ad209a9b1461024b578063bb51f1eb14610272578063df0dd0d514610285575f5ffd5b806366ae69a0146101b05780636f55bd32146101db5780638ab81d1314610202578063a401662b14610215575f5ffd5b806341c9634e116100ce57806341c9634e1461013e578063591d99ee146101545780635da57fe91461017b578063623b223d1461019b575f5ffd5b80630a7c8faa146100f45780632cdea7171461011e5780633666751314610136575b5f5ffd5b610100610dad60f31b81565b6040516001600160f01b031990911681526020015b60405180910390f35b61012661030f565b60405161011594939291906124a2565b610126610390565b6101465f5481565b604051908152602001610115565b6101467f000000000000000000000000000000000000000000000000000000000000000481565b61018e610189366004612572565b61040f565b60405161011591906125b9565b6101ae6101a9366004612621565b610447565b005b6001546101c3906001600160401b031681565b6040516001600160401b039091168152602001610115565b6101467f000000000000000000000000000000000000000000000000000000000000000281565b61018e610210366004612702565b6107d9565b610228610223366004612749565b6108b0565b6040519015158152602001610115565b6101ae610246366004612797565b6108bf565b6101467f000000000000000000000000000000000000000000000000000000000000001881565b6101ae6102803660046127ae565b610a37565b6102d3610293366004612797565b600a6020525f90815260409020805460018201546002909201546001600160401b0382169263ffffffff600160401b8404811693600160601b9004169185565b604080516001600160401b0396909616865263ffffffff948516602087015292909316918401919091526060830152608082015260a001610115565b6002805460035460408051600480546060602082028401810185529383018181526001600160801b0380881698600160801b9098041696948492849184018282801561037857602002820191905f5260205f20905b815481526020019060010190808311610364575b50505050508152602001600182015481525050905084565b6006805460075460408051600880546060602082028401810185529383018181526001600160801b0380881698600160801b9098041696948492849184018282801561037857602002820191905f5260205f20908154815260200190600101908083116103645750505050508152602001600182015481525050905084565b60608282101561043257604051635c85a0e760e01b815260040160405180910390fd5b61043d848484610ea1565b90505b9392505050565b5f6104518a610f2b565b8051906020012090505f61046e33835f9182526020526040902090565b905061047c818c8c8c61101d565b5f5f90505f6002905060065f015f9054906101000a90046001600160801b03166001600160801b03168d60200160208101906104b89190612843565b6001600160401b0316036104d2575060019050600661052c565b60025f015f9054906101000a90046001600160801b03166001600160801b03168d60200160208101906105059190612843565b6001600160401b03161461052c57604051636033c4fd60e11b815260040160405180910390fd5b61053b84848e8e858f8f61110c565b5f6105458e6112c2565b905082156106d657600654610564906001600160801b0316600161287d565b6001600160801b031661057d60808b0160608c01612843565b6001600160401b0316146105a3576040516263964160e91b815260040160405180910390fd5b5f6105c0826105b18c6113f4565b805190602001208b8b8b6114a8565b9050806105e05760405163128597bb60e01b815260040160405180910390fd5b60068054600160801b8082046001600160801b0390811690910291161760029081556007546003556008805460049061061c908290849061240d565b5060019182015491015550610639905060808b0160608c01612843565b600680546001600160801b0319166001600160401b039290921691909117905561066960a08b0160808c0161289c565b600680546001600160801b031663ffffffff92909216600160801b0291909117905560a08a0180356007556106b0906106a59060808d0161289c565b63ffffffff16611517565b805180516008916106c691839160200190612455565b5060208201518160010155905050505b5f8190556106e760208f018f61289c565b63ffffffff1660015f6101000a8154816001600160401b0302191690836001600160401b03160217905550600a5f8581526020019081526020015f205f5f82015f6101000a8154906001600160401b0302191690555f820160086101000a81549063ffffffff02191690555f8201600c6101000a81549063ffffffff0219169055600182015f9055600282015f905550507fd95fe1258d152dc91c81b09380498adc76ed36a6079bcb2ed31eff622ae2d0f1818f5f0160208101906107ac919061289c565b6040805192835263ffffffff90911660208301520160405180910390a15050505050505050505050505050565b60605f600a5f6107f233885f9182526020526040902090565b8152602001
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000200000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000008": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000009": "0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000007": "0x697ea2a8fe5b03468548a7a413424a6292ab44a82a6f5cc594c3fa7dda7ce402",
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000200000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x697ea2a8fe5b03468548a7a413424a6292ab44a82a6f5cc594c3fa7dda7ce402"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"23": {
"address": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
"code": "0x6080604052600436106101f1575f3560e01c8063886f119511610108578063a6a509be1161009d578063f2fde38b1161006d578063f2fde38b1461062e578063f5d4fed31461064d578063f6848d2414610662578063fabc1cbc1461069b578063fe243a17146106ba575f5ffd5b8063a6a509be1461059c578063cd6dc687146105b1578063d48e8894146105d0578063ea4d3c9b146105fb575f5ffd5b80639ba06275116100d85780639ba062751461050b578063a1ca780b1461053f578063a38406a31461055e578063a3d75e091461057d575f5ffd5b8063886f1195146104815780638da5cb5b146104b45780639104c319146104d15780639b4e4634146104f8575f5ffd5b8063595c6a67116101895780635c975abb116101595780635c975abb146103e9578063715018a614610407578063724af4231461041b57806374cdd7981461043a57806384d810621461046d575f5ffd5b8063595c6a6714610358578063595edbcb1461036c5780635a26fbf41461038b5780635ac86ab7146103aa575f5ffd5b80632eae418c116101c45780632eae418c146102c55780633fb99ca5146102e457806350ff72251461030357806354fd4d5014610337575f5ffd5b80630d1e9de1146101f5578063136439dd146102165780632704351a14610235578063292b7b2b1461027a575b5f5ffd5b348015610200575f5ffd5b5061021461020f366004611d7e565b6106d9565b005b348015610221575f5ffd5b50610214610230366004611d99565b610736565b348015610240575f5ffd5b50609f5461025c90600160a01b900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b348015610285575f5ffd5b506102ad7f0000000000000000000000004ed7c70f96b99c776995fb64377f0d4ab3b0e1c181565b6040516001600160a01b039091168152602001610271565b3480156102d0575f5ffd5b506102146102df366004611db0565b610770565b3480156102ef575f5ffd5b506102146102fe366004611dfe565b6109c9565b34801561030e575f5ffd5b5061032261031d366004611e48565b610a6d565b60408051928352602083019190915201610271565b348015610342575f5ffd5b5061034b610b1d565b6040516102719190611e86565b348015610363575f5ffd5b50610214610b4d565b348015610377575f5ffd5b50609f546102ad906001600160a01b031681565b348015610396575f5ffd5b506102146103a5366004611ebb565b610b61565b3480156103b5575f5ffd5b506103d96103c4366004611ee2565b606654600160ff9092169190911b9081161490565b6040519015158152602001610271565b3480156103f4575f5ffd5b506066545b604051908152602001610271565b348015610412575f5ffd5b50610214610be8565b348015610426575f5ffd5b506103f9610435366004611e48565b610bf9565b348015610445575f5ffd5b506102ad7f000000000000000000000000c7f2cf4845c6db0e1a1e91ed41bcd0fcc1b0e14181565b348015610478575f5ffd5b506102ad610d3e565b34801561048c575f5ffd5b506102ad7f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b3480156104bf575f5ffd5b506033546001600160a01b03166102ad565b3480156104dc575f5ffd5b506102ad73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b610214610506366004611f40565b610da1565b348015610516575f5ffd5b506102ad610525366004611d7e565b60986020525f90815260409020546001600160a01b031681565b34801561054a575f5ffd5b50610214610559366004611fb3565b610e52565b348015610569575f5ffd5b506102ad610578366004611d7e565b611073565b348015610588575f5ffd5b5061025c610597366004611d7e565b611144565b3480156105a7575f5ffd5b506103f960995481565b3480156105bc575f5ffd5b506102146105cb366004611fe5565b6111a4565b3480156105db575f5ffd5b506103f96105ea366004611d7e565b609b6020525f908152604090205481565b348015610606575f5ffd5b506102ad7f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd8281565b348015610639575f5ffd5b50610214610648366004611d7e565b6112c0565b348015610658575f5ffd5b506103f9609e5481565b34801561066d575f5ffd5b506103d961067c366004611d7e565b6001600160a01b039081165f9081526098602052604090205416151590565b3480156106a6575f5ffd5b506102146106b5366004611d99565b611339565b3480156106c5575f5ffd5b506103f96106d436600461200f565b6113a6565b6106e1611426565b609f80546001600160a01b0319166001600160a01b0383169081179091556040519081527f7025c71a9fe60d709e71b377dc5f7c72c3e1d8539f8022574254e736ceca01e5906020015b60405180910390a150565b61073e611480565b60665481811681146107635760405163c61dca5d60e01b815260040160405180910390fd5b61076c82611523565b5050565b336001600160a01b037f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd8216146107b95760405163f739589b60e01b815260040160405180910390fd5b6107c1611560565b6001600160a01b03831673beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0146107fe57604051632711b74d60e11b81526004016040
"storage": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
}
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
},
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"21": {
"address": "0x0000F90827F1C53a10cb7A02335B175320002935",
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500",
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 09:31:44 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000037": "0xc3bb8872bda7db0f4b06fd55ba82f6080cec0d11a9c17a0188d023438bef4351",
"0x000000000000000000000000000000000000000000000000000000000000002c": "0xa6c63845b6938d5eda2d1dec336f8e349a70c281df7b3dbbbe9da7e4ea3ecfef",
"0x0000000000000000000000000000000000000000000000000000000000000009": "0xdf927b470441e9b0bb341003f8a713a006eb1a013bf5e8c6bf16ce5957a92db9",
"0x000000000000000000000000000000000000000000000000000000000000004b": "0xe9435bc1e1264f5c3523c8469e0bf285f91814e904324a3a794b595dc5d85c89",
"0x000000000000000000000000000000000000000000000000000000000000003a": "0x68e6217d9cb26f8faafb48c45bd186e3ceac9ee5e57a34d20b91e1217c5e72e7",
"0x000000000000000000000000000000000000000000000000000000000000003f": "0x0b6c4990acf4aa3e9819cd9030b9c6d03e56004935e9f7dc946a076922a8d939",
"0x000000000000000000000000000000000000000000000000000000000000004f": "0xfa10c3e995cfbfd91e800324bf3246862a803b6b81d48d25f99f3a2ba06e89b1",
"0x0000000000000000000000000000000000000000000000000000000000000039": "0xc4a1793c6260c9c233e3492a9112601e1b26e68ee2df8d79885af1bdf81733a7",
"0x000000000000000000000000000000000000000000000000000000000000002b": "0x45b292dfaabd85187bab5c936c5c700b93f21b40995dd81d3476b532f3dcd21a",
"0x0000000000000000000000000000000000000000000000000000000000000025": "0x9223fb61686b28946fb887f56a0314de582328627024b49983817e4cf977f0a4",
"0x0000000000000000000000000000000000000000000000000000000000000027": "0x42cec78b6d6017a5835005a74f5af73d7d31419445e1d9c77ae436ac28404385",
"0x000000000000000000000000000000000000000000000000000000000000001b": "0x7e28dd60c2c050e8a724fbfa9674b2e9aab7deec8e59c4c4c88f61977a223ca1",
"0x0000000000000000000000000000000000000000000000000000000000000043": "0xb6438aa1df5dfcb4b819c1fa70ab94bf61ea393f0c215291d8e8ec5c03fcad2f",
"0x000000000000000000000000000000000000000000000000000000000000000e": "0x2bb3012e8cd807e33ad343ba0e72557764b3a53348f34cb4fbed973509952d39",
"0x0000000000000000000000000000000000000000000000000000000000000020": "0xb9d3824acedfaee57e5f848fe5751b24b3663ea1fddb194ead85ec9335b5a178",
"0x0000000000000000000000000000000000000000000000000000000000000008": "0x11ef59f70bba4837eb07e7c335f0acac038dcaa7c51f89e3313e51e1d4cad695",
"0x0000000000000000000000000000000000000000000000000000000000000022": "0xb64c36b04c4baff51851ea21c9deb0a33d2557f645b9ea93ec309678b6f694f7",
"0x000000000000000000000000000000000000000000000000000000000000003c": "0xa35ad2b20c886f59ab003696c6d38d75d43326e6402508fdf84ab1a98109a0b3",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x67d1ebf75f5f47f081df7addace82fa660bdc61c910297e1b8a5412519da721a",
"0x000000000000000000000000000000000000000000000000000000000000004d": "0x50770ebd9835f5a5e81a1568cb28355fc988740c55e077b552ada311eadc2753",
"0x0000000000000000000000000000000000000000000000000000000000000014": "0x33d27532ee2fd56a4cd374bd4ab00b4f325af308679e197fad6327201c61da11",
"0x000000000000000000000000000000000000000000000000000000000000000b": "0x525d57a135918592443b80b4e235aad71dc06017fc63886fd9b68f5abc6196cb",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0xb675be34b973879fbc9c761f923acae7b79c930d1c6a7f1773f27489f1e5862c",
"0x000000000000000000000000000000000000000000000000000000000000001d": "0x133011abf04da1ff0716596fbc01d2b9a7ef7af12f71caae720ce9023eae745b",
"0x0000000000000000000000000000000000000000000000000000000000000032": "0xcca603fd6d9f7b4cd9e77be7b67542c96ec0ebf9df7d34404ad151acc148145b",
"0x0000000000000000000000000000000000000000000000000000000000000024": "0x55c4ea97df6bb36698641225bfab62d6ea6e2b4f030008a897c3611d563e8e97",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0xb9cb6d489dd4c622f3ed0fd09ab20b6f97bfe330123ba9e804782e26a1cf0164",
"0x0000000000000000000000000000000000000000000000000000000000000026": "0xe582ba0051287d32856e2f46a40f442f798371b062be1e1a1b8391cc6ff0ebe3",
"0x0000000000000000000000000000000000000000000000000000000000000010": "0x42b9ade2549af9f5d2d49e6f0ac65f08cbd80ba993c57e64eba067305fac1fd4",
"0x0000000000000000000000000000000000000000000000000000000000000048": "0x14b6402281353f2427cf5b39fae38475c058470aa3fb63a762a3c50f38383e09",
"0x000000000000000000000000000000000000000000000000000000000000004e": "0x5b67128f266a8ff61df9899f841b158c73fd3ad76864159fb75b0ee3d4790f0f",
"0x0000000000000000000000000000000000000000000000000000000000000036": "0x9c7c0dda7c4183c6745b924eb0f517382360c50d74456d1a4649fe50fc207c32",
"0x0000000000000000000000000000000000000000000000000000000000000035": "0xeed53ff9c2df82c0ab1ca70cd485624c112ed518e7136b81fa14e51783a3ad97",
"0x0000000000000000000000000000000000000000000000000000000000000040": "0xe30c0499641a18c3d90e8613d11657e7df3d46cfa8029254ea43def5ca84c733",
"0x0000000000000000000000000000000000000000000000000000000000000013": "0x450429691f62225d171d9e679d1e142fe904e0b5af3423543d61aa51a07120e0",
"0x0000000000000000000000000000000000000000000000000000000000000018": "0xf43609b4cecdd1f155e9eef93da55b8dc384708805eed7290d325cae235fafbd",
"0x0000000000000000000000000000000000000000000000000000000000000049": "0x0381f6ad48d40ae7b5aeb1259618b473f7e16ba50b17949d0fdc9c620d1ec0bf",
"0x0000000000000000000000000000000000000000000000000000000000000042": "0xa294f1cad488cffd4c2566722a81f74bce9fcae6ef394dd24e9cacb2ec5a048f",
"0x000000000000000000000000000000000000000000000000000000000000004c": "0xf5818d6cd1bd45fc1c2441a4bfc35a58604339667b05bbc6da8bd5ab9db71317",
"0x000000000000000000000000000000000000000000000000000000000000000f": "0x7e069409fc683a5df1d25a12b1275f5cbeff1e786d01325fb86aa8d9757b02e6",
"0x0000000000000000000000000000000000000000000000000000000000000011": "0xf1a2e06944fb1db2e57a1e55b446e5192ad86430e1c80677f63cf45d23504ea1",
"0x0000000000000000000000000000000000000000000000000000000000000023": "0xe93ef10e01c6f3900fab1babdbeccb9a01877d0f8246031ef60803fa2f7ae6d9",
"0x000000000000000000000000000000000000000000000000000000000000003b": "0x240e016ec837819b047dd284c48fc12e2808f764ff30740818ee771da33af80e",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x8119a4b9fa45dceb04b3575e86a4bb54300f760490de297d64f5cd9e6d506e46",
"0x000000000000000000000000000000000000000000000000000000000000004a": "0x0b40cf694d97b3fcc90410812e9b7e4db44fe72bdbbebdd059115ebb1bc0a8ca",
"0x000000000000000000000000000000000000000000000000000000000000001a": "0x7d06c044981f5dc070f191e59a9edb6f5051ded8788c61506a272dc53be30210",
"0x000000000000000000000000000000000000000000000000000000000000001e": "0x3f10b39e173f681deaf93e385476dd9176985c50b8fcd7dad752f1b0935dba17",
"0x0000000000000000000000000000000000000000000000000000000000000006": "0xdb8e0005bf6870a724faa3674961c4bd28e1fb1107eb61fda12a388264861dad",
"0x0000000000000000000000000000000000000000000000000000000000000005": "0xb924a10741211041dad0db48cd07069d47d515474da53c6e4dbdcfa72359178b",
"0x000000000000000000000000000000000000000000000000000000000000002e": "0xf2fcb15a23df6d8d3f5a9341f5d4752ac95fe4e87a1d885de8c2a2a7c2dd7eb6",
"0x000000000000000000000000000000000000000000000000000000000000003d": "0x2b647074e74b957163286f8e0e1dd089e8c8a8106a6ee39e4d4c514b0852e5a4",
"0x000000000000000000000000000000000000000000000000000000000000001f": "0x90a60ccb29c4d17a653f51c9f0b5f0d481a0bef932acb36a7139086734c689a9",
"0x0000000000000000000000000000000000000000000000000000000000000046": "0x347e6ec499679ad04b4d13d37561ca6947801039ac63e5d5e319acf38f633792",
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x851c064d6b0ac5bfd088c8c7278306ddd5809e6195d184e216ba7c831aaff84d",
"0x0000000000000000000000000000000000000000000000000000000000000016": "0x25ce1231e8697971cbd70714f920b9d0ae804a5fa2957d0bcba1b16a501fd73d",
"0x0000000000000000000000000000000000000000000000000000000000000030": "0xbc068c88e0f7a6132b0cf00d9bc15a075a17a28230dada60778576b429bb2592",
"0x0000000000000000000000000000000000000000000000000000000000000050": "0xad39e0001a1fb8ceb5d8458c49d52bf421ed1e297c8e970dc73d828be28601ae",
"0x000000000000000000000000000000000000000000000000000000000000000d": "0x8196546872f863b13351af76fc3155694b9787d49865eaaee407dbdab4748e8d",
"0x0000000000000000000000000000000000000000000000000000000000000028": "0x3081e0e65bce74411ee16749567cab19305b2f343563f2d2961426dbea422984",
"0x0000000000000000000000000000000000000000000000000000000000000044": "0x6ec479fb702798f0210876ad3269e2e2317e1e32d6ec2c2246c8d50034631ae3",
"0x0000000000000000000000000000000000000000000000000000000000000029": "0x5eafbd4495d4087e7983f84d8c08695ac9965b52e25fa0224c2461d32d020dfa",
"0x0000000000000000000000000000000000000000000000000000000000000012": "0x4cd242658836718ee87a28a0374ba22540413e0efab35ed2399144216c19e1fa",
"0x000000000000000000000000000000000000000000000000000000000000002d": "0xb530d77d1e4f68406bd83bdd5abdbd5f03c2088b0543863f3fe1bd2c7a0d49b2",
"0x000000000000000000000000000000000000000000000000000000000000000c": "0x638a51000f26ca258e643bcd4ff8cd3635bec7815552717c483dc527e2f9020e",
"0x0000000000000000000000000000000000000000000000000000000000000004": "0x5d1bd5cc18bc20a3067f52970c05757d459e8d432aba00af7edbeb69e3c1b590",
"0x0000000000000000000000000000000000000000000000000000000000000041": "0xc8d8d8fdca80e01c77218762953384c430bb7455261d6039ba1c04ee1ad5b475",
"0x0000000000000000000000000000000000000000000000000000000000000047": "0x9d1734157352dc74f68091b6721d57bedee35dfddb5454e2b4653a5a5bf43f0c",
"0x000000000000000000000000000000000000000000000000000000000000000a": "0x0e45df1f34f9363627b5166ed572f2e6a001dda17d604a91b4a55470b3c6577c",
"0x000000000000000000000000000000000000000000000000000000000000002a": "0x8acfcd80b2c1ac3b31a3ce7ce8e969319f123de5415f6b9f01bfcf2df52cea12",
"0x000000000000000000000000000000000000000000000000000000000000003e": "0x98d0fd9437e48a97c2cd2c5a8183a3a359be078667f4247ea88cb9495266a641",
"0x0000000000000000000000000000000000000000000000000000000000000015": "0xf63f19d151c959ab16a2d8e81123e545deed0994820d030197b41e7f13927ffb",
"0x000000000000000000000000000000000000000000000000000000000000001c": "0x70ea32adeb4614840aa8c39fb13839a48d7d12b01c54c25485d8c4e8e4ec3cf2",
"0x0000000000000000000000000000000000000000000000000000000000000034": "0xaa38f84c99084ddb0947ea741e0a3322dfeaf43abb7532b318ea98d5a038ee3e",
"0x0000000000000000000000000000000000000000000000000000000000000021": "0x22ecc88f1f548073a37916e06307d04721cf24fad122028c67715fbc279ea596",
"0x0000000000000000000000000000000000000000000000000000000000000045": "0x6d08016389a28e0ffa69998ca02318cb8f8f6006e6a70a62af78c28f1250c4bf",
"0x0000000000000000000000000000000000000000000000000000000000000017": "0xe107aa9ce11a82e8beff228ee791226960534ce1332e4d3a7f238372661151b1",
"0x0000000000000000000000000000000000000000000000000000000000000031": "0x2bc2e3e448f540fa10fb8dcd1db718390202419aab1b976591317fd823b496db",
"0x0000000000000000000000000000000000000000000000000000000000000007": "0xf20187930f3d8be01f805f82c21eee8bef70feccab4ff64dbdd1515634d3f40b",
"0x0000000000000000000000000000000000000000000000000000000000000019": "0x0a0373316bd2270e04e3868cd2f3c8a32848ce4d6d218d0131d7ec1b16d082ff",
"0x000000000000000000000000000000000000000000000000000000000000002f": "0xd7c781db6525b6d4351d33d4e0b10792b6c5d1dd55b898a4664e468818935a59",
"0x0000000000000000000000000000000000000000000000000000000000000038": "0xa192567528f3a09e48fbee10d9805f118d2ca55254efa2adb6e8bc46f65dc1f7"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"22": {
"address": "0x95401dc811bb5740090279Ba06cfA8fcF6113778",
"code": "0x608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c806342966c681161008857806395d89b411161006357806395d89b41146101a7578063a457c2d7146101af578063a9059cbb146101c2578063dd62ed3e146101d5575f5ffd5b806342966c681461015757806370a082311461016c57806379cc679014610194575f5ffd5b806306fdde03146100cf578063095ea7b3146100ed57806318160ddd1461011057806323b872dd14610122578063313ce567146101355780633950935114610144575b5f5ffd5b6100d76101e8565b6040516100e49190610826565b60405180910390f35b6101006100fb366004610876565b610278565b60405190151581526020016100e4565b6002545b6040519081526020016100e4565b61010061013036600461089e565b610291565b604051601281526020016100e4565b610100610152366004610876565b6102b4565b61016a6101653660046108d8565b6102d5565b005b61011461017a3660046108ef565b6001600160a01b03165f9081526020819052604090205490565b61016a6101a2366004610876565b6102e2565b6100d76102fb565b6101006101bd366004610876565b61030a565b6101006101d0366004610876565b610389565b6101146101e336600461090f565b610396565b6060600380546101f790610940565b80601f016020809104026020016040519081016040528092919081815260200182805461022390610940565b801561026e5780601f106102455761010080835404028352916020019161026e565b820191905f5260205f20905b81548152906001019060200180831161025157829003601f168201915b5050505050905090565b5f336102858185856103c0565b60019150505b92915050565b5f3361029e8582856104e4565b6102a985858561055c565b506001949350505050565b5f336102858185856102c68383610396565b6102d09190610978565b6103c0565b6102df33826106fe565b50565b6102ed8233836104e4565b6102f782826106fe565b5050565b6060600480546101f790610940565b5f33816103178286610396565b90508381101561037c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102a982868684036103c0565b5f3361028581858561055c565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166104225760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610373565b6001600160a01b0382166104835760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610373565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b5f6104ef8484610396565b90505f19811461055657818110156105495760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610373565b61055684848484036103c0565b50505050565b6001600160a01b0383166105c05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610373565b6001600160a01b0382166106225760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610373565b6001600160a01b0383165f90815260208190526040902054818110156106995760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610373565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610556565b6001600160a01b03821661075e5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610373565b6001600160a01b0382165f90815260208190526040902054818110156107d15760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610373565b6001600160a01b0383165f818152602081815260408083208686039055600280548790039055
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000004": "0x5445535400000000000000000000000000000000000000000000000000000008",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000",
"0x14e04a66bf74771820a7400ff6cf065175b3d7eb25805a5bd1633b161af5d101": "0x0000000000000000000000000000000000000000000069e10de76676d0800000",
"0xd19bcde47e0ffe1c643525c3cff070e266ec404a07f499b41fcbc480ff16fff7": "0x0000000000000000000000000000000000000000000069e10de76676d0800000",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x54657374546f6b656e0000000000000000000000000000000000000000000012"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"1": {
"address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6",
"code": "0x732279b7a0a67db372996a5fab50d91eaa73d2ebe63014608060405260043610610034575f3560e01c8063a3499c7314610038575b5f5ffd5b818015610043575f5ffd5b50610057610052366004610230565b610059565b005b61006b836001600160a01b03166101b3565b610088576040516303777f6960e51b815260040160405180910390fd5b81836001600160a01b03163f146100b2576040516323e13ec960e21b815260040160405180910390fd5b6100da837f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55565b5f5f846001600160a01b0316836040516024016100f79190610309565b60408051601f198184030181529181526020820180516001600160e01b031663439fab9160e01b1790525161012c919061033e565b5f60405180830381855af49150503d805f8114610164576040519150601f19603f3d011682016040523d82523d5f602084013e610169565b606091505b509150915061017882826101fd565b506040516001600160a01b038616907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a25050505050565b5f6001600160a01b0382163f158015906101f757507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4706001600160a01b0383163f14155b92915050565b6060821561020c5750806101f7565b8151156100345781518083602001fd5b634e487b7160e01b5f52604160045260245ffd5b5f5f5f60608486031215610242575f5ffd5b83356001600160a01b0381168114610258575f5ffd5b925060208401359150604084013567ffffffffffffffff81111561027a575f5ffd5b8401601f8101861361028a575f5ffd5b803567ffffffffffffffff8111156102a4576102a461021c565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156102d3576102d361021c565b6040528181528282016020018810156102ea575f5ffd5b816020840160208301375f602083830101528093505050509250925092565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204f9eff25a489952d6ace57dae13f4311e26f51d13db35754950e1446aa6c4c3264736f6c634300081c003300",
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
"storage": {}
},
"15": {
"address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
"code": "0x608060405260043610610079575f3560e01c80639623609d1161004c5780639623609d1461010957806399a88ec41461011c578063f2fde38b1461013b578063f3b7dead1461015a575f5ffd5b8063204e1c7a1461007d578063715018a6146100b85780637eff275e146100ce5780638da5cb5b146100ed575b5f5ffd5b348015610088575f5ffd5b5061009c610097366004610479565b610179565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c3575f5ffd5b506100cc610204565b005b3480156100d9575f5ffd5b506100cc6100e836600461049b565b610217565b3480156100f8575f5ffd5b505f546001600160a01b031661009c565b6100cc6101173660046104e6565b61027a565b348015610127575f5ffd5b506100cc61013636600461049b565b6102e5565b348015610146575f5ffd5b506100cc610155366004610479565b61031b565b348015610165575f5ffd5b5061009c610174366004610479565b610399565b5f5f5f836001600160a01b031660405161019d90635c60da1b60e01b815260040190565b5f60405180830381855afa9150503d805f81146101d5576040519150601f19603f3d011682016040523d82523d5f602084013e6101da565b606091505b5091509150816101e8575f5ffd5b808060200190518101906101fc91906105bd565b949350505050565b61020c6103bd565b6102155f610416565b565b61021f6103bd565b6040516308f2839760e41b81526001600160a01b038281166004830152831690638f283970906024015b5f604051808303815f87803b158015610260575f5ffd5b505af1158015610272573d5f5f3e3d5ffd5b505050505050565b6102826103bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef2869034906102b290869086906004016105d8565b5f604051808303818588803b1580156102c9575f5ffd5b505af11580156102db573d5f5f3e3d5ffd5b5050505050505050565b6102ed6103bd565b604051631b2ce7f360e11b81526001600160a01b038281166004830152831690633659cfe690602401610249565b6103236103bd565b6001600160a01b03811661038d5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61039681610416565b50565b5f5f5f836001600160a01b031660405161019d906303e1469160e61b815260040190565b5f546001600160a01b031633146102155760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610384565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0381168114610396575f5ffd5b5f60208284031215610489575f5ffd5b813561049481610465565b9392505050565b5f5f604083850312156104ac575f5ffd5b82356104b781610465565b915060208301356104c781610465565b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b5f5f5f606084860312156104f8575f5ffd5b833561050381610465565b9250602084013561051381610465565b9150604084013567ffffffffffffffff81111561052e575f5ffd5b8401601f8101861361053e575f5ffd5b803567ffffffffffffffff811115610558576105586104d2565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610587576105876104d2565b60405281815282820160200188101561059e575f5ffd5b816020840160208301375f602083830101528093505050509250925092565b5f602082840312156105cd575f5ffd5b815161049481610465565b60018060a01b0383168152604060208201525f82518060408401528060208501606085015e5f606082850101526060601f19601f830116840101915050939250505056fea2646970667358221220d1857c1d79adf09a4456300c200565d4db0d5bcc151c34d3d6a7ed403fb9defd64736f6c634300081c003300",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000976ea74026e726554db657fa54763abd0c3a0aa9"
}
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
},
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"20": {
"address": "0x00000000219ab540356cBB839Cbe05303d7705Fa",
"code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
"0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
"0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
"0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
"0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
"0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c",
"0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
"0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
"0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
"0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
"0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
"0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
"0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
"0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
"0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c",
"0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
"0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
"0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
"0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
"0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
"0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
"0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
"0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
"0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
"0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b"
}
},
"27": {
"address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
"storage": {
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001"
}
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
},
"26": {
"address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000004a679253410272dd5232b3ff7cf5dbb88f295319",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65"
}
},
"2": {
"address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933",
"code": "0x608060405234801561000f575f5ffd5b50600436106100e5575f3560e01c80639100674511610088578063ad8aca7711610063578063ad8aca77146101df578063df595cb8146101f2578063eb5a4e8714610205578063fddbdefd14610218575f5ffd5b80639100674514610196578063950d806e146101b9578063ad5f2210146101cc575f5ffd5b806354fd4d50116100c357806354fd4d5014610124578063628806ef146101425780636bddfa1f14610155578063882a3b3814610175575f5ffd5b806306641201146100e9578063268959e5146100fe5780634f906cf914610111575b5f5ffd5b6100fc6100f7366004610dbd565b61022b565b005b6100fc61010c366004610e0e565b61034c565b6100fc61011f366004610e0e565b610427565b61012c6104ca565b6040516101399190610e3f565b60405180910390f35b6100fc610150366004610e74565b6104fa565b610168610163366004610e74565b610588565b6040516101399190610ed0565b610188610183366004610e0e565b6105b1565b604051610139929190610ee2565b6101a96101a4366004610e0e565b610712565b6040519015158152602001610139565b6100fc6101c7366004610dbd565b610782565b6101686101da366004610e74565b610893565b6101a96101ed366004610e0e565b610939565b6101a9610200366004610dbd565b61095a565b6100fc610213366004610e0e565b6109af565b610168610226366004610f44565b610a7d565b836102368133610712565b61025357604051637bfa4b9f60e01b815260040160405180910390fd5b6001600160a01b0385165f908152600160205260408120906102758585610abb565b6001600160a01b0387165f908152600484016020526040902090915061029b9082610ae8565b6102b85760405163262118cd60e01b815260040160405180910390fd5b6001600160a01b0386165f90815260048301602052604090206102db9082610aff565b505f81815260058301602052604090206102f59087610b0a565b50856001600160a01b0316876001600160a01b03167f18242326b6b862126970679759169f01f646bd55ec5bfcab85ba9f337a74e0c6878760405161033b929190610f84565b60405180910390a350505050505050565b816103578133610712565b61037457604051637bfa4b9f60e01b815260040160405180910390fd5b6001600160a01b0383165f9081526001602081905260409091206002019061039b82610b1e565b116103b9576040516310ce892b60e31b815260040160405180910390fd5b6103c38184610b0a565b6103e057604051630716d81b60e51b815260040160405180910390fd5b6040516001600160a01b0384811682528516907fdb9d5d31320daf5bc7181d565b6da4d12e30f0f4d5aa324a992426c14a1d19ce906020015b60405180910390a250505050565b816104328133610712565b61044f57604051637bfa4b9f60e01b815260040160405180910390fd5b6001600160a01b0383165f9081526001602052604090206104708184610b0a565b61048d5760405163bed8295f60e01b815260040160405180910390fd5b6040516001600160a01b0384811682528516907fd706ed7ae044d795b49e54c9f519f663053951011985f663a862cd9ee72a9ac790602001610419565b60606104f57f76312e302e300000000000000000000000000000000000000000000000000006610b27565b905090565b6001600160a01b0381165f90815260016020526040902061051b8133610b0a565b6105385760405163bed8295f60e01b815260040160405180910390fd5b6105456002820133610b64565b506040513381526001600160a01b038316907fbf265e8326285a2747e33e54d5945f7111f2b5edb826eb8c08d4677779b3ff979060200160405180910390a25050565b6001600160a01b0381165f9081526001602052604090206060906105ab90610b78565b92915050565b6001600160a01b038083165f9081526001602090815260408083209385168352600490930190529081206060918291906105ea82610b1e565b90505f8167ffffffffffffffff81111561060657610606610fa7565b60405190808252806020026020018201604052801561062f578160200160208202803683370190505b5090505f8267ffffffffffffffff81111561064c5761064c610fa7565b604051908082528060200260200182016040528015610675578160200160208202803683370190505b5090505f5b83811015610704576106a861068f8683610b84565b606081901c9160a09190911b6001600160e01b03191690565b8483815181106106ba576106ba610fbb565b602002602001018484815181106106d3576106d3610fbb565b6001600160e01b0319909316602093840291909101909201919091526001600160a01b03909116905260010161067a565b509097909650945050505050565b6001600160a01b0382165f90815260016020526040812061073590600201610b1e565b5f0361075757816001600160a01b0316836001600160a01b03161490506105ab565b6001600160a01b0383165f90815260016020526040902061077b9060020183610b8f565b9392505050565b8361078d8133610712565b6107aa57604051637bfa4b9f60e01b815260040160405180910390fd5b6001600160a01b0385165f908152600160205260408120906107cc8585610abb565b6001600160a01b0387165f90815260048401602052604090209091506107f29082610ae8565b1561081057
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
}
},
"29": {
"address": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x9254291d7d424716ad6728e8cf28d7329070cafa88280734e18f0a5f711cc416": "0x000000000000000000000000998abeb3e57409262ae5b751f60747921b33613e",
"0x4bad58e84dc127f47e7265bd5e504be070126f63f93af282fe2a4f1acbb07707": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000c5a5c42992decbae36851359345fe25997f5c42d",
"0x54ab5bc83c0127df10d352dbba9557880cef93f87419916bd513c73a26e9de39": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x165183f4d7a8ecead93a30c1491a78d70b212627d72d451cc2b61e9844bb6182": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x288c6faa56b91953378099dc2014a331affa988ca357fe83ca55e72915585282": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x38de7073e27519f272741044a68ab5a51022aa002af20801e32867226a9bb4bd": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788"
}
},
"36": {
"address": "0xdf077F5F72071dF6e8B0a78071E496bA17b5Ee0c",
"code": "0x608060405260043610610036575f3560e01c8063338c5371146100415780639bb66b2814610091578063e905182a146100be575f5ffd5b3661003d57005b5f5ffd5b34801561004c575f5ffd5b506100747f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f8168881565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561009c575f5ffd5b506100b06100ab3660046101ae565b6100ff565b604051610088929190610239565b3480156100c9575f5ffd5b506100f17f81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b7981565b604051908152602001610088565b5f6060336001600160a01b037f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f81688161461014a576040516282b42960e81b815260040160405180910390fd5b846001600160a01b03168484604051610164929190610277565b5f60405180830381855af49150503d805f811461019c576040519150601f19603f3d011682016040523d82523d5f602084013e6101a1565b606091505b5091509150935093915050565b5f5f5f604084860312156101c0575f5ffd5b83356001600160a01b03811681146101d6575f5ffd5b9250602084013567ffffffffffffffff8111156101f1575f5ffd5b8401601f81018613610201575f5ffd5b803567ffffffffffffffff811115610217575f5ffd5b866020828401011115610228575f5ffd5b939660209190910195509293505050565b8215158152604060208201525f82518060408401528060208501606085015e5f606082850101526060601f19601f8301168401019150509392505050565b818382375f910190815291905056fea26469706673582212208fe760f358faedf4a90fd4b23c39c8397def11c5b035ea1406af976ecc426bbf64736f6c634300081c00330000000000",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {}
},
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"4": {
"address": "0x1111111111111111111111111111111111111111",
"code": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80632baeceb71461004e5780638381f58a146100585780638da5cb5b14610073578063d826f88f1461009e575b5f5ffd5b6100566100a6565b005b6100605f5481565b6040519081526020015b60405180910390f35b600154610086906001600160a01b031681565b6040516001600160a01b03909116815260200161006a565b61005661010d565b5f5f54116100fb5760405162461bcd60e51b815260206004820152601f60248201527f4e756d6265722073686f756c642062652067726561746572207468616e20300060448201526064015b60405180910390fd5b60015f54610109919061016d565b5f55565b6001546001600160a01b031633146101675760405162461bcd60e51b815260206004820152601760248201527f4f6e6c792063616c6c61626c65206279206f776e65722100000000000000000060448201526064016100f2565b600a5f55565b8181038181111561018c57634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220ac5899491afd834afd223fd632497d1c0c7593961eda22f04c58db4b504999cf64736f6c634300081c0033000000",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000000a",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"
}
},
"39": {
"address": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
"code": "0x608060405234801561000f575f5ffd5b50600436106102b1575f3560e01c80636d70f7ae1161017b578063bb45fef2116100e4578063e4cc3f901161009e578063f698da2511610079578063f698da25146107ce578063fabc1cbc146107d6578063fd8aa88d146107e9578063fe4b84df146107fc575f5ffd5b8063e4cc3f9014610788578063eea9064b1461079b578063f0e0e676146107ae575f5ffd5b8063bb45fef2146106b9578063bfae3fd2146106e6578063c448feb8146106f9578063c978f7ac1461072d578063ca8aa7c71461074e578063da8be86414610775575f5ffd5b80639104c319116101355780639104c319146106175780639435bb431461063257806399f5371b14610645578063a178848414610665578063a33a343314610684578063b7f06ebe14610697575f5ffd5b80636d70f7ae1461057a5780636e1744481461058d578063778e55f3146105a057806378296ec5146105ca578063886f1195146105dd5780639004134714610604575f5ffd5b806354b7c96c1161021d5780635c975abb116101d75780635c975abb146104d45780635d975e88146104dc5780635dd68579146104fd57806360a0d1ce1461051e57806365da12641461053157806366d5ba9314610559575f5ffd5b806354b7c96c1461045b57806354fd4d501461046e578063595c6a6714610483578063597b36da1461048b5780635ac86ab71461049e5780635ae679a7146104c1575f5ffd5b806339b70e381161026e57806339b70e381461036a5780633c651cf2146103a95780633cdeb5e0146103bc5780633e28391d146103ea5780634657e26a1461040d5780634665bcda14610434575f5ffd5b806304a4f979146102b55780630b9f487a146102ef5780630dd8dd0214610302578063136439dd1461032257806325df922e146103375780632aa6d88814610357575b5f5ffd5b6102dc7f14bde674c9f64b2ad00eaaee4a8bed1fabef35c7507e3c5b9cfc9436909a2dad81565b6040519081526020015b60405180910390f35b6102dc6102fd366004614a7f565b61080f565b610315610310366004614b16565b610897565b6040516102e69190614b54565b610335610330366004614b8b565b610b09565b005b61034a610345366004614d20565b610b43565b6040516102e69190614dce565b610335610365366004614e30565b610ca3565b6103917f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb60750881565b6040516001600160a01b0390911681526020016102e6565b6103356103b7366004614e8e565b610df7565b6103916103ca366004614ed1565b6001600160a01b039081165f908152609960205260409020600101541690565b6103fd6103f8366004614ed1565b610f4a565b60405190151581526020016102e6565b6103917f0000000000000000000000003aa5ebb10dc797cac828524e59a333d0a371443c81565b6103917f000000000000000000000000959922be3caee4b8cd9a407cc3ac1c251c2007b181565b610335610469366004614eec565b610f69565b610476610fd7565b6040516102e69190614f51565b610335611007565b6102dc61049936600461501f565b61101b565b6103fd6104ac366004615050565b606654600160ff9092169190911b9081161490565b6102dc6104cf366004615084565b61104a565b6066546102dc565b6104ef6104ea366004614b8b565b6111bc565b6040516102e69291906151b9565b61051061050b366004614ed1565b6111d9565b6040516102e692919061522b565b61033561052c366004615298565b611303565b61039161053f366004614ed1565b609a6020525f90815260409020546001600160a01b031681565b61056c610567366004614ed1565b6114ae565b6040516102e69291906152d7565b6103fd610588366004614ed1565b6117ae565b6102dc61059b366004614eec565b6117e6565b6102dc6105ae366004614eec565b609860209081525f928352604080842090915290825290205481565b6103356105d83660046152e9565b611890565b6103917f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b61034a610612366004615339565b611926565b61039173beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b610335610640366004615385565b6119fc565b610658610653366004614b8b565b611ab7565b6040516102e69190615421565b6102dc610673366004614ed1565b609f6020525f908152604090205481565b610315610692366004615433565b611bd3565b6103fd6106a5366004614b8b565b609e6020525f908152604090205460ff1681565b6103fd6106c736600461551a565b609c60209081525f928352604080842090915290825290205460ff1681565b6102dc6106f4366004614eec565b611beb565b60405163ffffffff7f00000000000000000000000000000000000000000000000000000000000000321681526020016102e6565b61074061073b366004615339565b611c27565b6040516102e6929190615544565b6103917f00000000000000000000000068b1d87f95878fe05b998f19b66f4baba5de1aed81565b610315610783366004614ed1565b611eb4565b610335610796366004615563565b611fdd565b6103356107a9366004615433565b612015565b6107c16107bc3660046155e1565b612080565b6040516102e6919061568e565b6102dc612125565b6103356107e4366004614b8b565b6121de565b6103156107f7366004614ed1565b
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
}
},
"42": {
"address": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
"code": "0x608060405234801561000f575f5ffd5b5060043610610148575f3560e01c80637a8b2637116100bf578063ce7c2ac211610079578063ce7c2ac2146102d7578063d9caed12146102ea578063e3dae51c146102fd578063f3e7387514610310578063fabc1cbc14610323578063fdc371ce14610336575f5ffd5b80637a8b26371461025c578063886f11951461026f5780638c871019146102965780638f6a6240146102a9578063ab5921e1146102bc578063c4d66de8146102c4575f5ffd5b8063485cc95511610110578063485cc955146101e257806354fd4d50146101f5578063553ca5f81461020a578063595c6a671461021d5780635ac86ab7146102255780635c975abb14610254575f5ffd5b8063136439dd1461014c5780632495a5991461016157806339b70e38146101915780633a98ef39146101b857806347e7ef24146101cf575b5f5ffd5b61015f61015a3660046111cb565b610349565b005b603254610174906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101747f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb60750881565b6101c160335481565b604051908152602001610188565b6101c16101dd3660046111f6565b610383565b61015f6101f0366004611220565b6104b2565b6101fd61059d565b6040516101889190611257565b6101c161021836600461128c565b6105cd565b61015f6105e0565b6102446102333660046112bc565b6001805460ff9092161b9081161490565b6040519015158152602001610188565b6001546101c1565b6101c161026a3660046111cb565b6105f4565b6101747f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b6101c16102a43660046111cb565b61063d565b6101c16102b736600461128c565b610647565b6101fd610654565b61015f6102d236600461128c565b610674565b6101c16102e536600461128c565b61073a565b6101c16102f83660046112d7565b6107cc565b6101c161030b3660046111cb565b6108ce565b6101c161031e3660046111cb565b610905565b61015f6103313660046111cb565b61090f565b606454610174906001600160a01b031681565b61035161097c565b60015481811681146103765760405163c61dca5d60e01b815260040160405180910390fd5b61037f82610a1f565b5050565b5f5f61038e81610a5c565b336001600160a01b037f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb60750816146103d7576040516348da714f60e01b815260040160405180910390fd5b6103e18484610a92565b6033545f6103f16103e883611329565b90505f6103e86103ff610b4b565b6104099190611329565b90505f610416878361133c565b905080610423848961134f565b61042d9190611366565b9550855f0361044f57604051630c392ed360e11b815260040160405180910390fd5b6104598685611329565b60338190556f4b3b4ca85a86c47a098a223fffffffff101561048e57604051632f14e8a360e11b815260040160405180910390fd5b6104a7826103e86033546104a29190611329565b610bb5565b505050505092915050565b5f54610100900460ff16158080156104d057505f54600160ff909116105b806104e95750303b1580156104e957505f5460ff166001145b61050e5760405162461bcd60e51b815260040161050590611385565b60405180910390fd5b5f805460ff19166001179055801561052f575f805461ff0019166101001790555b606480546001600160a01b0319166001600160a01b03851617905561055382610c01565b8015610598575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b60606105c87f76312e302e300000000000000000000000000000000000000000000000000006610d4c565b905090565b5f6105da61026a8361073a565b92915050565b6105e861097c565b6105f25f19610a1f565b565b5f5f6103e86033546106069190611329565b90505f6103e8610614610b4b565b61061e9190611329565b90508161062b858361134f565b6106359190611366565b949350505050565b5f6105da826108ce565b5f6105da61031e8361073a565b60606040518060800160405280604d8152602001611456604d9139905090565b5f54610100900460ff161580801561069257505f54600160ff909116105b806106ab5750303b1580156106ab57505f5460ff166001145b6106c75760405162461bcd60e51b815260040161050590611385565b5f805460ff1916600117905580156106e8575f805461ff0019166101001790555b6106f182610c01565b801561037f575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b60405163fe243a1760e01b81526001600160a01b0382811660048301523060248301525f917f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb6075089091169063fe243a1790604401602060405180830381865afa1580156107a8573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105da91906113d3565b5f60016107d881610a5c565b336001600160a01b037f0000000000000000000000009a676e781a523b5d0c
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
}
},
"8": {
"address": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF",
"code": "0x60806040526004361061003e575f3560e01c806305b1137b1461004257806325ccedec14610063578063c6b295c114610082578063d0e30db014610061575b5f5ffd5b34801561004d575f5ffd5b5061006161005c36600461025e565b6100a1565b005b34801561006e575f5ffd5b5061006161007d366004610288565b6100b8565b34801561008d575f5ffd5b5061006161009c3660046102ef565b6100da565b6100b46001600160a01b038316826100f7565b5050565b6100d56001600160a01b038416836001600160801b038416610120565b505050565b5f6100e6848484610171565b9050806100f1575f5ffd5b50505050565b5f5f5f5f5f85875af19050806100d557604051633d2cec6f60e21b815260040160405180910390fd5b6040516001600160a01b0383166024820152604481018290526100d590849060640160408051601f198184030181529190526020810180516001600160e01b031663a9059cbb60e01b179052610188565b5f5f5f5f85516020870186895af195945050505050565b5f5f836001600160a01b0316836040516101a291906103be565b5f604051808303815f865af19150503d805f81146101db576040519150601f19603f3d011682016040523d82523d5f602084013e6101e0565b606091505b50915091505f82801561020b57508151158061020b57508180602001905181019061020b91906103d4565b905080158061022257506001600160a01b0385163b155b156102405760405163022e258160e11b815260040160405180910390fd5b5050505050565b6001600160a01b038116811461025b575f5ffd5b50565b5f5f6040838503121561026f575f5ffd5b823561027a81610247565b946020939093013593505050565b5f5f5f6060848603121561029a575f5ffd5b83356102a581610247565b925060208401356102b581610247565b915060408401356001600160801b03811681146102d0575f5ffd5b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f5f5f60608486031215610301575f5ffd5b833561030c81610247565b9250602084013567ffffffffffffffff811115610327575f5ffd5b8401601f81018613610337575f5ffd5b803567ffffffffffffffff811115610351576103516102db565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610380576103806102db565b604052818152828201602001881015610397575f5ffd5b816020840160208301375f9181016020019190915293969395505050506040919091013590565b5f82518060208501845e5f920191825250919050565b5f602082840312156103e4575f5ffd5b815180151581146103f3575f5ffd5b939250505056fea2646970667358221220590055fea5441ad6e827390b16005643886d2dc4ffe2b97b43ed3ab207076ab664736f6c634300081c003300",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {}
},
"32": {
"address": "0x998abeb3E57409262aE5b751f60747921B33613E",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000065": "0x000000000000000000000000000000000000000000084595161401484a000000",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000f5059a5d33d5853360d16c683c16e67980206f36",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x0000000000000000000000000000000000000000000000000000000000000032": "0x00000000000000000000000095401dc811bb5740090279ba06cfa8fcf6113778",
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000064": "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
}
},
"43": {
"address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570",
"code": "0x608060405260043610610212575f3560e01c8063a69d9bb61161011e578063c63fd502116100a8578063d6e588d41161006d578063d6e588d41461065f578063e2148f5a1461067e578063ef99ce111461069d578063f2fde38b146106dc578063fe776c2a146106fb575f5ffd5b8063c63fd502146105ba578063ceb29d61146105d9578063d156b911146105f8578063d4b6b8341461060c578063d4c250081461062b575f5ffd5b8063a997f0ca116100ee578063a997f0ca14610502578063a9a899cd14610523578063b526578714610542578063be6ab6ef1461057a578063c1a8e2c51461059b575f5ffd5b8063a69d9bb614610492578063a8294cd8146104b1578063a8315705146104c4578063a98fb355146104e3575f5ffd5b80636d4f5c0e1161019f578063740d83771161016f578063740d8377146103f057806374edeb6c1461042457806383821e8e146104435780638da5cb5b146104625780638f8ee5521461047f575f5ffd5b80636d4f5c0e1461037f5780636deab0191461039e578063715018a6146103bd5780637240f9af146103d1575f5ffd5b8063303ca956116101e5578063303ca956146102d857806333dcde4c146102f7578063501060021461031857806359b00534146103375780635d88746214610356575f5ffd5b80631500cd8d14610216578063191088391461023757806326597383146102565780632fb31ef1146102a7575b5f5ffd5b348015610221575f5ffd5b50610235610230366004613a31565b610729565b005b348015610242575f5ffd5b50610235610251366004613a4c565b6107a1565b348015610261575f5ffd5b5061028a610270366004613a31565b606b6020525f90815260409020546001600160601b031681565b6040516001600160601b0390911681526020015b60405180910390f35b3480156102b2575f5ffd5b506067546001600160a01b03165b6040516001600160a01b03909116815260200161029e565b3480156102e3575f5ffd5b506102356102f2366004613b02565b6109c1565b348015610302575f5ffd5b5061030b610b21565b60405161029e9190613b90565b348015610323575f5ffd5b5061030b610332366004613bb8565b610bb1565b348015610342575f5ffd5b50610235610351366004613a31565b6113a1565b348015610361575f5ffd5b5061036a602081565b60405163ffffffff909116815260200161029e565b34801561038a575f5ffd5b506065546102c0906001600160a01b031681565b3480156103a9575f5ffd5b506102356103b8366004613dae565b6114c2565b3480156103c8575f5ffd5b506102356119e2565b3480156103dc575f5ffd5b506102356103eb366004613e58565b6119f5565b3480156103fb575f5ffd5b506102c061040a366004613a31565b60696020525f90815260409020546001600160a01b031681565b34801561042f575f5ffd5b5061023561043e366004613a31565b611b26565b34801561044e575f5ffd5b5061023561045d366004613e89565b611ba6565b34801561046d575f5ffd5b506033546001600160a01b03166102c0565b34801561048a575f5ffd5b5061036a5f81565b34801561049d575f5ffd5b506102356104ac366004613a4c565b6120ac565b6102356104bf366004613ed5565b6122ac565b3480156104cf575f5ffd5b506102356104de366004613f15565b6123c2565b3480156104ee575f5ffd5b506102356104fd366004613e58565b6124b1565b34801561050d575f5ffd5b50610516612537565b60405161029e9190613f53565b34801561052e575f5ffd5b5061023561053d366004613a31565b6126ee565b34801561054d575f5ffd5b5061056a61055c366004613a31565b6001600160a01b0316301490565b604051901515815260200161029e565b348015610585575f5ffd5b5061058e6127b7565b60405161029e9190613ff6565b3480156105a6575f5ffd5b506102356105b5366004614008565b61285f565b3480156105c5575f5ffd5b506102356105d4366004614058565b6128a3565b3480156105e4575f5ffd5b506102356105f3366004613f15565b612ad5565b348015610603575f5ffd5b5061030b612d63565b348015610617575f5ffd5b50606a546102c0906001600160a01b031681565b348015610636575f5ffd5b506102c0610645366004613a31565b60686020525f90815260409020546001600160a01b031681565b34801561066a575f5ffd5b50610235610679366004613a31565b612d7f565b348015610689575f5ffd5b50610235610698366004613a31565b612dff565b3480156106a8575f5ffd5b5061056a6106b736600461412a565b606d60209081525f938452604080852082529284528284209052825290205460ff1681565b3480156106e7575f5ffd5b506102356106f6366004613a31565b612e79565b348015610706575f5ffd5b5061056a610715366004613a31565b60666020525f908152604090205460ff1681565b610731612eef565b6001600160a01b0381166107585760405163d92e233d60e01b815260040160405180910390fd5b606780546001600160a01b0319166001600160a01b0383169081179091556040517f6a8a174b559440c4e231f06fda7f0eb644f79306c33292fbb95f7602bef9aaf9905f90a250565b6107a9612eef565b6040805180820182523081525f60208201819052915163105dea1f60e21b81529091907f00000000000000000000000068b1d87f95878fe05b998f19b66f4baba5de1aed6001600160a01b031690
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"45": {
"address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
"code": "0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c298557814602a575b5f5ffd5b5f60405190815260200160405180910390f3fea26469706673582212204906941268839d569ecaf7e18f112ade90c540d0de5142ca9f7725e57df91e0964736f6c634300081c00330000000000000000",
"storage": {}
},
"41": {
"address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
"code": "0x73cf7ed3acca5a467e9e704c703e8d87f634fb0fc93014608060405260043610610034575f3560e01c8063d3b08db814610038575b5f5ffd5b61004b610046366004610399565b610061565b604051610058919061049c565b60405180910390f35b80518051606091825f5b82518163ffffffff1610156101055781838263ffffffff1681518110610093576100936104d1565b60200260200101516040516020016100c3919060609190911b6bffffffffffffffffffffffff1916815260140190565b60408051601f19818403018152908290526100e192916020016104fc565b604051602081830303815290604052915080806100fd9061052c565b91505061006b565b50630e02a00760e31b5f80610119866101b9565b8461018b8a602001515f65ff000000ff00600883811b91821664ff000000ff9185901c91821617601090811b67ff000000ff0000009390931666ff000000ff00009290921691909117901c17602081811b6bffffffffffffffff000000001691901c63ffffffff161760c01b92915050565b6040516020016101a096959493929190610550565b6040516020818303038152906040529350505050919050565b6060603f8263ffffffff16116101f657604051603f60fa1b60fa84901b1660208201526021015b6040516020818303038152906040529050919050565b613fff8263ffffffff16116102555761023261021e6403fffffffc600285901b1660016105a9565b600881811b62ffff001691901c60ff161790565b6040516020016101e0919060f09190911b6001600160f01b031916815260020190565b633fffffff8263ffffffff16116102c7576102a460028363ffffffff16901b600261028091906105a9565b600881811c62ff00ff1663ff00ff009290911b9190911617601081811c91901b1790565b6040516020016101e0919060e09190911b6001600160e01b031916815260040190565b604051600360f81b60208201526001600160e01b0319600884811c62ff00ff1663ff00ff009186901b9190911617601081811c91901b1760e01b1660218201526025016101e0565b919050565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff8111828210171561034b5761034b610314565b60405290565b604051601f8201601f1916810167ffffffffffffffff8111828210171561037a5761037a610314565b604052919050565b803567ffffffffffffffff8116811461030f575f5ffd5b5f602082840312156103a9575f5ffd5b813567ffffffffffffffff8111156103bf575f5ffd5b8201604081850312156103d0575f5ffd5b6103d8610328565b813567ffffffffffffffff8111156103ee575f5ffd5b8201601f810186136103fe575f5ffd5b803567ffffffffffffffff81111561041857610418610314565b8060051b61042860208201610351565b91825260208184018101929081019089841115610443575f5ffd5b6020850194505b8385101561047c57843592506001600160a01b038316831461046a575f5ffd5b8282526020948501949091019061044a565b85525061048f9250505060208301610382565b6020820152949350505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b634e487b7160e01b5f52603260045260245ffd5b5f81518060208401855e5f93019283525090919050565b5f61051061050a83866104e5565b846104e5565b949350505050565b634e487b7160e01b5f52601160045260245ffd5b5f63ffffffff821663ffffffff810361054757610547610518565b60010192915050565b6001600160e01b0319871681526001600160f81b03198681166004830152851660058201525f61058c61058660068401876104e5565b856104e5565b6001600160c01b0319939093168352505060080195945050505050565b63ffffffff81811683821601908111156105c5576105c5610518565b9291505056fea264697066735822122025360fa68d61e06c0f66cb21e1db2778b987cc6ccef457fe1dbef98965e5dabb64736f6c634300081c0033000000000000000000000000",
"storage": {}
},
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"46": {
"address": "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02",
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000003c86": "0x0c727439ad0c1f3bbcc4e78cbc3191d86cb8e2929333796a695e639f4273de3a",
"0x0000000000000000000000000000000000000000000000000000000000003c48": "0x500198120ba3324f9dd712a55a671a600e1ace1266ae5acbbd5bdd4e2c1a8604",
"0x0000000000000000000000000000000000000000000000000000000000001c77": "0x0000000000000000000000000000000000000000000000000000000069df4d63",
"0x0000000000000000000000000000000000000000000000000000000000003c49": "0x2e11959c0b29bdec3b154be63c1d575dfa80704b2fd50f9fc391973fb87d5463",
"0x0000000000000000000000000000000000000000000000000000000000001c4b": "0x0000000000000000000000000000000000000000000000000000000069df4d37",
"0x0000000000000000000000000000000000000000000000000000000000001c70": "0x0000000000000000000000000000000000000000000000000000000069df4d5c",
"0x0000000000000000000000000000000000000000000000000000000000003c6b": "0xc41f50ceaf8898cb909cddf8d0a39ab9c5c794fc04c5c19de217219db7db87e4",
"0x0000000000000000000000000000000000000000000000000000000000003c83": "0x3c1c6cbe4741928e1fb6e563cad0916541e8b620b607a64d52bee0ede43a4de6",
"0x0000000000000000000000000000000000000000000000000000000000001c73": "0x0000000000000000000000000000000000000000000000000000000069df4d5f",
"0x0000000000000000000000000000000000000000000000000000000000003c8d": "0x3f8f6f28395930bbedeb8ceecb25c74de2c018d91d8975fce2f66c0eea32d3a9",
"0x0000000000000000000000000000000000000000000000000000000000001c8a": "0x0000000000000000000000000000000000000000000000000000000069df4d76",
"0x0000000000000000000000000000000000000000000000000000000000001c60": "0x0000000000000000000000000000000000000000000000000000000069df4d4c",
"0x0000000000000000000000000000000000000000000000000000000000003c56": "0x14e18a40236dafce5db02e6cb0159376df32b1bb72b9ed9bb9dfd3a47ee37339",
"0x0000000000000000000000000000000000000000000000000000000000001c87": "0x0000000000000000000000000000000000000000000000000000000069df4d73",
"0x0000000000000000000000000000000000000000000000000000000000003c6e": "0x6aeefb51585e80416ed078542e5648c39072a1989361a7ccd092bab359f2e6b8",
"0x0000000000000000000000000000000000000000000000000000000000001c8f": "0x0000000000000000000000000000000000000000000000000000000069df4d7b",
"0x0000000000000000000000000000000000000000000000000000000000001c4f": "0x0000000000000000000000000000000000000000000000000000000069df4d3b",
"0x0000000000000000000000000000000000000000000000000000000000001c69": "0x0000000000000000000000000000000000000000000000000000000069df4d55",
"0x0000000000000000000000000000000000000000000000000000000000001c90": "0x0000000000000000000000000000000000000000000000000000000069df4d7c",
"0x0000000000000000000000000000000000000000000000000000000000001c51": "0x0000000000000000000000000000000000000000000000000000000069df4d3d",
"0x0000000000000000000000000000000000000000000000000000000000003c6a": "0xe298419518f1e0a881e58578e6e6773ed94e6ddbdd069dddd36a6bdb5e20a72a",
"0x0000000000000000000000000000000000000000000000000000000000003c7e": "0x143bb920d35fe892f67fb283b497a1813088e6d3b3ad0c8a155e282e58275e20",
"0x0000000000000000000000000000000000000000000000000000000000001c89": "0x0000000000000000000000000000000000000000000000000000000069df4d75",
"0x0000000000000000000000000000000000000000000000000000000000001c5d": "0x0000000000000000000000000000000000000000000000000000000069df4d49",
"0x0000000000000000000000000000000000000000000000000000000000001c5c": "0x0000000000000000000000000000000000000000000000000000000069df4d48",
"0x0000000000000000000000000000000000000000000000000000000000001c64": "0x0000000000000000000000000000000000000000000000000000000069df4d50",
"0x0000000000000000000000000000000000000000000000000000000000001c8b": "0x0000000000000000000000000000000000000000000000000000000069df4d77",
"0x0000000000000000000000000000000000000000000000000000000000001c6d": "0x0000000000000000000000000000000000000000000000000000000069df4d59",
"0x0000000000000000000000000000000000000000000000000000000000003c58": "0xb7f62236afe1884243f712cd7f1cf5b725ebf53bf0a1ea32c204425327874650",
"0x0000000000000000000000000000000000000000000000000000000000001c56": "0x0000000000000000000000000000000000000000000000000000000069df4d42",
"0x0000000000000000000000000000000000000000000000000000000000003c57": "0xef64c889438be0b2caae5735c12c6ef476b0f5a362c31805ab5d16e76af959ac",
"0x0000000000000000000000000000000000000000000000000000000000003c8b": "0xa830cbf5c29fbf3c597579d8e2ac5c7e0a35ceb969f66a1c4066ee23faa47b88",
"0x0000000000000000000000000000000000000000000000000000000000001c7f": "0x0000000000000000000000000000000000000000000000000000000069df4d6b",
"0x0000000000000000000000000000000000000000000000000000000000001c4c": "0x0000000000000000000000000000000000000000000000000000000069df4d38",
"0x0000000000000000000000000000000000000000000000000000000000003c71": "0x105874a3292db557ba828e6d231d9a404c6a382a72bf32df0306a85f55a7dccd",
"0x0000000000000000000000000000000000000000000000000000000000001c7b": "0x0000000000000000000000000000000000000000000000000000000069df4d67",
"0x0000000000000000000000000000000000000000000000000000000000003c59": "0xae348eff3433294201b1336f6fca53be7310049ff0a489a6909279ae1df17dde",
"0x0000000000000000000000000000000000000000000000000000000000001c7d": "0x0000000000000000000000000000000000000000000000000000000069df4d69",
"0x0000000000000000000000000000000000000000000000000000000000003c85": "0x69068b68eebdbb92910fd4e22bf53e7be5e22648c1a9b14a1c169700c0349a3c",
"0x0000000000000000000000000000000000000000000000000000000000001c93": "0x0000000000000000000000000000000000000000000000000000000069df4d7f",
"0x0000000000000000000000000000000000000000000000000000000000003c81": "0x2ce574013ea4cd7bfd3187f066c76025f75ae6dc7173c52c781d5b1bddae7532",
"0x0000000000000000000000000000000000000000000000000000000000003c44": "0xb6360d53b9135bf3413f3fd6e99884bd10b4d9bd4d8e1f21f109184368d6cd01",
"0x0000000000000000000000000000000000000000000000000000000000001c68": "0x0000000000000000000000000000000000000000000000000000000069df4d54",
"0x0000000000000000000000000000000000000000000000000000000000001c59": "0x0000000000000000000000000000000000000000000000000000000069df4d45",
"0x0000000000000000000000000000000000000000000000000000000000001c92": "0x0000000000000000000000000000000000000000000000000000000069df4d7e",
"0x0000000000000000000000000000000000000000000000000000000000001c5a": "0x0000000000000000000000000000000000000000000000000000000069df4d46",
"0x0000000000000000000000000000000000000000000000000000000000003c5f": "0x96bc4161384377cb6af98de381699fbd16d2cb4352a096985cb3be750688343e",
"0x0000000000000000000000000000000000000000000000000000000000001c49": "0x0000000000000000000000000000000000000000000000000000000069df4d35",
"0x0000000000000000000000000000000000000000000000000000000000001c47": "0x0000000000000000000000000000000000000000000000000000000069df4d33",
"0x0000000000000000000000000000000000000000000000000000000000003c8c": "0xc53833e07d32a0fd70ed63ed50c34ab087f460fc90ce86eb3eed53f240e7c799",
"0x0000000000000000000000000000000000000000000000000000000000001c6a": "0x0000000000000000000000000000000000000000000000000000000069df4d56",
"0x0000000000000000000000000000000000000000000000000000000000001c4d": "0x0000000000000000000000000000000000000000000000000000000069df4d39",
"0x0000000000000000000000000000000000000000000000000000000000001c4e": "0x0000000000000000000000000000000000000000000000000000000069df4d3a",
"0x0000000000000000000000000000000000000000000000000000000000001c79": "0x0000000000000000000000000000000000000000000000000000000069df4d65",
"0x0000000000000000000000000000000000000000000000000000000000001c6f": "0x0000000000000000000000000000000000000000000000000000000069df4d5b",
"0x0000000000000000000000000000000000000000000000000000000000003c54": "0xa3b0f6ffff51128f17e4c6c8c523fb32df2d6a88feeee11e66021cc3641b7795",
"0x0000000000000000000000000000000000000000000000000000000000003c92": "0x81e72758e7d6dbd624e0dbef2498bf7c0105fd88225999a645fe1f2e62e91636",
"0x0000000000000000000000000000000000000000000000000000000000001c78": "0x0000000000000000000000000000000000000000000000000000000069df4d64",
"0x0000000000000000000000000000000000000000000000000000000000001c7c": "0x0000000000000000000000000000000000000000000000000000000069df4d68",
"0x0000000000000000000000000000000000000000000000000000000000003c4c": "0x8ec51744e98ca0d955a91a59a3673ebef5de2341cbb8a5743040f693ddf3e0e7",
"0x0000000000000000000000000000000000000000000000000000000000001c81": "0x0000000000000000000000000000000000000000000000000000000069df4d6d",
"0x0000000000000000000000000000000000000000000000000000000000001c48": "0x0000000000000000000000000000000000000000000000000000000069df4d34",
"0x0000000000000000000000000000000000000000000000000000000000001c53": "0x0000000000000000000000000000000000000000000000000000000069df4d3f",
"0x0000000000000000000000000000000000000000000000000000000000003c69": "0x243336e45e38a2400511acbc53589a8a1fe7dd276b6272b16283d37dc989b5c6",
"0x0000000000000000000000000000000000000000000000000000000000003c70": "0xee161bec87a2edd7c31359aeee6a6c930e91cae989bcd435b95b3a8cc7d5c90e",
"0x0000000000000000000000000000000000000000000000000000000000003c80": "0x4157fb55d925575e0a65cf3ea5e3a4cd90e922b8c2975aa5c6f88af59c40f0b5",
"0x0000000000000000000000000000000000000000000000000000000000001c46": "0x0000000000000000000000000000000000000000000000000000000069df4d32",
"0x0000000000000000000000000000000000000000000000000000000000003c7f": "0xeb658dc683ea0eb59bb173da8e7b9af83f995c985f0903a46fe0e6d04837cd13",
"0x0000000000000000000000000000000000000000000000000000000000003c47": "0x9c40a8751b7cdbad144703bebdf21481b74511242327e93947e68e0f662ff378",
"0x0000000000000000000000000000000000000000000000000000000000003c79": "0x0f91f9132544315209475f70b9b6af439d8619f70f4b773d4f21f1d948105d59",
"0x0000000000000000000000000000000000000000000000000000000000003c74": "0x326b2a8ace2548867a306eac120199c12d9036920a77142b440aee7543761c06",
"0x0000000000000000000000000000000000000000000000000000000000001c7a": "0x0000000000000000000000000000000000000000000000000000000069df4d66",
"0x0000000000000000000000000000000000000000000000000000000000001c76": "0x0000000000000000000000000000000000000000000000000000000069df4d62",
"0x0000000000000000000000000000000000000000000000000000000000003c53": "0x37586171b244f4089478d2859897a6ce5906bc9b4a8fe339f626ba8c2e8bcf98",
"0x0000000000000000000000000000000000000000000000000000000000003c5e": "0xefe34ee15fb8cf6a6dc8eab8f21458061cae825ab2771f198c65496541e96fce",
"0x0000000000000000000000000000000000000000000000000000000000001c55": "0x0000000000000000000000000000000000000000000000000000000069df4d41",
"0x0000000000000000000000000000000000000000000000000000000000003c50": "0x7ebcd753dc0236d6458267d62ee4339f8143fdfb65b35215e40a721b3b160f46",
"0x0000000000000000000000000000000000000000000000000000000000003c75": "0x5e1fca0fb3fc2c195560dc07bab212d2330b0bdc4af726f58372f37675b0e47f",
"0x0000000000000000000000000000000000000000000000000000000000003c90": "0xa79d36b30c791f251cee1a4c05c813ca3a4083dad3f4e922904a0b6789f318e7",
"0x0000000000000000000000000000000000000000000000000000000000001c58": "0x0000000000000000000000000000000000000000000000000000000069df4d44",
"0x0000000000000000000000000000000000000000000000000000000000003c4f": "0xb48197e5871632d04eecfee9d1806f8972b3f68b3203b05692e004e9f038a1ed",
"0x0000000000000000000000000000000000000000000000000000000000003c7a": "0x9cab2c51ef058f6b60b3164cc5f7406b4e03e6d7d7215ee61997b8c2dff63c33",
"0x0000000000000000000000000000000000000000000000000000000000003c93": "0xd07aae25fae01b8c5c9454519a1e3a2de88503dbdd6d194cb65774295137f46e",
"0x0000000000000000000000000000000000000000000000000000000000003c84": "0x1f3d19bca9a483a4762c812457b1f351796bb2a853065b7abcfc0489712b1d64",
"0x0000000000000000000000000000000000000000000000000000000000001c8c": "0x0000000000000000000000000000000000000000000000000000000069df4d78",
"0x0000000000000000000000000000000000000000000000000000000000003c51": "0x1da6a3bc67fd7c414f3a3f394f02cd1e08f0868081b07e6e273ce8f356a9b8f5",
"0x0000000000000000000000000000000000000000000000000000000000003c68": "0xc4607996107f63f7054ad39b2d168e857d750821adc2dce463878751a97456e2",
"0x0000000000000000000000000000000000000000000000000000000000001c44": "0x0000000000000000000000000000000000000000000000000000000069df4d30",
"0x0000000000000000000000000000000000000000000000000000000000003c77": "0x5b0a25e6ee8ddd9552f2abb25ab5b2d598716642faaa773297823ceff74d3550",
"0x0000000000000000000000000000000000000000000000000000000000003c6d": "0xb549e5f47dc22bb73c5db41255ae81fdc770c1a49f8c70fcaeb97094d60eb7c9",
"0x0000000000000000000000000000000000000000000000000000000000001c63": "0x0000000000000000000000000000000000000000000000000000000069df4d4f",
"0x0000000000000000000000000000000000000000000000000000000000001c6c": "0x0000000000000000000000000000000000000000000000000000000069df4d58",
"0x0000000000000000000000000000000000000000000000000000000000001c86": "0x0000000000000000000000000000000000000000000000000000000069df4d72",
"0x0000000000000000000000000000000000000000000000000000000000003c7c": "0x2af78dc4f346aae3e465f7ecba02bdb50bb7bfd158a5cf3a9246f1cc3f97b9a2",
"0x0000000000000000000000000000000000000000000000000000000000003c61": "0x6e5773ba502916bbd888d1b282da2c4b8a417f5c7f889fffe60c2c4d19351a4a",
"0x0000000000000000000000000000000000000000000000000000000000001c57": "0x0000000000000000000000000000000000000000000000000000000069df4d43",
"0x0000000000000000000000000000000000000000000000000000000000003c73": "0xce147a5345cd6786f77b27d86d0f7c706de68dd7106749febcf5eade0e7867f5",
"0x0000000000000000000000000000000000000000000000000000000000001c85": "0x0000000000000000000000000000000000000000000000000000000069df4d71",
"0x0000000000000000000000000000000000000000000000000000000000003c88": "0x0b4f60fbf7bdd16015f9003c5dcac5c14a589be6a74c8ee8ca0d203ba0de2332",
"0x0000000000000000000000000000000000000000000000000000000000003c76": "0x7baf7770c2cddb1d8701e1551b80c33fddfbe90da3b3168f881dbd1f5e79027c",
"0x0000000000000000000000000000000000000000000000000000000000003c7d": "0xbf835c49ed48d9c062f969d6d4f731fe0e51e38f55c4a801d5bf109222b47558",
"0x0000000000000000000000000000000000000000000000000000000000003c64": "0x92e9eca15014b1cfb110f8b147e92b4d2ef6d3f85f00a093d87f4df4ab7a2825",
"0x0000000000000000000000000000000000000000000000000000000000003c72": "0xabe92a0f5934d7b8cdfa622697c8745cd2b1e58e45e8f84f439d79537cfb6b85",
"0x0000000000000000000000000000000000000000000000000000000000003c4b": "0xa81c961864bbbee55c4c123cbe844081b0c0882f0568aef394ecf47e34c52333",
"0x0000000000000000000000000000000000000000000000000000000000003c5a": "0xe7021b86d7da56cf45890c79816c3ca592e43ef01bdf4aabbcab2103a4ca1299",
"0x0000000000000000000000000000000000000000000000000000000000003c66": "0x40226b7b3d351b4b86c654398c326405a5fb092a987b829fe88f87b14f0517bb",
"0x0000000000000000000000000000000000000000000000000000000000003c5b": "0x7b7db36c264c533573ecc6bed38757c13880924563ccb6e7f55ba958b1b8b87b",
"0x0000000000000000000000000000000000000000000000000000000000001c82": "0x0000000000000000000000000000000000000000000000000000000069df4d6e",
"0x0000000000000000000000000000000000000000000000000000000000001c65": "0x0000000000000000000000000000000000000000000000000000000069df4d51",
"0x0000000000000000000000000000000000000000000000000000000000001c54": "0x0000000000000000000000000000000000000000000000000000000069df4d40",
"0x0000000000000000000000000000000000000000000000000000000000001c84": "0x0000000000000000000000000000000000000000000000000000000069df4d70",
"0x0000000000000000000000000000000000000000000000000000000000003c4d": "0x68a07a55270e78eb16124c39e99147c5b6db6572435473388e6b963eb56e9383",
"0x0000000000000000000000000000000000000000000000000000000000003c4e": "0x4182bb30af054cdb6442fea146406f22a967f467e818327ef2c41b27ed746431",
"0x0000000000000000000000000000000000000000000000000000000000001c94": "0x0000000000000000000000000000000000000000000000000000000069df4d80",
"0x0000000000000000000000000000000000000000000000000000000000003c6c": "0x33a4971ec631e6e7e13441632c57a74a18842a4c4ea2cf40317d7feff6414ee5",
"0x0000000000000000000000000000000000000000000000000000000000003c78": "0xea22f79155e547b4b7bdd14cdac5e19529e2a6901053d802c3b803417552ab34",
"0x0000000000000000000000000000000000000000000000000000000000003c82": "0xa4580a48e51c4ca25f2b13fcae16bab5fca452b758170343d301ddc4efffac75",
"0x0000000000000000000000000000000000000000000000000000000000001c6e": "0x0000000000000000000000000000000000000000000000000000000069df4d5a",
"0x0000000000000000000000000000000000000000000000000000000000001c75": "0x0000000000000000000000000000000000000000000000000000000069df4d61",
"0x0000000000000000000000000000000000000000000000000000000000001c80": "0x0000000000000000000000000000000000000000000000000000000069df4d6c",
"0x0000000000000000000000000000000000000000000000000000000000003c46": "0xe27293471113958e527f358046b25e447e031908e11e236e71695cfc8453856f",
"0x0000000000000000000000000000000000000000000000000000000000003c60": "0xaeba59953edfc34a3e3f935e53dc8c2444c1c5641e362ee6598736c4b61f2a9b",
"0x0000000000000000000000000000000000000000000000000000000000003c67": "0xb4cf29dc20fb16d4c0b1f66dace09f39a914b4b7aa788c0ac31a9b5c4d3cb55c",
"0x0000000000000000000000000000000000000000000000000000000000001c88": "0x0000000000000000000000000000000000000000000000000000000069df4d74",
"0x0000000000000000000000000000000000000000000000000000000000001c8e": "0x0000000000000000000000000000000000000000000000000000000069df4d7a",
"0x0000000000000000000000000000000000000000000000000000000000003c87": "0x0438dc07962830fdfed99ec4151d3ccf3d8b9e14594c1a833542b64921de592d",
"0x0000000000000000000000000000000000000000000000000000000000003c91": "0xa9ff927a8f85a34f06a21022c4d7de1db5b640e853dbfa90d62dbabea3fc29f5",
"0x0000000000000000000000000000000000000000000000000000000000001c4a": "0x0000000000000000000000000000000000000000000000000000000069df4d36",
"0x0000000000000000000000000000000000000000000000000000000000001c91": "0x0000000000000000000000000000000000000000000000000000000069df4d7d",
"0x0000000000000000000000000000000000000000000000000000000000003c5d": "0xf622017e6108b1a5c35da244761b1142591f5a8dd2f880544d0b0f85bc4398d6",
"0x0000000000000000000000000000000000000000000000000000000000003c8e": "0x37fcb93fff4295dddf03a8ed032efc3ef7d16c7d81eea7b87ca0b2afa7410eab",
"0x0000000000000000000000000000000000000000000000000000000000001c5b": "0x0000000000000000000000000000000000000000000000000000000069df4d47",
"0x0000000000000000000000000000000000000000000000000000000000001c7e": "0x0000000000000000000000000000000000000000000000000000000069df4d6a",
"0x0000000000000000000000000000000000000000000000000000000000001c62": "0x0000000000000000000000000000000000000000000000000000000069df4d4e",
"0x0000000000000000000000000000000000000000000000000000000000001c8d": "0x0000000000000000000000000000000000000000000000000000000069df4d79",
"0x0000000000000000000000000000000000000000000000000000000000001c52": "0x0000000000000000000000000000000000000000000000000000000069df4d3e",
"0x0000000000000000000000000000000000000000000000000000000000001c71": "0x0000000000000000000000000000000000000000000000000000000069df4d5d",
"0x0000000000000000000000000000000000000000000000000000000000003c62": "0x916d77895ea279507167cd0d3bf65c74051f1a8e85a104a3ff989fbbbd1151bc",
"0x0000000000000000000000000000000000000000000000000000000000001c5e": "0x0000000000000000000000000000000000000000000000000000000069df4d4a",
"0x0000000000000000000000000000000000000000000000000000000000001c5f": "0x0000000000000000000000000000000000000000000000000000000069df4d4b",
"0x0000000000000000000000000000000000000000000000000000000000003c65": "0xf65538433fde7b3158dbf2d634c798b384a53eba14a62cda68bddc2783bdcafb",
"0x0000000000000000000000000000000000000000000000000000000000001c74": "0x0000000000000000000000000000000000000000000000000000000069df4d60",
"0x0000000000000000000000000000000000000000000000000000000000003c45": "0x261ad5ec5c30f3cbfbe6a9f32e2ad7485eecc6d144daf9b2ddcad68c33d3bea5",
"0x0000000000000000000000000000000000000000000000000000000000003c7b": "0xa595f0f00dd764876ecd9846163af010d235317346935990c336ec21e7282c73",
"0x0000000000000000000000000000000000000000000000000000000000003c89": "0x8727bf15b8a6a190f9c242fb65b33c89a51bb405dd7f377bc2abeb3061b8d3a7",
"0x0000000000000000000000000000000000000000000000000000000000001c45": "0x0000000000000000000000000000000000000000000000000000000069df4d31",
"0x0000000000000000000000000000000000000000000000000000000000003c8a": "0x24ee175fc7617f66a0406232aeaba922b4243797e31aafb0d0415f992b8cbbe7",
"0x0000000000000000000000000000000000000000000000000000000000001c6b": "0x0000000000000000000000000000000000000000000000000000000069df4d57",
"0x0000000000000000000000000000000000000000000000000000000000001c83": "0x0000000000000000000000000000000000000000000000000000000069df4d6f",
"0x0000000000000000000000000000000000000000000000000000000000003c52": "0x8b72a22a6e4f07d87729c5a463107e15a8a9719ee068c6bea1d86ca69fd922e3",
"0x0000000000000000000000000000000000000000000000000000000000001c61": "0x0000000000000000000000000000000000000000000000000000000069df4d4d",
"0x0000000000000000000000000000000000000000000000000000000000003c55": "0x064a239f759b1e184772efa4cca9217a66c2d42397c695ea1c7eef3518b7a3fb",
"0x0000000000000000000000000000000000000000000000000000000000001c50": "0x0000000000000000000000000000000000000000000000000000000069df4d3c",
"0x0000000000000000000000000000000000000000000000000000000000001c67": "0x0000000000000000000000000000000000000000000000000000000069df4d53",
"0x0000000000000000000000000000000000000000000000000000000000003c4a": "0x42cc1d9cfaa4687d1807b56d68978bfc681a1af02b35b11820ccc2b334bb64d6",
"0x0000000000000000000000000000000000000000000000000000000000003c8f": "0x397a3108125ae125ae884760ef43b2a74daa5589392e53ce64ab2e252fc7444a",
"0x0000000000000000000000000000000000000000000000000000000000003c43": "0x81c3802681862bd02666fc033341496ff510f480fae99e272c11f988fa3783cc",
"0x0000000000000000000000000000000000000000000000000000000000003c5c": "0xc639519f9dfd92a11808e79c2f98eb2922d588c6a2fc7211184fd9591ce90755",
"0x0000000000000000000000000000000000000000000000000000000000001c66": "0x0000000000000000000000000000000000000000000000000000000069df4d52",
"0x0000000000000000000000000000000000000000000000000000000000003c63": "0x4e95ccb4cf676b1f88e8e4e7a0d52f736fa51869e7a6c5aee574c38feba9a60a",
"0x0000000000000000000000000000000000000000000000000000000000003c6f": "0xdc0deca8f866e49cfb3db40fe808725250ff159a500fd45984e18a4c06c1283e",
"0x0000000000000000000000000000000000000000000000000000000000001c72": "0x0000000000000000000000000000000000000000000000000000000069df4d5e"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"33": {
"address": "0x9A676e781A523b5d0C0e43731313A708CB607508",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65",
"0xedc9a600799bdec0e14ee5042ef794e1b4738abf52d225059d6b470373867218": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000a85233c63b9ee964add6f2cffe00fd84eb32338f",
"0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x00000000000000000000000000000000000000000000000000000000000000cb": "0x0000000000000000000000009965507d1a55bcc2695c58ba16fb37d819b0a4dc"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"12": {
"address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"code": "0x735fbdb2315678afecb367f032d93f642f64180aa33014608060405260043610610034575f3560e01c8063e5bad8da14610038575b5f5ffd5b61004b610046366004610256565b61005f565b604051901515815260200160405180910390f35b5f80610079610073368590038501856102fc565b85610104565b90506001600160a01b03851663a401662b8261009860c087018761039b565b8760e001356040518563ffffffff1660e01b81526004016100bc94939291906103e8565b602060405180830381865afa1580156100d7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100fb919061042c565b95945050505050565b81515f90819060f81b61013c8560200151600881811c62ff00ff1663ff00ff009290911b9190911617601081811c91901b1760e01b90565b85604001516101b287606001515f65ff000000ff00600883811b91821664ff000000ff9185901c91821617601090811b67ff000000ff0000009390931666ff000000ff00009290921691909117901c17602081811b6bffffffffffffffff000000001691901c63ffffffff161760c01b92915050565b6080880151600881811b63ff00ff001662ff00ff9290911c9190911617601081811b91901c1760e01b60a08901516040516001600160f81b031990961660208701526001600160e01b0319948516602187015260258601939093526001600160c01b0319909116604585015291909116604d83015260518201526071810184905260910160408051808303601f190181529190528051602090910120949350505050565b5f5f5f60608486031215610268575f5ffd5b83356001600160a01b038116811461027e575f5ffd5b925060208401359150604084013567ffffffffffffffff8111156102a0575f5ffd5b840161010081870312156102b2575f5ffd5b809150509250925092565b803560ff811681146102cd575f5ffd5b919050565b803563ffffffff811681146102cd575f5ffd5b803567ffffffffffffffff811681146102cd575f5ffd5b5f60c082840312801561030d575f5ffd5b5060405160c0810167ffffffffffffffff8111828210171561033d57634e487b7160e01b5f52604160045260245ffd5b604052610349836102bd565b8152610357602084016102d2565b602082015260408381013590820152610372606084016102e5565b6060820152610383608084016102d2565b608082015260a0928301359281019290925250919050565b5f5f8335601e198436030181126103b0575f5ffd5b83018035915067ffffffffffffffff8211156103ca575f5ffd5b6020019150600581901b36038213156103e1575f5ffd5b9250929050565b84815260606020820181905281018390525f6001600160fb1b0384111561040d575f5ffd5b8360051b80866080850137604083019390935250016080019392505050565b5f6020828403121561043c575f5ffd5b8151801515811461044b575f5ffd5b939250505056fea2646970667358221220202aa32fed9b1043addbc14e73c73106e521b4a0cbd4090b88a279664688a3ca64736f6c634300081c0033000000000000000000000000",
"storage": {}
},
"13": {
"address": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1",
"code": "0x608060405234801561000f575f5ffd5b5060043610610055575f3560e01c80633659cfe6146100595780635c60da1b1461006e578063715018a6146100975780638da5cb5b1461009f578063f2fde38b146100af575b5f5ffd5b61006c6100673660046102d7565b6100c2565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006c610109565b5f546001600160a01b031661007b565b61006c6100bd3660046102d7565b61011c565b6100ca61019a565b6100d3816101f3565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b61011161019a565b61011a5f610288565b565b61012461019a565b6001600160a01b03811661018e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61019781610288565b50565b5f546001600160a01b0316331461011a5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610185565b6001600160a01b0381163b6102665760405162461bcd60e51b815260206004820152603360248201527f5570677261646561626c65426561636f6e3a20696d706c656d656e746174696f6044820152721b881a5cc81b9bdd08184818dbdb9d1c9858dd606a1b6064820152608401610185565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156102e7575f5ffd5b81356001600160a01b03811681146102fd575f5ffd5b939250505056fea2646970667358221220003d7f443094069cb023dc39fb36d6ba29922db6cd9b714ea95af972fc56405e64736f6c634300081c003300000000000000",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000059b670e9fa9d0a427751af201d676719a970857b"
}
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
},
"0": {
"address": "0x00000961Ef480Eb55e80D19ad83579A64c007002",
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd00",
"storage": {}
},
"44": {
"address": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf",
"code": "0x60806040526004361061021d575f3560e01c8063805ce31d1161011e578063b39053c5116100a8578063d58a8be41161006d578063d58a8be4146106cb578063df4ed829146106de578063f2e500b2146106fd578063f906d30914610710578063fe61cc491461072f575f5ffd5b8063b39053c5146105f9578063be8d42c014610618578063c536218f1461066e578063c66414c51461068d578063c9bd1e5b146106ac575f5ffd5b806390ffc4f9116100ee57806390ffc4f914610561578063928bc49d14610594578063988062ea146105b357806398ea5fca146105d2578063b0a23d44146105da575f5ffd5b8063805ce31d146104d55780638450a97c146104f7578063860929ee146105165780638ce2e33914610542575f5ffd5b80633ae65d7e116101aa57806346cd27511161016f57806346cd27511461045157806352054834146104705780635c60da1b146104835780635e6dae26146104975780636a64d9fb146104b6575f5ffd5b80633ae65d7e1461038a5780633f8bb4d9146103a9578063423e69b6146103c857806342e3ccfa14610413578063439fab9114610432575f5ffd5b806327c1d325116101f057806327c1d325146102cb5780632a6c3229146102ea5780632dd677b1146103295780632fb8ac581461034857806338004f6914610367575f5ffd5b80630705f4651461022157806309824a80146102565780630b6176461461026b57806326aa101f1461029c575b5f5ffd5b34801561022c575f5ffd5b5061024061023b3660046129ee565b61074e565b60405161024d9190612a19565b60405180910390f35b610269610264366004612a47565b6107c8565b005b348015610276575f5ffd5b5061027f610843565b604080519283526001600160801b0390911660208301520161024d565b3480156102a7575f5ffd5b506102bb6102b6366004612a47565b6108b7565b604051901515815260200161024d565b3480156102d6575f5ffd5b506102696102e5366004612aa6565b610933565b3480156102f5575f5ffd5b506103096103043660046129ee565b6109bb565b604080516001600160401b0393841681529290911660208301520161024d565b348015610334575f5ffd5b50610269610343366004612aa6565b610a39565b348015610353575f5ffd5b50610269610362366004612ae4565b610a91565b348015610372575f5ffd5b505f516020613ae35f395f51905f525460ff16610240565b348015610395575f5ffd5b506102696103a4366004612aa6565b610b3e565b3480156103b4575f5ffd5b506102696103c3366004612aa6565b610bb8565b3480156103d3575f5ffd5b506103fb7f0000000000000000000000000e801d84fa97b50751dbf25036d067dcf18858bf81565b6040516001600160a01b03909116815260200161024d565b34801561041e575f5ffd5b5061026961042d366004612aa6565b610c10565b34801561043d575f5ffd5b5061026961044c366004612aa6565b610c68565b34801561045c575f5ffd5b5061026961046b366004612aa6565b610ca1565b61026961047e366004612b50565b610d1b565b34801561048e575f5ffd5b506103fb610da1565b3480156104a2575f5ffd5b506103fb6104b13660046129ee565b610dcf565b3480156104c1575f5ffd5b506102696104d0366004612ae4565b610dd9565b3480156104e0575f5ffd5b506104e9610e33565b60405190815260200161024d565b348015610502575f5ffd5b50610269610511366004612aa6565b610e9f565b348015610521575f5ffd5b5061052a610f19565b6040516001600160401b03909116815260200161024d565b34801561054d575f5ffd5b5061026961055c366004612c2f565b610f85565b34801561056c575f5ffd5b506103fb7f00000000000000000000000099bba657f2bbc93c02d617f8ba121cb8fc104acf81565b34801561059f575f5ffd5b506104e96105ae366004612ccb565b6110fd565b3480156105be575f5ffd5b506102696105cd366004612aa6565b61119c565b6102696111f4565b3480156105e5575f5ffd5b506102696105f4366004612aa6565b61122e565b348015610604575f5ffd5b506102696106133660046129ee565b611286565b348015610623575f5ffd5b506104e9610632366004612a47565b6001600160a01b03165f9081527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e602052604090206001015490565b348015610679575f5ffd5b50610269610688366004612aa6565b6112e8565b348015610698575f5ffd5b506102bb6106a7366004612d32565b611340565b3480156106b7575f5ffd5b506102696106c6366004612aa6565b611389565b6102696106d9366004612d5b565b6113e1565b3480156106e9575f5ffd5b506102696106f8366004612db4565b611438565b61026961070b366004612e3f565b61191f565b34801561071b575f5ffd5b5061026961072a366004612aa6565b6119ac565b34801561073a575f5ffd5b506103fb6107493660046129ee565b611a04565b604051630705f46560e01b8152600481018290525f9073e7f1725e7734ce288f8367e1bb143e90bb3f051290630705f46590602401602060405180830381865af415801561079e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107c29190612f00565b92915050565b5f5c156107d3575f5ffd5b60015f5d6040516213049560e71b81526001600160a01b038216600482015273e7f1725e
"storage": {}
},
"47": {
"address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
"code": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c806346fbf68e1461004e5780638568520614610085578063ce5484281461009a578063eab66d7a146100ad575b5f5ffd5b61007061005c36600461027a565b5f6020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b61009861009336600461029a565b6100d8565b005b6100986100a836600461027a565b610111565b6001546100c0906001600160a01b031681565b6040516001600160a01b03909116815260200161007c565b6001546001600160a01b031633146101035760405163794821ff60e01b815260040160405180910390fd5b61010d8282610148565b5050565b6001546001600160a01b0316331461013c5760405163794821ff60e01b815260040160405180910390fd5b610145816101cf565b50565b6001600160a01b03821661016f576040516339b190bb60e11b815260040160405180910390fd5b6001600160a01b0382165f8181526020818152604091829020805460ff19168515159081179091558251938452908301527f65d3a1fd4c13f05cba164f80d03ce90fb4b5e21946bfc3ab7dbd434c2d0b9152910160405180910390a15050565b6001600160a01b0381166101f6576040516339b190bb60e11b815260040160405180910390fd5b600154604080516001600160a01b03928316815291831660208301527f06b4167a2528887a1e97a366eefe8549bfbf1ea3e6ac81cb2564a934d20e8892910160405180910390a1600180546001600160a01b0319166001600160a01b0392909216919091179055565b80356001600160a01b0381168114610275575f5ffd5b919050565b5f6020828403121561028a575f5ffd5b6102938261025f565b9392505050565b5f5f604083850312156102ab575f5ffd5b6102b48361025f565b9150602083013580151581146102c8575f5ffd5b80915050925092905056fea2646970667358221220d968f6e7b0fa23955f1f9580db081bbf816d7e99d0b3ab6d1bf5c644ea927f8d64736f6c634300081c003300",
"storage": {
"0x723077b8a1b173adc35e5f0e7e3662fd1208212cb629f9c128551ea7168da722": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x14e04a66bf74771820a7400ff6cf065175b3d7eb25805a5bd1633b161af5d101": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc"
}
},
"37": {
"address": "0xac06641381166cf085281c45292147f833C622d7",
"code": "0x608060405260043610610036575f3560e01c8063338c5371146100415780639bb66b2814610091578063e905182a146100be575f5ffd5b3661003d57005b5f5ffd5b34801561004c575f5ffd5b506100747f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f8168881565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561009c575f5ffd5b506100b06100ab3660046101ae565b6100ff565b604051610088929190610239565b3480156100c9575f5ffd5b506100f17f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610088565b5f6060336001600160a01b037f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f81688161461014a576040516282b42960e81b815260040160405180910390fd5b846001600160a01b03168484604051610164929190610277565b5f60405180830381855af49150503d805f811461019c576040519150601f19603f3d011682016040523d82523d5f602084013e6101a1565b606091505b5091509150935093915050565b5f5f5f604084860312156101c0575f5ffd5b83356001600160a01b03811681146101d6575f5ffd5b9250602084013567ffffffffffffffff8111156101f1575f5ffd5b8401601f81018613610201575f5ffd5b803567ffffffffffffffff811115610217575f5ffd5b866020828401011115610228575f5ffd5b939660209190910195509293505050565b8215158152604060208201525f82518060408401528060208501606085015e5f606082850101526060601f19601f8301168401019150509392505050565b818382375f910190815291905056fea26469706673582212208fe760f358faedf4a90fd4b23c39c8397def11c5b035ea1406af976ecc426bbf64736f6c634300081c00330000000000",
"storage": {}
},
"19": {
"address": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
"code": "0x608060405234801561000f575f5ffd5b5060043610610148575f3560e01c8063a1060c88116100bf578063dce974b911610079578063dce974b914610334578063df5cf7231461035b578063ec76f44214610382578063f2fde38b146103b5578063f698da25146103c8578063fabc1cbc146103d0575f5ffd5b8063a1060c881461029a578063a364f4da146102ad578063a98fb355146102c0578063c825fe68146102d3578063cd6dc687146102fa578063d79aceab1461030d575f5ffd5b80635ac86ab7116101105780635ac86ab7146101fa5780635c975abb1461021d578063715018a61461022f578063886f1195146102375780638da5cb5b146102765780639926ee7d14610287575f5ffd5b8063136439dd1461014c578063374823b51461016157806349075da3146101a357806354fd4d50146101dd578063595c6a67146101f2575b5f5ffd5b61015f61015a3660046110dc565b6103e3565b005b61018e61016f366004611107565b609960209081525f928352604080842090915290825290205460ff1681565b60405190151581526020015b60405180910390f35b6101d06101b1366004611131565b609860209081525f928352604080842090915290825290205460ff1681565b60405161019a919061117c565b6101e561041d565b60405161019a91906111d0565b61015f61044d565b61018e6102083660046111e9565b606654600160ff9092169190911b9081161490565b6066545b60405190815260200161019a565b61015f610461565b61025e7f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b6040516001600160a01b03909116815260200161019a565b6033546001600160a01b031661025e565b61015f610295366004611277565b610472565b6102216102a8366004611364565b610673565b61015f6102bb3660046113a7565b6106f2565b61015f6102ce3660046113c2565b6107b9565b6102217f809c5ac049c45b7a7f050a20f00c16cf63797efbf8b1eb8d749fdfa39ff8f92981565b61015f610308366004611107565b610800565b6102217fda2c89bafdd34776a2b8bb9c83c82f419e20cc8c67207f70edd58249b92661bd81565b6102217f4ee65f64218c67b68da66fd0db16560040a6b973290b9e71912d661ee53fe49581565b61025e7f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd8281565b61015f6103903660046110dc565b335f90815260996020908152604080832093835292905220805460ff19166001179055565b61015f6103c33660046113a7565b61091c565b610221610995565b61015f6103de3660046110dc565b610a4e565b6103eb610ab4565b60665481811681146104105760405163c61dca5d60e01b815260040160405180910390fd5b61041982610b57565b5050565b60606104487f76312e302e300000000000000000000000000000000000000000000000000006610b94565b905090565b610455610ab4565b61045f5f19610b57565b565b610469610bd1565b61045f5f610c2b565b5f61047c81610c7c565b6001335f9081526098602090815260408083206001600160a01b038816845290915290205460ff1660018111156104b5576104b5611168565b036104d357604051631aa528bb60e11b815260040160405180910390fd5b6001600160a01b0383165f90815260996020908152604080832085830151845290915290205460ff161561051a57604051630d4c4c9160e21b815260040160405180910390fd5b6040516336b87bd760e11b81526001600160a01b0384811660048301527f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd821690636d70f7ae90602401602060405180830381865afa15801561057e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105a29190611430565b6105bf57604051639f88c8af60e01b815260040160405180910390fd5b6105e3836105d7853386602001518760400151610673565b84516040860151610ca7565b6001600160a01b0383165f81815260996020908152604080832086830151845282528083208054600160ff19918216811790925533808652609885528386208787529094529382902080549094168117909355519092917ff0952b1c65271d819d39983d2abb044b9cace59bcc4d4dd389f586ebdcb15b4191610666919061117c565b60405180910390a3505050565b604080517fda2c89bafdd34776a2b8bb9c83c82f419e20cc8c67207f70edd58249b92661bd60208201526001600160a01b038087169282019290925290841660608201526080810183905260a081018290525f906106e99060c00160405160208183030381529060405280519060200120610cff565b95945050505050565b5f6106fc81610c7c565b6001335f9081526098602090815260408083206001600160a01b038716845290915290205460ff16600181111561073557610735611168565b14610753576040516352df45c960e01b815260040160405180910390fd5b335f8181526098602090815260408083206001600160a01b0387168085529252808320805460ff191690555190917ff0952b1c65271d819d39983d2abb044b9cace59bcc4d4dd389f586ebdcb15b41916107ad919061117c565b60405180910390a35050565b336001600160a01b03167fa89c1dc243d8908a96dd84944bcc97d6bc6ac00dd78e20621576be6a3c94371383836040516107f492919061144f565b604051
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
}
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
},
"14": {
"address": "0xBeaAFDA2E17fC95E69Dc06878039d274E0d2B21A",
"code": "0x608060405260043610610036575f3560e01c8063338c5371146100415780639bb66b2814610091578063e905182a146100be575f5ffd5b3661003d57005b5f5ffd5b34801561004c575f5ffd5b506100747f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f8168881565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561009c575f5ffd5b506100b06100ab3660046101ae565b6100ff565b604051610088929190610239565b3480156100c9575f5ffd5b506100f17f03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c11131481565b604051908152602001610088565b5f6060336001600160a01b037f0000000000000000000000009d4454b023096f34b160d6b654540c56a1f81688161461014a576040516282b42960e81b815260040160405180910390fd5b846001600160a01b03168484604051610164929190610277565b5f60405180830381855af49150503d805f811461019c576040519150601f19603f3d011682016040523d82523d5f602084013e6101a1565b606091505b5091509150935093915050565b5f5f5f604084860312156101c0575f5ffd5b83356001600160a01b03811681146101d6575f5ffd5b9250602084013567ffffffffffffffff8111156101f1575f5ffd5b8401601f81018613610201575f5ffd5b803567ffffffffffffffff811115610217575f5ffd5b866020828401011115610228575f5ffd5b939660209190910195509293505050565b8215158152604060208201525f82518060408401528060208501606085015e5f606082850101526060601f19601f8301168401019150509392505050565b818382375f910190815291905056fea26469706673582212208fe760f358faedf4a90fd4b23c39c8397def11c5b035ea1406af976ecc426bbf64736f6c634300081c00330000000000",
"storage": {}
},
"38": {
"address": "0x59b670e9fA9D0A427751Af201D676719a970857b",
"code": "0x6080604052600436106101bd575f3560e01c80636691954e116100f2578063b522538a11610092578063d06d558711610062578063d06d55871461063f578063dda3346c1461065e578063ee94d67c1461067d578063f074ba621461069c575f5ffd5b8063b522538a146105ce578063c44e30dc146105ed578063c490744214610601578063c4d66de814610620575f5ffd5b80637439841f116100cd5780637439841f1461053457806374cdd7981461056957806388676cad1461059c5780639b4e4634146105bb575f5ffd5b80636691954e146104d65780636c0d2d5a146104e95780636fcd0e5314610508575f5ffd5b806342ecff2a1161015d57806352396a591161013857806352396a591461043657806354fd4d501461046a578063587533571461048b57806358eaee79146104aa575f5ffd5b806342ecff2a146102f25780634665bcda1461031857806347d283721461034b575f5ffd5b80632340e8d3116101985780632340e8d31461027a5780633474aa161461028f5780633f5fa57a146102c05780633f65cf19146102d3575f5ffd5b8063039157d2146101fb5780630b18ff661461021c5780631e51553314610258575f5ffd5b366101f7576040513481527f6fdd3dbdb173299608c0aa9f368735857c8842b581f8389238bf05bd04b3bf499060200160405180910390a1005b5f5ffd5b348015610206575f5ffd5b5061021a610215366004613a3e565b6106bb565b005b348015610227575f5ffd5b5060335461023b906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610263575f5ffd5b5061026c6109f0565b60405190815260200161024f565b348015610285575f5ffd5b5061026c60395481565b34801561029a575f5ffd5b506034546001600160401b03165b6040516001600160401b03909116815260200161024f565b61021a6102ce366004613af9565b610a11565b3480156102de575f5ffd5b5061021a6102ed366004613b37565b610d49565b3480156102fd575f5ffd5b50603a546102a890600160401b90046001600160401b031681565b348015610323575f5ffd5b5061023b7f000000000000000000000000959922be3caee4b8cd9a407cc3ac1c251c2007b181565b348015610356575f5ffd5b506103db6040805160a0810182525f80825260208201819052918101829052606081018290526080810191909152506040805160a081018252603c548152603d5462ffffff811660208301526001600160401b0363010000008204811693830193909352600160581b810460070b6060830152600160981b9004909116608082015290565b60405161024f91905f60a0820190508251825262ffffff60208401511660208301526001600160401b036040840151166040830152606083015160070b60608301526001600160401b03608084015116608083015292915050565b348015610441575f5ffd5b506102a8610450366004613c0e565b603b6020525f90815260409020546001600160401b031681565b348015610475575f5ffd5b5061047e610fd2565b60405161024f9190613c57565b348015610496575f5ffd5b50603e5461023b906001600160a01b031681565b3480156104b5575f5ffd5b506104c96104c4366004613ca6565b610ffd565b60405161024f9190613d0c565b61021a6104e4366004613af9565b61105f565b3480156104f4575f5ffd5b5061026c610503366004613c0e565b611361565b348015610513575f5ffd5b50610527610522366004613d1a565b61146f565b60405161024f9190613d31565b34801561053f575f5ffd5b506104c961054e366004613d1a565b5f90815260366020526040902054600160c01b900460ff1690565b348015610574575f5ffd5b5061023b7f000000000000000000000000c7f2cf4845c6db0e1a1e91ed41bcd0fcc1b0e14181565b3480156105a7575f5ffd5b5061021a6105b6366004613d91565b61151a565b61021a6105c9366004613dac565b61160f565b3480156105d9575f5ffd5b506105276105e8366004613ca6565b61178d565b3480156105f8575f5ffd5b5061026c61187c565b34801561060c575f5ffd5b5061021a61061b366004613e41565b611898565b34801561062b575f5ffd5b5061021a61063a366004613e6b565b6119cf565b34801561064a575f5ffd5b5061021a610659366004613e6b565b611b19565b348015610669575f5ffd5b5061021a610678366004613f56565b611bad565b348015610688575f5ffd5b50603a546102a8906001600160401b031681565b3480156106a7575f5ffd5b5061021a6106b6366004614028565b611d0c565b604051635ac86ab760e01b8152600660048201819052907f000000000000000000000000959922be3caee4b8cd9a407cc3ac1c251c2007b16001600160a01b031690635ac86ab790602401602060405180830381865afa158015610721573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610745919061408f565b156107635760405163840a48d560e01b815260040160405180910390fd5b604051635ac86ab760e01b8152600860048201819052907f000000000000000000000000959922be3caee4b8cd9a407cc3ac1c251c2007b16001600160a01b031690635ac86ab790602401602060405180830381865afa1580156107c9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107ed919061408f565b1561080b576040516384
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"9": {
"address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707",
"code": "0x735fc8d32690cc91d4c39d9d3abcbd16989f875707301460806040526004361061006b575f3560e01c8063017b73111461006f578063253946451461009057806365529675146100af5780638257f3d5146100ce578063ae8a4d98146100ed578063fe1aa59d1461010c575b5f5ffd5b81801561007a575f5ffd5b5061008e61008936600461080c565b61012b565b005b81801561009b575f5ffd5b5061008e6100aa36600461080c565b610155565b8180156100ba575f5ffd5b5061008e6100c9366004610860565b6101db565b8180156100d9575f5ffd5b5061008e6100e836600461080c565b610267565b8180156100f8575f5ffd5b5061008e61010736600461080c565b6102f8565b818015610117575f5ffd5b5061008e6101263660046108ae565b610328565b5f610138828401846109a7565b9050610150815f0151826020015183604001516103a5565b505050565b5f61016282840184610a68565b80516020820151604080840151905163a3499c7360e01b8152939450732279b7a0a67db372996a5fab50d91eaa73d2ebe69363a3499c73936101aa9390929091600401610b1b565b5f6040518083038186803b1580156101c0575f5ffd5b505af41580156101d2573d5f5f3e3d5ffd5b50505050505050565b5f6101e882840184610b4a565b90505f6102147f81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79610414565b82519091506001600160a01b0316610248576102438582846020015185604001516001600160801b031661046c565b610260565b6102608582845f0151856020015186604001516104c9565b5050505050565b5f61027482840184610b7b565b80517e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab805492935091829060ff1916600183818111156102b5576102b5610bc9565b021790555081516040517f4016a1377b8961c4aa6f3a2d3de830a685ddbfe0f228ffc0208eb96304c4cf1a916102ea91610bdd565b60405180910390a150505050565b5f61030582840184610c03565b9050610322815f015182602001518360400151846060015161052e565b50505050565b5f61033582840184610cbd565b90505f61034186610414565b90505f825f01518360200151846040015160405160240161036493929190610d45565b60408051601f198184030181529190526020810180516001600160e01b031663c6b295c160e01b179052905061039b828783610679565b5050505050505050565b5f6103af84610705565b6040516340c10f1960e01b81526001600160a01b0385811660048301526001600160801b0385166024830152919250908216906340c10f19906044015f604051808303815f87803b158015610402575f5ffd5b505af115801561039b573d5f5f3e3d5ffd5b5f8181527e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ad60205260409020546001600160a01b0316806104675760405163d3227c9b60e01b815260040160405180910390fd5b919050565b6040516001600160a01b0383166024820152604481018290525f9060640160408051601f198184030181529190526020810180516001600160e01b03166305b1137b60e01b17905290506104c1848683610679565b505050505050565b6040516001600160a01b038085166024830152831660448201526001600160801b03821660648201525f9060840160408051601f198184030181529190526020810180516001600160e01b03166309733b7b60e21b17905290506101d2858783610679565b5f8481527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23260205260408120547f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e906001600160a01b0316156105a457604051633ea7ffd960e11b815260040160405180910390fd5b5f8585856040516105b4906107bb565b6105c093929190610d78565b604051809103905ff0801580156105d9573d5f5f3e3d5ffd5b50604080518082018252600180825260208083018c81525f8d815260048901835285812080546001600160a01b0319166001600160a01b038916908117909155808252898452908690208551815460ff19169015151781559151919093015592519081529293509189917f57f58171b8777633d03aff1e7408b96a3d910c93a7ce433a8cb7fb837dc306a6910160405180910390a2509695505050505050565b60605f5f856001600160a01b0316639bb66b2886866040518363ffffffff1660e01b81526004016106ab929190610db0565b5f604051808303815f875af11580156106c6573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106ed9190810190610ddb565b915091506106fb8282610796565b9695505050505050565b5f8181527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23260205260408120547f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e906001600160a01b031661077a5760405163259ba1ad60e01b815260040160405180910390fd5b5f9283526004016020525060409020546001600160a01b031690565b606082156107a55750806107b5565b81511561006b5781518083602001fd5b92915050565b610c3580610e6883390190565b5f5f83601f8401126107d8575f5ffd5b5081356001600160401b038111156107ee575f5ffd5b602083019150836020
"storage": {}
},
"34": {
"address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000009635f643e140090a9a8dcd712ed6285858cebef",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x00000000000000000000000015d34aaf54267db7d7c367839aaf71a00a2c6a65",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x00000000000000000000000000000000000000000000000000000000000000cb": "0x000003e80000000000001c2090f79bf6eb2c4f870365e785982e1f101e93b906"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
"40": {
"address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"code": "0x73dc64a140aa3e981100a9beca4e685f962f0cf6c93014608060405260043610610090575f3560e01c8063741fad8811610063578063741fad88146101125780638257f3d514610131578063ae8a4d9814610150578063c31308d11461016f575f5ffd5b80630c86ea461461009457806325394645146100b55780635b2e9c4c146100d457806365529675146100f3575b5f5ffd5b81801561009f575f5ffd5b506100b36100ae366004610a3a565b61018e565b005b8180156100c0575f5ffd5b506100b36100cf366004610a3a565b610229565b8180156100df575f5ffd5b506100b36100ee366004610a3a565b6102af565b8180156100fe575f5ffd5b506100b361010d366004610a8f565b610337565b81801561011d575f5ffd5b506100b361012c366004610adf565b6103aa565b81801561013c575f5ffd5b506100b361014b366004610a3a565b610433565b81801561015b575f5ffd5b506100b361016a366004610a3a565b6104c4565b81801561017a575f5ffd5b506100b3610189366004610a8f565b6104ee565b7f59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f65f6101bc84840185610baf565b8051835560208101516001840180546fffffffffffffffffffffffffffffffff19166001600160801b039092169190911790556040808201516002850155519091507f5e3c25378b5946068b94aa2ea10c4c1e215cc975f994322b159ddc9237a973d4905f90a150505050565b5f61023682840184610c6d565b80516020820151604080840151905163a3499c7360e01b8152939450732279b7a0a67db372996a5fab50d91eaa73d2ebe69363a3499c739361027e9390929091600401610d22565b5f6040518083038186803b158015610294575f5ffd5b505af41580156102a6573d5f5f3e3d5ffd5b50505050505050565b7f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e5f6102dd84840185610d51565b805160208201516001600160801b03908116600160801b0291161760028401556040808201516003850155519091507f4793c0cb5bef4b1fdbbfbcf17e06991844eb881088b012442af17a12ff38d5cd905f90a150505050565b5f61034482840184610d86565b90505f610353825f01516105c4565b60208301519091506001600160a01b031661038a576103858582846040015185606001516001600160801b031661061c565b6103a3565b6103a38582846020015185604001518660600151610679565b5050505050565b60408051637061726160e01b602080830191909152607d60e31b602483015282516008818403018152602890920190925280519101206103eb9084906106de565b15610408576040516282b42960e81b815260040160405180910390fd5b5f61041582840184610de4565b905061042d815f0151826020015183604001516106e9565b50505050565b7e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ab5f61046084840185610e2f565b8051835491925090839060ff19166001838181111561048157610481610e7d565b021790555080516040517f4016a1377b8961c4aa6f3a2d3de830a685ddbfe0f228ffc0208eb96304c4cf1a916104b691610e91565b60405180910390a150505050565b5f6104d182840184610eb7565b905061042d815f0151826020015183604001518460600151610762565b5f6104fb82840184610f71565b90505f61050a825f01516105c4565b90508160200151515f03610531576040516309e256f760e21b815260040160405180910390fd5b5f5f836020015180602001905181019061054b919061104f565b90925090505f82801561056057610560610e7d565b036102a6575f5f5f8380602001905181019061057c919061109f565b919450925090506001600160a01b0383166105ab576105a68a8784846001600160801b031661061c565b6105b8565b6105b88a87858585610679565b50505050505050505050565b5f8181527e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ad60205260409020546001600160a01b0316806106175760405163d3227c9b60e01b815260040160405180910390fd5b919050565b6040516001600160a01b0383166024820152604481018290525f9060640160408051601f198184030181529190526020810180516001600160e01b03166305b1137b60e01b17905290506106718486836108ad565b505050505050565b6040516001600160a01b038085166024830152831660448201526001600160801b03821660648201525f9060840160408051601f198184030181529190526020810180516001600160e01b03166309733b7b60e21b17905290506102a68587836108ad565b818114155b92915050565b5f6106f384610939565b6040516340c10f1960e01b81526001600160a01b0385811660048301526001600160801b0385166024830152919250908216906340c10f19906044015f604051808303815f87803b158015610746575f5ffd5b505af1158015610758573d5f5f3e3d5ffd5b5050505050505050565b5f8481527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23260205260408120547f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c22e906001600160a01b0316156107d857604051633ea7ffd960e11b815260040160405180910390fd5b5f8585856040516107e8906109e9565b6107f4939291906110e9565b604051
"storage": {}
},
"10": {
"address": "0xf5059a5D33d5853360D16C683c16e67980206f36",
"code": "0x608060405234801561000f575f5ffd5b5060043610610187575f3560e01c80637a8b2637116100d9578063c4d66de811610093578063df6fadc11161006e578063df6fadc114610361578063e3dae51c1461037c578063f3e738751461038f578063fabc1cbc146103a2575f5ffd5b8063c4d66de814610328578063ce7c2ac21461033b578063d9caed121461034e575f5ffd5b80637a8b2637146102ad578063886f1195146102c05780638c871019146102e75780638f6a6240146102fa578063a6ab36f21461030d578063ab5921e114610320575f5ffd5b806347e7ef2411610144578063595c6a671161011f578063595c6a67146102655780635ac86ab71461026d5780635c975abb1461029c57806361b01b5d146102a4575f5ffd5b806347e7ef241461022a57806354fd4d501461023d578063553ca5f814610252575f5ffd5b806311c70c9d1461018b578063136439dd146101a05780632495a599146101b357806339b70e38146101e35780633a98ef391461020a57806343fe08b014610221575b5f5ffd5b61019e6101993660046111b1565b6103b5565b005b61019e6101ae3660046111d1565b6103cb565b6032546101c6906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101c67f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb60750881565b61021360335481565b6040519081526020016101da565b61021360645481565b6102136102383660046111fc565b610401565b610245610530565b6040516101da9190611226565b61021361026036600461125b565b610560565b61019e610573565b61028c61027b36600461128b565b6001805460ff9092161b9081161490565b60405190151581526020016101da565b600154610213565b61021360655481565b6102136102bb3660046111d1565b610587565b6101c67f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b6102136102f53660046111d1565b6105d0565b61021361030836600461125b565b6105da565b61019e61031b3660046112a6565b6105e7565b6102456106c2565b61019e61033636600461125b565b6106e2565b61021361034936600461125b565b6107a8565b61021361035c3660046112dc565b61083a565b606454606554604080519283526020830191909152016101da565b61021361038a3660046111d1565b61093c565b61021361039d3660046111d1565b610973565b61019e6103b03660046111d1565b61097d565b6103bd6109ea565b6103c78282610a9b565b5050565b6103d3610b3f565b60015481811681146103f85760405163c61dca5d60e01b815260040160405180910390fd5b6103c782610be2565b5f5f61040c81610c1f565b336001600160a01b037f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb6075081614610455576040516348da714f60e01b815260040160405180910390fd5b61045f8484610c55565b6033545f61046f6103e88361132e565b90505f6103e861047d610cac565b610487919061132e565b90505f6104948783611341565b9050806104a18489611354565b6104ab919061136b565b9550855f036104cd57604051630c392ed360e11b815260040160405180910390fd5b6104d7868561132e565b60338190556f4b3b4ca85a86c47a098a223fffffffff101561050c57604051632f14e8a360e11b815260040160405180910390fd5b610525826103e8603354610520919061132e565b610d16565b505050505092915050565b606061055b7f76312e302e300000000000000000000000000000000000000000000000000006610d62565b905090565b5f61056d6102bb836107a8565b92915050565b61057b610b3f565b6105855f19610be2565b565b5f5f6103e8603354610599919061132e565b90505f6103e86105a7610cac565b6105b1919061132e565b9050816105be8583611354565b6105c8919061136b565b949350505050565b5f61056d8261093c565b5f61056d61039d836107a8565b5f54610100900460ff161580801561060557505f54600160ff909116105b8061061e5750303b15801561061e57505f5460ff166001145b6106435760405162461bcd60e51b815260040161063a9061138a565b60405180910390fd5b5f805460ff191660011790558015610664575f805461ff0019166101001790555b61066e8484610a9b565b61067782610d9f565b80156106bc575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b60606040518060800160405280604d815260200161145b604d9139905090565b5f54610100900460ff161580801561070057505f54600160ff909116105b806107195750303b15801561071957505f5460ff166001145b6107355760405162461bcd60e51b815260040161063a9061138a565b5f805460ff191660011790558015610756575f805461ff0019166101001790555b61075f82610d9f565b80156103c7575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b60405163fe243a1760e01b81526001600160a01b0382811660048301523060248301525f917f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb6075089091169063fe243a17
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
}
},
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"5": {
"address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
"code": "0x738a791620dd6260079bf849dc5567adc3f2fdc3183014608060405260043610610090575f3560e01c8063ab55562e11610063578063ab55562e146100fd578063af18d14214610105578063c82b5f451461010d578063ded905d514610115575f5ffd5b80632db726161461009457806379d0e91c146100bc5780637cb1a954146100e05780639ce504ff146100f6575b5f5ffd5b6100a76100a23660046109da565b61011c565b60405190151581526020015b60405180910390f35b6100c7600160f81b81565b6040516001600160f81b031990911681526020016100b3565b6100e8600581565b6040519081526020016100b3565b6100c75f81565b6100e8600681565b6100e8600481565b6100e8600881565b6100e85f81565b5f6101318461012b8580610a64565b84610278565b61013c57505f61026f565b6101496020840184610a82565b6020013583806020019061015d9190610a82565b351061016a57505f61026f565b5f61017e866101798680610a64565b610419565b90505f6101c7826101926020880188610a82565b356101a06020890189610a82565b602001358880602001906101b49190610a82565b6101c2906040810190610a96565b610434565b90505f6101e56101df36889003880160408901610b1d565b836104da565b90506001600160a01b03891663a401662b826102056101008a018a610a96565b8a61012001356040518563ffffffff1660e01b815260040161022a9493929190610bbc565b602060405180830381865afa158015610245573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102699190610c00565b93505050505b95945050505050565b5f5f82610285575f61028b565b600160f81b5b90505f5b61029c6080860186610a96565b905081101561040c575f6102b36080870187610a96565b838181106102c3576102c3610c1b565b90506020028101906102d59190610a82565b3514801561032157506102eb6080860186610a96565b828181106102fb576102fb610c1b565b905060200281019061030d9190610a82565b61031b906040810190610c2f565b90506021145b801561039657506001600160f81b031982166103406080870187610a96565b8381811061035057610350610c1b565b90506020028101906103629190610a82565b610370906040810190610c2f565b5f81811061038057610380610c1b565b9050013560f81c60f81b6001600160f81b031916145b80156103f457506103aa6080860186610a96565b828181106103ba576103ba610c1b565b90506020028101906103cc9190610a82565b6103da906040810190610c2f565b6103e8916001908290610c72565b6103f191610c99565b86145b1561040457600192505050610412565b60010161028f565b505f9150505b9392505050565b5f610424838361062c565b8051906020012090505b92915050565b5f85815b838110156104cf57866001166001148061045457508587600101145b1561048b5761048485858381811061046e5761046e610c1b565b90506020020135835f9182526020526040902090565b91506104b9565b6104b6828686848181106104a1576104a1610c1b565b905060200201355f9182526020526040902090565b91505b600196871c965f19909601861c86019501610438565b509695505050505050565b81515f90819060f81b6105128560200151600881811c62ff00ff1663ff00ff009290911b9190911617601081811c91901b1760e01b90565b856040015161058887606001515f65ff000000ff00600883811b91821664ff000000ff9185901c91821617601090811b67ff000000ff0000009390931666ff000000ff00009290921691909117901c17602081811b6bffffffffffffffff000000001691901c63ffffffff161760c01b92915050565b6080880151600881811b63ff00ff001662ff00ff9290911c9190911617601081811b91901c1760e01b60a08901516040516001600160f81b031990961660208701526001600160e01b0319948516602187015260258601939093526001600160c01b0319909116604585015291909116604d83015260518201526071810184905260910160408051808303601f190181529190528051602090910120949350505050565b60605f823561063e60208501356106b8565b6040850135606086013561065d6106586080890189610a96565b6106e8565b604051602001610671959493929190610ccd565b60405160208183030381529060405290508361068d82516106b8565b826040516020016106a093929190610d00565b60405160208183030381529060405291505092915050565b606063ffffffff8211156106df57604051637404cccd60e11b815260040160405180910390fd5b61042e82610778565b60408051602081019091525f808252606091905b8381101561075c578161073186868481811061071a5761071a610c1b565b905060200281019061072c9190610a82565b6108d3565b604051602001610742929190610d24565b60408051601f1981840301815291905291506001016106fc565b50610766836106b8565b816040516020016106a0929190610d24565b6060603f8263ffffffff16116107b557604051603f60fa1b60fa84901b1660208201526021015b6040516020818303038152906040529050919050565b613fff8263ffffffff1611610814576107f16107dd6403fffffffc600285901b166001610d3a565b600881811b62ffff001691901c60ff161790565b604051602001
"storage": {}
},
"7": {
"address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
"code": "0x73a513e6e4b8f2a923d98304ec87f64353c4d5c853301460806040526004361061009b575f3560e01c806338412ce51161006e57806338412ce514610150578063480ff0651461016f5780636f378c061461018e578063957cae98146101ad578063c7f62387146101c0575f5ffd5b806319a79b481461009f5780631b8d43b0146100c057806320606b70146100f457806330adf81f14610129575b5f5ffd5b8180156100aa575f5ffd5b506100be6100b9366004610a4d565b6101df565b005b8180156100cb575f5ffd5b506100df6100da366004610acf565b610346565b60405190151581526020015b60405180910390f35b61011b7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b6040519081526020016100eb565b61011b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b81801561015b575f5ffd5b506100df61016a366004610b10565b61036b565b81801561017a575f5ffd5b506100be610189366004610b10565b610384565b818015610199575f5ffd5b506100df6101a8366004610b10565b6103cf565b61011b6101bb366004610b43565b6103dc565b8180156101cb575f5ffd5b506100be6101da366004610b10565b6103ec565b834211156102005760405163068568f360e21b815260040160405180910390fd5b5f61020a8961042d565b6001600160a01b0389165f90815260028c016020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928c928c928c9290919061025983610b6e565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810187905260e001604051602081830303815290604052805190602001206040516020016102d292919061190160f01b81526002810192909252602282015260420190565b6040516020818303038152906040528051906020012090505f6102f7828686866104dc565b9050886001600160a01b0316816001600160a01b03161461032b57604051638baa579f60e01b815260040160405180910390fd5b6103398b8a8a8a6001610502565b5050505050505050505050565b5f610353858533856105f0565b506103608585858561067c565b506001949350505050565b5f61037a843385856001610502565b5060019392505050565b5f6001600160a01b0383166103bd57604051639cfea58360e01b81526001600160a01b0390911660048201526024015b60405180910390fd5b506103ca835f84846106f8565b505050565b5f61037a8433858561067c565b5f6103e68261042d565b92915050565b5f6001600160a01b038316610420576040516313053d9360e21b81526001600160a01b0390911660048201526024016103b4565b506103ca83835f846106f8565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8260405161045d9190610b86565b60408051918290038220828201825260018352603160f81b6020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c001604051602081830303815290604052805190602001209050919050565b5f5f5f6104eb87878787610829565b915091506104f8816108e6565b5095945050505050565b5f6001600160a01b038516610536576040516322f051b160e21b81526001600160a01b0390911660048201526024016103b4565b505f6001600160a01b03841661056b5760405163270af7ed60e11b81526001600160a01b0390911660048201526024016103b4565b506001600160a01b038085165f908152600187016020908152604080832093871683529290522082905580156105e957826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516105e091815260200190565b60405180910390a35b5050505050565b6001600160a01b038084165f90815260018601602090815260408083209386168352929052908120545f198114610670578381848082101561065e57604051630c95cf2760e11b81526001600160a01b039093166004840152602483019190915260448201526064016103b4565b5050506106708686868685035f610502565b50600195945050505050565b5f6001600160a01b0384166106b0576040516313053d9360e21b81526001600160a01b0390911660048201526024016103b4565b505f6001600160a01b0383166106e557604051639cfea58360e01b81526001600160a01b0390911660048201526024016103b4565b506106f2848484846106f8565b50505050565b6001600160a01b0383166107245780846003015f8282546107199190610c22565b9091555061079a9050565b6001600160a01b0383165f90815260208590526040902054838183808210156107795760405163db42144d60e01b81526001600160a01b039093166004840152602483019190915260448201526064016103b4565b5050506001600160a01b0384165f9081526020869052604090209082900390555b6001600160a01b0382166107b85760038401805482900390556107d6565b6001600160a01b0382165f9081526020859052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068
"storage": {}
},
"24": {
"address": "0x0000BBdDc7CE488642fb579F8B00f3a590007251",
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd00",
"storage": {}
},
"30": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000033": "0x000000000000000000000000976ea74026e726554db657fa54763abd0c3a0aa9",
"0x0000000000000000000000000000000000000000000000000000000000000067": "0x0000000000000000000000009d4454b023096f34b160d6b654540c56a1f81688",
"0x000000000000000000000000000000000000000000000000000000000000006a": "0x000000000000000000000000976ea74026e726554db657fa54763abd0c3a0aa9",
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0xf028937c64180bb2d245c1eb4b6ebc3d79a092cccfbb2b854f9d7b4da63470b6": "0x0000000000000000000000000000000000000000000000000000000000000001",
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000036c02da8a0983159322a80ffe9f24b1acff8b570",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788",
"0x000000000000000000000000000000000000000000000000000000000000006c": "0x302e312e3000000000000000000000000000000000000000000000000000000a",
"0x0000000000000000000000000000000000000000000000000000000000000065": "0x000000000000000000000000ac06641381166cf085281c45292147f833c622d7"
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
}
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
},
"16": {
"address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"code": "0x73e7f1725e7734ce288f8367e1bb143e90bb3f0512301460806040526004361061009b575f3560e01c80632a6c32291161006e5780632a6c322914610191578063805ce31d146101c5578063928bc49d146101db57806399056fcc146101ee578063fe61cc491461020d575f5ffd5b80630705f4651461009f57806309824a80146100c85780630b617646146100e957806326aa101f1461014a575b5f5ffd5b6100b26100ad366004611fdf565b61026c565b6040516100bf919061200a565b60405180910390f35b8180156100d3575f5ffd5b506100e76100e236600461203a565b610282565b005b7f59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f6547f59ef95eb9983b1a4650e1bc666384b8507689fc8aca3edd429d7e07c0ca9d2f754604080519283526001600160801b039091166020830152016100bf565b61018161015836600461203a565b6001600160a01b03165f9081525f5160206125e85f395f51905f52602052604090205460ff1690565b60405190151581526020016100bf565b6101a461019f366004611fdf565b61033b565b6040805167ffffffffffffffff9384168152929091166020830152016100bf565b6101cd61036a565b6040519081526020016100bf565b6101cd6101e936600461207c565b610380565b8180156101f9575f5ffd5b506100e76102083660046120bc565b6103e0565b61025461021b366004611fdf565b5f9081527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23260205260409020546001600160a01b031690565b6040516001600160a01b0390911681526020016100bf565b5f5f61027783610494565b5460ff169392505050565b5f5160206125e85f395f51905f52610299826104ef565b60408051608081019091526001820154600160a01b900463ffffffff1681525f90602081016102c6610586565b815260028401546020909101906102e79086906001600160801b03166105e7565b81525f6020918201526040516001600160a01b03861681529192507ff78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7e910160405180910390a161033681610632565b505050565b5f5f5f61034784610494565b5467ffffffffffffffff6101008204811696600160481b90920416945092505050565b5f61037b610376610586565b610836565b905090565b6001600160a01b0383165f9081525f5160206125e85f395f51905f52602081905260408220805460ff166103c75760405163259ba1ad60e01b815260040160405180910390fd5b6103d461037686866108c7565b925050505b9392505050565b5f5160206125e85f395f51905f526001600160801b0382165f036104175760405163162908e360e11b815260040160405180910390fd5b6001600160a01b0387165f908152602082905260409020805460ff166104505760405163259ba1ad60e01b815260040160405180910390fd5b60018101546104745761046f61046a8989898989896109ae565b610632565b61048a565b61048a61046a82600101548a8a8a8a8a8a610b97565b5050505050505050565b5f8181527e96e2f02350077f4ff1746770dbe5db3c04b7db2c8763c8fc21bf66b35e96ac6020526040902060018101546001600160a01b03166104ea57604051636ddd9da960e01b815260040160405180910390fd5b919050565b610501816001600160a01b0316610cf9565b61051e5760405163c1ab6dc160e01b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206125e85f395f51905f5260208190526040909120805460ff168015610559575061055981610d3f565b1561057757604051633ea7ffd960e11b815260040160405180910390fd5b805460ff191660011790555050565b604080518082019091527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c230546001600160801b031681527f8d3b47662f045c362f825b520d7ddf7a0e5f6703a828606de6840b3652b8c23154602082015290565b60605f6105f346610d53565b5f6105fd86610dbc565b61060686610ded565b60405160200161061a959493929190612161565b60405160208183030381529060405290505b92915050565b805160408051637061726160e01b60208083019190915260e09390931b6001600160e01b031916602482015281516008818303018152602890910190915280519101205f61067f82610494565b905061068a81610e8c565b5f6106988460200151610836565b90505f84606001516001600160801b0316826106b491906121c4565b9050803410156106d757604051631c0b171360e31b815260040160405180910390fd5b60608501516001600160801b0316156107225761072285606001516001600160801b031661070f5f5160206125e85f395f51905f5290565b600101546001600160a01b031690610f08565b825461074090600160481b900467ffffffffffffffff1660016121d7565b835467ffffffffffffffff91909116600160481b0270ffffffffffffffff000000000000000000199091161783555f61077982346121f7565b9050610783610f31565b811115610794576107943382610f08565b83546040805160208101889052600160481b90920460c01b6001600160c01b031916908201525f9060480160405160208183030381529060405280519060200120905080867f7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b057
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {}
},
"3": {
"address": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
"code": "0x608060405234801561000f575f5ffd5b50600436106103b3575f3560e01c8063886f1195116101f5578063de02e50311610114578063f6efbb59116100a9578063fabc1cbc11610079578063fabc1cbc14610a33578063fbf1e2c114610a46578063fce36c7d14610a59578063ff9f6cce14610a6c575f5ffd5b8063f6efbb59146109e7578063f74e8eac146109fa578063f8cd844814610a0d578063f96abf2e14610a20575f5ffd5b8063ed71e6a2116100e4578063ed71e6a214610967578063f22cef8514610994578063f2f07ab4146109a7578063f2fde38b146109d4575f5ffd5b8063de02e50314610907578063e063f81f1461091a578063e810ce211461092d578063ea4d3c9b14610940575f5ffd5b8063a50a1d9c1161018a578063bf21a8aa1161015a578063bf21a8aa14610879578063c46db606146108a0578063ca8aa7c7146108cd578063dcbb03b3146108f4575f5ffd5b8063a50a1d9c14610807578063aebd8bae1461081a578063b3dbb0e014610847578063bb7e451f1461085a575f5ffd5b80639cb9a5fa116101c55780639cb9a5fa146107a75780639d45c281146107ba5780639de4b35f146107e1578063a0169ddd146107f4575f5ffd5b8063886f11951461074c5780638da5cb5b146107735780639104c319146107845780639be3d4e41461079f575f5ffd5b80634596021c116102e15780635c975abb11610276578063715018a611610246578063715018a6146106ff5780637b8f8b0514610707578063863cb9a91461070f578063865c695314610722575f5ffd5b80635c975abb146106a25780635e9d8348146106aa57806363f6a798146106bd5780636d21117e146106d2575f5ffd5b806354fd4d50116102b157806354fd4d501461064f57806358baaa3e14610664578063595c6a67146106775780635ac86ab71461067f575f5ffd5b80634596021c146105d85780634657e26a146105eb5780634b943960146106125780634d18cc3514610638575f5ffd5b8063149bc8721161035757806339b70e381161032757806339b70e38146105745780633a8c07861461059b5780633ccc861d146105b25780633efe1db6146105c5575f5ffd5b8063149bc872146104d95780632b9f64a4146104fa57806336af41fa1461053a57806337838ed01461054d575f5ffd5b80630e9a53cf116103925780630e9a53cf1461043f5780630eb383451461048c578063131433b41461049f578063136439dd146104c6575f5ffd5b806218572c146103b757806304a0c502146103ee5780630ca298991461042a575b5f5ffd5b6103d96103c5366004613a33565b60d16020525f908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6104157f0000000000000000000000000000000000000000000000000000000000278d0081565b60405163ffffffff90911681526020016103e5565b61043d610438366004613aab565b610a7f565b005b610447610d25565b6040516103e591905f6080820190508251825263ffffffff602084015116602083015263ffffffff604084015116604083015260608301511515606083015292915050565b61043d61049a366004613b07565b610e25565b6104157f0000000000000000000000000000000000000000000000000000000065fb788081565b61043d6104d4366004613b3e565b610ea5565b6104ec6104e7366004613b55565b610edf565b6040519081526020016103e5565b610522610508366004613a33565b60cc6020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016103e5565b61043d610548366004613b6f565b610f54565b6104157f000000000000000000000000000000000000000000000000000000000076a70081565b6105227f0000000000000000000000009a676e781a523b5d0c0e43731313a708cb60750881565b60cb5461041590600160a01b900463ffffffff1681565b61043d6105c0366004613bbe565b6110c5565b61043d6105d3366004613c14565b6110ec565b61043d6105e6366004613c3e565b6112c2565b6105227f0000000000000000000000003aa5ebb10dc797cac828524e59a333d0a371443c81565b610625610620366004613a33565b611325565b60405161ffff90911681526020016103e5565b60cb5461041590600160c01b900463ffffffff1681565b610657611380565b6040516103e59190613c90565b61043d610672366004613cc5565b6113b0565b61043d6113c4565b6103d961068d366004613cde565b606654600160ff9092169190911b9081161490565b6066546104ec565b6103d96106b8366004613cfe565b6113d8565b60cb5461062590600160e01b900461ffff1681565b6103d96106e0366004613d2f565b60cf60209081525f928352604080842090915290825290205460ff1681565b61043d611463565b60ca546104ec565b61043d61071d366004613a33565b611474565b6104ec610730366004613d59565b60cd60209081525f928352604080842090915290825290205481565b6105227f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b6033546001600160a01b0316610522565b61052273beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b610447611485565b61043d6107b5366004613d85565b611521565b6104157f000000000000000000000000000000000000000000000000000000000001518081565b6106256107ef366004613dbc565b61169c565b61043d
fix: 🩹 map validator address improvements (#460) ## Summary This is a follow up PR of #441, adding some proper error handling and removing inconsistent checks. ## Changes ### DataHavenServiceManager.sol 1. **updateSolochainAddressForValidator()** - Removed the unclear `existingEthOperator == msg.sender` check - Removed the `oldSolochainAddress != address(0)` guard on the `delete` (since `onlyValidator` guarantees the caller is registered and thus always has a solochain address set). - Added `require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned())` to reject no-op updates. - The delete of the old reverse mapping is now unconditional (safe because `oldSolochainAddress` is always non-zero for a registered validator). 2. **registerOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] == address(0), OperatorAlreadyRegistered())` to prevent re-registration. - Removed the old "update if already registered" logic (the `existingEthOperator == operator` branch and the `oldSolochainAddress` cleanup). - Simplified the solochain collision check to just `require(existingEthOperator == address(0), ...)`. 3. **deregisterOperator** - Added `require(validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered())`. - The delete of the reverse mapping is now unconditional (safe because the above check guarantees `oldSolochainAddress` is non-zero). 4. Added **OperatorAlreadyRegistered** and **OperatorNotRegistered** errors 5. Removed **_ethOperatorFromSolochain** helper — unknown solochain addresses no longer revert; they are now silently skipped via a `continue`. 6. **Rewards**: unknown operators are filtered out of the submission array 7. **Slashing**: unknown addresses in slashing requests are skipped instead of reverting ### Tests - Replaced `test_registerOperator_replacesSolochainAndClearsOldReverseMapping` (which tested the now-removed re-registration path) with `test_registerOperator_revertsIfAlreadyRegistered`. - Added `test_deregisterOperator_clearsBothMappings` — verifies both mappings are wiped on deregistration. - Added `test_deregisterOperator_revertsIfNotRegistered` — verifies the new OperatorNotRegistered guard. - Added `test_updateSolochainAddressForValidator_revertsIfSameAddress` — verifies the new same-address revert. - Replaced `revertsIfUnknownSolochainAddress` with `skipsUnknownSolochainAddress` variants that assert the skip-and-emit behavior.
2026-03-02 14:48:55 +00:00
"storage": {
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
feat: contracts upgrade command (#463) ## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 20:50:10 +00:00
}
},
"25": {
"address": "0xc5a5C42992dECbae36851359345FE25997F5C42d",
"code": "0x608060405234801561000f575f5ffd5b50600436106102ff575f3560e01c8063670d3ba211610195578063b2447af7116100e4578063db4df7611161009e578063f231bd0811610079578063f231bd08146107f7578063f605ce081461080a578063fabc1cbc1461081d578063fe4b84df14610830575f5ffd5b8063db4df76114610796578063dc2af692146107bd578063df5cf723146107d0575f5ffd5b8063b2447af714610708578063b66bd9891461071b578063b9fbaed11461072e578063ba1a84e51461075d578063c221d8ae14610770578063d3d96ff414610783575f5ffd5b8063886f11951161014f578063952899ee1161012a578063952899ee146106bc578063a9333ec8146106cf578063a9821821146106e2578063adc2e3d9146106f5575f5ffd5b8063886f1195146106625780638ce648541461068957806394d7d00c146106a9575f5ffd5b8063670d3ba2146105c45780636cfb4481146105d75780636e3492b5146106025780636e875dba1461061557806379ae50cd146106285780637bc1ef611461063b575f5ffd5b806340120dab1161025157806350feea201161020b57806356c483e6116101e657806356c483e61461057e578063595c6a67146105915780635ac86ab7146105995780635c975abb146105bc575f5ffd5b806350feea2014610543578063547afb871461055657806354fd4d5014610569575f5ffd5b806340120dab146104875780634177a87c146104a85780634657e26a146104c85780634a10ffe5146104ef5780634b5046ef1461050f5780634cfd293914610522575f5ffd5b8063261f84e0116102bc5780632bab2c4a116102975780632bab2c4a1461042d578063304c10cd1461044057806332a879e4146104535780633635205714610466575f5ffd5b8063261f84e0146103be5780632981eb77146103d15780632b453a9a1461040d575f5ffd5b80630f3df50e1461030357806310e1b9b8146103335780631352c3e614610353578063136439dd1461037657806315fe50281461038b578063260dc758146103ab575b5f5ffd5b610316610311366004614bf0565b610843565b6040516001600160a01b0390911681526020015b60405180910390f35b610346610341366004614c0a565b610884565b60405161032a9190614c51565b610366610361366004614c84565b6108bd565b604051901515815260200161032a565b610389610384366004614cb8565b610938565b005b61039e610399366004614ccf565b610972565b60405161032a9190614d4d565b6103666103b9366004614bf0565b610a89565b6103896103cc366004614d9f565b610aba565b6103f87f000000000000000000000000000000000000000000000000000000000000003281565b60405163ffffffff909116815260200161032a565b61042061041b366004614e84565b610b63565b60405161032a9190614f27565b61042061043b366004614f8a565b610b79565b61031661044e366004614ccf565b610c18565b61038961046136600461500e565b610c47565b61047961047436600461508e565b610d8e565b60405161032a9291906150e0565b61049a6104953660046150f8565b610ed1565b60405161032a929190615185565b6104bb6104b6366004614bf0565b61104c565b60405161032a91906151e2565b6103167f0000000000000000000000003aa5ebb10dc797cac828524e59a333d0a371443c81565b6105026104fd3660046151f4565b611070565b60405161032a9190615237565b61038961051d36600461500e565b611118565b610535610530366004614bf0565b6111ab565b60405190815260200161032a565b610389610551366004615282565b6111cd565b6105026105643660046152e0565b6112be565b610571611366565b60405161032a9190615322565b61038961058c366004615357565b611396565b61038961149b565b6103666105a7366004615381565b606654600160ff9092169190911b9081161490565b606654610535565b6103666105d2366004614c84565b6114af565b6105ea6105e53660046150f8565b6114db565b6040516001600160401b03909116815260200161032a565b6103896106103660046153b7565b6114f0565b6104bb610623366004614bf0565b6118b3565b61039e610636366004614ccf565b6118c4565b6103f87f000000000000000000000000000000000000000000000000000000000000000081565b6103167f000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e81565b61069c6106973660046153e8565b61199e565b60405161032a919061542b565b6105026106b736600461543d565b611a5a565b6103896106ca366004615498565b611b46565b6105ea6106dd3660046150f8565b611fe7565b6103896106f0366004615641565b612016565b6103896107033660046156bf565b6120c8565b610535610716366004614bf0565b612411565b610389610729366004615282565b612433565b61074161073c366004614ccf565b61258d565b60408051921515835263ffffffff90911660208301520161032a565b61053561076b366004614ccf565b612627565b6104bb61077e366004614c84565b612647565b6103896107913660046150f8565b612670565b6103167f000000000000000000000000c6e7df5e7b4f2a278906862b61205850344d4e7d81565b6103666107cb366004614ccf565b61279d565b6103167f0000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd8281565b
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000000000ff"
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
}
},
"28": {
"address": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
"code": "0x60806040523661001357610011610017565b005b6100115b61001f610168565b6001600160a01b0316330361015e5760606001600160e01b03195f35166364d3180d60e11b81016100595761005261019a565b9150610156565b63587086bd60e11b6001600160e01b0319821601610079576100526101ed565b63070d7c6960e41b6001600160e01b031982160161009957610052610231565b621eb96f60e61b6001600160e01b03198216016100b857610052610261565b63a39f25e560e01b6001600160e01b03198216016100d8576100526102a0565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b6101666102b3565b565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101a46102c3565b5f6101b23660048184610668565b8101906101bf91906106aa565b90506101da8160405180602001604052805f8152505f6102cd565b505060408051602081019091525f815290565b60605f806101fe3660048184610668565b81019061020b91906106d7565b9150915061021b828260016102cd565b60405180602001604052805f8152509250505090565b606061023b6102c3565b5f6102493660048184610668565b81019061025691906106aa565b90506101da816102f8565b606061026b6102c3565b5f610274610168565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b60606102aa6102c3565b5f61027461034f565b6101666102be61034f565b61035d565b3415610166575f5ffd5b6102d68361037b565b5f825111806102e25750805b156102f3576102f183836103ba565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610321610168565b604080516001600160a01b03928316815291841660208301520160405180910390a161034c816103e6565b50565b5f61035861048f565b905090565b365f5f375f5f365f845af43d5f5f3e808015610377573d5ff35b3d5ffd5b610384816104b6565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103df83836040518060600160405280602781526020016107e76027913961054a565b9392505050565b6001600160a01b03811661044b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161014d565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61018b565b6001600160a01b0381163b6105235760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161014d565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61046e565b60605f5f856001600160a01b031685604051610566919061079b565b5f60405180830381855af49150503d805f811461059e576040519150601f19603f3d011682016040523d82523d5f602084013e6105a3565b606091505b50915091506105b4868383876105be565b9695505050505050565b6060831561062c5782515f03610625576001600160a01b0385163b6106255760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161014d565b5081610636565b610636838361063e565b949350505050565b81511561064e5781518083602001fd5b8060405162461bcd60e51b815260040161014d91906107b1565b5f5f85851115610676575f5ffd5b83861115610682575f5ffd5b5050820193919092039150565b80356001600160a01b03811681146106a5575f5ffd5b919050565b5f602082840312156106ba575f5ffd5b6103df8261068f565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156106e8575f5ffd5b6106f18361068f565b9150602083013567ffffffffffffffff81111561070c575f5ffd5b8301601f8101851361071c575f5ffd5b803567ffffffffffffffff811115610736576107366106c3565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610765576107656106c3565b60405281815282820160200187101561077c575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c657665
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
"storage": {
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000067d269191c92caf3cd7723f116c85e6e9bf55933",
"0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad788"
feat: window-native rewards submission aligned to EigenLayer windows (#471) ## Summary Replace per-era rewards submission with EigenLayer-aligned window-based submission. Instead of submitting one rewards message per era, points are now accumulated into fixed-duration time-aligned windows as blocks are produced, and inflation is proportionally distributed across windows at era boundaries. Closed windows are submitted as `OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge. This ensures rewards submissions match EigenLayer's expected window boundaries (`GENESIS_REWARDS_TIMESTAMP` + N × `CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between Substrate era timing and EigenLayer reward windows. ### How rewards windows work **Points accumulation**: Each time `reward_by_ids()` is called (at session end), the current timestamp is snapped to the nearest EigenLayer-aligned window boundary. Points are written to `WindowOperatorPoints[window_start]`, so all sessions ending within the same window contribute to the same bucket. **Inflation allocation**: At era end, the era's scaled inflation is split across all windows that overlap with the era's time span, proportionally to the overlap duration: ``` overlap = min(era_end, window_end) - max(era_start, window_start) portion = scaled_inflation * overlap / era_duration ``` Integer division remainder is assigned to the last overlapping window to ensure conservation. **Submission**: `process_closed_windows()` iterates all fully elapsed windows starting from `NextWindowToSubmit`. For each closed window, if it has both points and inflation, a cross-chain message is sent via Snowbridge. Empty or zero-point windows are skipped. The pointer advances regardless of success or failure. ### Key changes #### Pallet (`external-validators-rewards`) - **Window-based point accumulation**: `reward_by_ids()` now writes operator points into `WindowOperatorPoints[window_start]` in addition to the existing per-era storage, where the window is determined by snapping the current timestamp to the nearest EigenLayer-aligned boundary - **Proportional inflation allocation**: At era end, `allocate_era_inflation_to_windows()` distributes the era's scaled inflation across overlapping windows based on time overlap, with rounding remainder assigned to the last window - **Closed window submission**: `process_closed_windows()` iterates fully elapsed windows, combines their points and inflation into a `RewardsPeriodUtils`, and sends the cross-chain message. A `NextWindowToSubmit` pointer ensures idempotent progression - **Removed legacy path**: The per-era `generate_era_rewards_utils` and direct submission in `on_era_end` are removed. The `rewards_duration()` method is removed from `RewardsSubmissionConfig` trait — duration now comes from `RewardsPeriodUtils` - **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and its fields are renamed to reflect window/period semantics (`era_index` → `period_index`, `era_start_timestamp` → `period_start`) - **New pallet config types**: `RewardsWindowGenesisTimestamp` (immutable EigenLayer genesis), `RewardsWindowDuration` (governance-tunable via `RewardsDuration` runtime param), and `UnixTime` - **Storage version bump**: 0 → 1 for the three new storage items - **New events**: `RewardsWindowSubmitted`, `RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces `RewardsMessageSent`) #### Contracts (`DataHavenServiceManager.sol`) - **Duplicate operator merge**: Replace `_sortOperatorRewards` with `_sortAndMergeDuplicateOperators` — after solochain-to-ETH address translation, multiple solochain addresses can map to the same ETH operator (e.g., operator deregistered and re-registered with a new solochain address within the same reward window). The new function sorts then merges consecutive duplicates by summing their amounts, satisfying EigenLayer's strict ascending uniqueness requirement. ### New storage items | Storage | Type | Purpose | |---------|------|---------| | `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window operator reward points | | `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated inflation | | `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to process | ### Out of scope (follow-up PRs) - **Translation robustness**: Retain `validatorSolochainAddressToEthAddress` on deregistration to prevent `UnknownSolochainAddress` reverts for delayed window submissions - **Retry-safe failure handling**: Preserve failed windows for replay - **Retained window snapshots**: Audit/replay support - **Benchmarks/weights**: Re-run pallet benchmarks for updated `on_era_end` weight ## Test plan - [x] Pallet unit tests pass (`cargo test -p pallet-external-validators-rewards` — 81 tests) - [x] `window_mode_attributes_points_to_aligned_window` — points land in correct aligned window - [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed window submission and pointer advancement - [x] `window_mode_era_inflation_split_across_multiple_windows` — pro-rata inflation splitting across 4 windows with remainder conservation - [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` — batch submission of multiple closed windows in one `on_era_end` - [x] `window_mode_delivery_failure_emits_submission_failed_event` — delivery failure emits `RewardsWindowSubmissionFailed`, clears storage, advances pointer - [x] Existing pallet tests updated to use window-based events (`RewardsWindowSubmitted`, `RewardsWindowSkipped`) - [x] Contract tests pass (`forge test --match-contract RewardsSubmitter` — 12 tests) - [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two solochain addresses mapping to same ETH operator are merged into single entry - [x] Runtime configs compile with new config types (mainnet/stagenet/testnet) - [x] E2E test with local network to verify cross-chain message reaches EigenLayer contracts --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 11:12:34 +00:00
}
}
}