oracleUtils.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import { Buffer } from "buffer";
  2. import { AnchorProvider, BN, Program, web3 } from "@coral-xyz/anchor";
  3. export const Magic = 0xa1b2c3d4;
  4. export const Version1 = 1;
  5. export const Version = Version1;
  6. export const PriceStatus = ["Unknown", "Trading", "Halted", "Auction"];
  7. export const CorpAction = ["NoCorpAct"];
  8. export const PriceType = ["Unknown", "Price", "TWAP", "Volatility"];
  9. const empty32Buffer = Buffer.alloc(32);
  10. const PKorNull = (data: Buffer) =>
  11. data.equals(empty32Buffer) ? null : new web3.PublicKey(data);
  12. interface ICreatePriceFeed {
  13. oracleProgram: Program;
  14. initPrice: number;
  15. confidence?: BN;
  16. expo?: number;
  17. provider: AnchorProvider;
  18. }
  19. export const createPriceFeed = async ({
  20. oracleProgram,
  21. initPrice,
  22. confidence,
  23. expo = -4,
  24. provider,
  25. }: ICreatePriceFeed) => {
  26. const conf = confidence || new BN((initPrice / 10) * 10 ** -expo);
  27. const collateralTokenFeed = new web3.Account();
  28. await oracleProgram.rpc.initialize(
  29. new BN(initPrice * 10 ** -expo),
  30. expo,
  31. conf,
  32. {
  33. accounts: { price: collateralTokenFeed.publicKey },
  34. signers: [collateralTokenFeed],
  35. instructions: [
  36. web3.SystemProgram.createAccount({
  37. fromPubkey: provider.wallet.publicKey,
  38. newAccountPubkey: collateralTokenFeed.publicKey,
  39. space: 3312,
  40. lamports: await provider.connection.getMinimumBalanceForRentExemption(
  41. 3312
  42. ),
  43. programId: oracleProgram.programId,
  44. }),
  45. ],
  46. }
  47. );
  48. return collateralTokenFeed.publicKey;
  49. };
  50. export const setFeedPrice = async (
  51. oracleProgram: Program,
  52. newPrice: number,
  53. priceFeed: web3.PublicKey
  54. ) => {
  55. const info = await oracleProgram.provider.connection.getAccountInfo(
  56. priceFeed
  57. );
  58. const data = parsePriceData(info.data);
  59. await oracleProgram.rpc.setPrice(new BN(newPrice * 10 ** -data.exponent), {
  60. accounts: { price: priceFeed },
  61. });
  62. };
  63. export const getFeedData = async (
  64. oracleProgram: Program,
  65. priceFeed: web3.PublicKey
  66. ) => {
  67. const info = await oracleProgram.provider.connection.getAccountInfo(
  68. priceFeed
  69. );
  70. return parsePriceData(info.data);
  71. };
  72. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L758
  73. const ERR_BUFFER_OUT_OF_BOUNDS = () =>
  74. new Error("Attempt to access memory outside buffer bounds");
  75. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L968
  76. const ERR_INVALID_ARG_TYPE = (name: string, expected: string, actual: any) =>
  77. new Error(
  78. `The "${name}" argument must be of type ${expected}. Received ${actual}`
  79. );
  80. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/errors.js#L1262
  81. const ERR_OUT_OF_RANGE = (str: string, range: string, received: number) =>
  82. new Error(
  83. `The value of "${str} is out of range. It must be ${range}. Received ${received}`
  84. );
  85. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/validators.js#L127-L130
  86. function validateNumber(value: any, name: string) {
  87. if (typeof value !== "number")
  88. throw ERR_INVALID_ARG_TYPE(name, "number", value);
  89. }
  90. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L68-L80
  91. function boundsError(value: number, length: number) {
  92. if (Math.floor(value) !== value) {
  93. validateNumber(value, "offset");
  94. throw ERR_OUT_OF_RANGE("offset", "an integer", value);
  95. }
  96. if (length < 0) throw ERR_BUFFER_OUT_OF_BOUNDS();
  97. throw ERR_OUT_OF_RANGE("offset", `>= 0 and <= ${length}`, value);
  98. }
  99. export function readBigInt64LE(buffer: Buffer, offset = 0): bigint {
  100. validateNumber(offset, "offset");
  101. const first = buffer[offset];
  102. const last = buffer[offset + 7];
  103. if (first === undefined || last === undefined)
  104. boundsError(offset, buffer.length - 8);
  105. const val =
  106. buffer[offset + 4] +
  107. buffer[offset + 5] * 2 ** 8 +
  108. buffer[offset + 6] * 2 ** 16 +
  109. (last << 24); // Overflow
  110. return (
  111. (BigInt(val) << BigInt(32)) +
  112. BigInt(
  113. first +
  114. buffer[++offset] * 2 ** 8 +
  115. buffer[++offset] * 2 ** 16 +
  116. buffer[++offset] * 2 ** 24
  117. )
  118. );
  119. }
  120. // https://github.com/nodejs/node/blob/v14.17.0/lib/internal/buffer.js#L89-L107
  121. export function readBigUInt64LE(buffer: Buffer, offset = 0): bigint {
  122. validateNumber(offset, "offset");
  123. const first = buffer[offset];
  124. const last = buffer[offset + 7];
  125. if (first === undefined || last === undefined)
  126. boundsError(offset, buffer.length - 8);
  127. const lo =
  128. first +
  129. buffer[++offset] * 2 ** 8 +
  130. buffer[++offset] * 2 ** 16 +
  131. buffer[++offset] * 2 ** 24;
  132. const hi =
  133. buffer[++offset] +
  134. buffer[++offset] * 2 ** 8 +
  135. buffer[++offset] * 2 ** 16 +
  136. last * 2 ** 24;
  137. return BigInt(lo) + (BigInt(hi) << BigInt(32)); // tslint:disable-line:no-bitwise
  138. }
  139. export const parsePriceData = (data: Buffer) => {
  140. // Pyth magic number.
  141. const magic = data.readUInt32LE(0);
  142. // Program version.
  143. const version = data.readUInt32LE(4);
  144. // Account type.
  145. const type = data.readUInt32LE(8);
  146. // Price account size.
  147. const size = data.readUInt32LE(12);
  148. // Price or calculation type.
  149. const priceType = data.readUInt32LE(16);
  150. // Price exponent.
  151. const exponent = data.readInt32LE(20);
  152. // Number of component prices.
  153. const numComponentPrices = data.readUInt32LE(24);
  154. // unused
  155. // const unused = accountInfo.data.readUInt32LE(28)
  156. // Currently accumulating price slot.
  157. const currentSlot = readBigUInt64LE(data, 32);
  158. // Valid on-chain slot of aggregate price.
  159. const validSlot = readBigUInt64LE(data, 40);
  160. // Time-weighted average price.
  161. const twapComponent = readBigInt64LE(data, 48);
  162. const twap = Number(twapComponent) * 10 ** exponent;
  163. // Annualized price volatility.
  164. const avolComponent = readBigUInt64LE(data, 56);
  165. const avol = Number(avolComponent) * 10 ** exponent;
  166. // Space for future derived values.
  167. const drv0Component = readBigInt64LE(data, 64);
  168. const drv0 = Number(drv0Component) * 10 ** exponent;
  169. const drv1Component = readBigInt64LE(data, 72);
  170. const drv1 = Number(drv1Component) * 10 ** exponent;
  171. const drv2Component = readBigInt64LE(data, 80);
  172. const drv2 = Number(drv2Component) * 10 ** exponent;
  173. const drv3Component = readBigInt64LE(data, 88);
  174. const drv3 = Number(drv3Component) * 10 ** exponent;
  175. const drv4Component = readBigInt64LE(data, 96);
  176. const drv4 = Number(drv4Component) * 10 ** exponent;
  177. const drv5Component = readBigInt64LE(data, 104);
  178. const drv5 = Number(drv5Component) * 10 ** exponent;
  179. // Product id / reference account.
  180. const productAccountKey = new web3.PublicKey(data.slice(112, 144));
  181. // Next price account in list.
  182. const nextPriceAccountKey = PKorNull(data.slice(144, 176));
  183. // Aggregate price updater.
  184. const aggregatePriceUpdaterAccountKey = new web3.PublicKey(
  185. data.slice(176, 208)
  186. );
  187. const aggregatePriceInfo = parsePriceInfo(data.slice(208, 240), exponent);
  188. // Price components - up to 32.
  189. const priceComponents = [];
  190. let offset = 240;
  191. let shouldContinue = true;
  192. while (offset < data.length && shouldContinue) {
  193. const publisher = PKorNull(data.slice(offset, offset + 32));
  194. offset += 32;
  195. if (publisher) {
  196. const aggregate = parsePriceInfo(
  197. data.slice(offset, offset + 32),
  198. exponent
  199. );
  200. offset += 32;
  201. const latest = parsePriceInfo(data.slice(offset, offset + 32), exponent);
  202. offset += 32;
  203. priceComponents.push({ publisher, aggregate, latest });
  204. } else {
  205. shouldContinue = false;
  206. }
  207. }
  208. return {
  209. magic,
  210. version,
  211. type,
  212. size,
  213. priceType,
  214. exponent,
  215. numComponentPrices,
  216. currentSlot,
  217. validSlot,
  218. twapComponent,
  219. twap,
  220. avolComponent,
  221. avol,
  222. drv0Component,
  223. drv0,
  224. drv1Component,
  225. drv1,
  226. drv2Component,
  227. drv2,
  228. drv3Component,
  229. drv3,
  230. drv4Component,
  231. drv4,
  232. drv5Component,
  233. drv5,
  234. productAccountKey,
  235. nextPriceAccountKey,
  236. aggregatePriceUpdaterAccountKey,
  237. ...aggregatePriceInfo,
  238. priceComponents,
  239. };
  240. };
  241. interface ProductAttributes {
  242. [index: string]: string;
  243. }
  244. export const parseProductData = (data: Buffer) => {
  245. // Pyth magic number.
  246. const magic = data.readUInt32LE(0);
  247. // Program version.
  248. const version = data.readUInt32LE(4);
  249. // Account type.
  250. const type = data.readUInt32LE(8);
  251. // Price account size.
  252. const size = data.readUInt32LE(12);
  253. // First price account in list.
  254. const priceAccountBytes = data.slice(16, 48);
  255. const priceAccountKey = new web3.PublicKey(priceAccountBytes);
  256. const product: ProductAttributes = {};
  257. let idx = 48;
  258. while (idx < data.length) {
  259. const keyLength = data[idx];
  260. idx++;
  261. if (keyLength) {
  262. const key = data.slice(idx, idx + keyLength).toString();
  263. idx += keyLength;
  264. const valueLength = data[idx];
  265. idx++;
  266. const value = data.slice(idx, idx + valueLength).toString();
  267. idx += valueLength;
  268. product[key] = value;
  269. }
  270. }
  271. return { magic, version, type, size, priceAccountKey, product };
  272. };
  273. const parsePriceInfo = (data: Buffer, exponent: number) => {
  274. // Aggregate price.
  275. const priceComponent = data.readBigUInt64LE(0);
  276. const price = Number(priceComponent) * 10 ** exponent;
  277. // Aggregate confidence.
  278. const confidenceComponent = data.readBigUInt64LE(8);
  279. const confidence = Number(confidenceComponent) * 10 ** exponent;
  280. // Aggregate status.
  281. const status = data.readUInt32LE(16);
  282. // Aggregate corporate action.
  283. const corporateAction = data.readUInt32LE(20);
  284. // Aggregate publish slot.
  285. const publishSlot = data.readBigUInt64LE(24);
  286. return {
  287. priceComponent,
  288. price,
  289. confidenceComponent,
  290. confidence,
  291. status,
  292. corporateAction,
  293. publishSlot,
  294. };
  295. };