Skip to content

feat(ingest): EVM ingest worker — ERC-20 Transfer log scanning #221

Description

@Emmyt24

Depends on: #218, #220, #223. Blocks: #222.

Description

octo-ingest polls Horizon /payments oldest-first from a saved
paging_token, attributes each payment by muxed id or memo id, and records it idempotently, with
dedup on the Horizon TOID. EVM deposit detection is structurally different:

  • No per-account payments feed. You scan eth_getLogs for ERC-20 Transfer events whose
    to topic matches one of your deposit addresses.
  • The cursor is a block number, not an opaque token.
  • Native ETH transfers emit no logs at all — they are only visible by inspecting block
    transactions or debug_traceBlock. Decide and document whether native ETH deposits are in scope
    (recommendation: out of scope for v1, ERC-20 stablecoins only, stated explicitly in the docs
    rather than silently unsupported).
  • Non-standard ERC-20s exist: fee-on-transfer tokens where the amount received ≠ the amount in the
    event, and rebasing tokens. The registry (feat(store): ERC-20 token registry #223) is the defence; this worker must trust the
    registry, not the token.

Build the EVM ingest worker to the same reliability bar as the Stellar one.

Requirements and context

Suggested execution

Branch: feat/evm-ingest-worker

Implement changes

Test and commit

  • Anvil integration tests (chore(testing): Anvil-based EVM integration test harness #219): a real ERC-20 transfer to a deposit address is detected, attributed,
    and recorded with the exact amount at 6 and 18 decimals.
  • Adversarial test — the critical one: deploy a hostile contract that emits a
    Transfer event with a deposit address in the to topic and a huge value, and assert it is
    not credited because the emitting contract is not a registered token. Model on
    crates/ingest/tests/adversarial_replay_tests.rs.
  • Replay/idempotency test: processing the same log range twice records nothing new.
  • Crash-resume test: kill mid-batch, restart, assert exactly-once processing. Mirror
    resume_replay_tests.rs.
  • Range-bisection test: a mock RPC returning RangeTooLarge causes bisection, not a stall.
  • Test that a transfer of an unregistered token to a known address is quarantined, not credited.
  • Update docs/ingest-integration.md.

Example commit message

feat(ingest): EVM deposit detection via ERC-20 Transfer logs

Scans eth_getLogs for registered token contracts with a block-number
cursor, advancing only after durable processing so a crash resumes
exactly-once — the same guarantee the Horizon path gives.

Logs are matched on the emitting contract address, not topics alone:
any contract can emit a Transfer event with arbitrary topics, so
topic-only attribution would let an attacker mint balances.

Deposits are recorded unconfirmed; crediting is gated on #222.

Refs #221

Guidelines

Large issue — split into stacked PRs (cursor/scanning, then decoding/attribution) if that helps
review. Do not merge a version that credits balances before #222 lands.


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea/backendBackend crates: api, store, ingest, webhooks, bin/serverdifficulty/hardHard, complex, cross-cuttingtype/epicTracking issue for a multi-issue epic

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions