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

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

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

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

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

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

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2025-07-10 08:47:39 +02:00
.github/workflows ci: 👷 Add CI to check PAPI metadata (#107) 2025-06-19 19:12:04 -03:00
contracts feat: make RewardsRegistry keep a history of roots and claim status (#106) 2025-07-10 08:47:39 +02:00
deploy feat: Deployment improvements & environmental overrides (#103) 2025-06-26 13:48:33 +02:00
operator feat: Add native token transfer support from Ethereum to DataHaven (#97) 2025-07-03 14:41:51 +00:00
resources test: Add E2E Tests (#36) 2025-04-14 16:22:43 -03:00
test feat: make RewardsRegistry keep a history of roots and claim status (#106) 2025-07-10 08:47:39 +02:00
.gitignore ci: 👷 Add CI to check PAPI metadata (#107) 2025-06-19 19:12:04 -03:00
.gitmodules build: Change Snowbridge contracts dependency from upstream to fork (#18) 2025-03-28 15:49:43 -03:00
biome.json fix: 🚨 Add error in TS for missing awaits (#81) 2025-05-19 22:28:43 +00:00
CLAUDE.md feat: 🧑‍💻 Add CLAUDE.md file for Claude Code (#102) 2025-06-16 15:02:11 -03:00
README.md feat: 🚀 Add deploy command to CLI (#87) 2025-06-12 10:24:03 +02:00
taplo.toml ci: 🐳 Start Publishing Docker Images (#64) 2025-05-08 20:32:55 -03:00

DataHaven 🫎

An EVM compatible Substrate chain, powered by StorageHub and secured by EigenLayer.

Repo Structure

datahaven/
├── .github/ # GitHub Actions workflows.
├── contracts/ # Implementation of the DataHaven AVS (Autonomous Verifiable Service) smart contracts to interact with EigenLayer.
├── operator/ # DataHaven node based on Substrate. The "Operator" in EigenLayer terms.
├── test/ # Integration tests for the AVS and Operator.
├── resources/ # Miscellaneous resources for the DataHaven project.
└── README.md

E2E CLI

This repo comes with a CLI for launching a local DataHaven network, packaged with:

  1. A full Ethereum network with:
    • 2 x Execution Layer clients (e.g., reth)
    • 2 x Consensus Layer clients (e.g., lodestar)
    • Blockscout Explorer services for EL (if enabled with --blockscout)
    • Dora Explorer service for CL
    • Contracts deployed and configured for the DataHaven network.
  2. A DataHaven solochain.
  3. Snowbridge relayers for cross-chain communication.

To launch the network, follow the instructions in the test README.

Docker

This repo publishes images to DockerHub.

Tip

If you cannot see this repo you must be added to the permission list for the private repo.

To aid with speed it employs the following:

  • sccache: De-facto caching tool to speed up rust builds.
  • cargo chef: A method of caching building the dependencies as a docker layer to cut down compilation times.
  • buildx cache mounts: Using buildx's new feature to mount an externally restored cache into a container.
  • cache dance: Weird workaround (endorsed by docker themselves) to inject caches into containers and return the result back to the CI.

To run a docker image locally (moonsonglabs/datahaven:local), from the /test folder run:

bun build:docker:operator

Working with IDEs

VS Code (and its forks)

IDE configurations are ignored from this repo's version control, to allow for personalisation. However, there are a few key configurations that we suggest for a better experience. Here are the key suggested configurations to add to your .vscode/settings.json file:

Rust

{
  "rust-analyzer.linkedProjects": ["./operator/Cargo.toml"],
  "rust-analyzer.cargo.allTargets": true,
  "rust-analyzer.procMacro.enable": false,
  "rust-analyzer.server.extraEnv": {
    "CARGO_TARGET_DIR": "target/.rust-analyzer",
    "SKIP_WASM_BUILD": 1
  },
  "rust-analyzer.diagnostics.disabled": ["unresolved-macro-call"],
  "rust-analyzer.cargo.buildScripts.enable": false
}

These settings optimise Rust Analyzer for the DataHaven codebase:

  • Marks the operator/ folder as a linked project for analysis. The root of this repo is a workspace, and this is the rust project that should be analysed by rust-analyzer.
  • Disables proc macros and build scripts to improve performance. Otherwise, Substrate's proc macros will make iterative checks from rust-analyzer unbearably slow.
  • Sets a dedicated target directory for Rust Analyzer to avoid conflicts with other build targets like release builds.
  • Disables WASM builds during analysis for faster feedback.

Solidity

For Juan Blanco's Solidity Extension, add the following to your .vscode/settings.json file:

{
  "solidity.formatter": "forge",
  "solidity.compileUsingRemoteVersion": "v0.8.28+commit.7893614a",
  "[solidity]": {
    "editor.defaultFormatter": "JuanBlanco.solidity"
  }
}

These settings configure Solidity support:

  • Uses Forge as the formatter for consistency with the project's tooling.
  • Sets a specific Solidity version for compilation. This one should match the version used in foundry.toml.
  • Sets the Solidity extension as the default formatter.

Typescript

This repo uses Biome for TypeScript linting and formatting. To make the extension work nicely with this repo, add the following to your .vscode/settings.json file:

{
  "biome.lsp.bin": "test/node_modules/.bin/biome",
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome",
    "editor.codeActionsOnSave": {
      "source.organizeImports.biome": "always"
    }
  }
}
  • Sets the Biome binary to the one in the test/ folder.
  • Sets Biome as the default formatter for TypeScript.
  • Sets Biome to always organise imports on save.

CI

Using the act binary, you can run GitHub Actions locally.

For example, to run the entire e2e workflow:

act -W .github/workflows/e2e.yml -s GITHUB_TOKEN="$(gh auth token)"

Which results in:

INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'
INFO[0000] Start server on http://192.168.1.97:34567
[E2E - Kurtosis Deploy and Verify/kurtosis] ⭐ Run Set up job
[E2E - Kurtosis Deploy and Verify/kurtosis] 🚀  Start image=catthehacker/ubuntu:rust-24.04
[E2E - Kurtosis Deploy and Verify/kurtosis]   🐳  docker pull image=catthehacker/ubuntu:rust-24.04 platform= username= forcePull=true
[E2E - Kurtosis Deploy and Verify/kurtosis] using DockerAuthConfig authentication for docker pull
[E2E - Kurtosis Deploy and Verify/kurtosis]   🐳  docker create image=catthehacker/ubuntu:rust-24.04 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[E2E - Kurtosis Deploy and Verify/kurtosis]   🐳  docker run image=catthehacker/ubuntu:rust-24.04 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[E2E - Kurtosis Deploy and Verify/kurtosis]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=
[E2E - Kurtosis Deploy and Verify/kurtosis]   ✅  Success - Set up job
[E2E - Kurtosis Deploy and Verify/kurtosis]   ☁  git clone 'https://github.com/oven-sh/setup-bun' # ref=v2
...
[E2E - Kurtosis Deploy and Verify/kurtosis]   ✅  Success - Post Install Foundry [212.864597ms]
[E2E - Kurtosis Deploy and Verify/kurtosis] ⭐ Run Complete job
[E2E - Kurtosis Deploy and Verify/kurtosis] Cleaning up container for job kurtosis
[E2E - Kurtosis Deploy and Verify/kurtosis]   ✅  Success - Complete job
[E2E - Kurtosis Deploy and Verify/kurtosis] 🏁  Job succeeded