Skip to content

Commit

Permalink
Kjs/foundry (#923)
Browse files Browse the repository at this point in the history
* fix: unfinalized start block in historical sync

* support future end block

* support future end block

* remove validateHistoricalBlockRange

* chore: changeset

* nits

* initial commit

* project foundry

* .

* prune by source sqlite

* foundry example

* revive foundry docs

* merge stash

* prune by source

* work on example foundry

* cleanup example

* readme

* cleanup

* fix path

* docs

* fix ui bug

* include run latest

* lockfile

* docs

* fix lint

* pruneByChainId

* disable caching

* docs tweaks

* readme

---------

Co-authored-by: typedarray <[email protected]>
  • Loading branch information
kyscott18 and typedarray authored Jun 4, 2024
1 parent de9949f commit 07e39ed
Show file tree
Hide file tree
Showing 32 changed files with 1,345 additions and 816 deletions.
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "examples/with-foundry/contracts/lib/forge-std"]
path = examples/with-foundry/contracts/lib/forge-std
[submodule "examples/with-foundry/foundry/lib/forge-std"]
path = examples/with-foundry/foundry/lib/forge-std
url = https://github.com/foundry-rs/forge-std
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"**/.ponder",
"docs/",
"benchmarks/apps/subgraph-*",
"pnpm-lock.yaml"
"pnpm-lock.yaml",
"**/*.sol"
]
},
"formatter": {
Expand Down
3 changes: 2 additions & 1 deletion docs/pages/docs/advanced/_meta.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default {
foundry: "Foundry",
logging: "Logging",
telemetry: "Telemetry",
metrics: "Metrics",
metrics: "Metrics"
};
126 changes: 126 additions & 0 deletions docs/pages/docs/advanced/foundry.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: "Integrate with Foundry"
description: "A guide for using Ponder with the Foundry smart contract development toolchain."
---

import { Callout, Steps } from "nextra/components";

# Foundry

This guide describes how to integrate Ponder and Foundry during local development.

Foundry projects follow various development workflows (test-driven, deploy to a fresh chain, deploy to a fork, etc). Rather than a one-size-fits-all integration, this page offers patterns that you can adapt to your workflow.

## Configure the `anvil` network

### Disable caching

Ponder's RPC request cache works well for live networks where the chain is generally immutable, but causes issues when indexing a local chain that "resets".

Use the `disableCache` option to **disable RPC request caching** for the Anvil network. With this option set to true, Ponder will clear the cache on start up and between hot reloads.

```ts filename="ponder.config.ts" {9}
import { createConfig } from "@ponder/core";
import { http } from "viem";

export default createConfig({
networks: {
anvil: {
chainId: 31337,
transport: http("http://127.0.0.1:8545"),
disableCache: true,
},
},
// ...
});
```

### Chain ID

We recommend using `31337` (the default Anvil chain ID) even when forking a live chain. This avoids common footguns when working with multiple networks.

### Mining mode

We recommend using [interval mining](https://book.getfoundry.sh/reference/anvil/#mining-modes) with a block time of ~2 seconds. This better simulates a live network.

<Callout type="warning">Known issue: When indexing Anvil with auto mining enabled in an app with multiple networks, indexing progress will get "stuck" at the timestamp of the latest Anvil block. </Callout>

## Generate ABI files

To enable end-to-end type safety, the contract ABIs generated by Foundry must be copied into TypeScript (`.ts`) source files.

### Wagmi CLI

The Wagmi CLI [Foundry plugin](https://wagmi.sh/cli/api/plugins/foundry) is an excellent tool to automate tedious ABI file management. For more information, visit the [Wagmi CLI documentation](https://wagmi.sh/cli/getting-started).

Here is the Wagmi CLI config file used by the Foundry [example project](https://github.com/ponder-sh/ponder/tree/main/examples/with-foundry).

```ts filename="wagmi.config.ts"
import { defineConfig } from "@wagmi/cli";
import { foundry } from "@wagmi/cli/plugins";

export default defineConfig({
out: "abis/CounterAbi.ts",
plugins: [
foundry({
project: "foundry",
include: ["Counter.sol/**"],
}),
],
});
```

## Import broadcast files

Foundry scripts write transaction inputs and receipts to JSON files in the `broadcast` directory. You can import these files directly into `ponder.config.ts` to automate address management and enable hot reloading.

<Callout type="info">Remember to enable [broadcast](https://book.getfoundry.sh/tutorials/solidity-scripting?highlight=deploy#deploying-locally) so that `forge script` submits transactions to Anvil.</Callout>

### Automate address management

To read the contract address and deployment block number from a broadcast file, import the file directly into `ponder.config.ts` and access properties from the JSON object.

The `ponder.config.ts` file from the Foundry [example project](https://github.com/ponder-sh/ponder/tree/main/examples/with-foundry) demonstrates this pattern. Here, the first transaction in the broadcast file deployed the `Counter.sol` contract. The location of the contract address and start block within the broadcast file depends on the order and number of transactions in your deployment script.

```ts filename="ponder.config.ts" {4, 6-7, 21-22}
import { createConfig } from "@ponder/core";
import { http, getAddress, hexToNumber } from "viem";
import { counterABI } from "../abis/CounterAbi";
import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json";

const address = getAddress(CounterDeploy.transactions[0]!.contractAddress);
const startBlock = hexToNumber(CounterDeploy.receipts[0]!.blockNumber);

export default createConfig({
networks: {
anvil: {
chainId: 31337,
transport: http("http://127.0.0.1:8545"),
disableCache: true,
},
},
contracts: {
Counter: {
network: "anvil",
abi: counterABI,
address,
startBlock,
},
},
});
```

### Enable hot reloading

If you import a JSON broadcast file in `ponder.config.ts`, the dev server will reload each time that file changes. This is a simple way to ensure that Ponder reloads every time you run a Foundry deployment script.

```ts filename="ponder.config.ts" {3-4}
import { createConfig } from "@ponder/core";
import { http } from "viem";
import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json";
// ^ The development server detects changes to this file and triggers a hot reload.

export default createConfig({
// ...
});
```
2 changes: 1 addition & 1 deletion docs/pages/docs/api-reference/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ export default {
config: "ponder.config.ts",
schema: "ponder.schema.ts",
"indexing-functions": "Indexing functions",
"ponder-cli": "Ponder CLI",
"ponder-cli": "CLI",
"create-ponder": "create-ponder",
};
29 changes: 15 additions & 14 deletions docs/pages/docs/api-reference/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ The `networks` field is an object where each key is a network name containing th
Most Ponder apps require a paid RPC provider plan to avoid rate-limiting.
</Callout>

| field | type | |
| :------------------------------- | :-------------------: | :----------------------------------------------------------------------------------------------------------- |
| **name** | `string` | A unique name for the blockchain. Must be unique across all networks. _Provided as an object property name._ |
| **chainId** | `number` | The [chain ID](https://chainlist.org) for the network. |
| **transport** | `viem.Transport` | A Viem `http`, `webSocket`, or `fallback` [Transport](https://viem.sh/docs/clients/transports/http.html). |
| **pollingInterval** | `number \| undefined` | **Default: `1_000`**. Frequency (in ms) used when polling for new events on this network. |
| **maxRequestsPerSecond** | `number \| undefined` | **Default: `50`**. Maximum number of RPC requests per second. Can be reduced to work around rate limits. |
| **maxHistoricalTaskConcurrency** | `number \| undefined` | **Default: `20`**. (Deprecated) Maximum concurrency of tasks during the historical sync. |
| field | type | |
| :------------------------------- | :--------------------: | :------------------------------------------------------------------------------------------------------------------------- |
| **name** | `string` | A unique name for the blockchain. Must be unique across all networks. _Provided as an object property name._ |
| **chainId** | `number` | The [chain ID](https://chainlist.org) for the network. |
| **transport** | `viem.Transport` | A Viem `http`, `webSocket`, or `fallback` [Transport](https://viem.sh/docs/clients/transports/http.html). |
| **pollingInterval** | `number \| undefined` | **Default: `1_000`**. Frequency (in ms) used when polling for new events on this network. |
| **maxRequestsPerSecond** | `number \| undefined` | **Default: `50`**. Maximum number of RPC requests per second. Can be reduced to work around rate limits. |
| **maxHistoricalTaskConcurrency** | `number \| undefined` | **Default: `20`**. (Deprecated) Maximum concurrency of tasks during the historical sync. |
| **disableCache** | `boolean \| undefined` | **Default: `false`**. Disables the RPC request cache. Use when indexing a [local node](/docs/advanced/foundry) like Anvil. |

```ts filename="ponder.config.ts" {7-12,16}
import { createConfig } from "@ponder/core";
Expand Down Expand Up @@ -168,12 +169,12 @@ export default createConfig({

### Postgres

| field | type | |
| :------------------- | :-----------------------: | :--------------------------------------------------------------------------------------------------------------- |
| **kind** | `"postgres"` | |
| **connectionString** | `string \| undefined` | **Default: `DATABASE_PRIVATE_URL` or `DATABASE_URL` env var**. Postgres database connection string. |
| **schema** | `string \| undefined` | **Default: `"public"`**. Postgres schema to use for indexed data. |
| **poolConfig** | [`PoolConfig`](https://node-postgres.com/apis/pool) \| `undefined` | **Default: `{ max: 30 }`**. Pool configuration passed to `node-postgres`. |
| field | type | |
| :------------------- | :----------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------- |
| **kind** | `"postgres"` | |
| **connectionString** | `string \| undefined` | **Default: `DATABASE_PRIVATE_URL` or `DATABASE_URL` env var**. Postgres database connection string. |
| **schema** | `string \| undefined` | **Default: `"public"`**. Postgres schema to use for indexed data. |
| **poolConfig** | [`PoolConfig`](https://node-postgres.com/apis/pool) \| `undefined` | **Default: `{ max: 30 }`**. Pool configuration passed to `node-postgres`. |

<details>
<summary><p>Example `ponder.config.ts` using Postgres</p></summary>
Expand Down
49 changes: 13 additions & 36 deletions examples/with-foundry/README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,37 @@
# Foundry Example

This example repo mimics a monorepo with ponder and a foundry dapp. It's intended to be used as a template for new ponder projects that want to integrate both.
This is an example monorepo Ponder and Foundry dapp. It's intended to be used as a template for new ponder projects that want to integrate both.

## Guide

Follow the [Ponder docs](https://ponder.sh) to learn more on how to use Ponder with Foundry.

Ensure you have Ponder installed as well as `foundry`. You will also need to install the example packages with:

```shell
pnpm install && cd ponder && pnpm install
```

And then install the foundry packages in the `/contracts`:

```shell
# In the `/contracts` directory
forge install
```
For more information, read the Foundry [integration guide](https://ponder.sh/docs/advanced/foundry).

## Usage

There are some handy scripts in `package.json` to help you get started with this example. Open two terminal windows and run the following commands in each which starts an anvil server and ponder service respectively:

```shell
pnpm run start:anvil
```
Start an Anvil local node:

```shell
pnpm run dev:ponder
anvil --block-time 1
```

Next, you'll want to open a third terminal window and deploy the contracts:
Compile contracts:

```shell
pnpm run deploy
forge build
```

After the contracts are deployed, you can run the following command to generate a single event:
Run a Foundry script to deploy contracts and generate some logs:

```shell
pnpm run generate:event
forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```

## Developing

If you'd like to develop the contracts further, make changes as required in `contracts/` and run the following command to recompile the contracts:
Generate ABIs:

```shell
pnpmn run generate:abi
pnpm wagmi generate
```

This uses `wagmi-cli` to generate the ABI typescript files. Before deploying, you'll need to restart anvil and redeploy the contracts. After that, you can reload the ponder service using a dev only route:
Start the Ponder development server:

```shell
curl -X POST http://localhost:42069/admin/reload?chainId=31337
```

This assumes you're using the default port and chainId. Replace the port and chainId if you're using different values.
pnpm ponder dev
```
Loading

0 comments on commit 07e39ed

Please sign in to comment.