Skip to content

Nothing verifies that the market a user signs for is the market they were shown #212

Description

@Muyideen-js

Problem

The market question a user reads and the market their money goes to are two unrelated values, and
no code checks that they agree.

What the user reads comes from a JavaScript literal, frontend/script.js:10:

{ category: "crypto",
  title: "Will Stellar XLM trade above $0.50 before September 30, 2026?",
  detail: "Live Stellar Testnet market #3...",
  close: "Sep 30, 2026",
  onchainId: 3 }

What the user signs is built from onchainId alone -- frontend/script.js:268 passes
marketId: market.onchainId into placeBet, which puts nativeToScVal(BigInt(marketId)) into the
operation. The title, the close date, and the resolution criteria never leave the browser.

get_market is never called anywhere in this codebase. Confirmed by grep: the only contract
call the frontend makes is place_bet. So the app has no idea what on-chain market 3 actually is.

Impact

The binding between the displayed question and the signed transaction is a hardcoded integer that
nobody validates. That breaks in ordinary operation, not just under attack:

  • A redeploy renumbers markets. scripts/deploy-testnet.sh deploys fresh contracts and
    scripts/create-mainnet-markets.sh creates markets in sequence. After any redeploy, on-chain
    market 3 is a different question -- but script.js still advertises the XLM question against id 3.
    The user reads one question and stakes XLM on another.
  • A resolved or cancelled market still accepts a click. The UI has no idea market 3 closed. The
    user only learns when simulation returns error 5 MarketExpired, 7 MarketResolved, or
    8 MarketCancelled -- after they have committed to the flow.
  • The close date is decoration. "Sep 30, 2026" is a string; the contract's real close ledger is
    never read, so the countdown a user makes their decision on may be wrong.
  • The odds are decoration too. yes: 50 is a literal, not derived from the YES/NO pools, so the
    implied payout shown before signing has no relationship to the actual payout.

The user is being asked to approve a financial transaction on the basis of information that the
application has never verified and cannot verify in its current shape.

What to do

  • Fetch the market with get_market(market_id) and render the question, close time, and
    resolution status from the contract, so the displayed market and the signed market are the
    same object by construction
  • Re-read market state immediately before building the transaction, and abort with a clear
    message if it is resolved, cancelled, frozen, or past close
  • Derive the implied probability and the estimated payout from the on-chain YES/NO pools and the
    contract's fee configuration, not from literals
  • Show the user, in the confirmation step, the exact market id and question the transaction
    targets, before they are handed to the wallet
  • Remove onchainId as a hand-maintained field -- the on-chain id should come from enumeration,
    never from a value typed into a source file
  • Surface the minimum stake and the fee from get_config rather than discovering them through
    error 10 BetTooSmall after submission

Why this is critical

This is a what-you-see-is-what-you-sign failure on a money path. The current design can take a real
XLM stake for a market the user never agreed to, through nothing more exotic than a routine
contract redeploy -- and the user has no way to detect it, because the only place the real question
exists is on chain, and the app never looks there.

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

    area: economicsMechanism design and protocol economicsarea: frontendWeb client, wallet, and user experiencearea: securitySecurity engineering and adversarial analysisbugSomething isn't workingpriority: criticalCritical to protocol safety or mainnet readiness

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions