MockPyth.sol 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // SPDX-License-Identifier: Apache-2.0
  2. pragma solidity ^0.8.0;
  3. import "./AbstractPyth.sol";
  4. import "./PythStructs.sol";
  5. import "./PythErrors.sol";
  6. contract MockPyth is AbstractPyth {
  7. mapping(bytes32 => PythStructs.PriceFeed) priceFeeds;
  8. uint singleUpdateFeeInWei;
  9. uint validTimePeriod;
  10. // Mock structure for TWAP price information
  11. struct MockTwapPriceInfo {
  12. int32 expo;
  13. int64 price;
  14. uint64 conf;
  15. uint64 publishTime;
  16. uint64 prevPublishTime;
  17. uint64 publishSlot;
  18. int128 cumulativePrice;
  19. uint128 cumulativeConf;
  20. uint64 numDownSlots;
  21. }
  22. constructor(uint _validTimePeriod, uint _singleUpdateFeeInWei) {
  23. singleUpdateFeeInWei = _singleUpdateFeeInWei;
  24. validTimePeriod = _validTimePeriod;
  25. }
  26. function queryPriceFeed(
  27. bytes32 id
  28. ) public view override returns (PythStructs.PriceFeed memory priceFeed) {
  29. if (priceFeeds[id].id == 0) revert PythErrors.PriceFeedNotFound();
  30. return priceFeeds[id];
  31. }
  32. function priceFeedExists(bytes32 id) public view override returns (bool) {
  33. return (priceFeeds[id].id != 0);
  34. }
  35. function getValidTimePeriod() public view override returns (uint) {
  36. return validTimePeriod;
  37. }
  38. // Takes an array of encoded price feeds and stores them.
  39. // You can create this data either by calling createPriceFeedUpdateData or
  40. // by using web3.js or ethers abi utilities.
  41. // @note: The updateData expected here is different from the one used in the main contract.
  42. // In particular, the expected format is:
  43. // [
  44. // abi.encode(
  45. // PythStructs.PriceFeed(
  46. // bytes32 id,
  47. // PythStructs.Price price,
  48. // PythStructs.Price emaPrice
  49. // ),
  50. // uint64 prevPublishTime
  51. // )
  52. // ]
  53. function updatePriceFeeds(
  54. bytes[] calldata updateData
  55. ) public payable override {
  56. uint requiredFee = getUpdateFee(updateData);
  57. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  58. for (uint i = 0; i < updateData.length; i++) {
  59. PythStructs.PriceFeed memory priceFeed = abi.decode(
  60. updateData[i],
  61. (PythStructs.PriceFeed)
  62. );
  63. uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime;
  64. if (lastPublishTime < priceFeed.price.publishTime) {
  65. // Price information is more recent than the existing price information.
  66. priceFeeds[priceFeed.id] = priceFeed;
  67. emit PriceFeedUpdate(
  68. priceFeed.id,
  69. uint64(priceFeed.price.publishTime),
  70. priceFeed.price.price,
  71. priceFeed.price.conf
  72. );
  73. }
  74. }
  75. }
  76. function getUpdateFee(
  77. bytes[] calldata updateData
  78. ) public view override returns (uint feeAmount) {
  79. return singleUpdateFeeInWei * updateData.length;
  80. }
  81. function parsePriceFeedUpdatesInternal(
  82. bytes[] calldata updateData,
  83. bytes32[] calldata priceIds,
  84. uint64 minPublishTime,
  85. uint64 maxPublishTime,
  86. bool unique
  87. ) internal returns (PythStructs.PriceFeed[] memory feeds) {
  88. uint requiredFee = getUpdateFee(updateData);
  89. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  90. feeds = new PythStructs.PriceFeed[](priceIds.length);
  91. for (uint i = 0; i < priceIds.length; i++) {
  92. for (uint j = 0; j < updateData.length; j++) {
  93. uint64 prevPublishTime;
  94. (feeds[i], prevPublishTime) = abi.decode(
  95. updateData[j],
  96. (PythStructs.PriceFeed, uint64)
  97. );
  98. uint publishTime = feeds[i].price.publishTime;
  99. if (priceFeeds[feeds[i].id].price.publishTime < publishTime) {
  100. priceFeeds[feeds[i].id] = feeds[i];
  101. emit PriceFeedUpdate(
  102. feeds[i].id,
  103. uint64(publishTime),
  104. feeds[i].price.price,
  105. feeds[i].price.conf
  106. );
  107. }
  108. if (feeds[i].id == priceIds[i]) {
  109. if (
  110. minPublishTime <= publishTime &&
  111. publishTime <= maxPublishTime &&
  112. (!unique || prevPublishTime < minPublishTime)
  113. ) {
  114. break;
  115. } else {
  116. feeds[i].id = 0;
  117. }
  118. }
  119. }
  120. if (feeds[i].id != priceIds[i])
  121. revert PythErrors.PriceFeedNotFoundWithinRange();
  122. }
  123. }
  124. function parsePriceFeedUpdates(
  125. bytes[] calldata updateData,
  126. bytes32[] calldata priceIds,
  127. uint64 minPublishTime,
  128. uint64 maxPublishTime
  129. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  130. return
  131. parsePriceFeedUpdatesInternal(
  132. updateData,
  133. priceIds,
  134. minPublishTime,
  135. maxPublishTime,
  136. false
  137. );
  138. }
  139. function parsePriceFeedUpdatesUnique(
  140. bytes[] calldata updateData,
  141. bytes32[] calldata priceIds,
  142. uint64 minPublishTime,
  143. uint64 maxPublishTime
  144. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  145. return
  146. parsePriceFeedUpdatesInternal(
  147. updateData,
  148. priceIds,
  149. minPublishTime,
  150. maxPublishTime,
  151. true
  152. );
  153. }
  154. function parseTwapPriceFeedUpdates(
  155. bytes[][] calldata updateData,
  156. bytes32[] calldata priceIds
  157. )
  158. external
  159. payable
  160. returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds)
  161. {
  162. // Validate inputs and fee
  163. if (updateData.length != 2) revert PythErrors.InvalidUpdateData();
  164. uint requiredFee = getUpdateFee(updateData[0]);
  165. if (requiredFee != getUpdateFee(updateData[1]))
  166. revert PythErrors.InvalidUpdateData();
  167. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  168. twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
  169. // Process each price ID
  170. for (uint i = 0; i < priceIds.length; i++) {
  171. processTwapPriceFeed(updateData, priceIds[i], i, twapPriceFeeds);
  172. }
  173. }
  174. function processTwapPriceFeed(
  175. bytes[][] calldata updateData,
  176. bytes32 priceId,
  177. uint index,
  178. PythStructs.TwapPriceFeed[] memory twapPriceFeeds
  179. ) private {
  180. // Find start price feed
  181. PythStructs.PriceFeed memory startFeed;
  182. uint64 startPrevPublishTime;
  183. bool foundStart = false;
  184. for (uint j = 0; j < updateData[0].length; j++) {
  185. (startFeed, startPrevPublishTime) = abi.decode(
  186. updateData[0][j],
  187. (PythStructs.PriceFeed, uint64)
  188. );
  189. if (startFeed.id == priceId) {
  190. foundStart = true;
  191. break;
  192. }
  193. }
  194. if (!foundStart) revert PythErrors.PriceFeedNotFoundWithinRange();
  195. // Find end price feed
  196. PythStructs.PriceFeed memory endFeed;
  197. uint64 endPrevPublishTime;
  198. bool foundEnd = false;
  199. for (uint j = 0; j < updateData[1].length; j++) {
  200. (endFeed, endPrevPublishTime) = abi.decode(
  201. updateData[1][j],
  202. (PythStructs.PriceFeed, uint64)
  203. );
  204. if (endFeed.id == priceId) {
  205. foundEnd = true;
  206. break;
  207. }
  208. }
  209. if (!foundEnd) revert PythErrors.PriceFeedNotFoundWithinRange();
  210. // Validate time ordering
  211. if (startFeed.price.publishTime >= endFeed.price.publishTime) {
  212. revert PythErrors.InvalidTwapUpdateDataSet();
  213. }
  214. // Convert to MockTwapPriceInfo
  215. MockTwapPriceInfo memory startInfo = createMockTwapInfo(
  216. startFeed,
  217. startPrevPublishTime
  218. );
  219. MockTwapPriceInfo memory endInfo = createMockTwapInfo(
  220. endFeed,
  221. endPrevPublishTime
  222. );
  223. if (startInfo.publishSlot >= endInfo.publishSlot) {
  224. revert PythErrors.InvalidTwapUpdateDataSet();
  225. }
  226. // Calculate and store TWAP
  227. twapPriceFeeds[index] = calculateTwap(priceId, startInfo, endInfo);
  228. // Emit event in a separate function to reduce stack depth
  229. emitTwapUpdate(
  230. priceId,
  231. startInfo.publishTime,
  232. endInfo.publishTime,
  233. twapPriceFeeds[index]
  234. );
  235. }
  236. function emitTwapUpdate(
  237. bytes32 priceId,
  238. uint64 startTime,
  239. uint64 endTime,
  240. PythStructs.TwapPriceFeed memory twapFeed
  241. ) private {
  242. emit TwapPriceFeedUpdate(
  243. priceId,
  244. startTime,
  245. endTime,
  246. twapFeed.twap.price,
  247. twapFeed.twap.conf,
  248. twapFeed.downSlotRatio
  249. );
  250. }
  251. function createMockTwapInfo(
  252. PythStructs.PriceFeed memory feed,
  253. uint64 prevPublishTime
  254. ) internal pure returns (MockTwapPriceInfo memory mockInfo) {
  255. mockInfo.expo = feed.price.expo;
  256. mockInfo.price = feed.price.price;
  257. mockInfo.conf = feed.price.conf;
  258. mockInfo.publishTime = uint64(feed.price.publishTime);
  259. mockInfo.prevPublishTime = prevPublishTime;
  260. // Use publishTime as publishSlot in mock implementation
  261. mockInfo.publishSlot = uint64(feed.price.publishTime);
  262. // Create mock cumulative values for demonstration
  263. // In a real implementation, these would accumulate over time
  264. mockInfo.cumulativePrice =
  265. int128(feed.price.price) *
  266. int128(uint128(mockInfo.publishSlot));
  267. mockInfo.cumulativeConf =
  268. uint128(feed.price.conf) *
  269. uint128(mockInfo.publishSlot);
  270. // Default to 0 down slots for mock
  271. mockInfo.numDownSlots = 0;
  272. return mockInfo;
  273. }
  274. function calculateTwap(
  275. bytes32 priceId,
  276. MockTwapPriceInfo memory twapPriceInfoStart,
  277. MockTwapPriceInfo memory twapPriceInfoEnd
  278. ) internal pure returns (PythStructs.TwapPriceFeed memory twapPriceFeed) {
  279. twapPriceFeed.id = priceId;
  280. twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
  281. twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
  282. // Calculate differences between start and end points for slots and cumulative values
  283. uint64 slotDiff = twapPriceInfoEnd.publishSlot -
  284. twapPriceInfoStart.publishSlot;
  285. int128 priceDiff = twapPriceInfoEnd.cumulativePrice -
  286. twapPriceInfoStart.cumulativePrice;
  287. uint128 confDiff = twapPriceInfoEnd.cumulativeConf -
  288. twapPriceInfoStart.cumulativeConf;
  289. // Calculate time-weighted average price (TWAP) and confidence
  290. int128 twapPrice = priceDiff / int128(uint128(slotDiff));
  291. uint128 twapConf = confDiff / uint128(slotDiff);
  292. twapPriceFeed.twap.price = int64(twapPrice);
  293. twapPriceFeed.twap.conf = uint64(twapConf);
  294. twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
  295. twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
  296. // Calculate downSlotRatio as a value between 0 and 1,000,000
  297. uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
  298. twapPriceInfoStart.numDownSlots;
  299. uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
  300. // Safely downcast to uint32 (sufficient for value range 0-1,000,000)
  301. twapPriceFeed.downSlotRatio = uint32(downSlotsRatio);
  302. return twapPriceFeed;
  303. }
  304. function createPriceFeedUpdateData(
  305. bytes32 id,
  306. int64 price,
  307. uint64 conf,
  308. int32 expo,
  309. int64 emaPrice,
  310. uint64 emaConf,
  311. uint64 publishTime,
  312. uint64 prevPublishTime
  313. ) public pure returns (bytes memory priceFeedData) {
  314. PythStructs.PriceFeed memory priceFeed;
  315. priceFeed.id = id;
  316. priceFeed.price.price = price;
  317. priceFeed.price.conf = conf;
  318. priceFeed.price.expo = expo;
  319. priceFeed.price.publishTime = publishTime;
  320. priceFeed.emaPrice.price = emaPrice;
  321. priceFeed.emaPrice.conf = emaConf;
  322. priceFeed.emaPrice.expo = expo;
  323. priceFeed.emaPrice.publishTime = publishTime;
  324. priceFeedData = abi.encode(priceFeed, prevPublishTime);
  325. }
  326. }