Skip to content

feat: Path B /api/notify summaries + erc20_overrides for run-node#627

Merged
chrisli30 merged 4 commits into
mainfrom
staging
Jun 25, 2026
Merged

feat: Path B /api/notify summaries + erc20_overrides for run-node#627
chrisli30 merged 4 commits into
mainfrom
staging

Conversation

@chrisli30

Copy link
Copy Markdown
Member

Staging → main release. Storage-check: ✅ no breaking changes (additive-only).

Included

Pre-merge checks

  • make storage-check → ✅ key templates unchanged, model structs unchanged

🤖 Generated with Claude Code

chrisli30 and others added 3 commits June 23, 2026 16:59
…n-state fix & RPC E2E proof) (#624)

Co-authored-by: Antriksh Gwal <antrikshgwal@gmail.com>
Co-authored-by: Will Zimmerman <will@avaprotocol.org>
…dler URLs resolve

The renamed config/test.yaml references the bundler endpoints via
${SEPOLIA_BUNDLER_URL} / ${BASE_SEPOLIA_BUNDLER_URL}, which the config
loader expands from the process environment. testutil only loaded .env,
and both dotenv files are gitignored — so in a fresh git worktree the vars
were unset, expanded to empty, and bundler-dependent tests panicked with
"BundlerURL is empty in test.yaml config".

LoadDotEnv now loads .env.local then .env (local overrides, real env wins
over both), logs when neither is present so a missing dotenv in a worktree
is no longer a silent mystery, and factors single-file parsing into
loadDotEnvFile.
…th B) (#626)

Co-authored-by: Will Zimmerman <will@avaprotocol.org>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR promotes staging → main changes that (1) shift email notification summarization/distribution to Studio’s /api/notify (“Path B”) and (2) add user-supplied erc20_overrides to RunNodeImmediately so isolated contract-write simulations can be seeded with ERC20 balance/allowance state (plus supporting docs/tests and OpenAPI/proto updates).

Changes:

  • Route REST notification nodes targeting /api/notify to forward raw execution payloads to Studio (gateway no longer does SendGrid-coupled summarization for that path) while preserving summary surfacing to SDK/UI.
  • Add erc20_overrides to RunNodeImmediately request flow (proto + OpenAPI + REST handler wiring), implement validation/state seeding, and fix simulation-state propagation so overrides actually reach Tenderly.
  • Improve test ergonomics and coverage (dotenv auto-load for tests; unit tests for override parsing/validation; E2E proof test for Uniswap swap with/without overrides).

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
protobuf/avs.proto Adds erc20_overrides + ERC20StateOverride message to RunNodeImmediately request.
protobuf/avs.pb.go Regenerated Go bindings for the proto changes.
api/openapi.yaml Exposes erc20Overrides + ERC20StateOverride schema in REST API spec.
aggregator/rest/generated/types.gen.go Regenerated OpenAPI types including ERC20StateOverride and request field.
aggregator/rest/handlers_nodes.go Wires REST erc20Overrides into proto request; slot widening helper.
core/testutil/utils.go Loads .env.local then .env for tests; adds clearer logging/behavior docs.
core/taskengine/run_node_immediately.go Threads and validates erc20_overrides into VM simulation state (simulation-only).
core/taskengine/vm.go Propagates simulationState into per-node temp VM so overrides reach Tenderly.
core/taskengine/simulation_state.go Adds uint256 parsing + explicit slot validation + ApplyUserERC20Override implementation.
core/taskengine/simulation_state_override_test.go New unit tests for uint256 parsing and ERC20 override validation/application.
core/taskengine/run_node_immediately_rpc_test.go Adds end-to-end RPC test proving overrides flip Uniswap swap outcome.
core/taskengine/vm_runner_rest.go Implements /api/notify “studio-notify” path, payload injection, and summary surfacing.
core/taskengine/summarizer_context_memory.go Adds BuildNotifyPayload to build raw execution-data payload for Studio /api/notify.
core/taskengine/summarizer_deterministic.go Updates notification provider naming/detection to “studio-notify” → “Ava Protocol”.
core/taskengine/summarizer_run_node_test.go Updates expectations for /api/notify provider display name.
core/taskengine/vm_runner_contract_write_uniswap_test.go Clarifies Uniswap quote test intent; tightens assertions and removes stale TODOs.
core/taskengine/TENDERLY_STATE_OVERRIDES.md Updates docs to reflect implemented override paths and user-facing API.
Files not reviewed (1)
  • protobuf/avs.pb.go: Generated file
Comments suppressed due to low confidence (1)

core/testutil/utils.go:142

  • The init comment is now incorrect: LoadDotEnv loads .env.local and .env (in that order), not just .env, and the inline comment still says it loads only .env. Please update these comments so future readers don’t assume only .env is supported.
// init loads environment variables from .env file only.
// Test config is now lazily loaded on first use to avoid loading during production binary startup.
func init() {
	// Load .env file first (if it exists)
	_ = LoadDotEnv()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 685 to 688
// Optionally compose summary and inject into provider payloads when enabled via nodeConfig.options.summarize
// Keep a reference to the composed summary so we can return it to clients even
// when the external notification provider (e.g., SendGrid) returns an empty body.
var summaryForClient *Summary
SendGrid handling was removed in #626; the empty-body case now only applies
to the Studio /api/notify provider. Per Copilot review on #627.
@chrisli30 chrisli30 merged commit 14ddfd7 into main Jun 25, 2026
17 of 18 checks passed
@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown

Code Review — PR #627

This PR bundles two independent features: (1) user-facing erc20_overrides for RunNodeImmediately, letting callers seed ERC20 balance/allowance storage slots for Tenderly simulation without an RPC probe; and (2) the gateway's Email/Telegram notification path switching from direct SendGrid to forwarding raw execution data to Studio's /api/notify for summarisation and distribution (Path B). The storage-slot math (erc20BalanceSlot, erc20AllowanceSlot) and the override-threading through the RPC handler are correct.


Findings

1. core/taskengine/vm_runner_rest.go:895-902 — Studio /api/notify HTTP 200 with ok:false body is silently classified as step success

Success is determined solely by HTTP status code (statusCode < 400). The isStudioNotify branch parses m["summary"] from the response body but never checks m["ok"]. If Studio returns {"ok": false, "error": "SMTP delivery failed"} with HTTP 200, stepSuccess is set to true, errorMessage is cleared, and the workflow reports green — email was never sent.

Fix: After the isStudioNotify body-parsing block, inspect the ok field:

if isStudioNotify {
    if m, ok2 := jsonData.(map[string]interface{}); ok2 {
        if okVal, _ := m["ok"].(bool); !okVal {
            stepSuccess = false
            errorMessage = fmt.Sprintf("notify: %v", m["error"])
        }
    }
}

2. core/taskengine/summarizer_deterministic.go:2220-2248isNotificationNode no longer excludes api.sendgrid.com URLs

The previous implementation recognised SendGrid URLs and excluded those REST nodes from workflow step counts. The new implementation only matches /api/notify (Studio) and Telegram. Any existing deployed workflow whose email node calls api.sendgrid.com directly (not yet migrated to /api/notify) will now be included in getTotalWorkflowSteps and buildErrorsArray, making summary lines say "executed N out of N+1 steps" instead of "N out of N".


3. core/taskengine/vm_runner_rest.go:700-717 — Summarizer unconfigured: Studio /api/notify receives request without execution context

When globalSummarizer is nil or not a *ContextMemorySummarizer (e.g. notifications.summary.enabled: false), the code logs a Warn and continues. The POST to /api/notify proceeds with the node's original body — missing steps, trigger, smartWallet, and other execution fields. Studio receives a partial payload and either errors silently or sends a blank notification.


4. core/taskengine/simulation_state.go:294-303ProbeERC20BalanceSlot silently falls back to slot 9 for tokens with all-zero reference balances

When both the target holder and reference addresses (0x1, 0x2) have zero balance, the function defaults to slot 9 (USDC FiatToken layout) and caches it permanently for the session. For any non-USDC-like token (OpenZeppelin uses slot 0, WETH uses slot 3) this seeds the wrong storage word — the simulation proceeds without error but contract-write simulations see the unmodified (zero) balance and revert with misleading ERC20 errors. This function should return an error rather than silently guessing.


5. core/taskengine/run_node_immediately.go:27, 3026-3027, 2722-2727erc20_overrides threaded via a magic key in the generic nodeConfig map[string]interface{}

The proto slice []*avsproto.ERC20StateOverride is stashed under the reserved key "__erc20Overrides" and extracted with a type assertion. If the same key were ever present from a JSON-deserialized config path (where the value would be []interface{}), the assertion at line 2473 panics with no recovery. The cleaner fix is an explicit parameter on runProcessingNodeWithInputs rather than using the config map as a side channel.


6. core/taskengine/run_node_immediately.go:31-45getRealisticBlockNumberForChain uses block numbers ~2 years stale

The hardcoded bases (Ethereum 19.5M, Base 11.5M) are from late 2024; as of June 2026 Ethereum mainnet is at ~22.5M and Base at ~26M. These flow into BlockNumber on synthetic types.Log objects. Any downstream HeaderByNumber call against an archive-pruned RPC node with this value returns "block not found", producing a zero block timestamp in simulation responses. Consider fetching the actual latest block number from the already-established chain-state reader, or at minimum document this as a synthetic placeholder.


7. core/taskengine/summarizer_run_node_test.go:14vm.AddVar() violates CLAUDE.md Go Test Standards

vm.AddVar("aa_sender", "0x5d814Cc9E94B2656f59Ee439D44AA1b6ca21434f")

CLAUDE.md states: "Never use vm.AddVar() to inject test data directly — this bypasses the real execution path and hides bugs." The aa_sender variable should flow from a properly constructed task rather than being injected as a fixture.


8. core/taskengine/summarizer_deterministic.go:648strings.Title() is deprecated in Go 1.18+

return strings.Title(strings.TrimSpace(t))  // deprecated

Will surface in make audit / go vet. Replace with golang.org/x/text/cases.Title(language.English).String(t), or since all expected branch types are handled by the switch, simply return t unchanged in the default case.


Summary

# Severity File Finding
1 High vm_runner_rest.go:895 Studio HTTP-200 ok:false response silently classified as step success
2 Medium summarizer_deterministic.go:2220 isNotificationNode drops SendGrid; existing workflows get wrong step counts
3 Medium vm_runner_rest.go:700 Missing summarizer sends incomplete body to /api/notify
4 Medium simulation_state.go:294 Silent slot-9 fallback seeds wrong ERC20 storage for non-USDC tokens
5 Low run_node_immediately.go:27 Magic config-key side-channel for ERC20 overrides; type-assert panic risk
6 Low run_node_immediately.go:31 Stale hardcoded block numbers (Eth 19.5M, Base 11.5M) — ~2 years behind
7 Low summarizer_run_node_test.go:14 vm.AddVar() violates CLAUDE.md test standards
8 Low summarizer_deterministic.go:648 Deprecated strings.Title()

🤖 Generated with Claude Code

chrisli30 pushed a commit that referenced this pull request Jun 26, 2026
…) before promotion

main carried an unsynced release (#627 Path B /api/notify + erc20_overrides, plus
version bumps v3.11.0 / v3.12.0) that never came back to staging, so the
staging→main PR (#636) conflicted. Back-merge main into staging to realign.

Conflict resolutions:
- summarizer_deterministic.go, summarizer_run_node_test.go: kept DELETED — staging
  removed the deterministic summarizer in #628; main only modified them.
- vm_runner_rest.go, summarizer_context_memory.go: kept STAGING's Path-B-only
  notify rewrite (#628/#629) — it supersedes main's older #627 notify code
  (removed the duplicate BuildNotifyPayload, legacy telegram branch, summaryForClient).
- run_node_immediately_rpc_test.go: kept staging's explicit per-node ChainId (G5 strict).

Net effect on staging: brings in main's version bump (3.10.4 → 3.12.0); everything
else from #627 was already present (#624/#626) or superseded. Build + affected
suites green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants