Pyth.sol 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "../libraries/external/UnsafeBytesLib.sol";
  5. import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
  6. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  7. import "./PythGetters.sol";
  8. import "./PythSetters.sol";
  9. import "./PythInternalStructs.sol";
  10. abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
  11. function _initialize(
  12. address wormhole,
  13. uint16 pyth2WormholeChainId,
  14. bytes32 pyth2WormholeEmitter
  15. ) internal {
  16. setWormhole(wormhole);
  17. setPyth2WormholeChainId(pyth2WormholeChainId);
  18. setPyth2WormholeEmitter(pyth2WormholeEmitter);
  19. }
  20. function updatePriceBatchFromVm(bytes calldata encodedVm) private {
  21. parseAndProcessBatchPriceAttestation(
  22. parseAndVerifyBatchAttestationVM(encodedVm)
  23. );
  24. }
  25. function updatePriceFeeds(
  26. bytes[] calldata updateData
  27. ) public payable override {
  28. uint requiredFee = getUpdateFee(updateData);
  29. require(msg.value >= requiredFee, "insufficient paid fee amount");
  30. for (uint i = 0; i < updateData.length; ) {
  31. updatePriceBatchFromVm(updateData[i]);
  32. unchecked {
  33. i++;
  34. }
  35. }
  36. }
  37. /// This method is deprecated, please use the `getUpdateFee(bytes[])` instead.
  38. function getUpdateFee(
  39. uint updateDataSize
  40. ) public view returns (uint feeAmount) {
  41. return singleUpdateFeeInWei() * updateDataSize;
  42. }
  43. function getUpdateFee(
  44. bytes[] calldata updateData
  45. ) public view override returns (uint feeAmount) {
  46. return singleUpdateFeeInWei() * updateData.length;
  47. }
  48. function verifyPythVM(
  49. IWormhole.VM memory vm
  50. ) private view returns (bool valid) {
  51. return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
  52. }
  53. function parseAndProcessBatchPriceAttestation(
  54. IWormhole.VM memory vm
  55. ) internal {
  56. // Most of the math operations below are simple additions.
  57. // In the places that there is more complex operation there is
  58. // a comment explaining why it is safe. Also, byteslib
  59. // operations have proper require.
  60. unchecked {
  61. bytes memory encoded = vm.payload;
  62. (
  63. uint index,
  64. uint nAttestations,
  65. uint attestationSize
  66. ) = parseBatchAttestationHeader(encoded);
  67. // Deserialize each attestation
  68. for (uint j = 0; j < nAttestations; j++) {
  69. (
  70. PythInternalStructs.PriceInfo memory info,
  71. bytes32 priceId
  72. ) = parseSingleAttestationFromBatch(
  73. encoded,
  74. index,
  75. attestationSize
  76. );
  77. // Respect specified attestation size for forward-compat
  78. index += attestationSize;
  79. // Store the attestation
  80. uint64 latestPublishTime = latestPriceInfoPublishTime(priceId);
  81. if (info.publishTime > latestPublishTime) {
  82. setLatestPriceInfo(priceId, info);
  83. emit PriceFeedUpdate(
  84. priceId,
  85. info.publishTime,
  86. info.price,
  87. info.conf
  88. );
  89. }
  90. }
  91. emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence);
  92. }
  93. }
  94. function parseSingleAttestationFromBatch(
  95. bytes memory encoded,
  96. uint index,
  97. uint attestationSize
  98. )
  99. internal
  100. pure
  101. returns (PythInternalStructs.PriceInfo memory info, bytes32 priceId)
  102. {
  103. unchecked {
  104. // NOTE: We don't advance the global index immediately.
  105. // attestationIndex is an attestation-local offset used
  106. // for readability and easier debugging.
  107. uint attestationIndex = 0;
  108. // Unused bytes32 product id
  109. attestationIndex += 32;
  110. priceId = UnsafeBytesLib.toBytes32(
  111. encoded,
  112. index + attestationIndex
  113. );
  114. attestationIndex += 32;
  115. info.price = int64(
  116. UnsafeBytesLib.toUint64(encoded, index + attestationIndex)
  117. );
  118. attestationIndex += 8;
  119. info.conf = UnsafeBytesLib.toUint64(
  120. encoded,
  121. index + attestationIndex
  122. );
  123. attestationIndex += 8;
  124. info.expo = int32(
  125. UnsafeBytesLib.toUint32(encoded, index + attestationIndex)
  126. );
  127. attestationIndex += 4;
  128. info.emaPrice = int64(
  129. UnsafeBytesLib.toUint64(encoded, index + attestationIndex)
  130. );
  131. attestationIndex += 8;
  132. info.emaConf = UnsafeBytesLib.toUint64(
  133. encoded,
  134. index + attestationIndex
  135. );
  136. attestationIndex += 8;
  137. {
  138. // Status is an enum (encoded as uint8) with the following values:
  139. // 0 = UNKNOWN: The price feed is not currently updating for an unknown reason.
  140. // 1 = TRADING: The price feed is updating as expected.
  141. // 2 = HALTED: The price feed is not currently updating because trading in the product has been halted.
  142. // 3 = AUCTION: The price feed is not currently updating because an auction is setting the price.
  143. uint8 status = UnsafeBytesLib.toUint8(
  144. encoded,
  145. index + attestationIndex
  146. );
  147. attestationIndex += 1;
  148. // Unused uint32 numPublishers
  149. attestationIndex += 4;
  150. // Unused uint32 numPublishers
  151. attestationIndex += 4;
  152. // Unused uint64 attestationTime
  153. attestationIndex += 8;
  154. info.publishTime = UnsafeBytesLib.toUint64(
  155. encoded,
  156. index + attestationIndex
  157. );
  158. attestationIndex += 8;
  159. if (status == 1) {
  160. // status == TRADING
  161. attestationIndex += 24;
  162. } else {
  163. // If status is not trading then the latest available price is
  164. // the previous price info that are passed here.
  165. // Previous publish time
  166. info.publishTime = UnsafeBytesLib.toUint64(
  167. encoded,
  168. index + attestationIndex
  169. );
  170. attestationIndex += 8;
  171. // Previous price
  172. info.price = int64(
  173. UnsafeBytesLib.toUint64(
  174. encoded,
  175. index + attestationIndex
  176. )
  177. );
  178. attestationIndex += 8;
  179. // Previous confidence
  180. info.conf = UnsafeBytesLib.toUint64(
  181. encoded,
  182. index + attestationIndex
  183. );
  184. attestationIndex += 8;
  185. }
  186. }
  187. require(
  188. attestationIndex <= attestationSize,
  189. "INTERNAL: Consumed more than `attestationSize` bytes"
  190. );
  191. }
  192. }
  193. // This is an overwrite of the same method in AbstractPyth.sol
  194. // to be more gas efficient.
  195. function updatePriceFeedsIfNecessary(
  196. bytes[] calldata updateData,
  197. bytes32[] calldata priceIds,
  198. uint64[] calldata publishTimes
  199. ) external payable override {
  200. require(
  201. priceIds.length == publishTimes.length,
  202. "priceIds and publishTimes arrays should have same length"
  203. );
  204. for (uint i = 0; i < priceIds.length; ) {
  205. // If the price does not exist, then the publish time is zero and
  206. // this condition will work fine.
  207. if (latestPriceInfoPublishTime(priceIds[i]) < publishTimes[i]) {
  208. updatePriceFeeds(updateData);
  209. return;
  210. }
  211. unchecked {
  212. i++;
  213. }
  214. }
  215. revert(
  216. "no prices in the submitted batch have fresh prices, so this update will have no effect"
  217. );
  218. }
  219. // This is an overwrite of the same method in AbstractPyth.sol
  220. // to be more gas efficient. It cannot move to PythGetters as it
  221. // is overwriting the interface. Even indirect calling of a similar
  222. // method from PythGetter has some gas overhead.
  223. function getPriceUnsafe(
  224. bytes32 id
  225. ) public view override returns (PythStructs.Price memory price) {
  226. PythInternalStructs.PriceInfo storage info = _state.latestPriceInfo[id];
  227. price.publishTime = info.publishTime;
  228. price.expo = info.expo;
  229. price.price = info.price;
  230. price.conf = info.conf;
  231. require(
  232. price.publishTime != 0,
  233. "price feed for the given id is not pushed or does not exist"
  234. );
  235. }
  236. // This is an overwrite of the same method in AbstractPyth.sol
  237. // to be more gas efficient. It cannot move to PythGetters as it
  238. // is overwriting the interface. Even indirect calling of a similar
  239. // method from PythGetter has some gas overhead.
  240. function getEmaPriceUnsafe(
  241. bytes32 id
  242. ) public view override returns (PythStructs.Price memory price) {
  243. PythInternalStructs.PriceInfo storage info = _state.latestPriceInfo[id];
  244. price.publishTime = info.publishTime;
  245. price.expo = info.expo;
  246. price.price = info.emaPrice;
  247. price.conf = info.emaConf;
  248. require(
  249. price.publishTime != 0,
  250. "price feed for the given id is not pushed or does not exist"
  251. );
  252. }
  253. function parseBatchAttestationHeader(
  254. bytes memory encoded
  255. )
  256. internal
  257. pure
  258. returns (uint index, uint nAttestations, uint attestationSize)
  259. {
  260. unchecked {
  261. index = 0;
  262. // Check header
  263. {
  264. uint32 magic = UnsafeBytesLib.toUint32(encoded, index);
  265. index += 4;
  266. require(magic == 0x50325748, "invalid magic value");
  267. uint16 versionMajor = UnsafeBytesLib.toUint16(encoded, index);
  268. index += 2;
  269. require(versionMajor == 3, "invalid version major, expected 3");
  270. uint16 versionMinor = UnsafeBytesLib.toUint16(encoded, index);
  271. index += 2;
  272. require(
  273. versionMinor >= 0,
  274. "invalid version minor, expected 0 or more"
  275. );
  276. uint16 hdrSize = UnsafeBytesLib.toUint16(encoded, index);
  277. index += 2;
  278. // NOTE(2022-04-19): Currently, only payloadId comes after
  279. // hdrSize. Future extra header fields must be read using a
  280. // separate offset to respect hdrSize, i.e.:
  281. //
  282. // uint hdrIndex = 0;
  283. // bpa.header.payloadId = UnsafeBytesLib.toUint8(encoded, index + hdrIndex);
  284. // hdrIndex += 1;
  285. //
  286. // bpa.header.someNewField = UnsafeBytesLib.toUint32(encoded, index + hdrIndex);
  287. // hdrIndex += 4;
  288. //
  289. // // Skip remaining unknown header bytes
  290. // index += bpa.header.hdrSize;
  291. uint8 payloadId = UnsafeBytesLib.toUint8(encoded, index);
  292. // Skip remaining unknown header bytes
  293. index += hdrSize;
  294. // Payload ID of 2 required for batch headerBa
  295. require(
  296. payloadId == 2,
  297. "invalid payload ID, expected 2 for BatchPriceAttestation"
  298. );
  299. }
  300. // Parse the number of attestations
  301. nAttestations = UnsafeBytesLib.toUint16(encoded, index);
  302. index += 2;
  303. // Parse the attestation size
  304. attestationSize = UnsafeBytesLib.toUint16(encoded, index);
  305. index += 2;
  306. // Given the message is valid the arithmetic below should not overflow, and
  307. // even if it overflows then the require would fail.
  308. require(
  309. encoded.length == (index + (attestationSize * nAttestations)),
  310. "invalid BatchPriceAttestation size"
  311. );
  312. }
  313. }
  314. function parseAndVerifyBatchAttestationVM(
  315. bytes calldata encodedVm
  316. ) internal view returns (IWormhole.VM memory vm) {
  317. {
  318. bool valid;
  319. string memory reason;
  320. (vm, valid, reason) = wormhole().parseAndVerifyVM(encodedVm);
  321. require(valid, reason);
  322. }
  323. require(verifyPythVM(vm), "invalid data source chain/emitter ID");
  324. }
  325. function parsePriceFeedUpdates(
  326. bytes[] calldata updateData,
  327. bytes32[] calldata priceIds,
  328. uint64 minPublishTime,
  329. uint64 maxPublishTime
  330. )
  331. external
  332. payable
  333. override
  334. returns (PythStructs.PriceFeed[] memory priceFeeds)
  335. {
  336. unchecked {
  337. {
  338. uint requiredFee = getUpdateFee(updateData);
  339. require(
  340. msg.value >= requiredFee,
  341. "insufficient paid fee amount"
  342. );
  343. }
  344. priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
  345. for (uint i = 0; i < updateData.length; i++) {
  346. bytes memory encoded;
  347. {
  348. IWormhole.VM memory vm = parseAndVerifyBatchAttestationVM(
  349. updateData[i]
  350. );
  351. encoded = vm.payload;
  352. }
  353. (
  354. uint index,
  355. uint nAttestations,
  356. uint attestationSize
  357. ) = parseBatchAttestationHeader(encoded);
  358. // Deserialize each attestation
  359. for (uint j = 0; j < nAttestations; j++) {
  360. // NOTE: We don't advance the global index immediately.
  361. // attestationIndex is an attestation-local offset used
  362. // for readability and easier debugging.
  363. uint attestationIndex = 0;
  364. // Unused bytes32 product id
  365. attestationIndex += 32;
  366. bytes32 priceId = UnsafeBytesLib.toBytes32(
  367. encoded,
  368. index + attestationIndex
  369. );
  370. // Check whether the caller requested for this data.
  371. uint k = 0;
  372. for (; k < priceIds.length; k++) {
  373. if (priceIds[k] == priceId) {
  374. break;
  375. }
  376. }
  377. // If priceFeed[k].id != 0 then it means that there was a valid
  378. // update for priceIds[k] and we don't need to process this one.
  379. if (k == priceIds.length || priceFeeds[k].id != 0) {
  380. index += attestationSize;
  381. continue;
  382. }
  383. (
  384. PythInternalStructs.PriceInfo memory info,
  385. ) = parseSingleAttestationFromBatch(
  386. encoded,
  387. index,
  388. attestationSize
  389. );
  390. require(
  391. info.publishTime != 0,
  392. "price feed for the given id is not pushed or does not exist"
  393. );
  394. priceFeeds[k].id = priceId;
  395. priceFeeds[k].price.price = info.price;
  396. priceFeeds[k].price.conf = info.conf;
  397. priceFeeds[k].price.expo = info.expo;
  398. priceFeeds[k].price.publishTime = uint(info.publishTime);
  399. priceFeeds[k].emaPrice.price = info.emaPrice;
  400. priceFeeds[k].emaPrice.conf = info.emaConf;
  401. priceFeeds[k].emaPrice.expo = info.expo;
  402. priceFeeds[k].emaPrice.publishTime = uint(info.publishTime);
  403. // Check the publish time of the price is within the given range
  404. // if it is not, then set the id to 0 to indicate that this price id
  405. // still does not have a valid price feed. This will allow other updates
  406. // for this price id to be processed.
  407. if (
  408. priceFeeds[k].price.publishTime < minPublishTime ||
  409. priceFeeds[k].price.publishTime > maxPublishTime
  410. ) {
  411. priceFeeds[k].id = 0;
  412. }
  413. index += attestationSize;
  414. }
  415. }
  416. for (uint k = 0; k < priceIds.length; k++) {
  417. require(
  418. priceFeeds[k].id != 0,
  419. "1 or more price feeds are not found in the updateData or they are out of the given time range"
  420. );
  421. }
  422. }
  423. }
  424. function queryPriceFeed(
  425. bytes32 id
  426. ) public view override returns (PythStructs.PriceFeed memory priceFeed) {
  427. // Look up the latest price info for the given ID
  428. PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
  429. require(
  430. info.publishTime != 0,
  431. "price feed for the given id is not pushed or does not exist"
  432. );
  433. priceFeed.id = id;
  434. priceFeed.price.price = info.price;
  435. priceFeed.price.conf = info.conf;
  436. priceFeed.price.expo = info.expo;
  437. priceFeed.price.publishTime = uint(info.publishTime);
  438. priceFeed.emaPrice.price = info.emaPrice;
  439. priceFeed.emaPrice.conf = info.emaConf;
  440. priceFeed.emaPrice.expo = info.expo;
  441. priceFeed.emaPrice.publishTime = uint(info.publishTime);
  442. }
  443. function priceFeedExists(bytes32 id) public view override returns (bool) {
  444. return (latestPriceInfoPublishTime(id) != 0);
  445. }
  446. function getValidTimePeriod() public view override returns (uint) {
  447. return validTimePeriodSeconds();
  448. }
  449. function version() public pure returns (string memory) {
  450. return "1.1.0";
  451. }
  452. }