Skip to content

Commit 339b2f6

Browse files
authored
Update price feeds early if part of a batch (#905)
1 parent 8f7b6ee commit 339b2f6

File tree

7 files changed

+93
-27
lines changed

7 files changed

+93
-27
lines changed

price_pusher/README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,20 @@ The parameters above are configured per price feed in a price configuration YAML
3737
time_difference: 60 # Time difference threshold (in seconds) to push a newer price feed.
3838
price_deviation: 0.5 # The price deviation (%) threshold to push a newer price feed.
3939
confidence_ratio: 1 # The confidence/price (%) threshold to push a newer price feed.
40+
# Optional block to configure whether this feed can be early updated. If at least one feed meets the
41+
# triggering conditions above, all other feeds who meet the early update conditions will be included in
42+
# the submitted batch of prices. This logic takes advantage of the fact that adding a feed to a larger
43+
# batch of updates incurs a minimal gas cost. All fields below are optional (and interpreted as infinity if omitted)
44+
# and have the same semantics as the corresponding fields above.
45+
early_update:
46+
time_difference: 30
47+
price_deviation: 0.1
48+
confidence_ratio: 0.5
4049
- ...
4150
```
4251
52+
Two sample YAML configuration files are available in the root of this repo.
53+
4354
You can get the list of available price feeds from
4455
[here](https://pyth.network/developers/price-feed-ids/).
4556
@@ -57,7 +68,7 @@ cd price_pusher
5768
npm run start -- evm --endpoint wss://example-rpc.com \
5869
--pyth-contract-address 0xff1a0f4744e8582DF...... \
5970
--price-service-endpoint https://example-pyth-price.com \
60-
--price-config-file "path/to/price-config-file.yaml.testnet.sample.yaml" \
71+
--price-config-file "path/to/price-config.testnet.sample.yaml" \
6172
--mnemonic-file "path/to/mnemonic.txt" \
6273
[--pushing-frequency 10] \
6374
[--polling-frequency 5] \
@@ -66,7 +77,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \
6677
# For Injective
6778
npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
6879
--pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-pyth-price.com" \
69-
--price-config-file "path/to/price-config-file.yaml.testnet.sample.yaml" \
80+
--price-config-file "path/to/price-config.testnet.sample.yaml" \
7081
--mnemonic-file "path/to/mnemonic.txt" \
7182
[--pushing-frequency 10] \
7283
[--polling-frequency 5] \
@@ -94,15 +105,6 @@ npm run start -- sui
94105
[--polling-frequency 5] \
95106

96107

97-
98-
--endpoint https://fullnode.testnet.aptoslabs.com/v1 \
99-
--pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 --price-service-endpoint "https://xc-testnet.pyth.network" \
100-
--price-config-file "./price-config.testnet.sample.yaml" \
101-
--mnemonic-file "path/to/mnemonic.txt" \
102-
[--pushing-frequency 10] \
103-
[--polling-frequency 5] \
104-
105-
106108
# Or, run the price pusher docker image instead of building from the source
107109
docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-arguments>
108110
```

price_pusher/price-config.mainnet.sample.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
time_difference: 60
99
price_deviation: 1
1010
confidence_ratio: 1
11+
early_update:
12+
time_difference: 30
13+
price_deviation: 0.5
14+
confidence_ratio: 0.1

price_pusher/price-config.testnet.sample.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
time_difference: 60
99
price_deviation: 1
1010
confidence_ratio: 1
11+
early_update:
12+
time_difference: 30
13+
price_deviation: 0.5
14+
confidence_ratio: 0.1

price_pusher/src/controller.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { UnixTimestamp } from "@pythnetwork/price-service-client";
22
import { DurationInSeconds, sleep } from "./utils";
3-
import { IPricePusher, IPriceListener } from "./interface";
4-
import { PriceConfig, shouldUpdate } from "./price-config";
3+
import { IPriceListener, IPricePusher } from "./interface";
4+
import { PriceConfig, shouldUpdate, UpdateCondition } from "./price-config";
55

66
export class Controller {
77
private pushingFrequency: DurationInSeconds;
@@ -28,6 +28,9 @@ export class Controller {
2828
await sleep(this.pushingFrequency * 1000);
2929

3030
for (;;) {
31+
// We will push all prices whose update condition is YES or EARLY as long as there is
32+
// at least one YES.
33+
let pushThresholdMet = false;
3134
const pricesToPush: PriceConfig[] = [];
3235
const pubTimesToPush: UnixTimestamp[] = [];
3336

@@ -39,12 +42,24 @@ export class Controller {
3942
const sourceLatestPrice =
4043
this.sourcePriceListener.getLatestPriceInfo(priceId);
4144

42-
if (shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice)) {
45+
const priceShouldUpdate = shouldUpdate(
46+
priceConfig,
47+
sourceLatestPrice,
48+
targetLatestPrice
49+
);
50+
if (priceShouldUpdate == UpdateCondition.YES) {
51+
pushThresholdMet = true;
52+
}
53+
54+
if (
55+
priceShouldUpdate == UpdateCondition.YES ||
56+
priceShouldUpdate == UpdateCondition.EARLY
57+
) {
4358
pricesToPush.push(priceConfig);
4459
pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1);
4560
}
4661
}
47-
if (pricesToPush.length !== 0) {
62+
if (pushThresholdMet) {
4863
console.log(
4964
"Some of the above values passed the threshold. Will push the price."
5065
);

price_pusher/src/evm/command.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export default {
9191
mnemonic,
9292
pythContractAddress
9393
);
94+
console.log(
95+
`Pushing updates from wallet address: ${pythContractFactory
96+
.createWeb3PayerProvider()
97+
.getAddress()}`
98+
);
9499

95100
const evmListener = new EvmPriceListener(pythContractFactory, priceItems, {
96101
pollingFrequency,

price_pusher/src/evm/evm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export class EvmPricePusher implements IPricePusher {
224224
// the update data is valid there is no possible rejection cause other than
225225
// the target chain price being already updated.
226226
console.log(
227-
"Execution reverted. With high probablity, the target chain price " +
227+
"Execution reverted. With high probability, the target chain price " +
228228
"has already updated, Skipping this push."
229229
);
230230
return;

price_pusher/src/price-config.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const PriceConfigFileSchema: Joi.Schema = Joi.array()
1515
time_difference: Joi.number().required(),
1616
price_deviation: Joi.number().required(),
1717
confidence_ratio: Joi.number().required(),
18+
early_update: Joi.object({
19+
time_difference: Joi.number().optional(),
20+
price_deviation: Joi.number().optional(),
21+
confidence_ratio: Joi.number().optional(),
22+
}).optional(),
1823
})
1924
)
2025
.unique("id")
@@ -27,6 +32,12 @@ export type PriceConfig = {
2732
timeDifference: DurationInSeconds;
2833
priceDeviation: PctNumber;
2934
confidenceRatio: PctNumber;
35+
36+
// An early update happens when another price has met the conditions to be pushed, so this
37+
// price can be included in a batch update for minimal gas cost.
38+
earlyUpdateTimeDifference: DurationInSeconds | undefined;
39+
earlyUpdatePriceDeviation: PctNumber | undefined;
40+
earlyUpdateConfidenceRatio: PctNumber | undefined;
3041
};
3142

3243
export function readPriceConfigFile(path: string): PriceConfig[] {
@@ -44,11 +55,24 @@ export function readPriceConfigFile(path: string): PriceConfig[] {
4455
timeDifference: priceConfigRaw.time_difference,
4556
priceDeviation: priceConfigRaw.price_deviation,
4657
confidenceRatio: priceConfigRaw.confidence_ratio,
58+
59+
earlyUpdateTimeDifference: priceConfigRaw.early_update?.time_difference,
60+
earlyUpdatePriceDeviation: priceConfigRaw.early_update?.price_deviation,
61+
earlyUpdateConfidenceRatio: priceConfigRaw.early_update?.confidence_ratio,
4762
};
4863
return priceConfig;
4964
});
5065
}
5166

67+
export enum UpdateCondition {
68+
// This price feed must be updated
69+
YES,
70+
// This price feed may be updated as part of a larger batch
71+
EARLY,
72+
// This price feed shouldn't be updated
73+
NO,
74+
}
75+
5276
/**
5377
* Checks whether on-chain price needs to be updated with the latest pyth price information.
5478
*
@@ -59,25 +83,25 @@ export function shouldUpdate(
5983
priceConfig: PriceConfig,
6084
sourceLatestPrice: PriceInfo | undefined,
6185
targetLatestPrice: PriceInfo | undefined
62-
): boolean {
86+
): UpdateCondition {
6387
const priceId = priceConfig.id;
6488

6589
// There is no price to update the target with.
6690
if (sourceLatestPrice === undefined) {
67-
return false;
91+
return UpdateCondition.YES;
6892
}
6993

7094
// It means that price never existed there. So we should push the latest price feed.
7195
if (targetLatestPrice === undefined) {
7296
console.log(
7397
`${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.`
7498
);
75-
return true;
99+
return UpdateCondition.YES;
76100
}
77101

78102
// The current price is not newer than the price onchain
79103
if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
80-
return false;
104+
return UpdateCondition.NO;
81105
}
82106

83107
const timeDifference =
@@ -99,19 +123,31 @@ export function shouldUpdate(
99123
console.log("Target latest price: ", targetLatestPrice);
100124

101125
console.log(
102-
`Time difference: ${timeDifference} (< ${priceConfig.timeDifference}?) OR ` +
126+
`Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` +
103127
`Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${
104128
priceConfig.priceDeviation
105-
}%?) OR ` +
129+
}%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` +
106130
`Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${
107131
priceConfig.confidenceRatio
108-
}%?)`
132+
}%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?)`
109133
);
110134

111-
const result =
135+
if (
112136
timeDifference >= priceConfig.timeDifference ||
113137
priceDeviationPct >= priceConfig.priceDeviation ||
114-
confidenceRatioPct >= priceConfig.confidenceRatio;
115-
116-
return result;
138+
confidenceRatioPct >= priceConfig.confidenceRatio
139+
) {
140+
return UpdateCondition.YES;
141+
} else if (
142+
(priceConfig.earlyUpdateTimeDifference !== undefined &&
143+
timeDifference >= priceConfig.earlyUpdateTimeDifference) ||
144+
(priceConfig.earlyUpdatePriceDeviation !== undefined &&
145+
priceDeviationPct >= priceConfig.earlyUpdatePriceDeviation) ||
146+
(priceConfig.earlyUpdateConfidenceRatio !== undefined &&
147+
confidenceRatioPct >= priceConfig.earlyUpdateConfidenceRatio)
148+
) {
149+
return UpdateCondition.EARLY;
150+
} else {
151+
return UpdateCondition.NO;
152+
}
117153
}

0 commit comments

Comments
 (0)