Pyth.sol 19 KB

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