124 lines
6.3 KiB
Plaintext
124 lines
6.3 KiB
Plaintext
module pyth::accumulator {
|
|
use std::vector::{Self};
|
|
use sui::clock::{Clock, Self};
|
|
use wormhole::bytes20::{Self, Bytes20};
|
|
use wormhole::cursor::{Self, Cursor};
|
|
use pyth::deserialize::{Self};
|
|
use pyth::price_identifier::{Self};
|
|
use pyth::price_info::{Self, PriceInfo};
|
|
use pyth::price_feed::{Self};
|
|
use pyth::merkle_tree::{Self};
|
|
|
|
const PRICE_FEED_MESSAGE_TYPE: u8 = 0;
|
|
const E_INVALID_UPDATE_DATA: u64 = 245;
|
|
const E_INVALID_PROOF: u64 = 345;
|
|
const E_INVALID_WORMHOLE_MESSAGE: u64 = 454;
|
|
const E_INVALID_ACCUMULATOR_PAYLOAD: u64 = 554;
|
|
const E_INVALID_ACCUMULATOR_HEADER: u64 = 657;
|
|
|
|
const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: u32 = 1096111958;
|
|
const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813;
|
|
|
|
const MINIMUM_SUPPORTED_MINOR_VERSION: u8 = 0;
|
|
const MAJOR_VERSION: u8 = 1;
|
|
|
|
friend pyth::pyth;
|
|
#[test_only]
|
|
friend pyth::pyth_tests;
|
|
|
|
// parse_and_verify_accumulator_message verifies that the price updates encoded in the
|
|
// accumulator message (accessed via cursor) belong to the merkle tree defined by the merkle root encoded in
|
|
// vaa_payload.
|
|
public(friend) fun parse_and_verify_accumulator_message(cursor: &mut Cursor<u8>, vaa_payload: vector<u8>, clock: &Clock): vector<PriceInfo> {
|
|
let header = deserialize::deserialize_u32(cursor);
|
|
|
|
if ((header as u64) != PYTHNET_ACCUMULATOR_UPDATE_MAGIC) {
|
|
abort E_INVALID_ACCUMULATOR_HEADER
|
|
};
|
|
|
|
let major = deserialize::deserialize_u8(cursor);
|
|
assert!(major == MAJOR_VERSION, E_INVALID_ACCUMULATOR_PAYLOAD);
|
|
|
|
let minor = deserialize::deserialize_u8(cursor);
|
|
assert!(minor >= MINIMUM_SUPPORTED_MINOR_VERSION, E_INVALID_ACCUMULATOR_PAYLOAD);
|
|
|
|
let trailing_size = deserialize::deserialize_u8(cursor);
|
|
deserialize::deserialize_vector(cursor, (trailing_size as u64));
|
|
|
|
let proof_type = deserialize::deserialize_u8(cursor);
|
|
assert!(proof_type == 0, E_INVALID_ACCUMULATOR_PAYLOAD);
|
|
|
|
// Ignore the vaa in the accumulator message because presumably it has already been verified
|
|
// and passed to this function as input.
|
|
let vaa_size = deserialize::deserialize_u16(cursor);
|
|
deserialize::deserialize_vector(cursor, (vaa_size as u64));
|
|
|
|
let merkle_root_hash = parse_accumulator_merkle_root_from_vaa_payload(vaa_payload);
|
|
parse_and_verify_accumulator_updates(cursor, merkle_root_hash, clock)
|
|
}
|
|
|
|
// parse_accumulator_merkle_root_from_vaa_payload takes in some VAA bytes, verifies that the vaa
|
|
// corresponds to a merkle update, and finally returns the merkle root.
|
|
// Note: this function is adapted from the Aptos Pyth accumulator
|
|
fun parse_accumulator_merkle_root_from_vaa_payload(message: vector<u8>): Bytes20 {
|
|
let msg_payload_cursor = cursor::new(message);
|
|
let payload_type = deserialize::deserialize_u32(&mut msg_payload_cursor);
|
|
assert!(payload_type == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC, E_INVALID_WORMHOLE_MESSAGE);
|
|
let wh_message_payload_type = deserialize::deserialize_u8(&mut msg_payload_cursor);
|
|
assert!(wh_message_payload_type == 0, E_INVALID_WORMHOLE_MESSAGE); // Merkle variant
|
|
let _merkle_root_slot = deserialize::deserialize_u64(&mut msg_payload_cursor);
|
|
let _merkle_root_ring_size = deserialize::deserialize_u32(&mut msg_payload_cursor);
|
|
let merkle_root_hash = deserialize::deserialize_vector(&mut msg_payload_cursor, 20);
|
|
cursor::take_rest<u8>(msg_payload_cursor);
|
|
bytes20::new(merkle_root_hash)
|
|
}
|
|
|
|
// Note: this parsing function is adapted from the Aptos Pyth parse_price_feed_message function
|
|
fun parse_price_feed_message(message_cur: &mut Cursor<u8>, clock: &Clock): PriceInfo {
|
|
let message_type = deserialize::deserialize_u8(message_cur);
|
|
|
|
assert!(message_type == PRICE_FEED_MESSAGE_TYPE, E_INVALID_UPDATE_DATA);
|
|
let price_identifier = price_identifier::from_byte_vec(deserialize::deserialize_vector(message_cur, 32));
|
|
let price = deserialize::deserialize_i64(message_cur);
|
|
let conf = deserialize::deserialize_u64(message_cur);
|
|
let expo = deserialize::deserialize_i32(message_cur);
|
|
let publish_time = deserialize::deserialize_u64(message_cur);
|
|
let _prev_publish_time = deserialize::deserialize_i64(message_cur);
|
|
let ema_price = deserialize::deserialize_i64(message_cur);
|
|
let ema_conf = deserialize::deserialize_u64(message_cur);
|
|
let price_info = price_info::new_price_info(
|
|
clock::timestamp_ms(clock) / 1000, // not used anywhere kept for backward compatibility
|
|
clock::timestamp_ms(clock) / 1000,
|
|
price_feed::new(
|
|
price_identifier,
|
|
pyth::price::new(price, conf, expo, publish_time),
|
|
pyth::price::new(ema_price, ema_conf, expo, publish_time),
|
|
)
|
|
);
|
|
price_info
|
|
}
|
|
|
|
// parse_and_verify_accumulator_updates takes as input a merkle root and cursor over the encoded update message (containing encoded
|
|
// leafs and merkle proofs), iterates over each leaf/proof pair and verifies it is part of the tree, and finally outputs the set of
|
|
// decoded and authenticated PriceInfos.
|
|
fun parse_and_verify_accumulator_updates(cursor: &mut Cursor<u8>, merkle_root: Bytes20, clock: &Clock): vector<PriceInfo> {
|
|
let update_size = deserialize::deserialize_u8(cursor);
|
|
let price_info_updates: vector<PriceInfo> = vector[];
|
|
while (update_size > 0) {
|
|
let message_size = deserialize::deserialize_u16(cursor);
|
|
let message = deserialize::deserialize_vector(cursor, (message_size as u64)); //should be safe to go from u16 to u16
|
|
let message_cur = cursor::new(message);
|
|
let price_info = parse_price_feed_message(&mut message_cur, clock);
|
|
cursor::take_rest(message_cur);
|
|
|
|
vector::push_back(&mut price_info_updates, price_info);
|
|
|
|
// isProofValid pops the next merkle proof from the front of cursor and checks if it proves that message is part of the
|
|
// merkle tree defined by merkle_root
|
|
assert!(merkle_tree::is_proof_valid(cursor, merkle_root, message), E_INVALID_PROOF);
|
|
update_size = update_size - 1;
|
|
};
|
|
price_info_updates
|
|
}
|
|
}
|