Skip to content

Commit b330e50

Browse files
gnd: update test docs for mock transport and file data sources
- Document getBalanceCalls and hasCodeCalls mock fields - Document IPFS and Arweave file data source mocking - Update architecture section to describe mock transport - Update troubleshooting for immediate unmocked call errors - Update known limitations table
1 parent 8a9e0f1 commit b330e50

1 file changed

Lines changed: 122 additions & 33 deletions

File tree

gnd/docs/gnd-test.md

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ gnd test --matchstick
2222

2323
Tests are JSON files that define:
2424
- Mock blockchain blocks with events
25-
- Mock `eth_call` responses
25+
- Mock Ethereum RPC responses (`eth_call`, `eth_getBalance`, `eth_getCode`)
2626
- GraphQL assertions to validate entity state
2727

2828
Place test files in a `tests/` directory with `.json` or `.test.json` extension.
@@ -82,6 +82,8 @@ Place test files in a `tests/` directory with `.json` or `.test.json` extension.
8282
| `baseFeePerGas` | No | None (pre-EIP-1559) | Base fee in wei |
8383
| `events` | No | Empty array | Log events in this block |
8484
| `ethCalls` | No | Empty array | Mock `eth_call` responses |
85+
| `getBalanceCalls` | No | Empty array | Mock `eth_getBalance` responses for `ethereum.getBalance()` |
86+
| `hasCodeCalls` | No | Empty array | Mock `eth_getCode` responses for `ethereum.hasCode()` |
8587

8688
### Empty Blocks
8789

@@ -359,7 +361,7 @@ Mock contract calls made from mapping handlers using `contract.call()`:
359361
| `function` | Yes | Full signature: `"functionName(inputTypes)(returnTypes)"` |
360362
| `params` | Yes | Array of input parameters (as strings) |
361363
| `returns` | Yes | Array of return values (as strings, ignored if `reverts: true`) |
362-
| `reverts` | No | Default `false`. If `true`, the call is cached as `Retval::Null` |
364+
| `reverts` | No | Default `false`. If `true`, the mock transport returns an RPC error |
363365

364366
### Function Signature Format
365367

@@ -414,6 +416,110 @@ From the ERC20 test:
414416
}
415417
```
416418

419+
## ethereum.getBalance() Mocking
420+
421+
Mock balance lookups made from mapping handlers using `ethereum.getBalance()`:
422+
423+
```json
424+
{
425+
"getBalanceCalls": [
426+
{
427+
"address": "0xaaaa000000000000000000000000000000000000",
428+
"value": "1000000000000000000"
429+
}
430+
]
431+
}
432+
```
433+
434+
### getBalanceCalls Fields
435+
436+
| Field | Required | Description |
437+
|-------|----------|-------------|
438+
| `address` | Yes | Account address (checksummed or lowercase hex) |
439+
| `value` | Yes | Balance in Wei as a decimal string |
440+
441+
## ethereum.hasCode() Mocking
442+
443+
Mock code existence checks made from mapping handlers using `ethereum.hasCode()`:
444+
445+
```json
446+
{
447+
"hasCodeCalls": [
448+
{
449+
"address": "0x1234000000000000000000000000000000000000",
450+
"hasCode": true
451+
}
452+
]
453+
}
454+
```
455+
456+
### hasCodeCalls Fields
457+
458+
| Field | Required | Description |
459+
|-------|----------|-------------|
460+
| `address` | Yes | Contract address (checksummed or lowercase hex) |
461+
| `hasCode` | Yes | Whether the address has deployed bytecode |
462+
463+
## File Data Sources
464+
465+
Mock IPFS and Arweave file contents for file data source handlers. Files are defined at the top level of the test JSON (not inside blocks).
466+
467+
### IPFS Files
468+
469+
```json
470+
{
471+
"name": "File data source test",
472+
"files": [
473+
{
474+
"cid": "QmExample...",
475+
"content": "{\"name\": \"Token\", \"description\": \"A token\"}"
476+
},
477+
{
478+
"cid": "QmAnother...",
479+
"file": "fixtures/metadata.json"
480+
}
481+
],
482+
"blocks": [...],
483+
"assertions": [...]
484+
}
485+
```
486+
487+
#### files Fields
488+
489+
| Field | Required | Description |
490+
|-------|----------|-------------|
491+
| `cid` | Yes | IPFS CID (`Qm...` or `bafy...`). The mock ignores hash/content relationship |
492+
| `content` | One of `content`/`file` | Inline UTF-8 content |
493+
| `file` | One of `content`/`file` | File path, resolved relative to the test JSON |
494+
495+
### Arweave Files
496+
497+
```json
498+
{
499+
"name": "Arweave data source test",
500+
"arweaveFiles": [
501+
{
502+
"txId": "abc123",
503+
"content": "{\"name\": \"Token\"}"
504+
},
505+
{
506+
"txId": "def456/metadata.json",
507+
"file": "fixtures/arweave-data.json"
508+
}
509+
],
510+
"blocks": [...],
511+
"assertions": [...]
512+
}
513+
```
514+
515+
#### arweaveFiles Fields
516+
517+
| Field | Required | Description |
518+
|-------|----------|-------------|
519+
| `txId` | Yes | Arweave transaction ID or bundle path (e.g. `"txid/filename.json"`) |
520+
| `content` | One of `content`/`file` | Inline UTF-8 content |
521+
| `file` | One of `content`/`file` | File path, resolved relative to the test JSON |
522+
417523
## Assertions
418524

419525
GraphQL queries to validate the indexed entity state after processing all blocks.
@@ -647,10 +753,12 @@ my-subgraph/
647753
|---------|--------|
648754
| Log events | ✅ Supported |
649755
| Block handlers (all filters) | ✅ Supported |
650-
| eth_call mocking | ✅ Supported |
756+
| `eth_call` mocking | ✅ Supported |
757+
| `ethereum.getBalance()` mocking | ✅ Supported |
758+
| `ethereum.hasCode()` mocking | ✅ Supported |
651759
| Dynamic/template data sources | ✅ Supported |
652760
| Transaction receipts (`receipt: true`) | ⚠️ Partial — `receipt.logs` is populated and grouped by `txHash`; other fields (gas, from, to, etc.) are hardcoded stubs (see [Transaction Receipts](#transaction-receipts)) |
653-
| File data sources / IPFS mocking | ❌ Not implemented |
761+
| File data sources (IPFS + Arweave) | ✅ Supported |
654762
| Call triggers (traces) | ❌ Not implemented |
655763
| `--json` CI output | ❌ Not implemented |
656764
| Parallel test execution | ❌ Not implemented |
@@ -764,10 +872,8 @@ GraphQL queries → Assertions
764872
**Key design principles:**
765873

766874
- **Isolated database per test:** Each test gets a pgtemp database dropped on completion (default), or a shared persistent database with post-test cleanup (`--postgres-url`)
767-
- **Real WASM runtime:** Uses `EthereumRuntimeAdapterBuilder` with real `ethereum.call` host function
768-
- **Pre-populated call cache:** `eth_call` responses are cached before indexing starts
875+
- **Mock transport layer:** A mock Alloy transport serves `eth_call`, `eth_getBalance`, and `eth_getCode` from test JSON data. All three flow through the real production code path — only the transport returns mock responses. Unmocked RPC calls fail immediately with a descriptive error.
769876
- **No IPFS for manifest:** Uses `FileLinkResolver` to load manifest/WASM from build directory
770-
- **Dummy RPC adapter:** Registered at `http://0.0.0.0:0` — exists so the runtime can resolve an adapter with the required capabilities. If a mapping makes an `ethereum.call` that has no matching mock in `ethCalls`, the call misses the cache and falls through to this dummy adapter. The connection is refused immediately (port 0 is invalid), which graph-node treats as a possible reorg and restarts the block stream. The indexer then loops until the 60-second test timeout. See [Unmocked eth_call](#unmocked-eth_call-causes-60-second-timeout) in Troubleshooting.
771877

772878
## Troubleshooting
773879

@@ -790,36 +896,19 @@ GraphQL queries → Assertions
790896
2. Simplify mapping logic
791897
3. Check for infinite loops in handler code
792898

793-
### eth_call Returns Wrong Value
794-
795-
**Cause:** Call cache miss — no matching mock in `ethCalls`.
796-
797-
**Fix:**
798-
1. Verify `address`, `function`, and `params` exactly match the call from your mapping
799-
2. Check function signature format: `"functionName(inputTypes)(returnTypes)"`
800-
3. Ensure parameters are in correct order
801-
802-
### Unmocked eth_call Causes 60-Second Timeout
899+
### Unmocked RPC Call
803900

804-
**Cause:** A mapping handler calls `ethereum.call` (directly or via a generated contract binding) for a call that has no matching entry in `ethCalls`. The call misses the pre-populated cache and is forwarded to the dummy RPC adapter at `http://0.0.0.0:0`. The connection is refused immediately, but graph-node interprets connection errors as a possible chain reorganisation and restarts the block stream instead of failing. The indexer loops indefinitely until the test runner's 60-second timeout expires.
901+
**Cause:** A mapping handler calls `ethereum.call`, `ethereum.getBalance`, or `ethereum.hasCode` for a call that has no matching mock entry.
805902

806-
**Symptom:** Test fails with `Sync timeout after 60s` with no indication of which call was missing.
903+
**Symptom:** Test fails immediately with a descriptive error like:
904+
```
905+
gnd test: unmocked eth_call to 0x1234... at block hash 0xabcd...
906+
Add a matching 'ethCalls' entry to this block in your test JSON.
907+
```
807908

808909
**Fix:**
809-
1. Add the missing call to `ethCalls` in your test block:
810-
```json
811-
"ethCalls": [
812-
{
813-
"address": "0xContractAddress",
814-
"function": "myFunction(uint256):(address)",
815-
"params": ["42"],
816-
"returns": ["0xSomeAddress"]
817-
}
818-
]
819-
```
820-
2. If the call is not supposed to happen, check the mapping logic — a code path may be executing unexpectedly.
821-
822-
**Known limitation:** There is currently no fail-fast error for unmocked calls. The only signal is the timeout. A future improvement will make the dummy adapter panic immediately on a cache miss with a descriptive message.
910+
1. Add the missing mock to the appropriate field in your test block (`ethCalls`, `getBalanceCalls`, or `hasCodeCalls`)
911+
2. If the call is not supposed to happen, check the mapping logic — a code path may be executing unexpectedly
823912

824913
### Block Handler Not Firing
825914

0 commit comments

Comments
 (0)