Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/hermes/server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/hermes/server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hermes"
version = "0.8.0"
version = "0.8.1"
description = "Hermes is an agent that provides Verified Prices from the Pythnet Pyth Oracle."
edition = "2021"

Expand Down
35 changes: 13 additions & 22 deletions apps/hermes/server/src/state/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,6 @@ where
}

let mut twaps = Vec::new();
let mut update_data = Vec::new();

// Iterate through start and end messages together
for (start_message, end_message) in start_messages.iter().zip(end_messages.iter()) {
Expand All @@ -676,34 +675,26 @@ where
end_timestamp: end_twap.publish_time,
down_slots_ratio,
});

// Combine messages for update data
let mut messages = Vec::new();
messages.push(start_message.clone().into());
messages.push(end_message.clone().into());

if let Ok(update) = construct_update_data(messages) {
update_data.push(update);
} else {
tracing::warn!(
"Failed to construct update data for price feed {:?}",
start_twap.feed_id
);
continue;
}
}
Err(e) => {
tracing::warn!(
return Err(anyhow!(
"Failed to calculate TWAP for price feed {:?}: {}",
start_twap.feed_id,
e
);
continue;
));
}
}
}
}

// Construct update data.
// update_data[0] will contain the start VAA and merkle proofs
// update_data[1] will contain the end VAA and merkle proofs
let start_updates =
construct_update_data(start_messages.into_iter().map(Into::into).collect())?;
let end_updates = construct_update_data(end_messages.into_iter().map(Into::into).collect())?;
let update_data = vec![start_updates, end_updates];

Ok(TwapsWithUpdateData { twaps, update_data })
}

Expand Down Expand Up @@ -1316,10 +1307,10 @@ mod test {
assert_eq!(twap_2.start_timestamp, 100);
assert_eq!(twap_2.end_timestamp, 200);

// Verify update data contains both start and end messages for both feeds
// Each update_data element contains a VAA and merkle proofs for both feeds
assert_eq!(result.update_data.len(), 2);
assert_eq!(result.update_data[0].len(), 2); // Should contain 2 messages
assert_eq!(result.update_data[1].len(), 2); // Should contain 2 messages
assert_eq!(result.update_data[0].len(), 1); // Start update
assert_eq!(result.update_data[1].len(), 1); // End update
}
#[tokio::test]

Expand Down
2 changes: 1 addition & 1 deletion price_service/sdk/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pythnetwork/price-service-sdk",
"version": "1.7.1",
"version": "1.8.0",
"description": "Pyth price service SDK",
"homepage": "https://pyth.network",
"main": "lib/index.js",
Expand Down
49 changes: 48 additions & 1 deletion price_service/sdk/js/src/AccumulatorUpdateData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ const MAJOR_VERSION = 1;
const MINOR_VERSION = 0;
const KECCAK160_HASH_SIZE = 20;
const PRICE_FEED_MESSAGE_VARIANT = 0;
const TWAP_MESSAGE_VARIANT = 1;

export type AccumulatorUpdateData = {
vaa: Buffer;
updates: { message: Buffer; proof: number[][] }[];
};

export type PriceFeedMessage = {
feedId: Buffer;
price: BN;
Expand All @@ -22,13 +22,25 @@ export type PriceFeedMessage = {
emaConf: BN;
};

export type TwapMessage = {
feedId: Buffer;
cumulativePrice: BN;
cumulativeConf: BN;
numDownSlots: BN;
exponent: number;
publishTime: BN;
prevPublishTime: BN;
publishSlot: BN;
};

export function isAccumulatorUpdateData(updateBytes: Buffer): boolean {
return (
updateBytes.toString("hex").slice(0, 8) === ACCUMULATOR_MAGIC &&
updateBytes[4] === MAJOR_VERSION &&
updateBytes[5] === MINOR_VERSION
);
}

export function parsePriceFeedMessage(message: Buffer): PriceFeedMessage {
let cursor = 0;
const variant = message.readUInt8(cursor);
Expand Down Expand Up @@ -64,6 +76,41 @@ export function parsePriceFeedMessage(message: Buffer): PriceFeedMessage {
};
}

export function parseTwapMessage(message: Buffer): TwapMessage {
let cursor = 0;
const variant = message.readUInt8(cursor);
if (variant !== TWAP_MESSAGE_VARIANT) {
throw new Error("Not a twap message");
}
cursor += 1;
const feedId = message.subarray(cursor, cursor + 32);
cursor += 32;
const cumulativePrice = new BN(message.subarray(cursor, cursor + 16), "be");
cursor += 16;
const cumulativeConf = new BN(message.subarray(cursor, cursor + 16), "be");
cursor += 16;
const numDownSlots = new BN(message.subarray(cursor, cursor + 8), "be");
cursor += 8;
const exponent = message.readInt32BE(cursor);
cursor += 4;
const publishTime = new BN(message.subarray(cursor, cursor + 8), "be");
cursor += 8;
const prevPublishTime = new BN(message.subarray(cursor, cursor + 8), "be");
cursor += 8;
const publishSlot = new BN(message.subarray(cursor, cursor + 8), "be");
cursor += 8;
return {
feedId,
cumulativePrice,
cumulativeConf,
numDownSlots,
exponent,
publishTime,
prevPublishTime,
publishSlot,
};
}

/**
* An AccumulatorUpdateData contains a VAA and a list of updates. This function returns a new serialized AccumulatorUpdateData with only the updates in the range [start, end).
*/
Expand Down
35 changes: 35 additions & 0 deletions price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
parseAccumulatorUpdateData,
parsePriceFeedMessage,
parseTwapMessage,
sliceAccumulatorUpdateData,
} from "../AccumulatorUpdateData";

Expand All @@ -10,7 +11,7 @@

describe("Test parse accumulator update", () => {
test("Happy path", async () => {
const { vaa, updates } = parseAccumulatorUpdateData(

Check warning on line 14 in price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts

View workflow job for this annotation

GitHub Actions / test

'vaa' is assigned a value but never used
Buffer.from(TEST_ACCUMULATOR_UPDATE_DATA, "base64")
);

Expand Down Expand Up @@ -72,7 +73,7 @@
).updates.length
).toBe(3);

const { vaa, updates } = parseAccumulatorUpdateData(

Check warning on line 76 in price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts

View workflow job for this annotation

GitHub Actions / test

'vaa' is assigned a value but never used
sliceAccumulatorUpdateData(
Buffer.from(TEST_ACCUMULATOR_UPDATE_DATA, "base64"),
1,
Expand Down Expand Up @@ -113,4 +114,38 @@
"Invalid accumulator message"
);
});

test("Parse TWAP message", () => {
// Sample data from the Hermes latest TWAP endpoint.
const test_accumulator_data_twap =
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd use camelcase or caps here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

woops, too much python & rust

"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";
const { updates } = parseAccumulatorUpdateData(
Buffer.from(test_accumulator_data_twap, "base64")
);

// Test that both messages are parsed successfully
const twapMessage1 = parseTwapMessage(updates[0].message);
expect(twapMessage1.feedId.toString("hex")).toBe(
"49f6b65cb1de6b10eaf75e7c03ca029c306d0357e91b5311b175084a5ad55688"
);
expect(twapMessage1.cumulativePrice.toString()).toBe("1760238576144013");
expect(twapMessage1.cumulativeConf.toString()).toBe("5113466755162");
expect(twapMessage1.numDownSlots.toString()).toBe("72037403");
Copy link
Contributor

@guibescos guibescos Dec 3, 2024

Choose a reason for hiding this comment

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

big numDownSlots here since stocks stop trading every night

expect(twapMessage1.exponent).toBe(-5);
expect(twapMessage1.publishTime.toString()).toBe("1733155135");
expect(twapMessage1.prevPublishTime.toString()).toBe("1733155134");
expect(twapMessage1.publishSlot.toString()).toBe("181871343");

const twapMessage2 = parseTwapMessage(updates[1].message);
expect(twapMessage2.feedId.toString("hex")).toBe(
"2b9ab1e972a281585084148ba1389800799bd4be63b957507db1349314e47445"
);
expect(twapMessage2.cumulativePrice.toString()).toBe("949830028892149802");
expect(twapMessage2.cumulativeConf.toString()).toBe("973071467813959");
expect(twapMessage2.numDownSlots.toString()).toBe("27140");
expect(twapMessage2.exponent).toBe(-8);
expect(twapMessage2.publishTime.toString()).toBe("1733155135");
expect(twapMessage2.prevPublishTime.toString()).toBe("1733155134");
expect(twapMessage2.publishSlot.toString()).toBe("181871343");
});
});
1 change: 1 addition & 0 deletions price_service/sdk/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export {
parseAccumulatorUpdateData,
AccumulatorUpdateData,
parsePriceFeedMessage,
parseTwapMessage,
} from "./AccumulatorUpdateData";

/**
Expand Down
Loading