PythTestUtils.t.sol 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. // SPDX-License-Identifier: Apache 2
  2. pragma solidity ^0.8.0;
  3. import "../../contracts/pyth/PythUpgradable.sol";
  4. import "../../contracts/pyth/PythInternalStructs.sol";
  5. import "../../contracts/pyth/PythAccumulator.sol";
  6. import "../../contracts/libraries/MerkleTree.sol";
  7. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  8. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  9. import "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol";
  10. import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
  11. import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
  12. import "forge-std/Test.sol";
  13. import "./WormholeTestUtils.t.sol";
  14. import "./RandTestUtils.t.sol";
  15. abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
  16. uint16 constant SOURCE_EMITTER_CHAIN_ID = 0x1;
  17. bytes32 constant SOURCE_EMITTER_ADDRESS =
  18. 0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
  19. uint16 constant GOVERNANCE_EMITTER_CHAIN_ID = 0x1;
  20. bytes32 constant GOVERNANCE_EMITTER_ADDRESS =
  21. 0x0000000000000000000000000000000000000000000000000000000000000011;
  22. uint constant SINGLE_UPDATE_FEE_IN_WEI = 1;
  23. function setUpPyth(address wormhole) public returns (address) {
  24. PythUpgradable implementation = new PythUpgradable();
  25. ERC1967Proxy proxy = new ERC1967Proxy(
  26. address(implementation),
  27. new bytes(0)
  28. );
  29. PythUpgradable pyth = PythUpgradable(address(proxy));
  30. uint16[] memory emitterChainIds = new uint16[](1);
  31. emitterChainIds[0] = SOURCE_EMITTER_CHAIN_ID;
  32. bytes32[] memory emitterAddresses = new bytes32[](1);
  33. emitterAddresses[0] = SOURCE_EMITTER_ADDRESS;
  34. pyth.initialize(
  35. wormhole,
  36. emitterChainIds,
  37. emitterAddresses,
  38. GOVERNANCE_EMITTER_CHAIN_ID,
  39. GOVERNANCE_EMITTER_ADDRESS,
  40. 0, // Initial governance sequence
  41. 60, // Valid time period in seconds
  42. SINGLE_UPDATE_FEE_IN_WEI // single update fee in wei
  43. );
  44. return address(pyth);
  45. }
  46. function singleUpdateFeeInWei() public pure returns (uint) {
  47. return SINGLE_UPDATE_FEE_IN_WEI;
  48. }
  49. /// Utilities to help generating price feed messages and VAAs for them
  50. struct PriceFeedMessage {
  51. bytes32 priceId;
  52. int64 price;
  53. uint64 conf;
  54. int32 expo;
  55. uint64 publishTime;
  56. uint64 prevPublishTime;
  57. int64 emaPrice;
  58. uint64 emaConf;
  59. }
  60. struct TwapPriceFeedMessage {
  61. bytes32 priceId;
  62. int128 cumulativePrice;
  63. uint128 cumulativeConf;
  64. uint64 numDownSlots;
  65. uint64 publishSlot;
  66. uint64 publishTime;
  67. uint64 prevPublishTime;
  68. int32 expo;
  69. }
  70. struct MerkleUpdateConfig {
  71. uint8 depth;
  72. uint8 numSigners;
  73. uint16 source_chain_id;
  74. bytes32 source_emitter_address;
  75. bool brokenVaa;
  76. }
  77. function encodePriceFeedMessages(
  78. PriceFeedMessage[] memory priceFeedMessages
  79. ) internal pure returns (bytes[] memory encodedPriceFeedMessages) {
  80. encodedPriceFeedMessages = new bytes[](priceFeedMessages.length);
  81. for (uint i = 0; i < priceFeedMessages.length; i++) {
  82. encodedPriceFeedMessages[i] = abi.encodePacked(
  83. uint8(PythAccumulator.MessageType.PriceFeed),
  84. priceFeedMessages[i].priceId,
  85. priceFeedMessages[i].price,
  86. priceFeedMessages[i].conf,
  87. priceFeedMessages[i].expo,
  88. priceFeedMessages[i].publishTime,
  89. priceFeedMessages[i].prevPublishTime,
  90. priceFeedMessages[i].emaPrice,
  91. priceFeedMessages[i].emaConf
  92. );
  93. }
  94. }
  95. function encodeTwapPriceFeedMessages(
  96. TwapPriceFeedMessage[] memory twapPriceFeedMessages
  97. ) internal pure returns (bytes[] memory encodedTwapPriceFeedMessages) {
  98. encodedTwapPriceFeedMessages = new bytes[](
  99. twapPriceFeedMessages.length
  100. );
  101. for (uint i = 0; i < twapPriceFeedMessages.length; i++) {
  102. encodedTwapPriceFeedMessages[i] = abi.encodePacked(
  103. uint8(PythAccumulator.MessageType.TwapPriceFeed),
  104. twapPriceFeedMessages[i].priceId,
  105. twapPriceFeedMessages[i].cumulativePrice,
  106. twapPriceFeedMessages[i].cumulativeConf,
  107. twapPriceFeedMessages[i].numDownSlots,
  108. twapPriceFeedMessages[i].publishSlot,
  109. twapPriceFeedMessages[i].publishTime,
  110. twapPriceFeedMessages[i].prevPublishTime,
  111. twapPriceFeedMessages[i].expo
  112. );
  113. }
  114. }
  115. function generateWhMerkleUpdateWithSource(
  116. PriceFeedMessage[] memory priceFeedMessages,
  117. MerkleUpdateConfig memory config
  118. ) internal returns (bytes memory whMerkleUpdateData) {
  119. bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
  120. priceFeedMessages
  121. );
  122. (bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
  123. .constructProofs(encodedPriceFeedMessages, config.depth);
  124. bytes memory wormholePayload = abi.encodePacked(
  125. uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
  126. uint8(PythAccumulator.UpdateType.WormholeMerkle),
  127. uint64(1), // Slot, not used in target networks
  128. uint32(0), // Ring size, not used in target networks
  129. rootDigest
  130. );
  131. bytes memory wormholeMerkleVaa = generateVaa(
  132. 0,
  133. config.source_chain_id,
  134. config.source_emitter_address,
  135. 0,
  136. wormholePayload,
  137. config.numSigners
  138. );
  139. if (config.brokenVaa) {
  140. uint mutPos = getRandUint() % wormholeMerkleVaa.length;
  141. // mutate the random position by 1 bit
  142. wormholeMerkleVaa[mutPos] = bytes1(
  143. uint8(wormholeMerkleVaa[mutPos]) ^ 1
  144. );
  145. }
  146. whMerkleUpdateData = abi.encodePacked(
  147. uint32(0x504e4155), // PythAccumulator.ACCUMULATOR_MAGIC
  148. uint8(1), // major version
  149. uint8(0), // minor version
  150. uint8(0), // trailing header size
  151. uint8(PythAccumulator.UpdateType.WormholeMerkle),
  152. uint16(wormholeMerkleVaa.length),
  153. wormholeMerkleVaa,
  154. uint8(priceFeedMessages.length)
  155. );
  156. for (uint i = 0; i < priceFeedMessages.length; i++) {
  157. whMerkleUpdateData = abi.encodePacked(
  158. whMerkleUpdateData,
  159. uint16(encodedPriceFeedMessages[i].length),
  160. encodedPriceFeedMessages[i],
  161. proofs[i]
  162. );
  163. }
  164. }
  165. function generateWhMerkleTwapUpdateWithSource(
  166. TwapPriceFeedMessage[] memory twapPriceFeedMessages,
  167. MerkleUpdateConfig memory config
  168. ) internal returns (bytes memory whMerkleTwapUpdateData) {
  169. bytes[]
  170. memory encodedTwapPriceFeedMessages = encodeTwapPriceFeedMessages(
  171. twapPriceFeedMessages
  172. );
  173. (bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
  174. .constructProofs(encodedTwapPriceFeedMessages, config.depth);
  175. bytes memory wormholePayload = abi.encodePacked(
  176. uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
  177. uint8(PythAccumulator.UpdateType.WormholeMerkle),
  178. uint64(0), // Slot, not used in target networks
  179. uint32(0), // Ring size, not used in target networks
  180. rootDigest
  181. );
  182. bytes memory wormholeMerkleVaa = generateVaa(
  183. 0,
  184. config.source_chain_id,
  185. config.source_emitter_address,
  186. 0,
  187. wormholePayload,
  188. config.numSigners
  189. );
  190. if (config.brokenVaa) {
  191. uint mutPos = getRandUint() % wormholeMerkleVaa.length;
  192. // mutate the random position by 1 bit
  193. wormholeMerkleVaa[mutPos] = bytes1(
  194. uint8(wormholeMerkleVaa[mutPos]) ^ 1
  195. );
  196. }
  197. whMerkleTwapUpdateData = abi.encodePacked(
  198. uint32(0x504e4155), // PythAccumulator.ACCUMULATOR_MAGIC
  199. uint8(1), // major version
  200. uint8(0), // minor version
  201. uint8(0), // trailing header size
  202. uint8(PythAccumulator.UpdateType.WormholeMerkle),
  203. uint16(wormholeMerkleVaa.length),
  204. wormholeMerkleVaa,
  205. uint8(twapPriceFeedMessages.length)
  206. );
  207. for (uint i = 0; i < twapPriceFeedMessages.length; i++) {
  208. whMerkleTwapUpdateData = abi.encodePacked(
  209. whMerkleTwapUpdateData,
  210. uint16(encodedTwapPriceFeedMessages[i].length),
  211. encodedTwapPriceFeedMessages[i],
  212. proofs[i]
  213. );
  214. }
  215. }
  216. function generateWhMerkleUpdate(
  217. PriceFeedMessage[] memory priceFeedMessages,
  218. uint8 depth,
  219. uint8 numSigners
  220. ) internal returns (bytes memory whMerkleUpdateData) {
  221. whMerkleUpdateData = generateWhMerkleUpdateWithSource(
  222. priceFeedMessages,
  223. MerkleUpdateConfig(
  224. depth,
  225. numSigners,
  226. SOURCE_EMITTER_CHAIN_ID,
  227. SOURCE_EMITTER_ADDRESS,
  228. false
  229. )
  230. );
  231. }
  232. function generateForwardCompatibleWhMerkleUpdate(
  233. PriceFeedMessage[] memory priceFeedMessages,
  234. uint8 depth,
  235. uint8 numSigners,
  236. uint8 minorVersion,
  237. bytes memory trailingHeaderData
  238. ) internal view returns (bytes memory whMerkleUpdateData) {
  239. bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
  240. priceFeedMessages
  241. );
  242. (bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
  243. .constructProofs(encodedPriceFeedMessages, depth);
  244. // refactoring some of these generateWormhole functions was necessary
  245. // to workaround the stack too deep limit.
  246. bytes
  247. memory wormholeMerkleVaa = generateForwardCompatibleWormholeMerkleVaa(
  248. rootDigest,
  249. trailingHeaderData,
  250. numSigners
  251. );
  252. {
  253. whMerkleUpdateData = abi.encodePacked(
  254. generateForwardCompatibleWormholeMerkleUpdateHeader(
  255. minorVersion,
  256. trailingHeaderData
  257. ),
  258. uint16(wormholeMerkleVaa.length),
  259. wormholeMerkleVaa,
  260. uint8(priceFeedMessages.length)
  261. );
  262. }
  263. for (uint i = 0; i < priceFeedMessages.length; i++) {
  264. whMerkleUpdateData = abi.encodePacked(
  265. whMerkleUpdateData,
  266. uint16(encodedPriceFeedMessages[i].length),
  267. encodedPriceFeedMessages[i],
  268. proofs[i]
  269. );
  270. }
  271. }
  272. function generateForwardCompatibleWormholeMerkleUpdateHeader(
  273. uint8 minorVersion,
  274. bytes memory trailingHeaderData
  275. ) private pure returns (bytes memory whMerkleUpdateHeader) {
  276. whMerkleUpdateHeader = abi.encodePacked(
  277. uint32(0x504e4155), // PythAccumulator.ACCUMULATOR_MAGIC
  278. uint8(1), // major version
  279. minorVersion,
  280. uint8(trailingHeaderData.length), // trailing header size
  281. trailingHeaderData,
  282. uint8(PythAccumulator.UpdateType.WormholeMerkle)
  283. );
  284. }
  285. function generateForwardCompatibleWormholeMerkleVaa(
  286. bytes20 rootDigest,
  287. bytes memory futureData,
  288. uint8 numSigners
  289. ) internal view returns (bytes memory wormholeMerkleVaa) {
  290. wormholeMerkleVaa = generateVaa(
  291. 0,
  292. SOURCE_EMITTER_CHAIN_ID,
  293. SOURCE_EMITTER_ADDRESS,
  294. 0,
  295. abi.encodePacked(
  296. uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
  297. uint8(PythAccumulator.UpdateType.WormholeMerkle),
  298. uint64(0), // Slot, not used in target networks
  299. uint32(0), // Ring size, not used in target networks
  300. rootDigest, // this can have bytes past this for future versions
  301. futureData
  302. ),
  303. numSigners
  304. );
  305. }
  306. function pricesToPriceFeedMessages(
  307. bytes32[] memory priceIds,
  308. PythStructs.Price[] memory prices
  309. ) public returns (PriceFeedMessage[] memory priceFeedMessages) {
  310. assertGe(priceIds.length, prices.length);
  311. priceFeedMessages = new PriceFeedMessage[](prices.length);
  312. for (uint i = 0; i < prices.length; ++i) {
  313. priceFeedMessages[i].priceId = priceIds[i];
  314. priceFeedMessages[i].price = prices[i].price;
  315. priceFeedMessages[i].conf = prices[i].conf;
  316. priceFeedMessages[i].expo = prices[i].expo;
  317. priceFeedMessages[i].publishTime = uint64(prices[i].publishTime);
  318. priceFeedMessages[i].prevPublishTime =
  319. uint64(prices[i].publishTime) -
  320. 1;
  321. priceFeedMessages[i].emaPrice = prices[i].price;
  322. priceFeedMessages[i].emaConf = prices[i].conf;
  323. }
  324. }
  325. }
  326. contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
  327. function assertCrossRateEquals(
  328. int64 price1,
  329. int32 expo1,
  330. int64 price2,
  331. int32 expo2,
  332. int32 targetExpo,
  333. uint256 expectedPrice
  334. ) internal {
  335. uint256 price = PythUtils.deriveCrossRate(
  336. price1,
  337. expo1,
  338. price2,
  339. expo2,
  340. targetExpo
  341. );
  342. assertEq(price, expectedPrice);
  343. }
  344. function assertCrossRateReverts(
  345. int64 price1,
  346. int32 expo1,
  347. int64 price2,
  348. int32 expo2,
  349. int32 targetExpo,
  350. bytes4 expectedError
  351. ) internal {
  352. vm.expectRevert(expectedError);
  353. PythUtils.deriveCrossRate(price1, expo1, price2, expo2, targetExpo);
  354. }
  355. function testConvertToUnit() public {
  356. // Test 1: Price can't be negative
  357. vm.expectRevert(PythErrors.NegativeInputPrice.selector);
  358. PythUtils.convertToUint(-100, -5, 18);
  359. // Test 2: Exponent can't be less than -255
  360. vm.expectRevert(PythErrors.InvalidInputExpo.selector);
  361. PythUtils.convertToUint(100, -256, 18);
  362. // Test 3: This test will fail as the 10 ** 237 is too large for a uint256
  363. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  364. assertEq(PythUtils.convertToUint(100, -255, 18), 0);
  365. // Test 4: Combined Exponent can't be greater than 58 and less than -58
  366. // See the calculation how we came up with 58 in PythUtils.sol
  367. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  368. assertEq(PythUtils.convertToUint(100, 50, 9), 0); // 50 + 9 = 59 > 58
  369. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  370. assertEq(PythUtils.convertToUint(100, -96, 37), 0); // -96 + 37 = -59 < -58
  371. // Test 5: Negative Exponent Tests
  372. // Price with 18 decimals and exponent -5
  373. assertEq(
  374. PythUtils.convertToUint(100, -5, 18),
  375. 100_0_000_000_000_000 // 100 * 10^13
  376. );
  377. // Price with 9 decimals and exponent -2
  378. assertEq(
  379. PythUtils.convertToUint(100, -2, 9),
  380. 100_0_000_000 // 100 * 10^7
  381. );
  382. // Test 6: Price with 4 decimals and exponent -5
  383. assertEq(PythUtils.convertToUint(100, -5, 4), 10);
  384. // Test 7: Price with 5 decimals and exponent -2
  385. // @note: We will lose precision here as price is
  386. // 0.00001 and we are targetDecimals is 2.
  387. assertEq(PythUtils.convertToUint(100, -5, 2), 0);
  388. assertEq(PythUtils.convertToUint(123, -8, 5), 0);
  389. // Test 8: Positive Exponent Tests
  390. // Price with 18 decimals and exponent 5
  391. assertEq(
  392. PythUtils.convertToUint(100, 5, 18),
  393. 100_00_000_000_000_000_000_000_000
  394. ); // 100 with 23 zeros
  395. // Test 9: Price with 9 decimals and exponent 2
  396. assertEq(PythUtils.convertToUint(100, 2, 9), 100_00_000_000_000); // 100 with 11 zeros
  397. // Test 10: Price with 2 decimals and exponent 1
  398. assertEq(PythUtils.convertToUint(100, 1, 2), 100_000); // 100 with 3 zeros
  399. // Special Cases
  400. // Test 11: price = 0, any expo/decimals returns 0
  401. assertEq(PythUtils.convertToUint(0, -58, 0), 0);
  402. assertEq(PythUtils.convertToUint(0, 0, 0), 0);
  403. assertEq(PythUtils.convertToUint(0, 58, 0), 0);
  404. assertEq(PythUtils.convertToUint(0, -58, 58), 0);
  405. // Test 12: smallest positive price, maximum downward exponent (should round to zero)
  406. assertEq(PythUtils.convertToUint(1, -58, 0), 0);
  407. assertEq(PythUtils.convertToUint(1, -58, 58), 1);
  408. // Test 13: deltaExponent == 0 (should be identical to price)
  409. assertEq(PythUtils.convertToUint(123456, 0, 0), 123456);
  410. assertEq(PythUtils.convertToUint(123456, -5, 5), 123456); // -5 + 5 == 0
  411. // Test 14: deltaExponent > 0 (should shift price up)
  412. assertEq(PythUtils.convertToUint(123456, 5, 0), 12345600000);
  413. assertEq(PythUtils.convertToUint(123456, 5, 2), 1234560000000);
  414. // Test 15: deltaExponent < 0 (should shift price down)
  415. assertEq(PythUtils.convertToUint(123456, -5, 0), 1);
  416. assertEq(PythUtils.convertToUint(123456, -5, 2), 123);
  417. // Test 16: division with truncation
  418. assertEq(PythUtils.convertToUint(999, -2, 0), 9); // 999/100 = 9 (truncated)
  419. assertEq(PythUtils.convertToUint(199, -2, 0), 1); // 199/100 = 1 (truncated)
  420. assertEq(PythUtils.convertToUint(99, -2, 0), 0); // 99/100 = 0 (truncated)
  421. // Test 17: Big price and scaling, but outside of bounds
  422. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  423. assertEq(PythUtils.convertToUint(100_000_000, 10, 50), 0);
  424. // Test 18: Big price and scaling
  425. assertEq(PythUtils.convertToUint(100_000_000, -50, 10), 0); // -50 + 10 = -40 > -58
  426. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  427. assertEq(PythUtils.convertToUint(100_000_000, 10, 50), 0); // 10 + 50 = 60 > 58
  428. // Test 19: Decimals just save from truncation
  429. assertEq(PythUtils.convertToUint(5, -1, 1), 5); // 5/10*10 = 5
  430. assertEq(PythUtils.convertToUint(5, -1, 2), 50); // 5/10*100 = 50
  431. // 10. Test: Big price and scaling, should be inside the bounds
  432. // We have to convert int64 -> int256 -> uint256 before multiplying by 10 ** 58
  433. assertEq(
  434. PythUtils.convertToUint(type(int64).max, 50, 8),
  435. uint256(int256(type(int64).max)) * 10 ** 58
  436. ); // 50 + 8 = 58
  437. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  438. assertEq(PythUtils.convertToUint(type(int64).max, 50, 9), 0);
  439. assertEq(PythUtils.convertToUint(type(int64).max, -64, 8), 0); // -64 + 8 = -56 > -58
  440. assertEq(PythUtils.convertToUint(type(int64).max, -50, 1), 0); // -50 + 1 = -49 > -58
  441. // 11. Test: Big price and scaling, should be inside the bounds
  442. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  443. assertEq(PythUtils.convertToUint(type(int64).max, 50, 9), 0); // 50 + 9 = 59 > 58
  444. vm.expectRevert(PythErrors.ExponentOverflow.selector);
  445. assertEq(PythUtils.convertToUint(type(int64).max, -60, 1), 0); // -60 + 1 = -59 < -58
  446. }
  447. function testDeriveCrossRate() public {
  448. // Test 1: Prices can't be negative
  449. assertCrossRateReverts(
  450. -100,
  451. -2,
  452. 100,
  453. -2,
  454. 5,
  455. PythErrors.NegativeInputPrice.selector
  456. );
  457. assertCrossRateReverts(
  458. 100,
  459. -2,
  460. -100,
  461. -2,
  462. 5,
  463. PythErrors.NegativeInputPrice.selector
  464. );
  465. assertCrossRateReverts(
  466. -100,
  467. -2,
  468. -100,
  469. -2,
  470. 5,
  471. PythErrors.NegativeInputPrice.selector
  472. );
  473. // Test 2: Exponent can't be less than -255
  474. assertCrossRateReverts(
  475. 100,
  476. -256,
  477. 100,
  478. -2,
  479. 5,
  480. PythErrors.InvalidInputExpo.selector
  481. );
  482. assertCrossRateReverts(
  483. 100,
  484. -2,
  485. 100,
  486. -256,
  487. 5,
  488. PythErrors.InvalidInputExpo.selector
  489. );
  490. assertCrossRateReverts(
  491. 100,
  492. -256,
  493. 100,
  494. -256,
  495. 5,
  496. PythErrors.InvalidInputExpo.selector
  497. );
  498. // Target exponent can't be less than -255
  499. assertCrossRateReverts(
  500. 100,
  501. -2,
  502. 100,
  503. -2,
  504. -256,
  505. PythErrors.InvalidInputExpo.selector
  506. );
  507. // Test 3: Basic Tests with negative exponents
  508. assertCrossRateEquals(500, -8, 500, -8, -5, 100000);
  509. assertCrossRateEquals(10_000, -8, 100, -2, -5, 10);
  510. assertCrossRateEquals(10_000, -2, 100, -8, -5, 100_00_000_000_000);
  511. // Test 4: Basic Tests with positive exponents
  512. assertCrossRateEquals(100, 2, 100, 2, -5, 100000); // 100 * 10^2 / 100 * 10^2 = 10000 / 10000 = 1 == 100000 * 10^-5
  513. // We will loose preistion as the the target exponent is 5 making the price 0.00001
  514. assertCrossRateEquals(100, 8, 100, 8, 5, 0);
  515. // Test 5: Different Exponent Tests
  516. assertCrossRateEquals(10_000, -2, 100, -4, 0, 10_000); // 10_000 / 100 = 100 * 10(-2 - -4) = 10_000 with 0 decimals = 10_000
  517. assertCrossRateEquals(10_000, -2, 100, -4, 5, 0); // 10_000 / 100 = 100 * 10(-2 - -4) = 10_000 with 5 decimals = 0
  518. assertCrossRateEquals(10_000, -2, 10_000, -1, 5, 0); // It will truncate to 0
  519. assertCrossRateEquals(10_000, -10, 10_000, -2, 0, 0); // It will truncate to 0
  520. assertCrossRateEquals(
  521. 100_000_000,
  522. -2,
  523. 100,
  524. -8,
  525. -8,
  526. 100_000_000_000_000_000_000
  527. ); // 100_000_000 / 100 = 1_000_000 * 10(-2 - -8) = 1000000 * 10^6 = 1000000000000
  528. // Test 6: Exponent Edge Tests
  529. assertCrossRateEquals(10_000, 0, 100, 0, 0, 100);
  530. assertCrossRateReverts(
  531. 10_000,
  532. 0,
  533. 100,
  534. 0,
  535. -255,
  536. PythErrors.ExponentOverflow.selector
  537. );
  538. assertCrossRateReverts(
  539. 10_000,
  540. 0,
  541. 100,
  542. -255,
  543. -255,
  544. PythErrors.ExponentOverflow.selector
  545. );
  546. assertCrossRateReverts(
  547. 10_000,
  548. -255,
  549. 100,
  550. 0,
  551. 0,
  552. PythErrors.ExponentOverflow.selector
  553. );
  554. assertCrossRateReverts(
  555. 10_000,
  556. -255,
  557. 100,
  558. -178,
  559. -5,
  560. PythErrors.ExponentOverflow.selector
  561. );
  562. // Test 7: Max int64 price and scaling
  563. assertCrossRateEquals(
  564. type(int64).max,
  565. 0,
  566. 1,
  567. 0,
  568. 0,
  569. uint256(int256(type(int64).max))
  570. );
  571. assertCrossRateEquals(1, 0, type(int64).max, 0, 0, 0);
  572. assertCrossRateEquals(type(int64).max, 0, type(int64).max, 0, 0, 1);
  573. // type(int64).max is approx 9.223e18
  574. assertCrossRateEquals(type(int64).max, 0, 1, 0, 18, 9);
  575. // 1 / type(int64).max is approx 1.085e-19
  576. assertCrossRateEquals(1, 0, type(int64).max, 0, -19, 1);
  577. // type(int64).max * 10 ** 58 / 1
  578. assertCrossRateEquals(
  579. type(int64).max,
  580. 50,
  581. 1,
  582. -8,
  583. 0,
  584. uint256(int256(type(int64).max)) * 10 ** 58
  585. );
  586. // 1 / (type(int64).max * 10 ** 58)
  587. assertCrossRateEquals(1, 0, type(int64).max, 50, 8, 0);
  588. // type(int64).max * 10 ** 59 / 1
  589. assertCrossRateReverts(
  590. type(int64).max,
  591. 50,
  592. 1,
  593. -9,
  594. 0,
  595. PythErrors.ExponentOverflow.selector
  596. );
  597. // 1 / (type(int64).max * 10 ** 59)
  598. assertCrossRateReverts(
  599. 1,
  600. 0,
  601. type(int64).max,
  602. 50,
  603. 9,
  604. PythErrors.ExponentOverflow.selector
  605. );
  606. // Realistic Tests
  607. // Test case 1: (StEth/Eth / Eth/USD = ETH/BTC)
  608. uint256 price = PythUtils.deriveCrossRate(
  609. 206487956502,
  610. -8,
  611. 206741615681,
  612. -8,
  613. -8
  614. );
  615. assertApproxEqRel(price, 100000000, 9e17); // $1
  616. // Test case 2:
  617. price = PythUtils.deriveCrossRate(520010, -8, 38591, -8, -8);
  618. assertApproxEqRel(price, 1347490347, 9e17); // $13.47
  619. // Test case 3:
  620. price = PythUtils.deriveCrossRate(520010, -8, 38591, -8, -12);
  621. assertApproxEqRel(price, 13474903475432, 9e17); // $13.47
  622. }
  623. }