oracleUtils.ts 9.4 KB

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