batch_price_attestation.move 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. module pyth::batch_price_attestation {
  2. use pyth::price_feed::{Self};
  3. use pyth::error;
  4. use pyth::price_info::{Self, PriceInfo};
  5. use pyth::price_identifier::{Self};
  6. use pyth::price_status;
  7. use pyth::deserialize::{Self};
  8. use aptos_framework::timestamp;
  9. use wormhole::cursor::{Self, Cursor};
  10. use std::vector::{Self};
  11. #[test_only]
  12. use pyth::price;
  13. #[test_only]
  14. use pyth::i64;
  15. #[test_only]
  16. use aptos_framework::account;
  17. const MAGIC: u64 = 0x50325748; // "P2WH" (Pyth2Wormhole) raw ASCII bytes
  18. struct BatchPriceAttestation {
  19. header: Header,
  20. attestation_size: u64,
  21. attestation_count: u64,
  22. price_infos: vector<PriceInfo>,
  23. }
  24. struct Header {
  25. magic: u64,
  26. version_major: u64,
  27. version_minor: u64,
  28. header_size: u64,
  29. payload_id: u8,
  30. }
  31. fun deserialize_header(cur: &mut Cursor<u8>): Header {
  32. let magic = deserialize::deserialize_u32(cur);
  33. assert!(magic == MAGIC, error::invalid_attestation_magic_value());
  34. let version_major = deserialize::deserialize_u16(cur);
  35. let version_minor = deserialize::deserialize_u16(cur);
  36. let header_size = deserialize::deserialize_u16(cur);
  37. let payload_id = deserialize::deserialize_u8(cur);
  38. assert!(header_size >= 1, error::invalid_batch_attestation_header_size());
  39. let unknown_header_bytes = header_size - 1;
  40. let _unknown = deserialize::deserialize_vector(cur, unknown_header_bytes);
  41. Header {
  42. magic: magic,
  43. header_size: header_size,
  44. version_minor: version_minor,
  45. version_major: version_major,
  46. payload_id: payload_id,
  47. }
  48. }
  49. public fun destroy(batch: BatchPriceAttestation): vector<PriceInfo> {
  50. let BatchPriceAttestation {
  51. header: Header {
  52. magic: _,
  53. version_major: _,
  54. version_minor: _,
  55. header_size: _,
  56. payload_id: _,
  57. },
  58. attestation_size: _,
  59. attestation_count: _,
  60. price_infos,
  61. } = batch;
  62. price_infos
  63. }
  64. public fun get_attestation_count(batch: &BatchPriceAttestation): u64 {
  65. batch.attestation_count
  66. }
  67. public fun get_price_info(batch: &BatchPriceAttestation, index: u64): &PriceInfo {
  68. vector::borrow(&batch.price_infos, index)
  69. }
  70. public fun deserialize(bytes: vector<u8>): BatchPriceAttestation {
  71. let cur = cursor::init(bytes);
  72. let header = deserialize_header(&mut cur);
  73. let attestation_count = deserialize::deserialize_u16(&mut cur);
  74. let attestation_size = deserialize::deserialize_u16(&mut cur);
  75. let price_infos = vector::empty();
  76. let i = 0;
  77. while (i < attestation_count) {
  78. let price_info = deserialize_price_info(&mut cur);
  79. vector::push_back(&mut price_infos, price_info);
  80. // Consume any excess bytes
  81. let parsed_bytes = 32+32+8+8+4+8+8+1+4+4+8+8+8+8+8;
  82. let _excess = deserialize::deserialize_vector(&mut cur, attestation_size - parsed_bytes);
  83. i = i + 1;
  84. };
  85. cursor::destroy_empty(cur);
  86. BatchPriceAttestation {
  87. header,
  88. attestation_count: attestation_count,
  89. attestation_size: attestation_size,
  90. price_infos: price_infos,
  91. }
  92. }
  93. fun deserialize_price_info(cur: &mut Cursor<u8>): PriceInfo {
  94. // Skip obselete field
  95. let _product_identifier = deserialize::deserialize_vector(cur, 32);
  96. let price_identifier = price_identifier::from_byte_vec(deserialize::deserialize_vector(cur, 32));
  97. let price = deserialize::deserialize_i64(cur);
  98. let conf = deserialize::deserialize_u64(cur);
  99. let expo = deserialize::deserialize_i32(cur);
  100. let ema_price = deserialize::deserialize_i64(cur);
  101. let ema_conf = deserialize::deserialize_u64(cur);
  102. let status = price_status::from_u64((deserialize::deserialize_u8(cur) as u64));
  103. // Skip obselete fields
  104. let _num_publishers = deserialize::deserialize_u32(cur);
  105. let _max_num_publishers = deserialize::deserialize_u32(cur);
  106. let attestation_time = deserialize::deserialize_u64(cur);
  107. let publish_time = deserialize::deserialize_u64(cur); //
  108. let prev_publish_time = deserialize::deserialize_u64(cur);
  109. let prev_price = deserialize::deserialize_i64(cur);
  110. let prev_conf = deserialize::deserialize_u64(cur);
  111. // Handle the case where the status is not trading. This logic will soon be moved into
  112. // the attester.
  113. // If status is trading, use the current price.
  114. // If not, use the the last known trading price.
  115. let current_price = pyth::price::new(price, conf, expo, publish_time);
  116. if (status != price_status::new_trading()) {
  117. current_price = pyth::price::new(prev_price, prev_conf, expo, prev_publish_time);
  118. };
  119. // If status is trading, use the timestamp of the aggregate as the timestamp for the
  120. // EMA price. If not, the EMA will have last been updated when the aggregate last had
  121. // trading status, so use prev_publish_time (the time when the aggregate last had trading status).
  122. let ema_timestamp = publish_time;
  123. if (status != price_status::new_trading()) {
  124. ema_timestamp = prev_publish_time;
  125. };
  126. price_info::new(
  127. attestation_time,
  128. timestamp::now_seconds(),
  129. price_feed::new(
  130. price_identifier,
  131. current_price,
  132. pyth::price::new(ema_price, ema_conf, expo, ema_timestamp),
  133. )
  134. )
  135. }
  136. #[test]
  137. #[expected_failure(abort_code = 65560)]
  138. fun test_deserialize_batch_price_attestation_invalid_magic() {
  139. // A batch price attestation with a magic number of 0x50325749
  140. let bytes = x"5032574900030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001";
  141. destroy(deserialize(bytes));
  142. }
  143. #[test(aptos_framework = @aptos_framework)]
  144. fun test_deserialize_batch_price_attestation(aptos_framework: signer) {
  145. // Set the arrival time
  146. account::create_account_for_test(@aptos_framework);
  147. timestamp::set_time_has_started_for_testing(&aptos_framework);
  148. let arrival_time = 1663074349;
  149. timestamp::update_global_time_for_test(1663074349 * 1000000);
  150. // A raw batch price attestation
  151. // The first attestation has a status of UNKNOWN
  152. let bytes = x"5032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001";
  153. let expected = BatchPriceAttestation {
  154. header: Header {
  155. magic: 0x50325748,
  156. version_major: 3,
  157. version_minor: 0,
  158. payload_id: 2,
  159. header_size: 1,
  160. },
  161. attestation_count: 4,
  162. attestation_size: 149,
  163. price_infos: vector<PriceInfo>[
  164. price_info::new(
  165. 1663680747,
  166. arrival_time,
  167. price_feed::new(
  168. price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"),
  169. price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740),
  170. price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740),
  171. ),
  172. ),
  173. price_info::new(
  174. 1663680747,
  175. arrival_time,
  176. price_feed::new(
  177. price_identifier::from_byte_vec(x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe"),
  178. price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745),
  179. price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745),
  180. ),
  181. ),
  182. price_info::new(
  183. 1663680747,
  184. arrival_time,
  185. price_feed::new(
  186. price_identifier::from_byte_vec(x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d"),
  187. price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745),
  188. price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745),
  189. ),
  190. ),
  191. price_info::new(
  192. 1663680747,
  193. arrival_time,
  194. price_feed::new(
  195. price_identifier::from_byte_vec(x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8"),
  196. price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745),
  197. price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745),
  198. ),
  199. ),
  200. ],
  201. };
  202. let deserialized = deserialize(bytes);
  203. assert!(&expected == &deserialized, 1);
  204. destroy(expected);
  205. destroy(deserialized);
  206. }
  207. }