pyth_lazer.move 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. module pyth_lazer::pyth_lazer;
  2. use pyth_lazer::i16::Self;
  3. use pyth_lazer::i64::Self;
  4. use pyth_lazer::update::{Self, Update};
  5. use pyth_lazer::feed::{Self, Feed};
  6. use pyth_lazer::channel::Self;
  7. use sui::bcs;
  8. use sui::ecdsa_k1::secp256k1_ecrecover;
  9. const SECP256K1_SIG_LEN: u32 = 65;
  10. const UPDATE_MESSAGE_MAGIC: u32 = 1296547300;
  11. const PAYLOAD_MAGIC: u32 = 2479346549;
  12. // TODO:
  13. // initializer
  14. // administration -> admin cap, upgrade cap, governance?
  15. // storage module -> trusted signers, update fee?, treasury?
  16. // error handling
  17. // standalone verify signature function
  18. /// Parse the Lazer update message and validate the signature.
  19. ///
  20. /// The parsing logic is based on the Lazer rust protocol definition defined here:
  21. /// https://github.com/pyth-network/pyth-crosschain/tree/main/lazer/sdk/rust/protocol
  22. public fun parse_and_verify_le_ecdsa_update(update: vector<u8>): Update {
  23. let mut cursor = bcs::new(update);
  24. let magic = cursor.peel_u32();
  25. assert!(magic == UPDATE_MESSAGE_MAGIC, 0);
  26. let mut signature = vector::empty<u8>();
  27. let mut sig_i = 0;
  28. while (sig_i < SECP256K1_SIG_LEN) {
  29. signature.push_back(cursor.peel_u8());
  30. sig_i = sig_i + 1;
  31. };
  32. let payload_len = cursor.peel_u16();
  33. let payload = cursor.into_remainder_bytes();
  34. assert!((payload_len as u64) == payload.length(), 0);
  35. // 0 stands for keccak256 hash
  36. let pubkey = secp256k1_ecrecover(&signature, &payload, 0);
  37. // Lazer signer pubkey
  38. // FIXME: validate against trusted signer set in storage
  39. assert!(pubkey == x"03a4380f01136eb2640f90c17e1e319e02bbafbeef2e6e67dc48af53f9827e155b", 0);
  40. let mut cursor = bcs::new(payload);
  41. let payload_magic = cursor.peel_u32();
  42. assert!(payload_magic == PAYLOAD_MAGIC, 0);
  43. let timestamp = cursor.peel_u64();
  44. let channel_value = cursor.peel_u8();
  45. let channel = if (channel_value == 0) {
  46. channel::new_invalid()
  47. } else if (channel_value == 1) {
  48. channel::new_real_time()
  49. } else if (channel_value == 2) {
  50. channel::new_fixed_rate_50ms()
  51. } else if (channel_value == 3) {
  52. channel::new_fixed_rate_200ms()
  53. } else {
  54. channel::new_invalid() // Default to Invalid for unknown values
  55. };
  56. let mut feeds = vector::empty<Feed>();
  57. let mut feed_i = 0;
  58. let feed_count = cursor.peel_u8();
  59. while (feed_i < feed_count) {
  60. let feed_id = cursor.peel_u32();
  61. let mut feed = feed::new(
  62. feed_id,
  63. option::none(),
  64. option::none(),
  65. option::none(),
  66. option::none(),
  67. option::none(),
  68. option::none(),
  69. option::none(),
  70. option::none(),
  71. option::none()
  72. );
  73. let properties_count = cursor.peel_u8();
  74. let mut properties_i = 0;
  75. while (properties_i < properties_count) {
  76. let property_id = cursor.peel_u8();
  77. if (property_id == 0) {
  78. let price = cursor.peel_u64();
  79. if (price != 0) {
  80. feed.set_price(option::some(option::some(i64::from_u64(price))));
  81. } else {
  82. feed.set_price(option::some(option::none()));
  83. }
  84. } else if (property_id == 1) {
  85. let best_bid_price = cursor.peel_u64();
  86. if (best_bid_price != 0) {
  87. feed.set_best_bid_price(option::some(option::some(i64::from_u64(best_bid_price))));
  88. } else {
  89. feed.set_best_bid_price(option::some(option::none()));
  90. }
  91. } else if (property_id == 2) {
  92. let best_ask_price = cursor.peel_u64();
  93. if (best_ask_price != 0) {
  94. feed.set_best_ask_price(option::some(option::some(i64::from_u64(best_ask_price))));
  95. } else {
  96. feed.set_best_ask_price(option::some(option::none()));
  97. }
  98. } else if (property_id == 3) {
  99. let publisher_count = cursor.peel_u16();
  100. feed.set_publisher_count(option::some(publisher_count));
  101. } else if (property_id == 4) {
  102. let exponent = cursor.peel_u16();
  103. feed.set_exponent(option::some(i16::from_u16(exponent)));
  104. } else if (property_id == 5) {
  105. let confidence = cursor.peel_u64();
  106. if (confidence != 0) {
  107. feed.set_confidence(option::some(option::some(i64::from_u64(confidence))));
  108. } else {
  109. feed.set_confidence(option::some(option::none()));
  110. }
  111. } else if (property_id == 6) {
  112. let exists = cursor.peel_u8();
  113. if (exists == 1) {
  114. let funding_rate = cursor.peel_u64();
  115. feed.set_funding_rate(option::some(option::some(i64::from_u64(funding_rate))));
  116. } else {
  117. feed.set_funding_rate(option::some(option::none()));
  118. }
  119. } else if (property_id == 7) {
  120. let exists = cursor.peel_u8();
  121. if (exists == 1) {
  122. let funding_timestamp = cursor.peel_u64();
  123. feed.set_funding_timestamp(option::some(option::some(funding_timestamp)));
  124. } else {
  125. feed.set_funding_timestamp(option::some(option::none()));
  126. }
  127. } else if (property_id == 8) {
  128. let exists = cursor.peel_u8();
  129. if (exists == 1) {
  130. let funding_rate_interval = cursor.peel_u64();
  131. feed.set_funding_rate_interval(option::some(option::some(funding_rate_interval)));
  132. } else {
  133. feed.set_funding_rate_interval(option::some(option::none()));
  134. }
  135. } else {
  136. // When we have an unknown property, we do not know its length, and therefore
  137. // we cannot ignore it and parse the next properties.
  138. abort 0 // FIXME: return more granular error messages
  139. };
  140. properties_i = properties_i + 1;
  141. };
  142. vector::push_back(&mut feeds, feed);
  143. feed_i = feed_i + 1;
  144. };
  145. let remaining_bytes = cursor.into_remainder_bytes();
  146. assert!(remaining_bytes.length() == 0, 0);
  147. update::new(timestamp, channel, feeds)
  148. }