MockPyth.sol 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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(
  71. bytes[] calldata updateData
  72. ) public view override returns (uint feeAmount) {
  73. return singleUpdateFeeInWei * updateData.length;
  74. }
  75. function parsePriceFeedUpdatesInternal(
  76. bytes[] calldata updateData,
  77. bytes32[] calldata priceIds,
  78. uint64 minPublishTime,
  79. uint64 maxPublishTime,
  80. bool unique
  81. )
  82. internal
  83. returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots)
  84. {
  85. uint requiredFee = getUpdateFee(updateData);
  86. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  87. feeds = new PythStructs.PriceFeed[](priceIds.length);
  88. slots = new uint64[](priceIds.length);
  89. for (uint i = 0; i < priceIds.length; i++) {
  90. for (uint j = 0; j < updateData.length; j++) {
  91. uint64 prevPublishTime;
  92. (feeds[i], prevPublishTime) = abi.decode(
  93. updateData[j],
  94. (PythStructs.PriceFeed, uint64)
  95. );
  96. uint publishTime = feeds[i].price.publishTime;
  97. slots[i] = uint64(publishTime); // use PublishTime as mock slot
  98. if (priceFeeds[feeds[i].id].price.publishTime < publishTime) {
  99. priceFeeds[feeds[i].id] = feeds[i];
  100. emit PriceFeedUpdate(
  101. feeds[i].id,
  102. uint64(publishTime),
  103. feeds[i].price.price,
  104. feeds[i].price.conf
  105. );
  106. }
  107. if (feeds[i].id == priceIds[i]) {
  108. if (
  109. minPublishTime <= publishTime &&
  110. publishTime <= maxPublishTime &&
  111. (!unique || prevPublishTime < minPublishTime)
  112. ) {
  113. break;
  114. } else {
  115. feeds[i].id = 0;
  116. }
  117. }
  118. }
  119. if (feeds[i].id != priceIds[i])
  120. revert PythErrors.PriceFeedNotFoundWithinRange();
  121. }
  122. }
  123. function parsePriceFeedUpdates(
  124. bytes[] calldata updateData,
  125. bytes32[] calldata priceIds,
  126. uint64 minPublishTime,
  127. uint64 maxPublishTime
  128. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  129. (feeds, ) = parsePriceFeedUpdatesInternal(
  130. updateData,
  131. priceIds,
  132. minPublishTime,
  133. maxPublishTime,
  134. false
  135. );
  136. }
  137. function parsePriceFeedUpdatesUnique(
  138. bytes[] calldata updateData,
  139. bytes32[] calldata priceIds,
  140. uint64 minPublishTime,
  141. uint64 maxPublishTime
  142. ) external payable override returns (PythStructs.PriceFeed[] memory feeds) {
  143. (feeds, ) = parsePriceFeedUpdatesInternal(
  144. updateData,
  145. priceIds,
  146. minPublishTime,
  147. maxPublishTime,
  148. true
  149. );
  150. }
  151. function parsePriceFeedUpdatesWithSlotsStrict(
  152. bytes[] calldata updateData,
  153. bytes32[] calldata priceIds,
  154. uint64 minPublishTime,
  155. uint64 maxPublishTime
  156. )
  157. external
  158. payable
  159. override
  160. returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots)
  161. {
  162. return
  163. parsePriceFeedUpdatesInternal(
  164. updateData,
  165. priceIds,
  166. minPublishTime,
  167. maxPublishTime,
  168. false
  169. );
  170. }
  171. function parseTwapPriceFeedUpdates(
  172. bytes[] calldata updateData,
  173. bytes32[] calldata priceIds
  174. )
  175. external
  176. payable
  177. override
  178. returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds)
  179. {
  180. uint requiredFee = getUpdateFee(updateData);
  181. if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
  182. twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
  183. // Process each price ID
  184. for (uint i = 0; i < priceIds.length; i++) {
  185. processTwapPriceFeed(updateData, priceIds[i], i, twapPriceFeeds);
  186. }
  187. return twapPriceFeeds;
  188. }
  189. // You can create this data either by calling createTwapPriceFeedUpdateData.
  190. // @note: The updateData expected here is different from the one used in the main contract.
  191. // In particular, the expected format is:
  192. // [
  193. // abi.encode(
  194. // bytes32 id,
  195. // PythStructs.TwapPriceInfo startInfo,
  196. // PythStructs.TwapPriceInfo endInfo
  197. // )
  198. // ]
  199. function processTwapPriceFeed(
  200. bytes[] calldata updateData,
  201. bytes32 priceId,
  202. uint index,
  203. PythStructs.TwapPriceFeed[] memory twapPriceFeeds
  204. ) private {
  205. // Decode TWAP feed directly
  206. PythStructs.TwapPriceFeed memory twapFeed = abi.decode(
  207. updateData[0],
  208. (PythStructs.TwapPriceFeed)
  209. );
  210. // Validate ID matches
  211. if (twapFeed.id != priceId)
  212. revert PythErrors.InvalidTwapUpdateDataSet();
  213. // Store the TWAP feed
  214. twapPriceFeeds[index] = twapFeed;
  215. // Emit event
  216. emit TwapPriceFeedUpdate(
  217. priceId,
  218. twapFeed.startTime,
  219. twapFeed.endTime,
  220. twapFeed.twap.price,
  221. twapFeed.twap.conf,
  222. twapFeed.downSlotsRatio
  223. );
  224. }
  225. /**
  226. * @notice Creates TWAP price feed update data with simplified parameters for testing
  227. * @param id The price feed ID
  228. * @param startTime Start time of the TWAP
  229. * @param endTime End time of the TWAP
  230. * @param price The price value
  231. * @param conf The confidence interval
  232. * @param expo Price exponent
  233. * @param downSlotsRatio Down slots ratio
  234. * @return twapData Encoded TWAP price feed data ready for parseTwapPriceFeedUpdates
  235. */
  236. function createTwapPriceFeedUpdateData(
  237. bytes32 id,
  238. uint64 startTime,
  239. uint64 endTime,
  240. int64 price,
  241. uint64 conf,
  242. int32 expo,
  243. uint32 downSlotsRatio
  244. ) public pure returns (bytes memory twapData) {
  245. PythStructs.Price memory twapPrice = PythStructs.Price({
  246. price: price,
  247. conf: conf,
  248. expo: expo,
  249. publishTime: endTime
  250. });
  251. PythStructs.TwapPriceFeed memory twapFeed = PythStructs.TwapPriceFeed({
  252. id: id,
  253. startTime: startTime,
  254. endTime: endTime,
  255. twap: twapPrice,
  256. downSlotsRatio: downSlotsRatio
  257. });
  258. twapData = abi.encode(twapFeed);
  259. }
  260. function createPriceFeedUpdateData(
  261. bytes32 id,
  262. int64 price,
  263. uint64 conf,
  264. int32 expo,
  265. int64 emaPrice,
  266. uint64 emaConf,
  267. uint64 publishTime,
  268. uint64 prevPublishTime
  269. ) public pure returns (bytes memory priceFeedData) {
  270. PythStructs.PriceFeed memory priceFeed;
  271. priceFeed.id = id;
  272. priceFeed.price.price = price;
  273. priceFeed.price.conf = conf;
  274. priceFeed.price.expo = expo;
  275. priceFeed.price.publishTime = publishTime;
  276. priceFeed.emaPrice.price = emaPrice;
  277. priceFeed.emaPrice.conf = emaConf;
  278. priceFeed.emaPrice.expo = expo;
  279. priceFeed.emaPrice.publishTime = publishTime;
  280. priceFeedData = abi.encode(priceFeed, prevPublishTime);
  281. }
  282. }