Skip to content

feat(scenarios): add GoonFi state preparation - #13

Open
92Infinitus92 wants to merge 12 commits into
feat/scenarios/raw-layoutfrom
feat/goonfi-support
Open

92Infinitus92 wants to merge 12 commits into
feat/scenarios/raw-layoutfrom
feat/goonfi-support

Conversation

@92Infinitus92

@92Infinitus92 92Infinitus92 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #8 — GoonFi is the same mechanism class as BisonFi and Tessera (PMM, no IDL) and reuses the raw-layout engine BisonFi introduced.

  • Two raw-layout accounts per market: a 32-byte external price oracle (bid, ask, freshness, decay) and the 2048-byte market holding the reference band that guards it. Four templates over the pair — price, reference band, freshness, stale quote — plus a live catalog read from program accounts and validated at creation.
  • Two builders. Price moves the oracle bid and ask together with the market's reference band and a persistent freshness stamp, since the deployed program rejects a decoupled move with 0x24. Liquidity resolves both token vaults from the market's own pointers and scales their balances through the generic spl-token-account-balance template.
  • Three MCP tools on the generic scenario path: list_goonfi_markets, create_goonfi_price_scenario and create_goonfi_liquidity_scenario. No protocol HTTP endpoints and no custom RPC methods.
  • The Slot raw encoding now takes a width, so GoonFi's 4-byte freshness field no longer clobbers the decay multiplier stored beside it. Existing 8-byte slot fields are unchanged.
  • The oracle carries no discriminator, so the raw-layout owner predicate is what keeps a write out of a foreign 32-byte account, and every builder resolves the oracle through the market's own pointer rather than trusting a caller-supplied address.
  • Verified against the deployed program on a fork: drained vaults reject with 0x1, a dislocated quote with a coupled band fills at the prepared price, an aged oracle rejects with 0x15, and a price moved out of its band rejects with 0x24.
  • The shared live-test helper duplicates the Tessera branch's on purpose; they fold together when the branches converge.

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

Summary

The PR adds GoonFi market discovery and scenario preparation using the shared raw-layout engine.

  • Adds coupled oracle-price, reference-band, freshness, and stale-quote templates.
  • Adds price and liquidity scenario builders with market, oracle, and vault relationship validation.
  • Exposes GoonFi catalog, price-scenario, and liquidity-scenario MCP tools.
  • Extends raw slot encoding to support the oracle’s four-byte freshness field.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  RPC[Surfnet RPC] --> Discovery[GoonFi market discovery]
  Discovery --> Market[Validated market]
  Market --> Price[Price scenario builder]
  Market --> Liquidity[Liquidity scenario builder]
  Price --> Oracle[Oracle price and freshness overrides]
  Price --> Band[Market reference-band override]
  Liquidity --> Base[Base-vault balance override]
  Liquidity --> Quote[Quote-vault balance override]
  Liquidity --> Fresh[Oracle freshness override]
Loading

Reviews (14) · Last reviewed commit: "docs(goonfi): say that the freshness ove..."

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Two improvements in the PR:

  • markets.rs:146: One missing oracle/mint or invalid market aborts the entire catalog. Skip invalid entries and report diagnostics so an obsolete market cannot hide all valid markets.
  • liquidity.rs:178: Vault validation accepts any token-program-owned account with at least 72 bytes—including an 82-byte mint. Unpack a token account and verify its mint matches the corresponding market mint before reading its balance.

Tests could not run: Rustup attempted to write into a read-only toolchain directory.

@92Infinitus92 92Infinitus92 self-assigned this Sep 9, 2026
Comment thread crates/core/src/scenarios/protocols/goonfi/v1/markets.rs Outdated
Comment thread crates/core/src/scenarios/protocols/goonfi/v1/price.rs
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
  • [P2] Validated market/oracle pairing remains mutableprice.rs:65. #[non_exhaustive] prevents external struct literals, but callers can still assign market.oracle = another_oracle after validation. The builder then modifies that oracle alongside the original market’s reference band, producing an inconsistent scenario. Make both fields private and expose read-only getters.

The catalog isolation issue from the earlier review is fixed in the latest commit. Review was static; tests were not run.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
  • [P2] Account validation still lacks address bindingprice.rs:90. validate checks the supplied oracle’s layout but cannot establish that it belongs to the market’s oracle pointer. Private fields do not close this gap for direct library callers. Accept keyed accounts and compare addresses, or fetch the referenced account within validation.

  • [P2] Liquidity scaling can use another vault’s balanceliquidity.rs:99. Any token account with the expected mint passes validation. Passing another market’s vault calculates the percentage from its balance, then writes that amount to this market’s vault—potentially increasing liquidity during a requested drain. Validate supplied vault addresses against the market pointers and add a mismatch regression test.

Tests could not run: rustup attempted to write to a read-only directory.

Comment thread crates/core/src/scenarios/protocols/goonfi/v1/price.rs
Comment thread crates/core/src/scenarios/protocols/goonfi/v1/liquidity.rs Outdated
Comment thread crates/core/src/scenarios/protocols/goonfi/v1/price.rs Outdated
Comment thread crates/core/src/scenarios/protocols/goonfi/v1/liquidity.rs
@92Infinitus92
92Infinitus92 changed the base branch from feat/bisonfi-support to feat/scenarios/raw-layout September 14, 2026 13:30
GoonFi is a two-account dark AMM with no IDL: a 32-byte external price oracle
plus a 2048-byte market carrying the reference band that guards it.

- Four raw-layout templates over the two accounts (price, reference band,
  freshness, stale quote), plus a live market catalog read from program accounts.
- Two builders. Price moves the oracle bid and ask together with the market's
  reference band and a persistent freshness stamp, because the deployed program
  rejects a decoupled move with 0x24. Liquidity resolves both token vaults from
  the market's own pointers and scales their balances.
- Three MCP tools on the generic scenario path: list_goonfi_markets,
  create_goonfi_price_scenario and create_goonfi_liquidity_scenario. No protocol
  HTTP endpoints.
- The Slot raw encoding now takes a width, so the 4-byte freshness field no
  longer clobbers the decay multiplier stored beside it.
- Live gated tests against the deployed program. The shared live-test helper
  duplicates the Tessera branch's on purpose; they fold together when the
  branches converge.
- Catalog discovery skips a market that fails validation with a warning instead of
  aborting the whole scan, so one obsolete market can no longer hide every valid one.
  An empty catalog from a program that does own markets stays an error.
- Vault balances are read through TokenAccount::unpack and checked against the market's
  own mint: an owner-and-length check also admits an 82-byte mint, whose bytes at the
  amount offset would be misread as a balance.
- GoonfiMarket is non_exhaustive, so a market and its oracle can only be paired through
  validate, which reads the oracle from the market's own pointer.
`non_exhaustive` only blocked building a GoonfiMarket from scratch outside the crate;
the fields stayed public, so a caller could still re-point a validated pair's oracle.
Making both private leaves `validate` as the only way to pair a market with an oracle,
and it reads that oracle from the market's own pointer.
The fields are private, so a caller that legitimately needs to know which market and
oracle a validated pair refers to had no way to ask.
A market account does not record its own address, so `validate` had to take the caller's
word that the address and the data describe the same market. Its vaults do record it: a
GoonFi vault is a token account whose authority is the market. `validate` now takes the
base vault the market itself names and compares that authority, the same shape of
cross-check the pump graduation builder makes through its curve vault's mint. The
liquidity builder applies it too, from accounts it already holds.

Neither path costs an extra round trip: discovery carries the base vault alongside the
mints and oracle so it batches with them, and the MCP market fetch reads it in the same
call as the oracle. Verified against all 33 live markets - every one names its market as
the base vault's authority.
The balance is read from the account passed in but written to the vault address decoded
from the market, so a vault of the right mint belonging to another market would scale the
wrong balance into this one - a requested drain could top the vault up instead. The
authority check now lives inside the balance read, where it covers the quote side too, and
the base-only call it replaces is gone.
Validate vault and oracle addresses against the market's pointers before
reading balances or constructing overrides. Preserve the existing RPC reads
and generic scenario templates.

Add mismatch regressions and update MCP, discovery, and live-test callers.
The Tessera, HumidiFi and GoonFi parameter structs deserialized their fields as snake_case, so a
client sending surfnetPort the way the pump, get_template and search_constant_options tools expect
it was ignored and the read fell back to port 8899. Scenario tools now share one convention.
@92Infinitus92

Copy link
Copy Markdown
Collaborator Author

@greptileai The address is checked before the bytes are used: vault_amount rejects a vault whose address differs from the market's pointer, and GoonfiMarket::validate does the same for the base vault and the oracle (tests: rejects_unrelated_vault_with_matching_mint_and_authority, rejects_unrelated_base_vault_with_target_market_authority). What remains is a caller labelling one account's bytes with another account's address. A pure builder cannot verify that; the reading layer can, and the MCP tools read every account by address from the surfnet. Same contract as the pump graduation builder, documented in the README.

@greptile-apps

greptile-apps Bot commented Sep 16, 2026

Copy link
Copy Markdown

You're right on the core point. vault_amount and GoonfiMarket::validate reject every address mismatch the builder can observe, but once an Account is supplied separately from its Pubkey, a pure builder cannot authenticate that the bytes originated at that address. That would require changing the API to fetch accounts itself or passing a keyed/account type that preserves provenance.

Given the documented library contract, the MCP path reads each account by address and passes the paired values, matching the existing pump graduation builder pattern. The remaining risk is caller misuse rather than an actionable defect in these builders. I would withdraw the P2 merge blocker; an API redesign could be considered separately if provenance must be enforced for all direct callers.

@92Infinitus92

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread crates/core/src/scenarios/protocols/goonfi/v1/markets.rs Outdated
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.

1 participant