Repository navigation
feat(scenarios): add the Pyth Core Compatible receiver price feed template - #33
92Infinitus92 wants to merge 17 commits into
Conversation
There was a problem hiding this comment.
Summary
Adds a pyth-pro-price-feed-v2 template for PriceUpdateV2 accounts owned by the Pyth Pro-compatible receiver (rec2HHDD…). Callers pass the account address directly; the template doesn't derive a PDA. The PR also bundles a copy of the Pyth IDL with only address changed, updates the registry counts and tests, and adds a mainnet integration test behind the integration-tests feature.
The approach works. The integration test is well built: every value it writes differs from the live one, it checks a no-op round trip, and it asserts that every changed byte falls inside a written field.
Issues
- The PR does more than the description says ("YAML and IDL only"). It also changes the
CallSurfnetRpcParams.paramsschemars description incrates/mcp, and it adds.github/workflows/claude-review.yml. Neither has anything to do with the Pyth template. Consider splitting them into their own PRs, or at least mentioning them in the description. - Duplicated IDL (~130 lines).
pyth/pro/v2/idl.jsonis byte-for-byte the v2 IDL apart fromaddress. If the upstream IDL is ever updated, the two copies will silently drift apart. You could loadPYTH_V2_IDL_CONTENT, overwriteidl.addresswith the Pro receiver, and drop the second file. - The live test depends on mainnet state. It hard-codes byte offsets that are only valid for
VerificationLevel::Full, and it relies on one specific feed account. The asserts fail with clear messages, which helps, but the test can break without any code change. That's acceptable since it's behind the feature flag.
|
|
||
| pub const PYTH_V2_IDL_CONTENT: &str = include_str!("./protocols/pyth/v2/idl.json"); | ||
| pub const PYTH_V2_OVERRIDES_CONTENT: &str = include_str!("./protocols/pyth/v2/overrides.yaml"); | ||
| pub const PYTH_PRO_V2_IDL_CONTENT: &str = include_str!("./protocols/pyth/pro/v2/idl.json"); |
There was a problem hiding this comment.
This IDL is identical to PYTH_V2_IDL_CONTENT except for address. To avoid keeping two copies in sync, you could parse the v2 IDL and overwrite address with rec2HHDDnjLfj4kE7VyEtFA1HPGQLK33259532cRyHp before calling load_collection.
| TIME: leave publish_time, prev_publish_time and posted_slot unset. fetchBeforeUse: true | ||
| brings the upstream account's current values when the override plays, so the price is as | ||
| fresh as upstream's. A time written into the scenario is fixed when the scenario is created | ||
| and ages until it plays. To make a price stale on purpose, follow the TIME FIELDS steps of |
There was a problem hiding this comment.
This tells the agent to follow the TIME FIELDS steps in another template's llm_context. An agent that only loaded this template won't have those steps, and the two can drift if pyth-price-feed-v2's context gets edited. Consider inlining a short version of the pause → read Clock → timeTravel (ms) steps. The "FIELDS OTHER THAN PRICE" guidance (exponent rescales everything, posted_slot is a slot) is also missing here.
| )] | ||
| pub method: String, | ||
| #[schemars(description = "The parameters to pass to the RPC method")] | ||
| #[schemars( |
There was a problem hiding this comment.
This MCP schema change has nothing to do with the Pyth Pro template and isn't in the PR description. Consider a separate PR. Also, Number is i64 only, so non-integer values can't be expressed. It's worth saying that explicitly, and mentioning the PublicKey/MintAddress/ProgramId/Null variants, or stating that String is the one to use for pubkeys.
There was a problem hiding this comment.
Summary
Adds pyth-pro-price-feed-v2, a template for PriceUpdateV2 accounts owned by the Pyth Pro-compatible receiver (rec2HH…). The bundled IDL is a copy of the Pyth v2 IDL. I diffed the two and the only change is address. The registry loads it under "pyth-pro", and register_builtin_template_idls also registers it with the SVM, so owner-based IDL lookup and the jsonParsed reads that llm_context relies on should work for these accounts. The registry tests (count 70→71, by-protocol, by-tag) are updated consistently, and the new live test is strong: it round-trips the bytes without changes, then checks that only the declared field ranges change.
Overall this looks correct and ready to merge. Minor points:
- Unrelated file in the diff:
.github/workflows/claude-review.ymlappears in the base…head diff. If it comes from another branch, rebase or drop it so this PR only contains the Pyth Pro change. - Duplicated IDL:
pyth/pro/v2/idl.jsonis a 130-line copy that differs in one field. That's acceptable for now. If more mirrored receivers are added, consider loading the base IDL and overridingaddressin code, so the copies can't drift apart. - MCP
paramsdescription: it lists everyJsonValuevariant exceptNull, which serializes as the plain string"Null". Mentioning that would stop LLM callers from guessing{"Null":null}.Numberis alsoi64only, so floats can't be expressed. "whole number" hints at this, but stating it outright would be clearer.
| description: Uncertainty band around the price, in the same scale as price | ||
| - path: price_message.exponent | ||
| label: Exponent | ||
| description: Decimal exponent, normally -8 and -10 on some feeds. Rescales every price field |
There was a problem hiding this comment.
This sentence is hard to parse. Suggest: "Decimal exponent, usually -8 (-10 on some feeds). Rescales every price field." It would also help to say that changing the exponent alone reinterprets the price, conf and EMA values, so an LLM doesn't edit only the exponent.
| let live = fetch(PRO_SOL_USD_FEED).await; | ||
| assert_eq!(live.owner.to_string(), PYTH_PRO_COMPATIBLE_RECEIVER); | ||
| // A Partial verification level shifts every offset below by one. | ||
| assert_eq!(live.data[40], 1, "verification level should be Full"); |
There was a problem hiding this comment.
These hard-coded offsets depend on the live mainnet account being Full. If 7AviUf… is ever posted with Partial verification, the test fails with a message that doesn't make clear it is an upstream data issue rather than a regression. Consider adding to the assertion message that the account's verification level changed upstream and the test needs a different feed or offsets shifted by one.
| pub method: String, | ||
| #[schemars(description = "The parameters to pass to the RPC method")] | ||
| #[schemars( | ||
| description = "The parameters to pass to the RPC method. Wrap every value at every depth by its type: a string as {\"String\":\"...\"}, a whole number as {\"Number\":5}, a boolean as {\"Bool\":true}, a list as {\"Array\":[...]} and an object as {\"Object\":{\"key\":<wrapped value>}}; object keys stay plain. Example for getProgramAccounts with a filter: [{\"String\":\"<program id>\"},{\"Object\":{\"encoding\":{\"String\":\"base64\"},\"filters\":{\"Array\":[{\"Object\":{\"memcmp\":{\"Object\":{\"offset\":{\"Number\":0},\"bytes\":{\"String\":\"<base58>\"}}}}}]}}}]" |
There was a problem hiding this comment.
The description lists every variant except Null. Because it is a unit variant, it serializes as the bare string "Null" rather than {"Null":...}, which LLM callers are unlikely to guess. Consider adding "null as "Null"". Number is also i64 only, so it may be worth saying that fractional numbers aren't supported.
…and bundle its full published IDL
…Compatible template guidance
…th-pro-receiver # Conflicts: # crates/core/src/scenarios/registry.rs
There was a problem hiding this comment.
Overview
This PR adds pyth-core-compatible-price-feed-v2, a template for PriceUpdateV2 accounts owned by the Pyth Core Compatible receiver (rec2HHDD…). It includes:
- the IDL, converted from the on-chain legacy IDL, with the account names capitalized so the discriminators match the live accounts
- the overrides YAML, the registry wiring and updated registry counts
- a unit test and a live mainnet integration test, gated behind
integration-tests
Assessment
The approach is sound and low-risk. No Rust logic changes, and the new IDL has its own address, so registering IDLs by owner program does not collide with the existing rec5EKMG… IDL.
The integration test is well built:
- It checks the verification level byte and the feed id before relying on fixed offsets.
- It checks that a no-op forge returns the bytes unchanged.
- It checks that only the expected byte ranges change.
- It requires a row for every template property, so adding a property without a test row fails.
I checked these and they look correct: -10i64.to_le_bytes()[..4] gives the right bytes for the i32 exponent, and data byte 40 is VerificationLevel::Full.
Notes / nits
- Unrelated changes:
.github/workflows/claude-review.ymland the MCPparamsdescription incrates/mcp/src/surfpool/mod.rshave nothing to do with the Pyth template. The PR body explains the MCP text, but not the workflow. Consider landing both separately so the PR history stays focused. - Receiver naming: the top-level scenarios README calls
rec5EKMG…the "default receiver", whilepyth/core-compatible/v2/README.mdandllm_contextcall it "Pyth Core", which matches Pyth's docs. Using one name everywhere would help (see inline comment). - Protocol name: both templates report
protocol: Pyth.by_protocol("Pyth")now returns two templates, and the tests are updated for that. Only the template id tells the two receivers apart, which is fine, but it is worth knowing.
|
|
||
| **Currently supported protocols:** | ||
| - **Pyth v2** - Price oracle with 4 price feed templates (SOL/USD, BTC/USD, ETH/BTC, ETH/USD) | ||
| - **Pyth v2** - Price oracle with a price feed template for the default receiver (`pyth-price-feed-v2`) and one for the Pyth Core Compatible receiver (`pyth-core-compatible-price-feed-v2`) |
There was a problem hiding this comment.
Nit: this line calls rec5EKMG… the "default receiver", but pyth/core-compatible/v2/README.md and the template's llm_context call it "Pyth Core", which is also Pyth's own name for it. Suggest Pyth Core receiver (pyth-price-feed-v2) and Pyth Core Compatible receiver (pyth-core-compatible-price-feed-v2) so readers don't take "default" to mean a third thing.
| fresh as upstream's. A time written into the scenario is fixed when the scenario is created | ||
| and ages until it plays. To make a price stale on purpose, follow the TIME FIELDS steps of | ||
| pyth-price-feed-v2. | ||
|
|
There was a problem hiding this comment.
llm_context sends readers to "the TIME FIELDS steps of pyth-price-feed-v2", so the two templates' contexts are now coupled. If that section of the other template is renamed or reworded, this pointer silently goes stale. Consider adding a short version of the staleness recipe here, or a registry test that checks the referenced heading still exists in pyth-price-feed-v2's llm_context.
| ("price_message.price", 73, 8, 9_100_000_000_000), | ||
| ("price_message.conf", 81, 8, 500_000_000), | ||
| ("price_message.exponent", 89, 4, -10), | ||
| ("price_message.publish_time", 93, 8, 1_800_000_000), |
There was a problem hiding this comment.
Minor: the comment says each value differs from the live one, but posted_slot = 500_000_000 is a slot mainnet will reach eventually (roughly 2027–2028 at the current rate). At that point the !diffs.is_empty() / per-field checks could pass or fail by coincidence. A clearly unreachable value such as u32::MAX as i64 * 4 would make the test permanent. The same reasoning loosely applies to publish_time = 1_800_000_000 (Jan 2027).
| pub method: String, | ||
| #[schemars(description = "The parameters to pass to the RPC method")] | ||
| #[schemars( | ||
| description = "The parameters to pass to the RPC method. Wrap every value at every depth by its type: a string as {\"String\":\"...\"}, a whole number as {\"Number\":5}, a boolean as {\"Bool\":true}, a list as {\"Array\":[...]} and an object as {\"Object\":{\"key\":<wrapped value>}}; object keys stay plain. Example for getProgramAccounts with a filter: [{\"String\":\"<program id>\"},{\"Object\":{\"encoding\":{\"String\":\"base64\"},\"filters\":{\"Array\":[{\"Object\":{\"memcmp\":{\"Object\":{\"offset\":{\"Number\":0},\"bytes\":{\"String\":\"<base58>\"}}}}}]}}}]" |
There was a problem hiding this comment.
This description change is unrelated to the Pyth template. The PR body says it is shared with other open PRs, but if those land first this hunk will conflict or become a no-op. Consider moving it into its own small PR so the protocol PRs don't depend on each other.
There was a problem hiding this comment.
Summary
Adds the pyth-core-compatible-price-feed-v2 template for PriceUpdateV2 accounts owned by the Pyth Core Compatible receiver (rec2HHDD…). The PR bundles that receiver's on-chain IDL (converted, with the account names capitalized so the discriminators match), registers it under the Pyth protocol, updates the registry counts and tests, and adds a feature-gated live test.
Assessment: looks good. I checked these points:
- The registry adds every template IDL to
registered_idls(register_builtin_template_idls), keyed byidl.address. So the new IDL is used forrec2…accounts and doesn't collide with the existingrec5…Pyth IDL, and thegetAccountInfo+jsonParsedstep inllm_contextworks. - The offsets in the live test are correct for a
Fullverification level: 8-byte discriminator + 32-bytewrite_authority, then the variant byte at 40 andfeed_idat 41–72. Writing-10asi64and keeping the first 4 little-endian bytes gives the righti32encoding. - The no-op round-trip check and the "only the written byte ranges changed" check are strong guards against IDL or layout drift.
- The cross-reference to "TIME FIELDS" in
pyth-price-feed-v2points to a section that exists.
Notes:
- Unrelated file in the diff:
.github/workflows/claude-review.yml(141 lines) appears inbase...head. It seems to come from mergingfeat/scenarios/override/pyth-pro-support, and the PR description doesn't mention it. Please confirm it's meant to ship with this PR, or rebase it out. - Test depends on a live account: the test pins one mainnet account (
7AviUf9…) and assumes it isFull-verified. If that account is ever re-posted asPartial, the test fails at line 60 by design, but it then has to be updated by hand. Consider choosing the offsets from the variant byte instead of asserting it. - Shared protocol name: both collections declare
protocol: Pyth, soby_protocol("Pyth")now returns two templates whose IDLs belong to different programs. This works today, but any consumer that assumes one IDL per protocol will mix them up. Using a separate protocol label, askamino-scopedoes, would avoid that.
| description: Uncertainty band around the price, in the same scale as price | ||
| - path: price_message.exponent | ||
| label: Exponent | ||
| description: Decimal exponent, normally -8 and -10 on some feeds. Rescales every price field |
There was a problem hiding this comment.
Nit: "normally -8 and -10 on some feeds" reads awkwardly. Suggest: "Decimal exponent, usually -8 (-10 on some feeds)."
| let live = fetch(CORE_COMPATIBLE_SOL_USD_FEED).await; | ||
| assert_eq!(live.owner.to_string(), PYTH_CORE_COMPATIBLE_RECEIVER); | ||
| // A Partial verification level shifts every offset below by one. | ||
| assert_eq!(live.data[40], 1, "verification level should be Full"); |
There was a problem hiding this comment.
This hard-fails if Pyth ever re-posts this account at a Partial level, and then the test needs a new address or new offsets. You could keep the assertion but derive a base offset from live.data[40] (0 → +1 byte for num_signatures), so the test survives that change. Optional.
SummaryAdds the The PR also adds keep-alive republishing ( Issues
The tag list is missing
This changes the behaviour of the existing template: a |
SummaryThis PR adds The structure is clean and the tests are strong. The live test checks byte offsets and that only the price fields change, and the keep-alive test covers an overridden feed, a feed loaded later, a feed with Issues
Overall this looks good once the tag is added and the change to the existing template is called out.
This collection is missing the
The equivalent section in
This changes behaviour for existing |
There was a problem hiding this comment.
Overview. This PR adds pyth-core-compatible-price-feed-v2. It writes PriceUpdateV2 accounts owned by rec2HHDD…, and the account to write is chosen by address (type: pubkey). It also bundles the receiver's IDL with the account names capitalized, plus a README that records the source. Pyth templates now use keep-alive: every price account of a receiver that the surfnet has loaded gets its publish_time and posted_slot rewritten each slot. The unit tests and the live integration tests are thorough. The byte-diff checks against a real mainnet account and the stale-feed and loaded-later cases are especially good.
Scope. The description says the PR is the new template, its IDL, the registry entry and a test. The diff also changes how the existing pyth-price-feed-v2 behaves:
- It adds a
keep_aliveimport over every account owned byrec5EKMG…. - It rewrites that template's
llm_context, dropping the old pause-clock / read-clock / time-travel recipe for stale prices.
From the moment a scenario plays, every loaded Pyth Core feed gets republished each slot, including feeds no override names. Users who relied on feeds aging naturally will see different results. This should be stated in the PR description, or split into its own PR.
Other notes.
- The new IDL's account discriminator for
PriceUpdateV2([34,241,35,99,157,126,244,205]) matches the one in the existingpyth/v2/idl.json, so the capitalization fix described in the README checks out. - The MCP
paramsdescription change has nothing to do with Pyth. The body says it is shared with other PRs, so whichever PR merges first should own it, to avoid conflicts.
| tags: | ||
| - oracle | ||
| - price-feed | ||
| - defi |
There was a problem hiding this comment.
The clock-sensitive tag is missing here, though pyth/v2/overrides.yaml has it. These accounts carry the same publish_time and posted_slot checked against the Clock. Without the tag, Studio won't warn before pausing the clock, even though fetched accounts can end up ahead of the surfnet's time. Please add the tag, and add this template id to test_clock_sensitive_tag_marks_pyth_and_every_phoenix_template.
| description: Decimal exponent, normally -8 and -10 on some feeds. Rescales every price field | ||
| - path: price_message.publish_time | ||
| label: Publish Time | ||
| description: Unix seconds of this update, interpreted against the Surfnet clock |
There was a problem hiding this comment.
publish_time (here) and posted_slot (line 45) are still offered as editable properties. But the llm_context says a value set for them 'never takes effect' unless keepAlive: false. Studio users only see the property description, not the llm_context. Consider saying so in the description, e.g. "…; only takes effect with keepAlive: false". The same applies to pyth/v2/overrides.yaml.
| - defi | ||
| - clock-sensitive | ||
|
|
||
| keep_alive: |
There was a problem hiding this comment.
This turns on keep-alive for every loaded rec5EKMG… account whenever a pyth-price-feed-v2 override plays. That changes behavior for existing scenarios: feeds no override names no longer go stale. The PR description doesn't mention it. Please call it out, or move it to a separate PR, since this PR is described as only adding the Core Compatible template.
|
|
||
| pub fn load_pyth_overrides(&mut self) { | ||
| self.load_protocol_overrides(PYTH_V2_IDL_CONTENT, PYTH_V2_OVERRIDES_CONTENT, "pyth"); | ||
| let idl = serde_json::from_str(PYTH_V2_IDL_CONTENT).expect("pyth idl"); |
There was a problem hiding this comment.
Minor: load_protocol_overrides already parses this IDL. Parsing it again here with expect is harmless for an embedded constant. If other protocols repeat this pattern, a small helper (or having load_keep_alive accept the IDL string) would remove the duplication.
… read its receiver
There was a problem hiding this comment.
Summary
This PR adds pyth-core-compatible-price-feed-v2 for PriceUpdateV2 accounts owned by rec2HHDD…. It bundles the receiver's IDL with the two account names capitalized so the discriminators match live accounts, and adds keep-alive publication templates for both Pyth receivers. The YAML/IDL wiring looks right. The integration tests check byte offsets and that only the price fields change, which is good. The registry unit tests have been updated for the new counts.
Concerns
- The PR description leaves out a behavior change to
pyth-price-feed-v2. The description says "No protocol Rust", but the PR also addspyth/v2/keep-alive.yamland imports it intopyth-price-feed-v2. Every existing scenario that uses that template now republishes every loaded Pyth Core price account every slot. The old stale-price recipe (setpublish_time, then time travel) is removed and would no longer work: whatever value it writes is overwritten. Please say so in the PR body and changelog, or split it into its own PR. clock-sensitivetag missing. The new collection holds clock-checked accounts just likepyth/v2, but it isn't tagged, so Studio won't warn before pausing the clock.test_clock_sensitive_tag_marks_pyth_and_every_phoenix_templateshould also cover the new template.- Inaccurate llm_context. The "never takes effect" wording ignores
keepAlive: false, and the text cites Project 0'sconfig.oracle_keys.0although no Project 0 templates exist in this repo. - Minor:
load_pyth_overridesparses each IDL JSON a second time just forload_keep_alive. It works, but it could reuse the IDL thatload_protocol_overridesalready parsed.
| tags: | ||
| - oracle | ||
| - price-feed | ||
| - defi |
There was a problem hiding this comment.
Missing the clock-sensitive tag that pyth/v2/overrides.yaml carries. These accounts hold publish_time/posted_slot, which consumers check against the Clock. Without the tag, Studio won't warn before pausing the clock. Please add the tag, and extend test_clock_sensitive_tag_marks_pyth_and_every_phoenix_template to assert it for pyth-core-compatible-price-feed-v2.
| Surfnet clock's unix seconds and posted_slot the slot, also after a time travel. This includes | ||
| feeds no override names and, from the next slot, feeds a transaction loads later, so a | ||
| liquidation that reads the borrower's other feeds finds them fresh. Price, conf and the EMA | ||
| fields keep their values. A value set for publish_time or posted_slot never takes effect, so |
There was a problem hiding this comment.
"never takes effect" is only true while keep-alive is on. With keepAlive: false, a value set for publish_time/posted_slot is written and stays. Consider "never takes effect unless the override sets keepAlive: false", here and in pyth/v2/overrides.yaml:276. Otherwise an LLM may never set a deliberate stale timestamp.
| - defi | ||
| - clock-sensitive | ||
|
|
||
| keep_alive: |
There was a problem hiding this comment.
This changes how the existing pyth-price-feed-v2 behaves: every loaded Pyth Core price account is now republished every slot once any override of this template plays. The previous stale-price recipe (set publish_time, then time travel) is removed and would no longer work. The PR description doesn't mention this. Please call it out (breaking for existing scenarios) or move it to a separate PR.
| WHICH RECEIVER: this template writes price accounts of the Pyth Core receiver | ||
| rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ, which Save and Kamino read. Project 0 reads the | ||
| Pyth Core Compatible receiver instead: for a Project 0 price use | ||
| pyth-core-compatible-price-feed-v2 on the bank's config.oracle_keys.0. When a protocol's own |
There was a problem hiding this comment.
This points to Project 0's config.oracle_keys.0, but no Project 0 templates are registered in this repo. Unless those templates land first, the instruction can't be followed as written. Consider wording it generically, or adding it only together with the Project 0 templates.
|
|
||
| pub fn load_pyth_overrides(&mut self) { | ||
| self.load_protocol_overrides(PYTH_V2_IDL_CONTENT, PYTH_V2_OVERRIDES_CONTENT, "pyth"); | ||
| let idl = serde_json::from_str(PYTH_V2_IDL_CONTENT).expect("pyth idl"); |
There was a problem hiding this comment.
Nit: load_protocol_overrides already parses this IDL JSON, and it's parsed again here just for load_keep_alive, and again on line 253 for the core compatible IDL. A small helper, or having load_protocol_overrides return the parsed IDL, would avoid the duplicate parsing and the duplicate expect strings.
SummaryAdds Issues
Minor: the integration test depends on a specific live mainnet account (
Missing
This block and the STALE PRICE block below are near copies of the text in |
…with keepAlive false
There was a problem hiding this comment.
Summary
Adds a pyth-core-compatible-price-feed-v2 template for PriceUpdateV2 accounts owned by the Core Compatible receiver (rec2HH…). The PR bundles the receiver's converted IDL, a registry entry, unit tests and live mainnet tests. It also adds an owned-kind keep-alive (publish_time/posted_slot ← $unix_s/$slot) to both Pyth templates.
The new template looks right. The IDL provenance in the README is good, and the byte-offset live test is thorough.
Issues
- Undisclosed behaviour change to
pyth-price-feed-v2. The PR body says it adds a template. It also changes the existing one, andkeepAlivedefaults totrue(default_keep_alive). Any existing scenario that usespyth-price-feed-v2will now republish every loaded Pyth Core price account every slot, and anypublish_time/posted_slotit sets is silently overwritten. Scenarios built on the old stale-price recipe (setpublish_time, then time-travel) stop producing stale prices unless they addkeepAlive: false. This needs to be in the PR description and changelog, or split into its own PR. - Old guidance dropped. The
pyth-price-feed-v2llm_contextno longer says thatpublish_timeis read against the surfnet clock and not wall-clock time. That still matters whenkeepAlive: falseis used to set a time by hand. - Unrelated change in the same PR. The MCP
paramsschema description incrates/mcp/src/surfpool/mod.rshas nothing to do with this feature. The PR says it is shared with other PRs, which will cause merge conflicts or churn whichever lands first.
| tags: | ||
| - oracle | ||
| - price-feed | ||
| - defi |
There was a problem hiding this comment.
The clock-sensitive tag is missing. pyth/v2/overrides.yaml has it, and the scenarios README says it covers Pyth's publish_time. Without it, Studio won't warn when a scenario that uses this template is played with the clock paused, even though this template also writes publish_time and posted_slot. Add - clock-sensitive, and add the template to test_clock_sensitive_tag_marks_pyth_and_every_phoenix_template.
| - defi | ||
| - clock-sensitive | ||
|
|
||
| keep_alive: |
There was a problem hiding this comment.
This changes the behaviour of an existing template, and the PR description doesn't mention it. keepAlive defaults to true, so every existing pyth-price-feed-v2 scenario now rewrites publish_time/posted_slot on all loaded Pyth Core price accounts every slot. An explicit publish_time is silently ignored, and the old stale-price recipe no longer works. Please call this out, or split it into its own PR.
| WHICH RECEIVER: this template writes price accounts of the Pyth Core receiver | ||
| rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ, which Save and Kamino read. Project 0 reads the | ||
| Pyth Core Compatible receiver instead: for a Project 0 price use | ||
| pyth-core-compatible-price-feed-v2 on the bank's config.oracle_keys.0. When a protocol's own |
There was a problem hiding this comment.
This points to config.oracle_keys.0 and to Project 0 templates, which are not in this repo; the only Project 0 reference in the scenarios is this line. Until that PR lands, the LLM is told to follow a field no template defines. Either make the wording generic or make this PR depend on the Project 0 PR.
Adds
pyth-core-compatible-price-feed-v2, a template for thePriceUpdateV2accounts owned by Pyth's Core Compatible receiver (rec2HHDDnjLfj4kE7VyEtFA1HPGQLK33259532cRyHp), as Pyth's Solana contract addresses name it, and keeps the price accounts of both Pyth receivers published on a surfnet.Some programs, Project 0 among them, read their prices from this receiver rather than the Pyth Core one (
rec5EKMG…), sopyth-price-feed-v2cannot write them. The bundled IDL is the one the receiver publishes on chain, converted to the current Anchor format, with one change made before the conversion: the account namespriceUpdateV2andtwapUpdateare capitalized as the program's Rust types are, so the discriminators match the live accounts. The README inpyth/core-compatible/v2records the source and both sha256 values. The template takes the price account's address; itsllm_contextexplains to take it from the consuming program.Keep-alive. This PR is stacked on #53 (the
ownedkeep-alive kind and the per-overridekeepAliveflag); merge #53 first.PriceUpdateV2account of their receiver. Once an override of either template withkeepAlive: trueplays, every price account of that receiver the surfnet has loaded getspublish_timeandposted_slotfrom the Clock every slot, also feeds that no override names.pyth-price-feed-v2for existing scenarios: after such an override plays, the receiver's feeds no longer age. Its old stale-price recipe (pause the clock, setpublish_time, time travel) is removed, because keep-alive rewritespublish_time. Thellm_contextof both templates now gives the stale-price recipe: an override withkeepAlive: falseon the stale feed and one withkeepAlive: trueon another feed, both writing the current price.pyth-price-feed-v2'sllm_contextalso says which receiver a protocol reads: Save and Kamino read Pyth Core, Project 0 reads Pyth Core Compatible (its templates come in feat(scenarios): add Project 0 support - state templates #34).Tests:
core_compatible_price_feed_touches_only_price_fieldswrites a real mainnet price account and checks that only the price fields change.price_feeds_stay_published_through_surfnet_svmchecks keep-alive across slots, for a feed loaded later and after a time travel.Shared with the other open protocol PRs, byte for byte: the MCP
paramsdescription.