datahaven/contracts/test/storage/StorageLayout.t.sol

197 lines
7.6 KiB
Solidity
Raw Permalink Normal View History

test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.27;
import {Test} from "forge-std/Test.sol";
import {
ITransparentUpgradeableProxy
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {AVSDeployer} from "../utils/AVSDeployer.sol";
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
/// @title Storage Layout Tests for DataHavenServiceManager
/// @notice Verifies that proxy upgrades preserve state correctly
contract StorageLayoutTest is AVSDeployer {
function setUp() public {
_deployMockEigenLayerAndAVS();
}
/// @notice Proves state is preserved across proxy upgrade
function test_upgradePreservesState() public {
// 1. Populate state
address testValidator = address(0x1234);
address newRewardsInitiator = address(0x9999);
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
address testSubmitter = address(0x5678);
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
vm.startPrank(avsOwner);
serviceManager.addValidatorToAllowlist(testValidator);
serviceManager.setRewardsInitiator(newRewardsInitiator);
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
serviceManager.setValidatorSetSubmitter(testSubmitter);
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
vm.stopPrank();
// 2. Record state before upgrade
bool allowlistBefore = serviceManager.validatorsAllowlist(testValidator);
address rewardsInitiatorBefore = serviceManager.rewardsInitiator();
address ownerBefore = serviceManager.owner();
address gatewayBefore = serviceManager.snowbridgeGateway();
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
address submitterBefore = serviceManager.validatorSetSubmitter();
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
// 3. Deploy new implementation
DataHavenServiceManager newImpl =
new DataHavenServiceManager(rewardsCoordinator, allocationManager);
// 4. Upgrade proxy
vm.prank(proxyAdminOwner);
proxyAdmin.upgrade(ITransparentUpgradeableProxy(address(serviceManager)), address(newImpl));
// 5. Verify state preserved
assertEq(
serviceManager.validatorsAllowlist(testValidator),
allowlistBefore,
"validatorsAllowlist should be preserved"
);
assertEq(
serviceManager.rewardsInitiator(),
rewardsInitiatorBefore,
"rewardsInitiator should be preserved"
);
assertEq(serviceManager.owner(), ownerBefore, "owner should be preserved");
assertEq(
serviceManager.snowbridgeGateway(),
gatewayBefore,
"snowbridgeGateway should be preserved"
);
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
assertEq(
serviceManager.validatorSetSubmitter(),
submitterBefore,
"validatorSetSubmitter should be preserved"
);
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
}
/// @notice Verifies validatorEthAddressToSolochainAddress mapping is preserved
function test_upgradePreservesValidatorMappings() public {
address testValidator = address(0xABCD);
address testSolochainAddress = address(0xDEF0);
// Add validator to allowlist first
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(testValidator);
// Register operator via allocationManager to set the solochain address mapping
uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = 0; // VALIDATORS_SET_ID
vm.prank(address(allocationManager));
serviceManager.registerOperator(
testValidator,
address(serviceManager),
operatorSetIds,
abi.encodePacked(testSolochainAddress)
);
// Record state before upgrade
bool inAllowlistBefore = serviceManager.validatorsAllowlist(testValidator);
address solochainAddressBefore =
serviceManager.validatorEthAddressToSolochainAddress(testValidator);
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
address ethOperatorBefore =
serviceManager.validatorSolochainAddressToEthAddress(testSolochainAddress);
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
// Verify the mapping was set correctly before upgrade
assertEq(solochainAddressBefore, testSolochainAddress, "Solochain address should be set");
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
assertEq(ethOperatorBefore, testValidator, "Eth operator should be set");
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
// Deploy new implementation and upgrade
DataHavenServiceManager newImpl =
new DataHavenServiceManager(rewardsCoordinator, allocationManager);
vm.prank(proxyAdminOwner);
proxyAdmin.upgrade(ITransparentUpgradeableProxy(address(serviceManager)), address(newImpl));
// Verify both mappings preserved after upgrade
assertEq(
serviceManager.validatorsAllowlist(testValidator),
inAllowlistBefore,
"validatorsAllowlist mapping should be preserved after upgrade"
);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(testValidator),
solochainAddressBefore,
"validatorEthAddressToSolochainAddress mapping should be preserved after upgrade"
);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(testValidator),
testSolochainAddress,
"validatorEthAddressToSolochainAddress should have correct value after upgrade"
);
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(testSolochainAddress),
ethOperatorBefore,
"validatorSolochainAddressToEthAddress mapping should be preserved after upgrade"
);
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(testSolochainAddress),
testValidator,
"validatorSolochainAddressToEthAddress should have correct value after upgrade"
);
test: ✨ Add storage layout checks for upgradeable contracts (#420) ## Summary Implements storage layout testing for the upgradeable `DataHavenServiceManager` contract to prevent state corruption during proxy upgrades. ## Changes ### New Files - **`contracts/storage-snapshots/DataHavenServiceManager.storage.json`** - Baseline storage layout snapshot - **`contracts/storage-snapshots/README.md`** - Documentation for updating snapshots and known limitations - **`contracts/scripts/check-storage-layout.sh`** - CI script that compares current layout against snapshot - **`contracts/test/storage/StorageLayout.t.sol`** - Upgrade simulation tests verifying state preservation - **`.github/workflows/task-storage-layout.yml`** - CI workflow for storage layout checks ### Modified Files - **`.github/workflows/CI.yml`** - Added `storage-layout` job to run in parallel with other checks ## How It Works **Two-pronged approach:** 1. **Snapshot Diff** - Compares current storage layout against committed snapshot using `forge inspect`. Catches unintended variable reordering, type changes, or gap modifications. 2. **Upgrade Simulation** - Foundry tests that populate state, perform a proxy upgrade, and verify all values survive: - `test_upgradePreservesState` - Verifies core state variables - `test_upgradePreservesValidatorMappings` - Verifies `validatorEthAddressToSolochainAddress` mapping - `test_upgradePreservesMultipleValidators` - Verifies `validatorsAllowlist` with multiple entries - `test_functionalityAfterUpgrade` - Verifies contract remains functional post-upgrade ## Normalization The snapshot comparison normalizes JSON to avoid false positives: - Removes `astId` (changes with compiler runs) - Removes `contract` (contains full file path) - Removes `.types` section (contains unstable AST IDs embedded in type keys) - Sorts by slot number ## Usage ```bash # Check storage layout against snapshot ./scripts/check-storage-layout.sh # Run upgrade simulation tests forge test --match-contract StorageLayoutTest -vvv # Update snapshot (when intentionally changing storage) forge inspect DataHavenServiceManager storage --json > storage-snapshots/DataHavenServiceManager.storage.json ``` ## Test Plan - ./scripts/check-storage-layout.sh passes - forge test --match-contract StorageLayoutTest -vvv passes (4 tests) - CI workflow runs successfully
2026-02-05 11:08:35 +00:00
}
/// @notice Verifies multiple validators in allowlist are preserved
function test_upgradePreservesMultipleValidators() public {
address[] memory validators = new address[](3);
validators[0] = address(0x1111);
validators[1] = address(0x2222);
validators[2] = address(0x3333);
// Add multiple validators
vm.startPrank(avsOwner);
for (uint256 i = 0; i < validators.length; i++) {
serviceManager.addValidatorToAllowlist(validators[i]);
}
vm.stopPrank();
// Deploy new implementation and upgrade
DataHavenServiceManager newImpl =
new DataHavenServiceManager(rewardsCoordinator, allocationManager);
vm.prank(proxyAdminOwner);
proxyAdmin.upgrade(ITransparentUpgradeableProxy(address(serviceManager)), address(newImpl));
// Verify all validators still in allowlist
for (uint256 i = 0; i < validators.length; i++) {
assertTrue(
serviceManager.validatorsAllowlist(validators[i]),
"All validators should remain in allowlist after upgrade"
);
}
}
/// @notice Verifies that upgrade doesn't affect functionality
function test_functionalityAfterUpgrade() public {
// Deploy new implementation and upgrade
DataHavenServiceManager newImpl =
new DataHavenServiceManager(rewardsCoordinator, allocationManager);
vm.prank(proxyAdminOwner);
proxyAdmin.upgrade(ITransparentUpgradeableProxy(address(serviceManager)), address(newImpl));
// Verify functionality still works
address newValidator = address(0xBEEF);
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(newValidator);
assertTrue(
serviceManager.validatorsAllowlist(newValidator),
"Should be able to add validators after upgrade"
);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(newValidator);
assertFalse(
serviceManager.validatorsAllowlist(newValidator),
"Should be able to remove validators after upgrade"
);
}
}