-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port price updaters from arbitrum-lp app
- Loading branch information
Showing
14 changed files
with
448 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useAllPriceFeeds'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@snx-v3/useAllPriceFeeds", | ||
"private": true, | ||
"main": "index.ts", | ||
"version": "0.0.2", | ||
"dependencies": { | ||
"@snx-v3/contracts": "workspace:*", | ||
"@snx-v3/useBlockchain": "workspace:*", | ||
"@tanstack/react-query": "^5.8.3", | ||
"react": "^18.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { importExtras } from '@snx-v3/contracts'; | ||
import { Network, useNetwork } from '@snx-v3/useBlockchain'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
|
||
export function useAllPriceFeeds(customNetwork?: Network) { | ||
const { network } = useNetwork(); | ||
const targetNetwork = customNetwork || network; | ||
return useQuery({ | ||
enabled: Boolean(targetNetwork?.id && targetNetwork?.preset), | ||
queryKey: [targetNetwork?.id, 'AllPriceFeeds'], | ||
queryFn: async () => { | ||
if (!(targetNetwork?.id && targetNetwork?.preset)) { | ||
throw 'OMFG'; | ||
} | ||
const extras = await importExtras(targetNetwork.id, targetNetwork.preset); | ||
return Object.entries(extras) | ||
.filter( | ||
([key, value]) => | ||
String(value).length === 66 && | ||
(key.startsWith('pyth_feed_id_') || (key.startsWith('pyth') && key.endsWith('FeedId'))) | ||
) | ||
.map(([, value]) => value); | ||
}, | ||
staleTime: 60 * 60 * 1000, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { ethers } from 'ethers'; | ||
|
||
export function decodeBuiltinErrors(data: string) { | ||
let sighash = ethers.utils.id('Panic(uint256)').slice(0, 10); | ||
if (data.startsWith(sighash)) { | ||
// this is the `Panic` builtin opcode | ||
const reason = ethers.utils.defaultAbiCoder.decode(['uint256'], '0x' + data.slice(10))[0]; | ||
switch (reason.toNumber()) { | ||
case 0x00: | ||
return { name: 'Panic("generic/unknown error")', sighash, args: [{ reason }] }; | ||
case 0x01: | ||
return { name: 'Panic("assertion failed")', sighash, args: [{ reason }] }; | ||
case 0x11: | ||
return { name: 'Panic("unchecked underflow/overflow")', sighash, args: [{ reason }] }; | ||
case 0x12: | ||
return { name: 'Panic("division by zero")', sighash, args: [{ reason }] }; | ||
case 0x21: | ||
return { name: 'Panic("invalid number to enum conversion")', sighash, args: [{ reason }] }; | ||
case 0x22: | ||
return { | ||
name: 'Panic("access to incorrect storage byte array")', | ||
sighash, | ||
args: [{ reason }], | ||
}; | ||
case 0x31: | ||
return { name: 'Panic("pop() empty array")', sighash, args: [{ reason }] }; | ||
case 0x32: | ||
return { name: 'Panic("out of bounds array access")', sighash, args: [{ reason }] }; | ||
case 0x41: | ||
return { name: 'Panic("out of memory")', sighash, args: [{ reason }] }; | ||
case 0x51: | ||
return { name: 'Panic("invalid internal function")', sighash, args: [{ reason }] }; | ||
default: | ||
return { name: 'Panic("unknown")', sighash, args: [{ reason }] }; | ||
} | ||
} | ||
sighash = ethers.utils.id('Error(string)').slice(0, 10); | ||
if (data.startsWith(sighash)) { | ||
// this is the `Error` builtin opcode | ||
const reason = ethers.utils.defaultAbiCoder.decode(['string'], '0x' + data.slice(10)); | ||
return { name: `Error("${reason}")`, sighash, args: [{ reason }] }; | ||
} | ||
|
||
return; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { ethers } from 'ethers'; | ||
|
||
export const PythErrorsABI = [ | ||
// Function arguments are invalid (e.g., the arguments lengths mismatch) | ||
// Signature: 0xa9cb9e0d | ||
'error InvalidArgument()', | ||
// Update data is coming from an invalid data source. | ||
// Signature: 0xe60dce71 | ||
'error InvalidUpdateDataSource()', | ||
// Update data is invalid (e.g., deserialization error) | ||
// Signature: 0xe69ffece | ||
'error InvalidUpdateData()', | ||
// Insufficient fee is paid to the method. | ||
// Signature: 0x025dbdd4 | ||
'error InsufficientFee()', | ||
// There is no fresh update, whereas expected fresh updates. | ||
// Signature: 0xde2c57fa | ||
'error NoFreshUpdate()', | ||
// There is no price feed found within the given range or it does not exists. | ||
// Signature: 0x45805f5d | ||
'error PriceFeedNotFoundWithinRange()', | ||
// Price feed not found or it is not pushed on-chain yet. | ||
// Signature: 0x14aebe68 | ||
'error PriceFeedNotFound()', | ||
// Requested price is stale. | ||
// Signature: 0x19abf40e | ||
'error StalePrice()', | ||
// Given message is not a valid Wormhole VAA. | ||
// Signature: 0x2acbe915 | ||
'error InvalidWormholeVaa()', | ||
// Governance message is invalid (e.g., deserialization error). | ||
// Signature: 0x97363b35 | ||
'error InvalidGovernanceMessage()', | ||
// Governance message is not for this contract. | ||
// Signature: 0x63daeb77 | ||
'error InvalidGovernanceTarget()', | ||
// Governance message is coming from an invalid data source. | ||
// Signature: 0x360f2d87 | ||
'error InvalidGovernanceDataSource()', | ||
// Governance message is old. | ||
// Signature: 0x88d1b847 | ||
'error OldGovernanceMessage()', | ||
// The wormhole address to set in SetWormholeAddress governance is invalid. | ||
// Signature: 0x13d3ed82 | ||
'error InvalidWormholeAddressToSet()', | ||
]; | ||
|
||
export function decodePythErrors(data: string) { | ||
const PythInterface = new ethers.utils.Interface(PythErrorsABI); | ||
try { | ||
const decodedError = PythInterface.parseError(data); | ||
Object.assign(decodedError, { | ||
name: `PythError.${decodedError.name}`, | ||
}); | ||
return decodedError; | ||
} catch (e) { | ||
// whatever | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './useErrorParser'; | ||
export * from './parseError'; | ||
export * from './decodeBuiltinErrors'; | ||
export * from './decodePythErrors'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@snx-v3/useErrorParser", | ||
"private": true, | ||
"main": "index.ts", | ||
"version": "0.0.2", | ||
"dependencies": { | ||
"@snx-v3/contracts": "workspace:*", | ||
"@snx-v3/useBlockchain": "workspace:*", | ||
"ethers": "^5.7.2", | ||
"react": "^18.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* eslint-disable no-console */ | ||
import { importAllErrors } from '@snx-v3/contracts'; | ||
import { ethers } from 'ethers'; | ||
import { decodeBuiltinErrors } from './decodeBuiltinErrors'; | ||
import { decodePythErrors } from './decodePythErrors'; | ||
|
||
export async function parseError({ | ||
error, | ||
chainId, | ||
preset, | ||
}: { | ||
error: Error | any; | ||
chainId: number; | ||
preset: string; | ||
}) { | ||
const errorData = | ||
error?.error?.error?.error?.data || | ||
error?.error?.error?.data || | ||
error?.error?.data?.data || | ||
error?.error?.data || | ||
error?.data?.data || | ||
error?.data; | ||
|
||
if (typeof errorData !== 'string') { | ||
console.log('Error data missing', { error }); | ||
throw error; | ||
} | ||
const AllErrorsContract = await importAllErrors(chainId, preset); | ||
const errorParsed = (() => { | ||
try { | ||
const panic = decodeBuiltinErrors(errorData); | ||
if (panic) { | ||
return panic; | ||
} | ||
const pythError = decodePythErrors(errorData); | ||
if (pythError) { | ||
return pythError; | ||
} | ||
const AllErrorsInterface = new ethers.utils.Interface(AllErrorsContract.abi); | ||
const data = AllErrorsInterface.parseError(errorData); | ||
console.log({ decodedError: data }); | ||
|
||
if ( | ||
data?.name === 'OracleDataRequired' && | ||
data?.args?.oracleContract && | ||
data?.args?.oracleQuery | ||
) { | ||
const oracleAddress = data?.args?.oracleContract; | ||
const oracleQueryRaw = data?.args?.oracleQuery; | ||
|
||
let updateType, publishTime, stalenessTolerance, feedIds, priceId; | ||
// eslint-disable-next-line prefer-const | ||
[updateType, publishTime, priceId] = ethers.utils.defaultAbiCoder.decode( | ||
['uint8', 'uint64', 'bytes32'], | ||
oracleQueryRaw | ||
); | ||
|
||
if (updateType === 1) { | ||
[updateType, stalenessTolerance, feedIds] = ethers.utils.defaultAbiCoder.decode( | ||
['uint8', 'uint64', 'bytes32[]'], | ||
oracleQueryRaw | ||
); | ||
publishTime = undefined; | ||
} else { | ||
feedIds = [priceId]; | ||
} | ||
Object.assign(error, { | ||
name: data.name, | ||
error, | ||
args: { | ||
oracleAddress, | ||
oracleQuery: { | ||
updateType, | ||
publishTime: Number(publishTime), | ||
stalenessTolerance: Number(stalenessTolerance), | ||
feedIds, | ||
}, | ||
oracleQueryRaw, | ||
}, | ||
signature: data.signature, | ||
sighash: data.sighash, | ||
errorFragment: data.errorFragment, | ||
}); | ||
return error; | ||
} | ||
return data; | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
return error; | ||
})(); | ||
if (!errorParsed.name) { | ||
throw error; | ||
} | ||
const args = errorParsed?.args | ||
? Object.fromEntries( | ||
Object.entries(errorParsed.args).filter(([key]) => `${parseInt(key)}` !== key) | ||
) | ||
: {}; | ||
error.message = `${errorParsed?.name}, ${errorParsed?.sighash} (${JSON.stringify(args)})`; | ||
throw error; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Network, useNetwork } from '@snx-v3/useBlockchain'; | ||
import React from 'react'; | ||
import { parseError } from './parseError'; | ||
|
||
export function useErrorParser(customNetwork?: Network) { | ||
const { network: walletNetwork } = useNetwork(); | ||
const network = customNetwork ? customNetwork : walletNetwork; | ||
return React.useCallback( | ||
async (error: Error) => { | ||
if (network?.id && network?.preset) { | ||
return await parseError({ error, chainId: network?.id, preset: network?.preset }); | ||
} | ||
throw error; | ||
}, | ||
[network?.id, network?.preset] | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* eslint-disable no-console */ | ||
import { EvmPriceServiceConnection } from '@pythnetwork/pyth-evm-js'; | ||
import { ethers } from 'ethers'; | ||
|
||
export async function fetchPriceUpdateTxn({ | ||
provider, | ||
MulticallContract, | ||
PythERC7412WrapperContract, | ||
priceIds, | ||
}: { | ||
provider: ethers.providers.BaseProvider; | ||
MulticallContract: { address: string; abi: string[] }; | ||
PythERC7412WrapperContract: { address: string; abi: string[] }; | ||
priceIds: string[]; | ||
}) { | ||
console.time('fetchPriceUpdateTxn'); | ||
const stalenessTolerance = 1800; // half of 3600 required tolerance | ||
|
||
const MulticallInterface = new ethers.utils.Interface(MulticallContract.abi); | ||
const PythERC7412WrapperInterface = new ethers.utils.Interface(PythERC7412WrapperContract.abi); | ||
const txs = priceIds.map((priceId) => ({ | ||
target: PythERC7412WrapperContract.address, | ||
callData: PythERC7412WrapperInterface.encodeFunctionData('getLatestPrice', [ | ||
priceId, | ||
stalenessTolerance, | ||
]), | ||
value: 0, | ||
requireSuccess: false, | ||
})); | ||
|
||
const result = await provider.call({ | ||
to: MulticallContract.address, | ||
data: MulticallInterface.encodeFunctionData('aggregate3Value', [txs]), | ||
}); | ||
const [latestPrices] = MulticallInterface.decodeFunctionResult('aggregate3Value', result); | ||
const stalePriceIds = priceIds.filter((_priceId, i) => !latestPrices[i].success); | ||
if (stalePriceIds.length < 1) { | ||
return { | ||
target: PythERC7412WrapperContract.address, | ||
callData: ethers.constants.HashZero, | ||
value: 0, | ||
requireSuccess: false, | ||
}; | ||
} | ||
console.log({ stalePriceIds }); | ||
|
||
const priceService = new EvmPriceServiceConnection('https://hermes.pyth.network'); | ||
const signedOffchainData = await priceService.getPriceFeedsUpdateData(stalePriceIds); | ||
const updateType = 1; | ||
const data = ethers.utils.defaultAbiCoder.encode( | ||
['uint8', 'uint64', 'bytes32[]', 'bytes[]'], | ||
[updateType, stalenessTolerance, stalePriceIds, signedOffchainData] | ||
); | ||
console.timeEnd('fetchPriceUpdateTxn'); | ||
const priceUpdateTxn = { | ||
target: PythERC7412WrapperContract.address, | ||
callData: PythERC7412WrapperInterface.encodeFunctionData('fulfillOracleQuery', [data]), | ||
value: stalePriceIds.length, | ||
requireSuccess: true, | ||
}; | ||
console.log({ priceUpdateTxn }); | ||
return priceUpdateTxn; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './usePriceUpdateTxn'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "@snx-v3/usePriceUpdateTxn", | ||
"private": true, | ||
"main": "index.ts", | ||
"version": "0.0.2", | ||
"dependencies": { | ||
"@pythnetwork/pyth-evm-js": "^1.42.0", | ||
"@snx-v3/contracts": "workspace:*", | ||
"@snx-v3/useBlockchain": "workspace:*", | ||
"@snx-v3/useErrorParser": "workspace:*", | ||
"@tanstack/react-query": "^5.8.3", | ||
"ethers": "^5.7.2", | ||
"react": "^18.2.0" | ||
} | ||
} |
Oops, something went wrong.