anchor/examples/pyth/tests/oracleUtils.ts

273 lines
9.1 KiB
TypeScript

import { Buffer } from 'buffer'
import { BN, Program, web3 } from '@project-serum/anchor'
export const Magic = 0xa1b2c3d4
export const Version1 = 1
export const Version = Version1
export const PriceStatus = ['Unknown', 'Trading', 'Halted', 'Auction']
export const CorpAction = ['NoCorpAct']
export const PriceType = ['Unknown', 'Price', 'TWAP', 'Volatility']
const empty32Buffer = Buffer.alloc(32)
const PKorNull = (data: Buffer) => (data.equals(empty32Buffer) ? null : new web3.PublicKey(data))
interface ICreatePriceFeed {
oracleProgram: Program
initPrice: number
confidence?: BN
expo?: number
}
export const createPriceFeed = async ({
oracleProgram,
initPrice,
confidence,
expo = -4,
}: ICreatePriceFeed) => {
const conf = confidence || new BN((initPrice / 10) * 10 ** -expo)
const collateralTokenFeed = new web3.Account()
await oracleProgram.rpc.initialize(new BN(initPrice * 10 ** -expo), expo, conf, {
accounts: { price: collateralTokenFeed.publicKey },
signers: [collateralTokenFeed],
instructions: [
web3.SystemProgram.createAccount({
fromPubkey: oracleProgram.provider.wallet.publicKey,
newAccountPubkey: collateralTokenFeed.publicKey,
space: 3312,
lamports: await oracleProgram.provider.connection.getMinimumBalanceForRentExemption(3312),
programId: oracleProgram.programId,
}),
],
})
return collateralTokenFeed.publicKey
}
export const setFeedPrice = async (
oracleProgram: Program,
newPrice: number,
priceFeed: web3.PublicKey
) => {
const info = await oracleProgram.provider.connection.getAccountInfo(priceFeed)
const data = parsePriceData(info.data)
await oracleProgram.rpc.setPrice(new BN(newPrice * 10 ** -data.exponent), {
accounts: { price: priceFeed },
})
}
export const getFeedData = async (oracleProgram: Program, priceFeed: web3.PublicKey) => {
const info = await oracleProgram.provider.connection.getAccountInfo(priceFeed)
return parsePriceData(info.data)
}
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L758
const ERR_BUFFER_OUT_OF_BOUNDS = () => new Error('Attempt to access memory outside buffer bounds')
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L968
const ERR_INVALID_ARG_TYPE = (name: string, expected: string, actual: any) =>
new Error(`The "${name}" argument must be of type ${expected}. Received ${actual}`)
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L1262
const ERR_OUT_OF_RANGE = (str: string, range: string, received: number) =>
new Error(`The value of "${str} is out of range. It must be ${range}. Received ${received}`)
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/validators.js#L127-L130
function validateNumber(value: any, name: string) {
if (typeof value !== 'number') throw ERR_INVALID_ARG_TYPE(name, 'number', value)
}
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L68-L80
function boundsError(value: number, length: number) {
if (Math.floor(value) !== value) {
validateNumber(value, 'offset')
throw ERR_OUT_OF_RANGE('offset', 'an integer', value)
}
if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS()
throw ERR_OUT_OF_RANGE('offset', `>= 0 and <= ${length}`, value)
}
export function readBigInt64LE(buffer: Buffer, offset = 0): bigint {
validateNumber(offset, 'offset')
const first = buffer[offset]
const last = buffer[offset + 7]
if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8)
const val =
buffer[offset + 4] + buffer[offset + 5] * 2 ** 8 + buffer[offset + 6] * 2 ** 16 + (last << 24) // Overflow
return (
(BigInt(val) << BigInt(32)) +
BigInt(
first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24
)
)
}
// https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L89-L107
export function readBigUInt64LE(buffer: Buffer, offset = 0): bigint {
validateNumber(offset, 'offset')
const first = buffer[offset]
const last = buffer[offset + 7]
if (first === undefined || last === undefined) boundsError(offset, buffer.length - 8)
const lo =
first + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + buffer[++offset] * 2 ** 24
const hi =
buffer[++offset] + buffer[++offset] * 2 ** 8 + buffer[++offset] * 2 ** 16 + last * 2 ** 24
return BigInt(lo) + (BigInt(hi) << BigInt(32)) // tslint:disable-line:no-bitwise
}
export const parsePriceData = (data: Buffer) => {
// Pyth magic number.
const magic = data.readUInt32LE(0)
// Program version.
const version = data.readUInt32LE(4)
// Account type.
const type = data.readUInt32LE(8)
// Price account size.
const size = data.readUInt32LE(12)
// Price or calculation type.
const priceType = data.readUInt32LE(16)
// Price exponent.
const exponent = data.readInt32LE(20)
// Number of component prices.
const numComponentPrices = data.readUInt32LE(24)
// unused
// const unused = accountInfo.data.readUInt32LE(28)
// Currently accumulating price slot.
const currentSlot = readBigUInt64LE(data, 32)
// Valid on-chain slot of aggregate price.
const validSlot = readBigUInt64LE(data, 40)
// Time-weighted average price.
const twapComponent = readBigInt64LE(data, 48)
const twap = Number(twapComponent) * 10 ** exponent
// Annualized price volatility.
const avolComponent = readBigUInt64LE(data, 56)
const avol = Number(avolComponent) * 10 ** exponent
// Space for future derived values.
const drv0Component = readBigInt64LE(data, 64)
const drv0 = Number(drv0Component) * 10 ** exponent
const drv1Component = readBigInt64LE(data, 72)
const drv1 = Number(drv1Component) * 10 ** exponent
const drv2Component = readBigInt64LE(data, 80)
const drv2 = Number(drv2Component) * 10 ** exponent
const drv3Component = readBigInt64LE(data, 88)
const drv3 = Number(drv3Component) * 10 ** exponent
const drv4Component = readBigInt64LE(data, 96)
const drv4 = Number(drv4Component) * 10 ** exponent
const drv5Component = readBigInt64LE(data, 104)
const drv5 = Number(drv5Component) * 10 ** exponent
// Product id / reference account.
const productAccountKey = new web3.PublicKey(data.slice(112, 144))
// Next price account in list.
const nextPriceAccountKey = PKorNull(data.slice(144, 176))
// Aggregate price updater.
const aggregatePriceUpdaterAccountKey = new web3.PublicKey(data.slice(176, 208))
const aggregatePriceInfo = parsePriceInfo(data.slice(208, 240), exponent)
// Price components - up to 32.
const priceComponents = []
let offset = 240
let shouldContinue = true
while (offset < data.length && shouldContinue) {
const publisher = PKorNull(data.slice(offset, offset + 32))
offset += 32
if (publisher) {
const aggregate = parsePriceInfo(data.slice(offset, offset + 32), exponent)
offset += 32
const latest = parsePriceInfo(data.slice(offset, offset + 32), exponent)
offset += 32
priceComponents.push({ publisher, aggregate, latest })
} else {
shouldContinue = false
}
}
return {
magic,
version,
type,
size,
priceType,
exponent,
numComponentPrices,
currentSlot,
validSlot,
twapComponent,
twap,
avolComponent,
avol,
drv0Component,
drv0,
drv1Component,
drv1,
drv2Component,
drv2,
drv3Component,
drv3,
drv4Component,
drv4,
drv5Component,
drv5,
productAccountKey,
nextPriceAccountKey,
aggregatePriceUpdaterAccountKey,
...aggregatePriceInfo,
priceComponents,
}
}
interface ProductAttributes {
[index: string]: string
}
export const parseProductData = (data: Buffer) => {
// Pyth magic number.
const magic = data.readUInt32LE(0)
// Program version.
const version = data.readUInt32LE(4)
// Account type.
const type = data.readUInt32LE(8)
// Price account size.
const size = data.readUInt32LE(12)
// First price account in list.
const priceAccountBytes = data.slice(16, 48)
const priceAccountKey = new web3.PublicKey(priceAccountBytes)
const product: ProductAttributes = {}
let idx = 48
while (idx < data.length) {
const keyLength = data[idx]
idx++
if (keyLength) {
const key = data.slice(idx, idx + keyLength).toString()
idx += keyLength
const valueLength = data[idx]
idx++
const value = data.slice(idx, idx + valueLength).toString()
idx += valueLength
product[key] = value
}
}
return { magic, version, type, size, priceAccountKey, product }
}
const parsePriceInfo = (data: Buffer, exponent: number) => {
// Aggregate price.
const priceComponent = data.readBigUInt64LE(0)
const price = Number(priceComponent) * 10 ** exponent
// Aggregate confidence.
const confidenceComponent = data.readBigUInt64LE(8)
const confidence = Number(confidenceComponent) * 10 ** exponent
// Aggregate status.
const status = data.readUInt32LE(16)
// Aggregate corporate action.
const corporateAction = data.readUInt32LE(20)
// Aggregate publish slot.
const publishSlot = data.readBigUInt64LE(24)
return {
priceComponent,
price,
confidenceComponent,
confidence,
status,
corporateAction,
publishSlot,
}
}