MockPyth.sol 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. import "./PythUtils.sol";
  7. contract MockPyth is AbstractPyth {
  8. mapping(bytes32 => PythStructs.PriceFeed) priceFeeds;
  9. uint singleUpdateFeeInWei;
  10. uint validTimePeriod;
  11. constructor(uint _validTimePeriod, uint _singleUpdateFeeInWei) {
  12. singleUpdateFeeInWei = _singleUpdateFeeInWei;
  13. validTimePeriod = _validTimePeriod;
  14. }
  15. function queryPriceFeed(
  16. bytes32 id
  17. ) public view override returns (PythStructs.PriceFeed memory priceFeed) {
  18. if (priceFeeds[id].id == 0) revert PythErrors.PriceFeedNotFound();
  19. return priceFeeds[id];
  20. }
  21. function priceFeedExists(bytes32 id) public view override returns (bool) {
  22. return (priceFeeds[id].id != 0);
  23. }
  24. function getValidTimePeriod() public view override returns (uint) {
  25. return validTimePeriod;
  26. }
  27. // Takes an array of encoded price feeds and stores them.
  28. // You can create this data either by calling createPriceFeedUpdateData or
  29. // by using web3.js or ethers abi utilities.
  30. // @note: The updateData expected here is different from the one used in the main contract.
  31. // In particular, the expected format is:
  32. // [
  33. // abi.encode(
  34. // PythStructs.PriceFeed(
  35. // bytes32 id,
  36. // PythStructs.Price price,
  37. // PythStructs.Price emaPrice
  38. // ),
  39. // uint64 prevPublishTime
  40. // )
  41. // ]
  42. function updatePriceFeeds(
  43. bytes[] calldata updateData
  44. ) public payable override {
  45. uint requiredFee = getUpdateFee(updateData);
  46. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  47. for (uint i = 0; i < updateData.length; i++) {
  48. PythStructs.PriceFeed memory priceFeed = abi.decode(
  49. updateData[i],
  50. (PythStructs.PriceFeed)
  51. );
  52. uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime;
  53. if (lastPublishTime < priceFeed.price.publishTime) {
  54. // Price information is more recent than the existing price information.
  55. priceFeeds[priceFeed.id] = priceFeed;
  56. emit PriceFeedUpdate(
  57. priceFeed.id,
  58. uint64(priceFeed.price.publishTime),
  59. priceFeed.price.price,
  60. priceFeed.price.conf
  61. );
  62. }
  63. }
  64. }
  65. function getUpdateFee(
  66. bytes[] calldata updateData
  67. ) public view override returns (uint feeAmount) {
  68. return singleUpdateFeeInWei * updateData.length;
  69. }
  70. function getTwapUpdateFee() public view override returns (uint feeAmount) {
  71. return singleUpdateFeeInWei;
  72. }
  73. function parsePriceFeedUpdatesInternal(
  74. bytes[] calldata updateData,
  75. bytes32[] calldata priceIds,
  76. uint64 minPublishTime,
  77. uint64 maxPublishTime,
  78. bool unique
  79. )
  80. internal
  81. returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots)
  82. {
  83. uint requiredFee = getUpdateFee(updateData);
  84. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  85. feeds = new PythStructs.PriceFeed[](priceIds.length);
  86. slots = new uint64[](priceIds.length);
  87. for (uint i = 0; i < priceIds.length; i++) {
  88. for (uint j = 0; j < updateData.length; j++) {
  89. uint64 prevPublishTime;
  90. (feeds[i], prevPublishTime) = abi.decode(
  91. updateData[j],
  92. (PythStructs.PriceFeed, uint64)
  93. );
  94. uint publishTime = feeds[i].price.publishTime;
  95. slots[i] = uint64(publishTime); // use PublishTime as mock slot
  96. if (priceFeeds[feeds[i].id].price.publishTime < publishTime) {
  97. priceFeeds[feeds[i].id] = feeds[i];
  98. emit PriceFeedUpdate(
  99. feeds[i].id,
  100. uint64(publishTime),
  101. feeds[i].price.price,
  102. feeds[i].price.conf
  103. );
  104. }
  105. if (feeds[i].id == priceIds[i]) {
  106. if (
  107. minPublishTime <= publishTime &&
  108. publishTime <= maxPublishTime &&
  109. (!unique || prevPublishTime < minPublishTime)
  110. ) {
  111. break;
  112. } else {
  113. feeds[i].id = 0;
  114. }
  115. }
  116. }
  117. if (feeds[i].id != priceIds[i])
  118. revert PythErrors.PriceFeedNotFoundWithinRange();
  119. }
  120. }
  121. function parsePriceFeedUpdates(
  122. bytes[] calldata updateData,
  123. bytes32[] calldata priceIds,
  124. uint64 minPublishTime,
  125. uint64 maxPublishTime
  126. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  127. (feeds, ) = parsePriceFeedUpdatesInternal(
  128. updateData,
  129. priceIds,
  130. minPublishTime,
  131. maxPublishTime,
  132. false
  133. );
  134. }
  135. function parsePriceFeedUpdatesUnique(
  136. bytes[] calldata updateData,
  137. bytes32[] calldata priceIds,
  138. uint64 minPublishTime,
  139. uint64 maxPublishTime
  140. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  141. (feeds, ) = parsePriceFeedUpdatesInternal(
  142. updateData,
  143. priceIds,
  144. minPublishTime,
  145. maxPublishTime,
  146. true
  147. );
  148. }
  149. function parsePriceFeedUpdatesWithSlots(
  150. bytes[] calldata updateData,
  151. bytes32[] calldata priceIds,
  152. uint64 minPublishTime,
  153. uint64 maxPublishTime
  154. )
  155. external
  156. payable
  157. override
  158. returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots)
  159. {
  160. return
  161. parsePriceFeedUpdatesInternal(
  162. updateData,
  163. priceIds,
  164. minPublishTime,
  165. maxPublishTime,
  166. false
  167. );
  168. }
  169. function parseTwapPriceFeedUpdates(
  170. bytes[] calldata updateData,
  171. bytes32[] calldata priceIds
  172. )
  173. external
  174. payable
  175. override
  176. returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds)
  177. {
  178. uint requiredFee = getUpdateFee(updateData);
  179. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  180. twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
  181. // Process each price ID
  182. for (uint i = 0; i < priceIds.length; i++) {
  183. processTwapPriceFeed(updateData, priceIds[i], i, twapPriceFeeds);
  184. }
  185. return twapPriceFeeds;
  186. }
  187. // You can create this data either by calling createTwapPriceFeedUpdateData.
  188. // @note: The updateData expected here is different from the one used in the main contract.
  189. // In particular, the expected format is:
  190. // [
  191. // abi.encode(
  192. // bytes32 id,
  193. // PythStructs.TwapPriceInfo startInfo,
  194. // PythStructs.TwapPriceInfo endInfo
  195. // )
  196. // ]
  197. function processTwapPriceFeed(
  198. bytes[] calldata updateData,
  199. bytes32 priceId,
  200. uint index,
  201. PythStructs.TwapPriceFeed[] memory twapPriceFeeds
  202. ) private {
  203. // Decode TWAP feed directly
  204. PythStructs.TwapPriceFeed memory twapFeed = abi.decode(
  205. updateData[0],
  206. (PythStructs.TwapPriceFeed)
  207. );
  208. // Validate ID matches
  209. if (twapFeed.id != priceId)
  210. revert PythErrors.InvalidTwapUpdateDataSet();
  211. // Store the TWAP feed
  212. twapPriceFeeds[index] = twapFeed;
  213. // Emit event
  214. emit TwapPriceFeedUpdate(
  215. priceId,
  216. twapFeed.startTime,
  217. twapFeed.endTime,
  218. twapFeed.twap.price,
  219. twapFeed.twap.conf,
  220. twapFeed.downSlotsRatio
  221. );
  222. }
  223. /**
  224. * @notice Creates TWAP price feed update data with simplified parameters for testing
  225. * @param id The price feed ID
  226. * @param startTime Start time of the TWAP
  227. * @param endTime End time of the TWAP
  228. * @param price The price value
  229. * @param conf The confidence interval
  230. * @param expo Price exponent
  231. * @param downSlotsRatio Down slots ratio
  232. * @return twapData Encoded TWAP price feed data ready for parseTwapPriceFeedUpdates
  233. */
  234. function createTwapPriceFeedUpdateData(
  235. bytes32 id,
  236. uint64 startTime,
  237. uint64 endTime,
  238. int64 price,
  239. uint64 conf,
  240. int32 expo,
  241. uint32 downSlotsRatio
  242. ) public pure returns (bytes memory twapData) {
  243. PythStructs.Price memory twapPrice = PythStructs.Price({
  244. price: price,
  245. conf: conf,
  246. expo: expo,
  247. publishTime: endTime
  248. });
  249. PythStructs.TwapPriceFeed memory twapFeed = PythStructs.TwapPriceFeed({
  250. id: id,
  251. startTime: startTime,
  252. endTime: endTime,
  253. twap: twapPrice,
  254. downSlotsRatio: downSlotsRatio
  255. });
  256. twapData = abi.encode(twapFeed);
  257. }
  258. function createPriceFeedUpdateData(
  259. bytes32 id,
  260. int64 price,
  261. uint64 conf,
  262. int32 expo,
  263. int64 emaPrice,
  264. uint64 emaConf,
  265. uint64 publishTime,
  266. uint64 prevPublishTime
  267. ) public pure returns (bytes memory priceFeedData) {
  268. PythStructs.PriceFeed memory priceFeed;
  269. priceFeed.id = id;
  270. priceFeed.price.price = price;
  271. priceFeed.price.conf = conf;
  272. priceFeed.price.expo = expo;
  273. priceFeed.price.publishTime = publishTime;
  274. priceFeed.emaPrice.price = emaPrice;
  275. priceFeed.emaPrice.conf = emaConf;
  276. priceFeed.emaPrice.expo = expo;
  277. priceFeed.emaPrice.publishTime = publishTime;
  278. priceFeedData = abi.encode(priceFeed, prevPublishTime);
  279. }
  280. }