From 38f34deaf043929caba13284502909f56b4808b1 Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 16 Jan 2026 10:50:44 -1000 Subject: [PATCH 1/2] feat(amp): buildout infra for running amp --- .gitignore | 16 +++++- .gitmodules | 6 +++ RUNNING_AMP.md | 20 ++++++++ contracts/lib/forge-std | 1 + contracts/lib/solady | 1 + contracts/script/DeployCounter.s.sol | 19 ++++++++ contracts/script/utils/Factory.sol | 14 ++++++ contracts/script/utils/Script.sol | 42 ++++++++++++++++ contracts/src/Counter.sol | 24 +++++++++ docker-compose.yml | 73 ++++++++++++++++++++++++++++ foundry.lock | 14 ++++++ foundry.toml | 8 +++ infra/amp/config.toml | 37 ++++++++++++++ infra/amp/providers/anvil.toml | 3 ++ infra/postgres/init.sh | 18 +++++++ infra/postgres/postgres.conf | 2 + justfile | 49 +++++++++++++++++++ 17 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 RUNNING_AMP.md create mode 160000 contracts/lib/forge-std create mode 160000 contracts/lib/solady create mode 100644 contracts/script/DeployCounter.s.sol create mode 100644 contracts/script/utils/Factory.sol create mode 100644 contracts/script/utils/Script.sol create mode 100644 contracts/src/Counter.sol create mode 100644 docker-compose.yml create mode 100644 foundry.lock create mode 100644 foundry.toml create mode 100644 infra/amp/config.toml create mode 100644 infra/amp/providers/anvil.toml create mode 100755 infra/postgres/init.sh create mode 100644 infra/postgres/postgres.conf create mode 100644 justfile diff --git a/.gitignore b/.gitignore index 2c3bbb8..bdaa44a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ .direnv/ # Generated by TypeScript -dist/ +node_modules/ build/ +dist/ .tsbuildinfo/ tsconfig.tsbuildinfo @@ -17,3 +18,16 @@ node_modules/ scratchpad/**/*.md scratchpad/**/*.ts !scratchpad/index.ts + +# Generated by foundry +out/ +broadcast/ +cache/ + +# Postgres data volume for testing with amp +/infra/postgres/data/ +/infra/amp/data +/infra/amp/datasets +/infra/amp/providers/*.toml +/infra/amp/anvil.json +!/infra/amp/providers/anvil.toml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a38682c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "contracts/lib/solady"] + path = contracts/lib/solady + url = https://github.com/vectorized/solady +[submodule "contracts/lib/forge-std"] + path = contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/RUNNING_AMP.md b/RUNNING_AMP.md new file mode 100644 index 0000000..4e00ff3 --- /dev/null +++ b/RUNNING_AMP.md @@ -0,0 +1,20 @@ +# Running Amp + +Amp is setup to run with [docker-compose](./docker-compose.yml). It requires: + +- `postgres` +- `anvil` - used as a local blockchain/rpc that amp connects to for the local dataset/contracts +- `foundry` - smart contract dev/orchestration framework. builds our contracts. ex: [`Counter.sol`](./contracts/src/Counter.sol) + - once compiled, forge ouputs the ABI into a [`Counter.json`](./contracts/out/Counter.sol/Counter.json). +- `lgtm` - metrics/traces/logs, etc. not required but very handy +- `amp-proxy` - also not required, but handy for running queries, especially in a browser + +## Getting started + +There is a [`justfile`](./justfile) that has recipes for installing, building and running amp + +1. **Install deps** -> `just install`. installs pnpm deps as well as forge deps (for smart contract dev) +2. **Install amp** -> `just ampup`. installs amp through ampup cmd. also installs `ampctl` which is a cli tool for registering Amp datasets. +3. **Starting docker** -> `just up`. runs `docker compose up -d` to spinup the docker compose registered services listed above. With this started, amp is now running on your machine (assuming things started correctly). +4. **Deploy smart contracts** -> `just deploy`. uses forge to deploy the smart contracts such as [`Counter.sol`](./contracts/src/Counter.sol). +5. **Initialize the anvil dataset, start listening** -> `just dev-amp`. this will create an EVM-RPC dataset against the running anvil RPC source. With tabled: blocks, logs, transactions. Dataset can be built from this source. diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std new file mode 160000 index 0000000..f61e4dd --- /dev/null +++ b/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit f61e4dd133379a4536a54ee57a808c9c00019b60 diff --git a/contracts/lib/solady b/contracts/lib/solady new file mode 160000 index 0000000..90db92c --- /dev/null +++ b/contracts/lib/solady @@ -0,0 +1 @@ +Subproject commit 90db92ce173856605d24a554969f2c67cadbc7e9 diff --git a/contracts/script/DeployCounter.s.sol b/contracts/script/DeployCounter.s.sol new file mode 100644 index 0000000..40aeefa --- /dev/null +++ b/contracts/script/DeployCounter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {console} from "forge-std/Script.sol"; +import {Script} from "./utils/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract DeployCounter is Script { + function run() public { + vm.startBroadcast(); + + bytes memory code = abi.encodePacked(type(Counter).creationCode); + address counter = deploy(keccak256("Counter"), code); + + console.log("Counter deployed to:", counter); + + vm.stopBroadcast(); + } +} diff --git a/contracts/script/utils/Factory.sol b/contracts/script/utils/Factory.sol new file mode 100644 index 0000000..51fa35e --- /dev/null +++ b/contracts/script/utils/Factory.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {CREATE3} from "solady/utils/CREATE3.sol"; + +contract Factory { + function deploy(bytes32 salt, bytes memory creationCode) external payable returns (address deployed) { + return CREATE3.deployDeterministic(msg.value, creationCode, salt); + } + + function getDeployed(bytes32 salt) external view returns (address) { + return CREATE3.predictDeterministicAddress(salt, address(this)); + } +} diff --git a/contracts/script/utils/Script.sol b/contracts/script/utils/Script.sol new file mode 100644 index 0000000..9029c59 --- /dev/null +++ b/contracts/script/utils/Script.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script as ForgeScript} from "forge-std/Script.sol"; +import {Factory} from "./Factory.sol"; + +abstract contract Script is ForgeScript { + bytes32 internal constant SALT = 0; + address internal constant CREATE2 = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + function getOrDeployFactory() internal returns (Factory) { + address factoryAddress = vm.computeCreate2Address(SALT, keccak256(type(Factory).creationCode), CREATE2); + + if (factoryAddress.code.length == 0) { + return new Factory{salt: SALT}(); + } + + return Factory(factoryAddress); + } + + function getFactory() internal pure returns (Factory) { + address factoryAddress = vm.computeCreate2Address(SALT, keccak256(type(Factory).creationCode), CREATE2); + + return Factory(factoryAddress); + } + + function deploy(bytes32 salt, bytes memory creationCode) internal returns (address) { + Factory factory = getOrDeployFactory(); + address deployed = factory.getDeployed(salt); + + if (deployed.code.length == 0) { + return factory.deploy(salt, creationCode); + } + + return deployed; + } + + function getDeployed(bytes32 salt) internal view returns (address) { + Factory factory = getFactory(); + return factory.getDeployed(salt); + } +} diff --git a/contracts/src/Counter.sol b/contracts/src/Counter.sol new file mode 100644 index 0000000..3a7d77e --- /dev/null +++ b/contracts/src/Counter.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.30; + +contract Counter { + event Incremented(uint256 count); + event Decremented(uint256 count); + + uint256 public count; + + constructor() { + count = 0; + } + + function increment() public { + count++; + emit Incremented(count); + } + + function decrement() public { + require(count > 0, "Counter: count is zero"); + count--; + emit Decremented(count); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d5e9b10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,73 @@ +name: amp-typescript-amp-playground +services: + postgres: + image: postgres:18-alpine + command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] + environment: + POSTGRES_DB_LIST: amp_typescript_playground + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + - 6435:5432 + volumes: + - ./infra/postgres/postgres.conf:/etc/postgresql/postgresql.conf:ro + - ./infra/postgres/init.sh:/docker-entrypoint-initdb.d/init.sh:ro + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + anvil: + image: ghcr.io/foundry-rs/foundry + entrypoint: ["anvil", "--host", "0.0.0.0"] + ports: + - 8545:8545 + + lgtm: + image: grafana/otel-lgtm + ports: + - 3030:3000 # Grafana UI + - 4317:4317 # OTLP gRPC + - 4318:4318 # OTLP HTTP + + # Forwards ConnectRPC Arrow Flight requests to the Amp Arrow Flight gRPC server. This + # is useful if you want to e.g. query Amp directly from the browser. + proxy: + image: ghcr.io/edgeandnode/arrow-flight-proxy:latest + depends_on: + - amp + - lgtm + restart: unless-stopped + ports: + - 8080:8080 # HTTP + environment: + PROXY_UPSTREAM: amp:1602 + PROXY_UPSTREAM_INSECURE: true + PROXY_METRICS: lgtm:4317 + PROXY_METRICS_INSECURE: true + PROXY_FORWARD_HEADERS: "amp-*,authorization" + + # Amp is a data engineering layer for Ethereum. + # https://github.com/edgeandnode/amp + amp: + image: ghcr.io/edgeandnode/amp:latest + command: ["--config", "/var/lib/amp/config.toml", "dev"] + depends_on: + - postgres + volumes: + - ./infra/amp/config.toml:/var/lib/amp/config.toml + - ./infra/amp/providers:/var/lib/amp/providers + - ./infra/amp/datasets:/var/lib/amp/datasets + - ./infra/amp/data:/var/lib/amp/data + restart: unless-stopped + ports: + - 1610:1610 # Admin API + - 1603:1603 # JSON Lines + - 1602:1602 # Arrow Flight + +volumes: + postgres_data: diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..6c1f5b1 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,14 @@ +{ + "contracts/lib/forge-std": { + "tag": { + "name": "v1.14.0", + "rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6" + } + }, + "contracts/lib/solady": { + "tag": { + "name": "v0.1.26", + "rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b" + } + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..f9c0782 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,8 @@ +[profile.default] +src = "contracts/src" +out = "contracts/out" +libs = ["contracts/lib"] +script = "contracts/script" +test = "contracts/test" +broadcast = "contracts/broadcast" +cache_path = "contracts/cache" diff --git a/infra/amp/config.toml b/infra/amp/config.toml new file mode 100644 index 0000000..e936ccd --- /dev/null +++ b/infra/amp/config.toml @@ -0,0 +1,37 @@ +# Sample of a configuration. Copy this file and edit it. Set the environment variable `AMP_CONFIG` +# to its location. See the CONFIG.md for more context. +# +# Note that using a config file is not mandatory. You can alternatively provide some or all values +# through env vars. See the 'Using env vars' section of CONFIG.md for how to do this. +# +# When using filesystem paths, any relative paths will be resolved from the directory of this file. + +# Where the extracted datasets are stored. +data_dir = "data" + +# Path to a providers directory. Each provider is configured as a separate toml file in this +# directory. Dataset definitions will reference providers by a relative path to this directory. +providers_dir = "providers" + +# Path to a directory containing dataset definition files. +dataset_defs_dir = "datasets" + +# Metadata database url. +metadata_db_url = "postgres://postgres:postgres@postgres:5432/amp_typescript_playground?sslmode=disable" + +# How much memory the server can use in MB. Setting this to 0 means unlimited +max_mem_mb = 0 + +# Paths where DataFusion can create temporary files. Setting this to an +# empty array disables spill-to-disk +spill_location = [] + +# Service addresses (optional - defaults shown below) +# Arrow Flight RPC server address +# flight_addr = "0.0.0.0:1602" + +# JSON Lines server address +# jsonl_addr = "0.0.0.0:1603" + +# Admin API server address +# admin_api_addr = "0.0.0.0:1610" diff --git a/infra/amp/providers/anvil.toml b/infra/amp/providers/anvil.toml new file mode 100644 index 0000000..e4218f3 --- /dev/null +++ b/infra/amp/providers/anvil.toml @@ -0,0 +1,3 @@ +kind = "evm-rpc" +url = "http://anvil:8545" +network = "anvil" diff --git a/infra/postgres/init.sh b/infra/postgres/init.sh new file mode 100755 index 0000000..984e89e --- /dev/null +++ b/infra/postgres/init.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -e +set -u + +function create_user_and_database() { + local db=$1 + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "postgres" <<-EOSQL + CREATE DATABASE $db; + GRANT ALL PRIVILEGES ON DATABASE $db TO $POSTGRES_USER; +EOSQL +} + +if [ -n "${POSTGRES_DB_LIST:-}" ]; then + for db in $(echo $POSTGRES_DB_LIST | tr ',' ' '); do + create_user_and_database $db + done +fi diff --git a/infra/postgres/postgres.conf b/infra/postgres/postgres.conf new file mode 100644 index 0000000..8c8a1ca --- /dev/null +++ b/infra/postgres/postgres.conf @@ -0,0 +1,2 @@ +listen_addresses = '*' +wal_level = logical diff --git a/justfile b/justfile new file mode 100644 index 0000000..907ee98 --- /dev/null +++ b/justfile @@ -0,0 +1,49 @@ +# Support both docker and podman +docker := if `command -v podman >/dev/null 2>&1; echo $?` == "0" { "podman" } else { "docker" } + +# Display available commands and their descriptions (default target) +default: + @just --list + +# Install dependencies +install: + pnpm install + forge build + +# Install amp +ampup: + curl --proto '=https' --tlsv1.2 -sSf https://ampup.sh/install | sh + +# Start service dependencies +up *args: + @mkdir -p ./infra/postgres/data + @mkdir -p ./infra/amp/data + @mkdir -p ./infra/amp/datasets + {{docker}} compose up -d --wait {{args}} + just deploy + +# Tail logs for service dependencies +logs *args: + {{docker}} compose logs -f --tail 100 {{args}} + +# Stop service dependencies +stop *args: + {{docker}} compose stop {{args}} + +# Stop all services and remove volumes +down: + {{docker}} compose down --volumes + @rm -rf ./infra/postgres/data + @rm -rf ./infra/amp/data + @rm -rf ./infra/amp/datasets + +# Deploy the contract +[working-directory: "contracts"] +deploy: + forge script script/DeployCounter.s.sol --broadcast --rpc-url http://localhost:8545 --private-key "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +# Deploy the dataset and watch for config changes +dev-amp: + ampctl manifest generate --network anvil --kind evm-rpc --out ./infra/amp/anvil.json + ampctl dataset register _/anvil -t 0.0.1 ./infra/amp/anvil.json + ampctl dataset deploy _/anvil@dev From 063d66d696fe535ac5985e0a3fe9862d994491e3 Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 16 Jan 2026 10:52:34 -1000 Subject: [PATCH 2/2] feat(amp): add step for stopping --- RUNNING_AMP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RUNNING_AMP.md b/RUNNING_AMP.md index 4e00ff3..93f31c7 100644 --- a/RUNNING_AMP.md +++ b/RUNNING_AMP.md @@ -18,3 +18,4 @@ There is a [`justfile`](./justfile) that has recipes for installing, building an 3. **Starting docker** -> `just up`. runs `docker compose up -d` to spinup the docker compose registered services listed above. With this started, amp is now running on your machine (assuming things started correctly). 4. **Deploy smart contracts** -> `just deploy`. uses forge to deploy the smart contracts such as [`Counter.sol`](./contracts/src/Counter.sol). 5. **Initialize the anvil dataset, start listening** -> `just dev-amp`. this will create an EVM-RPC dataset against the running anvil RPC source. With tabled: blocks, logs, transactions. Dataset can be built from this source. +6. **Kill** -> `just down`. spins down the docker compose services, deletes emphemeral dir.