Skip to content

Conversation

@ayushboss
Copy link
Contributor

@ayushboss ayushboss commented Jul 9, 2025

Summary

Implemented the parse price feed updates function for the stylus contract, and restructured the architecture of the update functions to deal with that.

Rationale

parsePriceFeedUpdates is a necessary component of the Pythnet API.

How has this been tested?

  • Current tests cover my changes
  • Added new tests
  • Manually tested the code

I have test cases validating these methods.

ayushboss and others added 14 commits July 7, 2025 13:55
…g vector indices

- Modified parse_price_feed_updates_internal to build a BTreeMap internally mapping price IDs to PriceInfoReturn
- Updated parse_price_feed_updates_with_config to return a vector with matching indices to input price_ids
- Added price_ids parameter to parse_price_feed_updates_internal function
- Maintained existing error handling and validation logic

Co-Authored-By: [email protected] <[email protected]>
…y ordering

- parse_price_feed_updates_internal now returns Vec<([u8; 32], PriceInfoReturn)> without taking price_ids parameter
- parse_price_feed_updates_with_config converts internal result to BTreeMap for efficient lookup and returns ordered vector
- This enables update_price_feeds functions to use the dictionary directly as requested

Co-Authored-By: [email protected] <[email protected]>
…eed data

- Extract price data from price_feed_message instead of reading from storage
- Fixes test failures where price data wasn't being processed correctly
- Maintains separation of concerns between parsing and storage updates

Co-Authored-By: [email protected] <[email protected]>
- Add validation to check if publish times are within [min_allowed_publish_time, max_allowed_publish_time] range
- Return PythReceiverError::PriceFeedNotFoundWithinRange (error code 16) when publish times are outside allowed range
- Fix type casting from u64 to i64 for proper comparison with price_feed_message.publish_time

Co-Authored-By: [email protected] <[email protected]>
…es_internal

- Add uniqueness checking logic when check_uniqueness parameter is true
- Require minAllowedPublishTime > prevPublishTime for uniqueness validation
- Return PythReceiverError::PriceFeedNotFoundWithinRange when uniqueness check fails
- Retrieve previous publish time from latest_price_info storage

Co-Authored-By: [email protected] <[email protected]>
@vercel
Copy link

vercel bot commented Jul 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
api-reference ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
component-library ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
developer-hub ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
entropy-debugger ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
entropy-explorer ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
insights ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
proposals ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm
staking ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 14, 2025 3:27pm

@ayushboss ayushboss marked this pull request as ready for review July 11, 2025 20:44
Comment on lines 227 to 233
_unique: bool,
) -> Result<Vec<([u8; 32], PriceInfoReturn)>, PythReceiverError> {
let price_pairs = self.parse_price_feed_updates_internal(
update_data,
min_publish_time,
max_publish_time,
false, // check_uniqueness
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not pass unique to parse_price_feed_updates_internal?

Comment on lines +445 to +452
let price_info_return = (
U64::from(publish_time),
I32::from_be_bytes(price_feed_message.exponent.to_be_bytes()),
I64::from_be_bytes(price_feed_message.price.to_be_bytes()),
U64::from(price_feed_message.conf),
I64::from_be_bytes(price_feed_message.ema_price.to_be_bytes()),
U64::from(price_feed_message.ema_conf),
);
Copy link
Contributor

@tejasbadadare tejasbadadare Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed you're turning the signed ints to bytes and back again. There should be from/try_from impls for them as well, any reason you're going to bytes and back?

Copy link
Contributor Author

@ayushboss ayushboss Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the alloy_primitives crate doesn't have a normal From<> for the I32 type, so this was the cleanest workaround I could think of.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a try_from which seems to work in my brief testing, would try that out. Also, it looks like the from and from_be_bytes methods panic if the conversion fails. I think it would be better to use try_from and gracefully catch and propagate any errors. (Can do this in a future PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok will do. Will add this into the next PR that I'll send out in a second

Comment on lines 272 to 275
fn get_total_fee(&self, total_num_updates: u64) -> U256 {
U256::from(total_num_updates).saturating_mul(self.single_update_fee_in_wei.get())
+ self.transaction_fee_in_wei.get()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just have get_update_fee(&self, update_data: Vec<Vec<u8>>) since the old version that just takes the update data length is deprecated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I went ahead and compressed the two functions. This one was just a helper function for the public facing get_update_fee function though that took the whole update data.

}

#[motsu::test]
fn test_multiple_updates_same_id_updates_latest(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfinished tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next PR in the line completely cleaned up the test suite and added more, so if it's okay can we circle back to reviewing the tests on that PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good!

&mut self,
update_data: Vec<u8>,
) -> Result<(), PythReceiverError> {
_price_ids: Vec<[u8; 32]>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like price_ids is unused here because we don't pass it in parse_price_feed_updates_with_config -- we parse and update all the data, and then filter them using price_ids. There was a subtle griefing attack possibility with this behavior in the Pulse use case, but I don't think that's really a factor here and there are other mitigations (check_update_data_is_minimal.)

We can remove the _price_ids arg here.

Copy link
Contributor

@tejasbadadare tejasbadadare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a small reply. Nice work!!

@ayushboss ayushboss merged commit 3664ef3 into main Jul 16, 2025
9 checks passed
@ayushboss ayushboss deleted the pyth-stylus-parse-updates branch July 16, 2025 19:39
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.

2 participants