Skip to content
This repository was archived by the owner on Jan 10, 2025. It is now read-only.

Commit 17b948c

Browse files
author
Ludo Galabru
committed
fix: stale-price-threshold - access time
1 parent 697dd5f commit 17b948c

File tree

3 files changed

+145
-40
lines changed

3 files changed

+145
-40
lines changed

contracts/pyth-store-v1.clar

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
prev-publish-time: uint,
5454
}))
5555
(let ((stale-price-threshold (contract-call? .pyth-governance-v1 get-stale-price-threshold))
56-
(latest-bitcoin-timestamp (unwrap! (get-block-info? time burn-block-height) ERR_STALE_PRICE)))
56+
(latest-bitcoin-timestamp (unwrap! (get-block-info? time (- block-height u1)) ERR_STALE_PRICE)))
5757
;; Ensure that we have not processed a newer price
5858
(asserts! (is-price-update-more-recent (get price-identifier entry) (get publish-time entry)) ERR_NEWER_PRICE_AVAILABLE)
5959
;; Ensure that price is not stale

unit-tests/pyth/helpers.ts

+39-6
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,27 @@ export namespace pyth {
3030
"ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17",
3131
"hex",
3232
);
33-
export const BatPriceIdentifer = Buffer.from(
33+
export const BatPriceIdentifier = Buffer.from(
3434
"8e860fb74e60e5736b455d82f60b3728049c348e94961add5f961b02fdee2535",
3535
"hex",
3636
);
37-
export const DaiPriceIdentifer = Buffer.from(
37+
export const DaiPriceIdentifier = Buffer.from(
3838
"b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd",
3939
"hex",
4040
);
41-
export const UsdcPriceIdentifer = Buffer.from(
41+
export const UsdcPriceIdentifier = Buffer.from(
4242
"eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a",
4343
"hex",
4444
);
45-
export const UsdtPriceIdentifer = Buffer.from(
45+
export const UsdtPriceIdentifier = Buffer.from(
4646
"2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b",
4747
"hex",
4848
);
49-
export const WbtcPriceIdentifer = Buffer.from(
49+
export const WbtcPriceIdentifier = Buffer.from(
5050
"c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33",
5151
"hex",
5252
);
53-
export const TbtcPriceIdentifer = Buffer.from(
53+
export const TbtcPriceIdentifier = Buffer.from(
5454
"56a3121958b01f99fdc4e1fd01e81050602c7ace3a571918bb55c6a96657cca9",
5555
"hex",
5656
);
@@ -665,6 +665,39 @@ export namespace pyth {
665665
return res;
666666
}
667667

668+
export function applyStalePriceThresholdUpdate(
669+
updateStalePriceThreshold: { threshold: bigint },
670+
emitter: wormhole.Emitter,
671+
guardianSet: wormhole.Guardian[],
672+
txSenderAddress: string,
673+
pythGovernanceContractName: string,
674+
wormholeCoreContractName: string,
675+
sequence: bigint,
676+
) {
677+
let ptgmVaaPayload = pyth.buildPtgmVaaPayload({
678+
updateStalePriceThreshold,
679+
});
680+
let payload = pyth.serializePtgmVaaPayloadToBuffer(ptgmVaaPayload);
681+
let body = wormhole.buildValidVaaBodySpecs({ payload, sequence, emitter });
682+
let header = wormhole.buildValidVaaHeader(guardianSet, body, {
683+
version: 1,
684+
guardianSetId: 1,
685+
});
686+
let vaa = wormhole.serializeVaaToBuffer(header, body);
687+
let wormholeContract = Cl.contractPrincipal(
688+
simnet.deployer,
689+
wormholeCoreContractName,
690+
);
691+
let res = simnet.callPublicFn(
692+
pythGovernanceContractName,
693+
`update-stale-price-threshold`,
694+
[Cl.buffer(vaa), wormholeContract],
695+
txSenderAddress,
696+
);
697+
698+
return res;
699+
}
700+
668701
export namespace fc_ext {
669702
export const priceUpdate = (opts?: PriceUpdateBuildOptions) => {
670703
// price

unit-tests/pyth/pnau.test.ts

+105-33
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => {
1616
let pricesUpdates = pyth.buildPriceUpdateBatch([
1717
[pyth.BtcPriceIdentifier],
1818
[pyth.StxPriceIdentifier],
19-
[pyth.BatPriceIdentifer],
20-
[pyth.DaiPriceIdentifer],
21-
[pyth.TbtcPriceIdentifer],
22-
[pyth.UsdcPriceIdentifer],
23-
[pyth.UsdtPriceIdentifer],
24-
[pyth.WbtcPriceIdentifer],
19+
[pyth.BatPriceIdentifier],
20+
[pyth.DaiPriceIdentifier],
21+
[pyth.TbtcPriceIdentifier],
22+
[pyth.UsdcPriceIdentifier],
23+
[pyth.UsdtPriceIdentifier],
24+
[pyth.WbtcPriceIdentifier],
2525
]);
2626
let pricesUpdatesToSubmit = [
2727
pyth.BtcPriceIdentifier,
2828
pyth.StxPriceIdentifier,
29-
pyth.UsdcPriceIdentifer,
29+
pyth.UsdcPriceIdentifier,
3030
];
3131
let pricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(pricesUpdates);
3232

@@ -137,17 +137,17 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
137137
let pricesUpdates = pyth.buildPriceUpdateBatch([
138138
[pyth.BtcPriceIdentifier],
139139
[pyth.StxPriceIdentifier],
140-
[pyth.BatPriceIdentifer],
141-
[pyth.DaiPriceIdentifer],
142-
[pyth.TbtcPriceIdentifer],
143-
[pyth.UsdcPriceIdentifer],
144-
[pyth.UsdtPriceIdentifer],
145-
[pyth.WbtcPriceIdentifer],
140+
[pyth.BatPriceIdentifier],
141+
[pyth.DaiPriceIdentifier],
142+
[pyth.TbtcPriceIdentifier],
143+
[pyth.UsdcPriceIdentifier],
144+
[pyth.UsdtPriceIdentifier],
145+
[pyth.WbtcPriceIdentifier],
146146
]);
147147
let pricesUpdatesToSubmit = [
148148
pyth.BtcPriceIdentifier,
149149
pyth.StxPriceIdentifier,
150-
pyth.UsdcPriceIdentifer,
150+
pyth.UsdcPriceIdentifier,
151151
];
152152
let pricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(pricesUpdates);
153153
let executionPlan = Cl.tuple({
@@ -194,6 +194,16 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
194194
3n,
195195
);
196196

197+
pyth.applyStalePriceThresholdUpdate(
198+
{ threshold: 10800n },
199+
pyth.DefaultGovernanceDataSource,
200+
guardianSet,
201+
sender,
202+
pythGovernanceContractName,
203+
wormholeCoreContractName,
204+
4n,
205+
);
206+
197207
let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload);
198208
let vaaBody = wormhole.buildValidVaaBodySpecs({
199209
payload,
@@ -211,14 +221,53 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
211221
pricesUpdatesToSubmit,
212222
});
213223

214-
pricesUpdates.decoded[0];
215-
216-
simnet.callPublicFn(
224+
let res = simnet.callPublicFn(
217225
pythOracleContractName,
218226
"verify-and-update-price-feeds",
219227
[Cl.buffer(pnau), executionPlan],
220228
sender,
221229
);
230+
231+
expect(res.result).toBeOk(
232+
Cl.list([
233+
Cl.tuple({
234+
"price-identifier": Cl.buffer(pyth.BtcPriceIdentifier),
235+
price: Cl.int(pricesUpdates.decoded[0].price),
236+
conf: Cl.uint(pricesUpdates.decoded[0].conf),
237+
"ema-conf": Cl.uint(pricesUpdates.decoded[0].emaConf),
238+
"ema-price": Cl.int(pricesUpdates.decoded[0].emaPrice),
239+
expo: Cl.int(pricesUpdates.decoded[0].expo),
240+
"prev-publish-time": Cl.uint(
241+
pricesUpdates.decoded[0].prevPublishTime,
242+
),
243+
"publish-time": Cl.uint(pricesUpdates.decoded[0].publishTime),
244+
}),
245+
Cl.tuple({
246+
"price-identifier": Cl.buffer(pyth.StxPriceIdentifier),
247+
price: Cl.int(pricesUpdates.decoded[1].price),
248+
conf: Cl.uint(pricesUpdates.decoded[1].conf),
249+
"ema-conf": Cl.uint(pricesUpdates.decoded[1].emaConf),
250+
"ema-price": Cl.int(pricesUpdates.decoded[1].emaPrice),
251+
expo: Cl.int(pricesUpdates.decoded[1].expo),
252+
"prev-publish-time": Cl.uint(
253+
pricesUpdates.decoded[1].prevPublishTime,
254+
),
255+
"publish-time": Cl.uint(pricesUpdates.decoded[1].publishTime),
256+
}),
257+
Cl.tuple({
258+
"price-identifier": Cl.buffer(pyth.UsdcPriceIdentifier),
259+
price: Cl.int(pricesUpdates.decoded[2].price),
260+
conf: Cl.uint(pricesUpdates.decoded[2].conf),
261+
"ema-conf": Cl.uint(pricesUpdates.decoded[2].emaConf),
262+
"ema-price": Cl.int(pricesUpdates.decoded[2].emaPrice),
263+
expo: Cl.int(pricesUpdates.decoded[2].expo),
264+
"prev-publish-time": Cl.uint(
265+
pricesUpdates.decoded[2].prevPublishTime,
266+
),
267+
"publish-time": Cl.uint(pricesUpdates.decoded[2].publishTime),
268+
}),
269+
]),
270+
);
222271
});
223272

224273
it("should succeed updating prices once", () => {
@@ -248,12 +297,12 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
248297
let outdatedPricesUpdates = pyth.buildPriceUpdateBatch([
249298
[pyth.BtcPriceIdentifier, { price: 0n, publishTime: 9999999n }],
250299
[pyth.StxPriceIdentifier],
251-
[pyth.BatPriceIdentifer],
252-
[pyth.DaiPriceIdentifer],
253-
[pyth.TbtcPriceIdentifer],
254-
[pyth.UsdcPriceIdentifer],
255-
[pyth.UsdtPriceIdentifer],
256-
[pyth.WbtcPriceIdentifer],
300+
[pyth.BatPriceIdentifier],
301+
[pyth.DaiPriceIdentifier],
302+
[pyth.TbtcPriceIdentifier],
303+
[pyth.UsdcPriceIdentifier],
304+
[pyth.UsdtPriceIdentifier],
305+
[pyth.WbtcPriceIdentifier],
257306
]);
258307
let outdatedPricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(
259308
outdatedPricesUpdates,
@@ -310,12 +359,12 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
310359
let actualPricesUpdates = pyth.buildPriceUpdateBatch([
311360
[pyth.BtcPriceIdentifier],
312361
[pyth.StxPriceIdentifier],
313-
[pyth.BatPriceIdentifer],
314-
[pyth.DaiPriceIdentifer],
315-
[pyth.TbtcPriceIdentifer],
316-
[pyth.UsdcPriceIdentifer],
317-
[pyth.UsdtPriceIdentifer],
318-
[pyth.WbtcPriceIdentifer],
362+
[pyth.BatPriceIdentifier],
363+
[pyth.DaiPriceIdentifier],
364+
[pyth.TbtcPriceIdentifier],
365+
[pyth.UsdcPriceIdentifier],
366+
[pyth.UsdtPriceIdentifier],
367+
[pyth.WbtcPriceIdentifier],
319368
]);
320369
let actualPricesUpdatesVaaPayload =
321370
pyth.buildAuwvVaaPayload(actualPricesUpdates);
@@ -622,13 +671,16 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
622671
expect(res.result).toBeErr(Cl.uint(2008));
623672
});
624673

625-
it("should fail if the price is above stale threshold", () => {
674+
it("should fail if the price is below stale threshold", () => {
675+
// Everytime a simnet block is being mined, we add 1500s to the initial genesis time - usually bringing us to the future.
676+
let onChainTime = pyth.timestampNow() + 1500n * BigInt(simnet.blockHeight);
677+
simnet.mineEmptyBlocks(7); // stale threshold set to 10800 (3 hours), so by mining 7 blocks (1800s), we are advancing enough
626678
let actualPricesUpdates = pyth.buildPriceUpdateBatch([
627679
[
628680
pyth.BtcPriceIdentifier,
629681
{
630682
price: 100n,
631-
publishTime: pyth.timestampNow() - (5n * 365n * 60n * 60n + 1n),
683+
publishTime: onChainTime,
632684
},
633685
],
634686
]);
@@ -663,16 +715,24 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
663715
});
664716

665717
it("should only return validated prices and filter invalid prices", () => {
718+
// Everytime a simnet block is being mined, we add 1500s to the initial genesis time - usually bringing us to the future.
719+
let previousOnChainTime =
720+
pyth.timestampNow() + 1500n * BigInt(simnet.blockHeight);
721+
simnet.mineEmptyBlocks(7); // stale threshold set to 10800 (3 hours), so by mining 7 blocks (1800s), we are advancing enough
722+
let newOnChainTime =
723+
pyth.timestampNow() + 1500n * BigInt(simnet.blockHeight);
666724
let actualPricesUpdates = pyth.buildPriceUpdateBatch([
667725
[
668726
pyth.BtcPriceIdentifier,
669727
{
670728
price: 100n,
671-
publishTime: pyth.timestampNow() - (5n * 365n * 60n * 60n + 1n),
729+
publishTime: previousOnChainTime,
672730
},
673731
],
674-
[pyth.StxPriceIdentifier, { price: 100n }],
732+
[pyth.StxPriceIdentifier, { price: 100n, publishTime: newOnChainTime }],
733+
[pyth.UsdcPriceIdentifier, { price: 100n, publishTime: newOnChainTime }],
675734
]);
735+
676736
let actualPricesUpdatesVaaPayload =
677737
pyth.buildAuwvVaaPayload(actualPricesUpdates);
678738
let payload = pyth.serializeAuwvVaaPayloadToBuffer(
@@ -714,6 +774,18 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds failures", () => {
714774
),
715775
"publish-time": Cl.uint(actualPricesUpdates.decoded[1].publishTime),
716776
}),
777+
Cl.tuple({
778+
"price-identifier": Cl.buffer(pyth.UsdcPriceIdentifier),
779+
price: Cl.int(actualPricesUpdates.decoded[2].price),
780+
conf: Cl.uint(actualPricesUpdates.decoded[2].conf),
781+
"ema-conf": Cl.uint(actualPricesUpdates.decoded[2].emaConf),
782+
"ema-price": Cl.int(actualPricesUpdates.decoded[2].emaPrice),
783+
expo: Cl.int(actualPricesUpdates.decoded[2].expo),
784+
"prev-publish-time": Cl.uint(
785+
actualPricesUpdates.decoded[2].prevPublishTime,
786+
),
787+
"publish-time": Cl.uint(actualPricesUpdates.decoded[2].publishTime),
788+
}),
717789
]),
718790
);
719791
});

0 commit comments

Comments
 (0)