|
|
@@ -1,10 +1,6 @@
|
|
|
module pyth_lazer::pyth_lazer;
|
|
|
|
|
|
-use pyth_lazer::channel;
|
|
|
-use pyth_lazer::feed::{Self, Feed};
|
|
|
use pyth_lazer::state::{Self, State};
|
|
|
-use pyth_lazer::i64::{Self};
|
|
|
-use pyth_lazer::i16::{Self};
|
|
|
use pyth_lazer::update::{Self, Update};
|
|
|
use sui::bcs;
|
|
|
use sui::clock::Clock;
|
|
|
@@ -15,12 +11,10 @@ const UPDATE_MESSAGE_MAGIC: u32 = 1296547300;
|
|
|
const PAYLOAD_MAGIC: u32 = 2479346549;
|
|
|
|
|
|
// Error codes
|
|
|
-const EInvalidUpdate: u64 = 1;
|
|
|
const ESignerNotTrusted: u64 = 2;
|
|
|
const ESignerExpired: u64 = 3;
|
|
|
-
|
|
|
-// TODO:
|
|
|
-// error handling
|
|
|
+const EInvalidMagic: u64 = 4;
|
|
|
+const EInvalidPayloadLength: u64 = 6;
|
|
|
|
|
|
/// The `PYTH_LAZER` resource serves as the one-time witness.
|
|
|
/// It has the `drop` ability, allowing it to be consumed immediately after use.
|
|
|
@@ -83,161 +77,39 @@ public(package) fun verify_le_ecdsa_message(
|
|
|
/// * `update` - The LeEcdsa formatted Lazer update
|
|
|
///
|
|
|
/// # Errors
|
|
|
-/// * `EInvalidUpdate` - Failed to parse the update according to the protocol definition
|
|
|
+/// * `EInvalidMagic` - Invalid magic number in update or payload
|
|
|
+/// * `EInvalidPayloadLength` - Payload length doesn't match actual data
|
|
|
/// * `ESignerNotTrusted` - The recovered public key is not in the trusted signers list
|
|
|
+/// * `ESignerExpired` - The signer's certificate has expired
|
|
|
public fun parse_and_verify_le_ecdsa_update(s: &State, clock: &Clock, update: vector<u8>): Update {
|
|
|
let mut cursor = bcs::new(update);
|
|
|
|
|
|
- // TODO: introduce helper functions to check data len before peeling. allows us to return more
|
|
|
- // granular error messages.
|
|
|
-
|
|
|
+ // Parse and validate message magic
|
|
|
let magic = cursor.peel_u32();
|
|
|
- assert!(magic == UPDATE_MESSAGE_MAGIC, 0);
|
|
|
+ assert!(magic == UPDATE_MESSAGE_MAGIC, EInvalidMagic);
|
|
|
|
|
|
+ // Parse signature
|
|
|
let mut signature = vector::empty<u8>();
|
|
|
-
|
|
|
let mut sig_i = 0;
|
|
|
while (sig_i < SECP256K1_SIG_LEN) {
|
|
|
signature.push_back(cursor.peel_u8());
|
|
|
sig_i = sig_i + 1;
|
|
|
};
|
|
|
|
|
|
+ // Parse expected payload length and get remaining bytes as payload
|
|
|
let payload_len = cursor.peel_u16();
|
|
|
-
|
|
|
let payload = cursor.into_remainder_bytes();
|
|
|
|
|
|
- assert!((payload_len as u64) == payload.length(), 0);
|
|
|
-
|
|
|
- let mut cursor = bcs::new(payload);
|
|
|
- let payload_magic = cursor.peel_u32();
|
|
|
- assert!(payload_magic == PAYLOAD_MAGIC, 0);
|
|
|
+ // Validate expectedpayload length
|
|
|
+ assert!((payload_len as u64) == payload.length(), EInvalidPayloadLength);
|
|
|
|
|
|
- let timestamp = cursor.peel_u64();
|
|
|
+ // Parse payload
|
|
|
+ let mut payload_cursor = bcs::new(payload);
|
|
|
+ let payload_magic = payload_cursor.peel_u32();
|
|
|
+ assert!(payload_magic == PAYLOAD_MAGIC, EInvalidMagic);
|
|
|
|
|
|
// Verify the signature against trusted signers
|
|
|
verify_le_ecdsa_message(s, clock, &signature, &payload);
|
|
|
|
|
|
- let channel_value = cursor.peel_u8();
|
|
|
- let channel = if (channel_value == 0) {
|
|
|
- channel::new_invalid()
|
|
|
- } else if (channel_value == 1) {
|
|
|
- channel::new_real_time()
|
|
|
- } else if (channel_value == 2) {
|
|
|
- channel::new_fixed_rate_50ms()
|
|
|
- } else if (channel_value == 3) {
|
|
|
- channel::new_fixed_rate_200ms()
|
|
|
- } else {
|
|
|
- channel::new_invalid() // Default to Invalid for unknown values
|
|
|
- };
|
|
|
-
|
|
|
- let mut feeds = vector::empty<Feed>();
|
|
|
- let mut feed_i = 0;
|
|
|
-
|
|
|
- let feed_count = cursor.peel_u8();
|
|
|
-
|
|
|
- while (feed_i < feed_count) {
|
|
|
- let feed_id = cursor.peel_u32();
|
|
|
- let mut feed = feed::new(
|
|
|
- feed_id,
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- option::none(),
|
|
|
- );
|
|
|
-
|
|
|
- let properties_count = cursor.peel_u8();
|
|
|
- let mut properties_i = 0;
|
|
|
-
|
|
|
- while (properties_i < properties_count) {
|
|
|
- let property_id = cursor.peel_u8();
|
|
|
-
|
|
|
- if (property_id == 0) {
|
|
|
- let price = cursor.peel_u64();
|
|
|
- if (price != 0) {
|
|
|
- feed.set_price(option::some(option::some(i64::from_u64(price))));
|
|
|
- } else {
|
|
|
- feed.set_price(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 1) {
|
|
|
- let best_bid_price = cursor.peel_u64();
|
|
|
- if (best_bid_price != 0) {
|
|
|
- feed.set_best_bid_price(
|
|
|
- option::some(option::some(i64::from_u64(best_bid_price))),
|
|
|
- );
|
|
|
- } else {
|
|
|
- feed.set_best_bid_price(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 2) {
|
|
|
- let best_ask_price = cursor.peel_u64();
|
|
|
- if (best_ask_price != 0) {
|
|
|
- feed.set_best_ask_price(
|
|
|
- option::some(option::some(i64::from_u64(best_ask_price))),
|
|
|
- );
|
|
|
- } else {
|
|
|
- feed.set_best_ask_price(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 3) {
|
|
|
- let publisher_count = cursor.peel_u16();
|
|
|
- feed.set_publisher_count(option::some(publisher_count));
|
|
|
- } else if (property_id == 4) {
|
|
|
- let exponent = cursor.peel_u16();
|
|
|
- feed.set_exponent(option::some(i16::from_u16(exponent)));
|
|
|
- } else if (property_id == 5) {
|
|
|
- let confidence = cursor.peel_u64();
|
|
|
- if (confidence != 0) {
|
|
|
- feed.set_confidence(option::some(option::some(i64::from_u64(confidence))));
|
|
|
- } else {
|
|
|
- feed.set_confidence(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 6) {
|
|
|
- let exists = cursor.peel_u8();
|
|
|
- if (exists == 1) {
|
|
|
- let funding_rate = cursor.peel_u64();
|
|
|
- feed.set_funding_rate(option::some(option::some(i64::from_u64(funding_rate))));
|
|
|
- } else {
|
|
|
- feed.set_funding_rate(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 7) {
|
|
|
- let exists = cursor.peel_u8();
|
|
|
-
|
|
|
- if (exists == 1) {
|
|
|
- let funding_timestamp = cursor.peel_u64();
|
|
|
- feed.set_funding_timestamp(option::some(option::some(funding_timestamp)));
|
|
|
- } else {
|
|
|
- feed.set_funding_timestamp(option::some(option::none()));
|
|
|
- }
|
|
|
- } else if (property_id == 8) {
|
|
|
- let exists = cursor.peel_u8();
|
|
|
-
|
|
|
- if (exists == 1) {
|
|
|
- let funding_rate_interval = cursor.peel_u64();
|
|
|
- feed.set_funding_rate_interval(
|
|
|
- option::some(option::some(funding_rate_interval)),
|
|
|
- );
|
|
|
- } else {
|
|
|
- feed.set_funding_rate_interval(option::some(option::none()));
|
|
|
- }
|
|
|
- } else {
|
|
|
- // When we have an unknown property, we do not know its length, and therefore
|
|
|
- // we cannot ignore it and parse the next properties.
|
|
|
- abort EInvalidUpdate // FIXME: return more granular error messages
|
|
|
- };
|
|
|
-
|
|
|
- properties_i = properties_i + 1;
|
|
|
- };
|
|
|
-
|
|
|
- vector::push_back(&mut feeds, feed);
|
|
|
-
|
|
|
- feed_i = feed_i + 1;
|
|
|
- };
|
|
|
-
|
|
|
- let remaining_bytes = cursor.into_remainder_bytes();
|
|
|
- assert!(remaining_bytes.length() == 0, 0);
|
|
|
-
|
|
|
- update::new(timestamp, channel, feeds)
|
|
|
+ update::parse_from_cursor(payload_cursor)
|
|
|
}
|