Stage: GRC/FRC
Authors: cargopete (Petko Pavlovski) · GitHub
Related: Add Rust ABI support for WASM subgraphs by cargopete · Pull Request #6462 · graphprotocol/graph-node · GitHub ·
Summary
This GRC proposes adding first-class Rust support to graph-node as a second mapping language alongside AssemblyScript. Subgraph developers write handlers in Rust, compile to wasm32-unknown-unknown, and declare language: wasm/rust in their manifest. Graph-node dispatches to a new rust_abi serialization layer; the existing HostExports layer is unchanged.
A working implementation exists: the Graphite SDK compiles ERC20 handlers to WASM, and the graph-node fork has been live-tested indexing real USDC Transfer events from Ethereum mainnet via GraphQL.
Motivation
AssemblyScript’s pain points are well-documented in the community, but they are structural — not fixable at the SDK layer.
Nullable handling crashes at runtime. AS type narrowing only works on local variables. Accessing a nullable property directly compiles without error but produces a WASM trap at runtime. Rust’s Option eliminates this entire class of bug at compile time.
No closures, no functional programming. array.map(), .filter(), and .forEach() all crash the AS compiler. Rust’s iterator chains are transformational for handler code.
Hostile debugging. When the AS compiler crashes, the official guidance is to “comment the whole file and uncomment it little by little.” Rust’s compiler gives actionable errors pointing directly to the problem.
Additional issues: no Date support, no regex, === performs identity not value comparison, Matchstick requires PostgreSQL and produces silent type-mismatch failures.
The Graph has already validated this path. Substreams uses Rust compiled to WASM for high-performance indexing. This proposal extends that foundation to the subgraph layer.
Design
HostExports is already fully language-agnostic — it operates on native Rust types (String, HashMap<String, Value>, Vec). The AS coupling lives entirely in runtime/wasm/src/asc_abi/. Adding Rust support means a parallel rust_abi/ module and dispatching on language: in the manifest.
Graph-node calls Rust handlers with a simple C calling convention:
extern “C” fn handle_transfer(event_ptr: u32, event_len: u32) → u32;
Serialization uses a simple TLV (Type-Length-Value) format. The module owns a bump allocator; graph-node writes serialized event data in, calls the handler, then calls reset_arena(). No managed heap, no graph-node reaching into AS-style TypedMaps.
What we tried to learn from the ASC ABI:
- No managed heap — bump allocator owned by the module, arena reset after each handler
- Explicit TLV format with a fixed, documented value tag table — no implicit type coercions, endianness specified in the protocol
- Language detection via manifest (language: wasm/rust) — clean dispatch, no heuristics
- Versioned from day one — apiVersion: 0.0.1 gives an explicit migration path
Developer experience
A Rust ERC20 handler:
use graphite::prelude::*;
#[handler]
pub fn handle_transfer(event: TransferEvent) {
let mut transfer = Transfer::new(&format!(“{}-{}”,
event.tx_hash, event.log_index));
transfer.from = event.from;
transfer.to = event.to;
transfer.value = event.value.clone();
transfer.save();
}
Testing runs natively with cargo test — no WASM compilation, no PostgreSQL:
#[test]
fn transfer_creates_entity() {
let mut host = MockHost::default();
handle_transfer(&mut host, &mock_event());
assert_eq!(host.store.entity_count(“Transfer”), 1);
}
Implementation status
This is not a design-only proposal. A complete implementation
exists and has been live-tested.
graph-node fork (cargopete/graph-node, branch rust-abi-support):
- rust_abi/ module (~1,450 LOC): types, entity serialization,
trigger serialization, host function wrappers - Manifest parsing and language dispatch
- Rust handler invocation with arena reset
- Wasmtime fuel metering (10B instruction budget, OutOfFuel as a
deterministic error) - All existing tests passing
Graphite SDK (cargopete/graphite):
- #[derive(Entity)] and #[handler] proc macros
- TLV deserializer, ABI + schema codegen, full CLI
- MockHost for native unit testing, no external dependencies
Live test: deployed ERC20 subgraph to the graph-node fork, indexed real USDC Transfer events from Ethereum mainnet (block 24756400+), queried via GraphQL. All fields correct.
Scope of graph-node changes
The change is fully additive — existing AS subgraphs are unaffected. New code lives in rust_abi/ (~1,450 LOC across 5 files). Modified files: manifest parsing, linker dispatch, handler
invocation, Ethereum trigger serialization. HostExports is unchanged. Adding a new host function requires one thin ptr+len wrapper in rust_abi/host.rs — no changes to the AS path.
Open questions for community feedback
- Namespace. Host functions currently use “graphite” as the WASM import namespace. Should this be “graph” or something more official?
- API versioning. Should apiVersion: 0.0.1 be independent of the AS versioning scheme, or aligned with it?
- Shared constants. TLV value tags are duplicated between graph-node and the SDK. Worth a shared graph-abi crate, or is a spec doc sufficient?
- Chain coverage. Ethereum triggers are fully serialized. NEAR is stubbed. Proposed scope for initial merge: Ethereum-only.
- ABI evolution. If the TLV format needs to change, apiVersion in the manifest is the current migration story — is that sufficient?
References
- Add Rust ABI support for WASM subgraphs by cargopete · Pull Request #6462 · graphprotocol/graph-node · GitHub
- GitHub - cargopete/graphite: A Rust SDK for building subgraphs on The Graph · GitHub
- https://github.com/cargopete/graphite/blob/main/rfc-rust-subgraph
.md - Substreams Quick Start | Docs | The Graph — prior art for
Rust→WASM in The Graph ecosystem