feat(lazer): L7 — IHelperProgram.lazer_price + worked consumer#187
Merged
Conversation
Solidity side of the Pyth Lazer integration. Adds the EVM-callable
surface for the wrapper that ships in rome-evm-private (master after
the lazer-pyth-integration PR merges).
## Changes
`contracts/interface.sol` (+65 LOC):
- `LazerFeedPrice` struct inside IHelperProgram (matching the
Rust ABI-encoded return-tuple layout from L3's encode_return):
uint32 feed_id;
int64 price;
uint64 conf;
int32 expo;
- `lazer_price(bytes envelope, uint8 ed25519_ix_idx, uint8 sig_idx)
external view returns (LazerFeedPrice[] memory feeds, uint64 publish_time_us)`
- Selector pinned: `0xa5f15a86` (verified against `cast keccak`
in rome-evm-private/program/src/non_evm/helper.rs:lazer_price_selector_locked).
- Documentation captures the wrapper's wire-format guarantees +
the consumer responsibilities (replay protection + freshness
are consumer-side per spec §3.4).
`contracts/examples/lazer_consumer.sol` (new, ~80 LOC):
- Reference consumer demonstrating the two consumer-side
responsibilities the wrapper does NOT enforce:
1. Per-feed monotonic replay protection via
`mapping(uint32 => uint64) lastAcceptedPublishTimeUs`.
(Multi-feed monotonicity is mandatory — a single envelope-
level timestamp is INSUFFICIENT per spec §5.2.)
2. Staleness enforcement with consumer-chosen MAX_STALENESS_US.
- `getEthPrice` is NOT `view` because it mutates the per-feed
monotonicity tracker — high-stakes use cases (lending
liquidations, bridge value verification) MUST track in storage.
Low-stakes UI display can do a pure-view variant without the
monotonicity check.
## Spec compliance
Per `rome-specs/main/active/technical/2026-05-19-pyth-lazer-integration.md` §5:
- Solidity surface matches §5.1's IHelperProgram extension
- Consumer pattern matches §5.2's worked example
- LazerFeedPrice struct matches §3.3.1's ABI-encoded shape
- Selector matches §3.1's `cast keccak` derivation
- Per-feed monotonicity callout matches §3.4 + §5.2's "multi-feed
monotonicity is mandatory" prose
## Verification
- `npx hardhat compile` → clean (46 files compiled, no new warnings
on touched files; existing meteora-factory size warning is unrelated)
## Cross-repo coordination
This commit pairs with the rome-evm-private `lazer-pyth-integration`
branch which adds the on-chain wrapper at the dispatched selector. The
two MUST be merged in this order to avoid a Solidity surface that
points at an unimplemented selector:
1. rome-evm-private/lazer-pyth-integration merges to master
2. Hadrian (or any test chain) rebuilds with new program
3. THIS PR (rome-solidity/lazer-pyth-integration) merges to master
4. (optional) Contracts deploy the worked consumer for end-to-end smoke
If a chain runs rome-solidity master from this commit against a Rome
program that predates the Lazer wrapper, lazer_price calls revert with
`Unimplemented(method is not supported by HelperProgram 0xa5f15a86)` —
clear surface, no silent data corruption.
## Iron rule respected
One Lazer feature → one PR per repo. This is the rome-solidity-side
single PR (the rome-evm-private side is a separate PR in that repo).
Per [[feedback_multi_phase_features_one_pr]].
valiksinev
approved these changes
May 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Solidity side of the Pyth Lazer integration. Companion to rome-protocol/rome-evm-private#373 (which adds the on-chain wrapper at selector
0xa5f15a86).Single commit, single PR (rome-solidity scope of the feature is minimal — interface + worked consumer).
Changes
contracts/interface.solLazerFeedPricestruct +lazer_price(bytes,uint8,uint8)function insideIHelperProgramcontracts/examples/lazer_consumer.solSpec
rome-specs/active/technical/2026-05-19-pyth-lazer-integration.md§5 (Solidity interface + worked consumer).The interface
Selector pinned
0xa5f15a86=keccak256("lazer_price(bytes,uint8,uint8)")[..4], verified against the matching Rust const inrome-evm-private/program/src/non_evm/helper.rs:lazer_price_selector_locked.Consumer pattern (per spec §5.2)
The wrapper does NOT enforce replay protection or staleness — those are consumer-side per spec §3.4. The worked example demonstrates:
Per-feed (not envelope-level) monotonicity tracking is mandatory because a single envelope can carry N feeds at independent cadences — collapsing to one timestamp either rejects valid feeds or accepts replayed ones.
Merge order
This PR MUST merge AFTER rome-protocol/rome-evm-private#373:
lazer_priceselector at0xa5f15a86IHelperProgram.lazer_priceis now callableIf rome-solidity master ships ahead of rome-evm-private, consumer contracts calling
lazer_priceget a clearUnimplemented(method is not supported by HelperProgram 0xa5f15a86)— clear surface, no silent data corruption, but a deploy-time gotcha. The order matters.Verification
npx hardhat compile→ 46 files compiled clean (existing meteora factory size warning is unrelated)npx hardhat test tests/oracle/PythPullParser.test.ts tests/oracle/SwitchboardParser.test.ts→ 22/22 pass (no regressions on adjacent parser tests)Test plan
contracts/examples/lazer_consumer.soldeploys + can be called with a captured envelope