Skip to content
This repository was archived by the owner on Apr 3, 2023. It is now read-only.

Commit 979fa4a

Browse files
authored
Abehjati/use-update-if-needed (#36)
* Update price pusher to use updateIfNecessary * Update pusher * improve logs * Update solidity sdk to 0.5.2
1 parent 1737c4e commit 979fa4a

File tree

3 files changed

+95
-32
lines changed

3 files changed

+95
-32
lines changed

pyth-evm-price-pusher/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyth-evm-price-pusher/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"dependencies": {
4848
"@pythnetwork/pyth-evm-js": "^0.5.0",
49-
"@pythnetwork/pyth-sdk-solidity": "^0.4.0",
49+
"@pythnetwork/pyth-sdk-solidity": "^0.5.2",
5050
"@truffle/hdwallet-provider": "^2.0.8",
5151
"joi": "^17.6.0",
5252
"web3": "^1.5.3",

pyth-evm-price-pusher/src/pusher.ts

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { EvmPriceServiceConnection } from "@pythnetwork/pyth-evm-js";
2-
import { DurationInSeconds, sleep } from "./utils";
3-
import { PriceListener } from "./price-listener";
1+
import {
2+
EvmPriceServiceConnection,
3+
UnixTimestamp,
4+
} from "@pythnetwork/pyth-evm-js";
5+
import { addLeading0x, DurationInSeconds, sleep } from "./utils";
6+
import { PriceInfo, PriceListener } from "./price-listener";
47
import { Contract } from "web3-eth-contract";
58
import AbstractPythAbi from "@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json";
69
import Web3 from "web3";
710
import HDWalletProvider from "@truffle/hdwallet-provider";
811
import { PriceConfig } from "./price-config";
12+
import { TransactionReceipt } from "ethereum-protocol";
913

1014
export class Pusher {
1115
private connection: EvmPriceServiceConnection;
1216
private pythContract: Contract;
1317
private targetPriceListener: PriceListener;
14-
private srcPriceListener: PriceListener;
18+
private sourcePriceListener: PriceListener;
1519
private priceConfigs: PriceConfig[];
1620

1721
private cooldownDuration: DurationInSeconds;
@@ -22,15 +26,15 @@ export class Pusher {
2226
mnemonic: string,
2327
pythContractAddr: string,
2428
targetPriceListener: PriceListener,
25-
srcPriceListener: PriceListener,
29+
sourcePriceListener: PriceListener,
2630
priceConfigs: PriceConfig[],
2731
config: {
2832
cooldownDuration: DurationInSeconds;
2933
}
3034
) {
3135
this.connection = connection;
3236
this.targetPriceListener = targetPriceListener;
33-
this.srcPriceListener = srcPriceListener;
37+
this.sourcePriceListener = sourcePriceListener;
3438
this.priceConfigs = priceConfigs;
3539

3640
this.cooldownDuration = config.cooldownDuration;
@@ -55,21 +59,46 @@ export class Pusher {
5559

5660
async start() {
5761
for (;;) {
58-
const pricesToPush = this.priceConfigs.filter(
59-
this.shouldUpdate.bind(this)
60-
);
61-
this.pushUpdates(pricesToPush);
62+
const pricesToPush: PriceConfig[] = [];
63+
const pubTimesToPush: UnixTimestamp[] = [];
64+
65+
for (const priceConfig of this.priceConfigs) {
66+
const priceId = priceConfig.id;
67+
68+
const targetLatestPrice =
69+
this.targetPriceListener.getLatestPriceInfo(priceId);
70+
const sourceLatestPrice =
71+
this.sourcePriceListener.getLatestPriceInfo(priceId);
72+
73+
if (
74+
this.shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice)
75+
) {
76+
pricesToPush.push(priceConfig);
77+
pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1);
78+
}
79+
}
80+
this.pushUpdates(pricesToPush, pubTimesToPush);
6281
await sleep(this.cooldownDuration * 1000);
6382
}
6483
}
6584

66-
async pushUpdates(pricesToPush: PriceConfig[]) {
85+
// The pubTimes are passed here to use the values that triggered the push.
86+
// This is an optimization to avoid getting a newer value (as an update comes)
87+
// and will help multiple price pushers to have consistent behaviour.
88+
async pushUpdates(
89+
pricesToPush: PriceConfig[],
90+
pubTimesToPush: UnixTimestamp[]
91+
) {
6792
if (pricesToPush.length === 0) {
6893
return;
6994
}
7095

96+
const priceIds = pricesToPush.map((priceConfig) =>
97+
addLeading0x(priceConfig.id)
98+
);
99+
71100
const priceFeedUpdateData = await this.connection.getPriceFeedsUpdateData(
72-
pricesToPush.map((priceConfig) => priceConfig.id)
101+
priceIds
73102
);
74103

75104
console.log(
@@ -80,10 +109,44 @@ export class Pusher {
80109
);
81110

82111
this.pythContract.methods
83-
.updatePriceFeeds(priceFeedUpdateData)
112+
.updatePriceFeedsIfNecessary(
113+
priceFeedUpdateData,
114+
priceIds,
115+
pubTimesToPush
116+
)
84117
.send()
85118
.on("transactionHash", (hash: string) => {
86-
console.log(`Tx hash: ${hash}`);
119+
console.log(`Successful. Tx hash: ${hash}`);
120+
})
121+
.on("error", (err: Error, receipt: TransactionReceipt) => {
122+
if (
123+
err.message.includes(
124+
"no prices in the submitted batch have fresh prices, so this update will have no effect"
125+
)
126+
) {
127+
console.log(
128+
"The target chain price has already updated, Skipping this push."
129+
);
130+
return;
131+
}
132+
133+
if (err.message.includes("the tx doesn't have the correct nonce.")) {
134+
console.log(
135+
"Multiple users are using the same accounts and nonce is incorrect. Skipping this push."
136+
);
137+
return;
138+
}
139+
140+
if (
141+
err.message.includes("sender doesn't have enough funds to send tx.")
142+
) {
143+
console.error("Payer is out of balance, please top it up.");
144+
throw err;
145+
}
146+
147+
console.error("An unidentified error has occured:");
148+
console.error(err, receipt);
149+
console.error("Skipping this push.");
87150
});
88151
}
89152

@@ -93,15 +156,15 @@ export class Pusher {
93156
* @param priceConfig Config of the price feed to check
94157
* @returns True if the on-chain price needs to be updated.
95158
*/
96-
shouldUpdate(priceConfig: PriceConfig): boolean {
159+
shouldUpdate(
160+
priceConfig: PriceConfig,
161+
sourceLatestPrice: PriceInfo | undefined,
162+
targetLatestPrice: PriceInfo | undefined
163+
): boolean {
97164
const priceId = priceConfig.id;
98165

99-
const targetLatestPrice =
100-
this.targetPriceListener.getLatestPriceInfo(priceId);
101-
const srcLatestPrice = this.srcPriceListener.getLatestPriceInfo(priceId);
102-
103166
// There is no price to update the target with.
104-
if (srcLatestPrice === undefined) {
167+
if (sourceLatestPrice === undefined) {
105168
return false;
106169
}
107170

@@ -114,21 +177,21 @@ export class Pusher {
114177
}
115178

116179
// The current price is not newer than the price onchain
117-
if (srcLatestPrice.publishTime < targetLatestPrice.publishTime) {
180+
if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
118181
return false;
119182
}
120183

121184
const timeDifference =
122-
srcLatestPrice.publishTime - targetLatestPrice.publishTime;
185+
sourceLatestPrice.publishTime - targetLatestPrice.publishTime;
123186

124187
const priceDeviationPct =
125188
(Math.abs(
126-
Number(srcLatestPrice.price) - Number(targetLatestPrice.price)
189+
Number(sourceLatestPrice.price) - Number(targetLatestPrice.price)
127190
) /
128191
Number(targetLatestPrice.price)) *
129192
100;
130193
const confidenceRatioPct = Math.abs(
131-
(Number(srcLatestPrice.conf) / Number(srcLatestPrice.price)) * 100
194+
(Number(sourceLatestPrice.conf) / Number(sourceLatestPrice.price)) * 100
132195
);
133196

134197
console.log(`Analyzing price ${priceConfig.alias} (${priceId})`);

0 commit comments

Comments
 (0)