Skip to content

feat(store): ERC-20 token registry #223

Description

@Emmyt24

Depends on: #214, #215. Blocks: #221, #225.

Description

On Stellar, a wallet's acceptable assets are knowable on-chain: a trustline is an explicit opt-in,
which is what crates/api/src/routes/trustlines.rs
exposes. ERC-20 has no equivalent — any address can receive any token, unsolicited, including
tokens designed to look like real ones.

There is also an existing wart worth fixing here: the testnet USDC issuer is a hard-coded constant
duplicated in two crates, with a comment acknowledging the duplication — see
USDC_TESTNET_ISSUER in crates/ingest/src/lib.rs and
crates/api/src/routes/payment_links.rs. A registry
subsumes both.

Build a per-chain token registry that is the single authority on what Octo will credit.

Requirements and context

  • Registry rows are keyed by CAIP-19 asset id (AD-1) and carry chain_id, contract address,
    symbol, decimals, and enabled.
  • decimals must be verified on-chain at registration by calling decimals(), not taken from
    operator input. USDC is 6 and DAI is 18; a wrong value here misprices every deposit of that token
    by a factor of 10^12.
  • Unregistered tokens are never credited. They may be recorded for visibility (quarantine),
    matching how unattributable Stellar deposits are handled today.
  • Registration is an admin-only operation. A user-registerable registry reintroduces exactly the
    attack it exists to prevent.
  • Document the known-bad token classes explicitly, since operators will ask: fee-on-transfer
    (received ≠ event value), rebasing (balance changes without a transfer), and tokens with an
    upgradeable proxy or a blacklist/pause function that can freeze Octo's own treasury.
  • Security: an attacker deploying a contract with symbol() == "USDC" costs nothing. Symbol is
    a display string with no uniqueness guarantee — match on contract address only, never symbol.

Suggested execution

Branch: feat/erc20-token-registry

Implement changes

  • Migration 00NN_token_registry.sql: a tokens table keyed by CAIP-19 with the fields above,
    UNIQUE (chain_id, contract_address) on the lowercase-normalised address, and rows seeded for
    USDC on the target chains plus the existing Stellar assets so the hard-coded constants can be
    deleted.
  • Store methods: register_token (admin), list_tokens(chain_id), get_token(caip19),
    is_creditable(chain_id, contract_address).
  • On registration, call decimals() / symbol() via feat(evm-rpc): Resilient JSON-RPC client for EVM chains #218 and reject a mismatch against the
    submitted values rather than silently trusting either side.
  • Replace both USDC_TESTNET_ISSUER constants with registry lookups.
  • Add read-only API endpoints so clients can discover supported assets per chain.

Test and commit

  • Test that registration rejects a decimals mismatch against the on-chain value.
  • Test that a disabled token is not creditable and that an unregistered token is quarantined rather
    than credited or dropped.
  • Test address normalisation: registering 0xABC... and looking up 0xabc... resolves.
  • Test that two different contracts both reporting symbol() == "USDC" are distinct registry
    entries
    , and only the registered one is creditable.
  • Test that the Stellar asset path still resolves the same USDC issuer it did via the constant —
    proving the constant removal is behaviour-preserving.
  • Document the registry and the known-bad token classes in docs/architecture.md.

Example commit message

feat(store): per-chain ERC-20 token registry

ERC-20 has no trustline equivalent, so any address can receive any
token; the registry becomes the single authority on what Octo credits.

decimals() is read on-chain at registration rather than trusted from
input — USDC is 6 and DAI is 18, and a wrong value misprices deposits
by 10^12. Matching is on contract address only, never symbol, which
any attacker can spoof for free.

Also removes the duplicated USDC_TESTNET_ISSUER constants in
octo-ingest and octo-api.

Refs #223

Activity

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

Metadata

Metadata

Assignees

No one assigned

    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/mediumMedium difficultygood first issueGood for newcomerstype/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