Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5ffcf3c
Chore: Update editorconfig with specific rules
Quantumlyy May 30, 2026
1fb31ae
Feat: Introduce core devnet utilities and prool dependency
Quantumlyy May 30, 2026
5c0f40e
Feat: Add main devnet runner script
Quantumlyy May 30, 2026
8208fa2
Chore: Prepare Docker environment for devnet
Quantumlyy May 30, 2026
fc891e7
Feat: Integrate devnet into Docker Compose configurations
Quantumlyy May 30, 2026
a800c50
Chore: Update package.json for devnet scripts and project details
Quantumlyy May 30, 2026
fbc009e
Test: Add EFP devnet integration tests
Quantumlyy May 30, 2026
87e56e7
chore(deps): update bun lockfile
Quantumlyy May 30, 2026
bf9af22
ci(devnet): add workflow to build, test, and publish devnet image
Quantumlyy May 30, 2026
c644b32
Potential fix for pull request finding
Quantumlyy May 30, 2026
22cad80
Apply suggestions from code review
Quantumlyy May 30, 2026
2041d7e
chore(bun): update lockfile format to bun.lock
Quantumlyy May 30, 2026
5384d43
refactor: Update SPDX license identifier to MIT in all Solidity files
Quantumlyy May 30, 2026
72862fa
chore(dockerfiles): update to use bun.lock
Quantumlyy May 30, 2026
0f63bd1
chore(dockerignore): explicitly include bun.lock for docker builds
Quantumlyy May 30, 2026
8d72481
fix(devnet): remove extraneous brace in shutdown handlers
Quantumlyy May 30, 2026
1dc38a2
Apply suggestions from code review
Quantumlyy May 30, 2026
c42bcf2
feat: Introduce 'blockTime' for Anvil interval mining
Quantumlyy May 30, 2026
9bcb53d
refactor(devnet): Update SetupDevnetOptions to use 'blockTime'
Quantumlyy May 30, 2026
898ed76
refactor(devnet): Use 'blockTime' in setupDevnet function
Quantumlyy May 30, 2026
0e1e023
refactor(scripts): Replace --no-auto-mine with --block-time in runDevnet
Quantumlyy May 30, 2026
a49cfe0
ci(build-devnet): limit pull request triggers to relevant paths
Quantumlyy May 30, 2026
d50b269
style(scripts): remove extraneous closing brace
Quantumlyy May 30, 2026
2bd9543
feat(devnet): integrate devnet tests into CI and refine runtime options
Quantumlyy May 31, 2026
7140d24
chore(solidity): update SPDX license identifier to UNLICENSED
Quantumlyy May 31, 2026
65a94a0
Update scripts/devnet/anvil.ts
Quantumlyy May 31, 2026
94616d2
ci(build-devnet): update push trigger branch to master
Quantumlyy May 31, 2026
5944bc3
feat(devnet): pin Foundry version in Dockerfile
Quantumlyy May 31, 2026
91f847b
fix(devnet): improve private key security in deploy script
Quantumlyy May 31, 2026
e48f99b
fix(devnet): stop anvil on setup failure
Quantumlyy May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
!generated/
!lib/
!scripts/
!scripts/devnet/
!deployments/
!Dockerfile.devnet
!src/

# needed for forge
Expand Down
21 changes: 19 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,22 @@ indent_size = 2
insert_final_newline = true
max_line_length = 120
trim_trailing_whitespace = true
use_tabs = false
use_single_quotes = true

[*.json]
indent_size = 2

[package.json]
indent_size = 4

[funding.json]
indent_size = 4

[env.d.ts]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

[*.sol]
indent_size = 2
max_line_length = 120
101 changes: 101 additions & 0 deletions .github/workflows/build-devnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: build-devnet

on:
push:
branches: [main]
pull_request:
paths:
Comment thread
greptile-apps[bot] marked this conversation as resolved.
- 'src/**'
- 'scripts/**'
- 'lib/**'
- 'generated/**'
- 'Dockerfile.devnet'
- 'foundry.toml'
- 'package.json'
- 'bun.lockb'
- '.github/workflows/build-devnet.yml'
Comment thread
Quantumlyy marked this conversation as resolved.
Comment thread
Quantumlyy marked this conversation as resolved.
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE: ghcr.io/${{ github.repository }}/devnet

concurrency:
group: build-devnet-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build & publish devnet image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,format=long
type=sha,format=short,prefix=${{ github.ref_name }}-,enable={{is_default_branch}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.devnet
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke test (deploy + seed + health)
env:
IMAGE_REF: ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}
run: |
docker run -d --name efp-devnet -p 8545:8545 -p 8000:8000 "$IMAGE_REF"
for i in $(seq 1 90); do
if curl -fsS http://localhost:8000/health >/dev/null; then
echo "Healthy after ${i}s"
exit 0
fi
if [ -z "$(docker ps -q -f name=efp-devnet -f status=running)" ]; then
echo "::error::container exited early"
docker logs efp-devnet
exit 1
fi
sleep 1
done
echo "::error::devnet did not become healthy"
docker logs efp-devnet
exit 1

- name: Tear down smoke test
if: always()
run: docker rm -f efp-devnet || true

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push image
if: github.event_name != 'pull_request'
run: docker push --all-tags "${IMAGE}"
36 changes: 36 additions & 0 deletions Dockerfile.devnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM oven/bun:1.2.13

# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Install Foundry
RUN curl -L https://foundry.paradigm.xyz | bash
ENV PATH="/root/.foundry/bin:${PATH}"
RUN foundryup
Comment thread
Quantumlyy marked this conversation as resolved.
Outdated

WORKDIR /app

# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# Copy source and build contracts
COPY . .
RUN forge install && forge build

EXPOSE 8545 8000

ENV FOUNDRY_DISABLE_NIGHTLY_WARNING=true
# Bind to all interfaces inside the container; everything else is env-driven:
# DEVNET_RPC_URL attach to an existing node (e.g. http://devnet:8545) instead of spawning anvil
# DEVNET_SCENARIO composable scenario to seed (empty | minimal | demoGraph)
# DEVNET_CHAIN_ID chain id when spawning a node (default 31337)
ENV DEVNET_HOST=0.0.0.0
ENV DEVNET_SCENARIO=demoGraph

CMD ["bun", "scripts/runDevnet.ts"]
Binary file modified bun.lockb
Binary file not shown.
57 changes: 57 additions & 0 deletions compose.attach.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: efp-ens-devnet

# Runs the EFP devnet ALONGSIDE the ENS devnet, deploying the EFP contracts onto
# the same anvil node that ENS uses. This mirrors the ENS devnet service from
# https://github.com/namehash/ensnode/blob/main/docker/services/devnet.yml
#
# docker compose -f compose.attach.yml up
#
# Result: one chain (id 31337) with both ENS v2 and EFP contracts deployed,
# reachable at devnet:8545. EFP addresses are written to the shared
# `efp-deployments` volume as /deployments/devnet-31337.json.
services:
# The ENS devnet (prebuilt image), exactly as ensnode runs it.
devnet:
container_name: devnet
image: ghcr.io/ensdomains/contracts-v2:main-5677359
command: ./script/runDevnet.ts
pull_policy: always
ports:
- '8545:8545'
environment:
ANVIL_IP_ADDR: '0.0.0.0'
healthcheck:
test: ['CMD', 'curl', '--fail', '-s', 'http://localhost:8000/health']
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
start_interval: 1s

# Deploys + seeds EFP onto the ENS devnet node, then serves its own health.
efp-devnet:
container_name: efp-devnet
build:
context: .
dockerfile: Dockerfile.devnet
depends_on:
devnet:
condition: service_healthy
ports:
- '8001:8000'
environment:
DEVNET_HOST: 0.0.0.0
DEVNET_RPC_URL: http://devnet:8545
DEVNET_SCENARIO: ${DEVNET_SCENARIO:-demoGraph}
volumes:
- efp-deployments:/app/deployments
healthcheck:
test: ['CMD', 'curl', '--fail', '-s', 'http://localhost:8000/health']
interval: 10s
timeout: 5s
retries: 5
start_period: 60s
start_interval: 1s

volumes:
efp-deployments:
52 changes: 21 additions & 31 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
version: '3.8'

name: efp-contracts

networks:
default:
driver: bridge

# Standalone EFP devnet: spawns its own anvil, deploys the EFP contracts, and
# seeds a scenario. RPC on :8545, healthcheck on :8000.
#
# To run EFP alongside (and on top of) the ENS devnet instead, see
# compose.attach.yml.
services:
anvil:
container_name: efp-contracts-anvil
image: ghcr.io/foundry-rs/foundry:latest
command: anvil
tty: true
environment:
- ANVIL_IP_ADDR=0.0.0.0
ports:
- 8545:8545
networks:
- default

contracts:
container_name: efp-contracts
restart: no
efp-devnet:
container_name: efp-devnet
build:
context: .
dockerfile: Dockerfile
command: bun launch:localhost
dockerfile: Dockerfile.devnet
ports:
- '8545:8545'
- '8000:8000'
environment:
- ETHEREUM_LOCAL_NODE_URL=${ETHEREUM_LOCAL_NODE_URL:-http://host.docker.internal:8545}
- ANVIL_ACCOUNT_PRIVATE_KEY=${ANVIL_ACCOUNT_PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}
- PRIVATE_KEY=${PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}
- MINT_TOTAL_SUPPLY=${MINT_TOTAL_SUPPLY:-100}
- FORGE_ARGS=${FORGE_ARGS:-}
network_mode: host
stdin_open: true
tty: true
DEVNET_HOST: 0.0.0.0
DEVNET_CHAIN_ID: ${DEVNET_CHAIN_ID:-31337}
DEVNET_SCENARIO: ${DEVNET_SCENARIO:-demoGraph}
healthcheck:
test: ['CMD', 'curl', '--fail', '-s', 'http://localhost:8000/health']
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
start_interval: 1s
Empty file added deployments/.gitkeep
Empty file.
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"name": "beta-contracts",
"version": "1.0.0",
"repository": "github:ethereumfollowprotocol/beta-contractst",
"repository": "github:ethereumfollowprotocol/contracts",
"type": "module",
"scripts": {
"build": "wagmi generate && bun format:ts && (cp ./generated/abi.ts ../indexer/src/abi/generated/index.ts || true)",
"clean": "forge clean",
"devnet": "bun scripts/runDevnet.ts",
"devnet:seed": "bun scripts/runDevnet.ts --scenario demoGraph",
"devnet:attach": "bun scripts/runDevnet.ts --rpc-url http://127.0.0.1:8545 --scenario demoGraph",
"devnet:test": "bun test --timeout 120000 scripts/devnet",
"docs": "forge doc --serve --port 4433 --open",
"deploy:localhost": "forge script scripts/deploy.s.sol --fork-url https://eth-sepolia.g.alchemy.com/v2/[ALCHEMY_ID] --broadcast --private-key [PRIVATE_KEY] && bun enable-public-mint",
"enable-public-mint": "bun ./scripts/update-mint-state.ts --public-batch",
Expand All @@ -21,17 +25,17 @@
},
"dependencies": {
"@wagmi/cli": "^2.0.4",
"prool": "^0.2.4",
"viem": "^2.9.29",
"wagmi": "^2.3.1"
},
"devDependencies": {
"@types/bun": "^1.0.2",
"@types/node": "^20.11.5",
"bun": "^1.0.24",
"prettier": "^3.2.4",
"solc": "^0.8.23",
"solhint": "^4.1.1",
"typescript": "^5.3.3"
},
"license": "UNLICENSED"
}
"license": "MIT"
Comment thread
Quantumlyy marked this conversation as resolved.
}
26 changes: 26 additions & 0 deletions scripts/devnet/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { mnemonicToAccount, type HDAccount } from 'viem/accounts'

/**
* The canonical Anvil/Hardhat test mnemonic. The ENS devnet uses the same
* phrase, so these accounts are funded on both our spawned node and a shared
* ENS devnet node.
*/
export const DEVNET_MNEMONIC =
'test test test test test test test test test test test junk' as const

/** Private key for account #0 of {@link DEVNET_MNEMONIC} (the deployer). */
export const DEPLOYER_PRIVATE_KEY =
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' as const

export const ACCOUNT_NAMES = ['deployer', 'alice', 'bob', 'carol', 'dave'] as const

export type AccountName = (typeof ACCOUNT_NAMES)[number]

export function getNamedAccounts(): Record<AccountName, HDAccount> {
return Object.fromEntries(
ACCOUNT_NAMES.map((name, index) => [
name,
mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: index })
])
) as Record<AccountName, HDAccount>
}
Loading
Loading