PythTest.spec.ts 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466
  1. import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
  2. import { Cell, CommonMessageInfoInternal, Message, toNano } from "@ton/core";
  3. import "@ton/test-utils";
  4. import { compile } from "@ton/blueprint";
  5. import { HexString, Price } from "@pythnetwork/price-service-sdk";
  6. import { PythTest, PythTestConfig } from "../wrappers/PythTest";
  7. import {
  8. BTC_PRICE_FEED_ID,
  9. HERMES_BTC_ETH_UPDATE,
  10. PYTH_SET_DATA_SOURCES,
  11. PYTH_SET_FEE,
  12. TEST_GUARDIAN_ADDRESS1,
  13. PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER,
  14. PYTH_REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER,
  15. TEST_GUARDIAN_ADDRESS2,
  16. ETH_PRICE_FEED_ID,
  17. HERMES_BTC_PRICE,
  18. HERMES_ETH_PRICE,
  19. HERMES_ETH_PUBLISH_TIME,
  20. HERMES_BTC_PUBLISH_TIME,
  21. HERMES_BTC_CONF,
  22. HERMES_BTC_EXPO,
  23. HERMES_BTC_EMA_CONF,
  24. HERMES_BTC_EMA_EXPO,
  25. HERMES_BTC_EMA_PRICE,
  26. HERMES_BTC_EMA_PUBLISH_TIME,
  27. HERMES_ETH_CONF,
  28. HERMES_ETH_EMA_CONF,
  29. HERMES_ETH_EMA_EXPO,
  30. HERMES_ETH_EMA_PRICE,
  31. HERMES_ETH_EMA_PUBLISH_TIME,
  32. HERMES_ETH_EXPO,
  33. HERMES_BTC_ETH_UNIQUE_UPDATE,
  34. HERMES_ETH_UNIQUE_EMA_PRICE,
  35. HERMES_BTC_UNIQUE_CONF,
  36. HERMES_BTC_UNIQUE_EMA_CONF,
  37. HERMES_BTC_UNIQUE_EMA_EXPO,
  38. HERMES_BTC_UNIQUE_EMA_PRICE,
  39. HERMES_BTC_UNIQUE_EMA_PUBLISH_TIME,
  40. HERMES_BTC_UNIQUE_EXPO,
  41. HERMES_BTC_UNIQUE_PRICE,
  42. HERMES_BTC_UNIQUE_PUBLISH_TIME,
  43. HERMES_ETH_UNIQUE_CONF,
  44. HERMES_ETH_UNIQUE_EMA_CONF,
  45. HERMES_ETH_UNIQUE_EMA_EXPO,
  46. HERMES_ETH_UNIQUE_EMA_PUBLISH_TIME,
  47. HERMES_ETH_UNIQUE_EXPO,
  48. HERMES_ETH_UNIQUE_PRICE,
  49. HERMES_ETH_UNIQUE_PUBLISH_TIME,
  50. } from "./utils/pyth";
  51. import { GUARDIAN_SET_0, MAINNET_UPGRADE_VAAS } from "./utils/wormhole";
  52. import { DataSource } from "@pythnetwork/xc-admin-common";
  53. import { createAuthorizeUpgradePayload } from "./utils";
  54. import {
  55. UniversalAddress,
  56. createVAA,
  57. serialize,
  58. } from "@wormhole-foundation/sdk-definitions";
  59. import { mocks } from "@wormhole-foundation/sdk-definitions/testing";
  60. import { calculateUpdatePriceFeedsFee } from "@pythnetwork/pyth-ton-js";
  61. const TIME_PERIOD = 60;
  62. const PRICE = new Price({
  63. price: "1",
  64. conf: "2",
  65. expo: 3,
  66. publishTime: 4,
  67. });
  68. const EMA_PRICE = new Price({
  69. price: "5",
  70. conf: "6",
  71. expo: 7,
  72. publishTime: 8,
  73. });
  74. const SINGLE_UPDATE_FEE = 1;
  75. const DATA_SOURCES: DataSource[] = [
  76. {
  77. emitterChain: 26,
  78. emitterAddress:
  79. "e101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71",
  80. },
  81. ];
  82. const TEST_GOVERNANCE_DATA_SOURCES: DataSource[] = [
  83. {
  84. emitterChain: 1,
  85. emitterAddress:
  86. "0000000000000000000000000000000000000000000000000000000000000029",
  87. },
  88. {
  89. emitterChain: 2,
  90. emitterAddress:
  91. "000000000000000000000000000000000000000000000000000000000000002b",
  92. },
  93. {
  94. emitterChain: 1,
  95. emitterAddress:
  96. "0000000000000000000000000000000000000000000000000000000000000000",
  97. },
  98. ];
  99. describe("PythTest", () => {
  100. let code: Cell;
  101. beforeAll(async () => {
  102. code = await compile("PythTest");
  103. });
  104. let blockchain: Blockchain;
  105. let deployer: SandboxContract<TreasuryContract>;
  106. let pythTest: SandboxContract<PythTest>;
  107. beforeEach(async () => {
  108. blockchain = await Blockchain.create();
  109. deployer = await blockchain.treasury("deployer");
  110. });
  111. async function deployContract(
  112. priceFeedId: HexString = BTC_PRICE_FEED_ID,
  113. price: Price = PRICE,
  114. emaPrice: Price = EMA_PRICE,
  115. singleUpdateFee: number = SINGLE_UPDATE_FEE,
  116. dataSources: DataSource[] = DATA_SOURCES,
  117. guardianSetIndex: number = 0,
  118. guardianSet: string[] = GUARDIAN_SET_0,
  119. chainId: number = 1,
  120. governanceChainId: number = 1,
  121. governanceContract: string = "0000000000000000000000000000000000000000000000000000000000000004",
  122. governanceDataSource?: DataSource
  123. ) {
  124. const config: PythTestConfig = {
  125. priceFeedId,
  126. price,
  127. emaPrice,
  128. singleUpdateFee,
  129. dataSources,
  130. guardianSetIndex,
  131. guardianSet,
  132. chainId,
  133. governanceChainId,
  134. governanceContract,
  135. governanceDataSource,
  136. };
  137. pythTest = blockchain.openContract(PythTest.createFromConfig(config, code));
  138. const deployResult = await pythTest.sendDeploy(
  139. deployer.getSender(),
  140. toNano("0.05")
  141. );
  142. expect(deployResult.transactions).toHaveTransaction({
  143. from: deployer.address,
  144. to: pythTest.address,
  145. deploy: true,
  146. success: true,
  147. });
  148. }
  149. async function updateGuardianSets(
  150. pythTest: SandboxContract<PythTest>,
  151. deployer: SandboxContract<TreasuryContract>
  152. ) {
  153. for (const vaa of MAINNET_UPGRADE_VAAS) {
  154. const result = await pythTest.sendUpdateGuardianSet(
  155. deployer.getSender(),
  156. Buffer.from(vaa, "hex")
  157. );
  158. expect(result.transactions).toHaveTransaction({
  159. from: deployer.address,
  160. to: pythTest.address,
  161. success: true,
  162. });
  163. }
  164. }
  165. it("should correctly get price unsafe", async () => {
  166. await deployContract();
  167. const result = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  168. expect(result.price).toBe(1);
  169. expect(result.conf).toBe(2);
  170. expect(result.expo).toBe(3);
  171. expect(result.publishTime).toBe(4);
  172. });
  173. it("should correctly get price no older than", async () => {
  174. const timeNow = Math.floor(Date.now() / 1000) - TIME_PERIOD + 5; // 5 seconds buffer
  175. const price = new Price({
  176. price: "1",
  177. conf: "2",
  178. expo: 3,
  179. publishTime: timeNow,
  180. });
  181. await deployContract(BTC_PRICE_FEED_ID, price, EMA_PRICE);
  182. const result = await pythTest.getPriceNoOlderThan(
  183. TIME_PERIOD,
  184. BTC_PRICE_FEED_ID
  185. );
  186. expect(result.price).toBe(1);
  187. expect(result.conf).toBe(2);
  188. expect(result.expo).toBe(3);
  189. expect(result.publishTime).toBe(timeNow);
  190. });
  191. it("should fail to get price no older than", async () => {
  192. await deployContract();
  193. await expect(
  194. pythTest.getPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  195. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  196. });
  197. it("should correctly get ema price no older than", async () => {
  198. const timeNow = Math.floor(Date.now() / 1000) - TIME_PERIOD + 5; // 5 seconds buffer
  199. const emaPrice = new Price({
  200. price: "5",
  201. conf: "6",
  202. expo: 7,
  203. publishTime: timeNow,
  204. });
  205. await deployContract(BTC_PRICE_FEED_ID, PRICE, emaPrice);
  206. const result = await pythTest.getEmaPriceNoOlderThan(
  207. TIME_PERIOD,
  208. BTC_PRICE_FEED_ID
  209. );
  210. expect(result.price).toBe(5);
  211. expect(result.conf).toBe(6);
  212. expect(result.expo).toBe(7);
  213. expect(result.publishTime).toBe(timeNow);
  214. });
  215. it("should fail to get ema price no older than", async () => {
  216. await deployContract();
  217. await expect(
  218. pythTest.getEmaPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  219. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  220. });
  221. it("should correctly get ema price unsafe", async () => {
  222. await deployContract();
  223. const result = await pythTest.getEmaPriceUnsafe(BTC_PRICE_FEED_ID);
  224. expect(result.price).toBe(5);
  225. expect(result.conf).toBe(6);
  226. expect(result.expo).toBe(7);
  227. expect(result.publishTime).toBe(8);
  228. });
  229. it("should correctly get update fee", async () => {
  230. await deployContract();
  231. const result = await pythTest.getUpdateFee(
  232. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex")
  233. );
  234. expect(result).toBe(2);
  235. });
  236. it("should correctly update price feeds", async () => {
  237. await deployContract();
  238. let result;
  239. await updateGuardianSets(pythTest, deployer);
  240. // Check initial prices
  241. const initialBtcPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  242. expect(initialBtcPrice.price).not.toBe(HERMES_BTC_PRICE);
  243. // Expect an error for ETH price feed as it doesn't exist initially
  244. await expect(pythTest.getPriceUnsafe(ETH_PRICE_FEED_ID)).rejects.toThrow(
  245. "Unable to execute get method. Got exit_code: 2000"
  246. ); // ERROR_PRICE_FEED_NOT_FOUND = 2000
  247. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  248. const updateFee = await pythTest.getUpdateFee(updateData);
  249. result = await pythTest.sendUpdatePriceFeeds(
  250. deployer.getSender(),
  251. updateData,
  252. toNano(updateFee)
  253. );
  254. expect(result.transactions).toHaveTransaction({
  255. from: deployer.address,
  256. to: pythTest.address,
  257. success: true,
  258. });
  259. // Check if both BTC and ETH prices have been updated
  260. const updatedBtcPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  261. expect(updatedBtcPrice.price).toBe(HERMES_BTC_PRICE);
  262. expect(updatedBtcPrice.publishTime).toBe(HERMES_BTC_PUBLISH_TIME);
  263. const updatedEthPrice = await pythTest.getPriceUnsafe(ETH_PRICE_FEED_ID);
  264. expect(updatedEthPrice.price).toBe(HERMES_ETH_PRICE);
  265. expect(updatedEthPrice.publishTime).toBe(HERMES_ETH_PUBLISH_TIME);
  266. });
  267. it("should fail to get update fee with invalid data", async () => {
  268. await deployContract();
  269. await updateGuardianSets(pythTest, deployer);
  270. const invalidUpdateData = Buffer.from("invalid data");
  271. await expect(pythTest.getUpdateFee(invalidUpdateData)).rejects.toThrow(
  272. "Unable to execute get method. Got exit_code: 2002"
  273. ); // ERROR_INVALID_MAGIC = 2002
  274. });
  275. it("should fail to update price feeds with invalid data", async () => {
  276. await deployContract();
  277. await updateGuardianSets(pythTest, deployer);
  278. const invalidUpdateData = Buffer.from("invalid data");
  279. // Use a fixed value for updateFee since we can't get it from getUpdateFee
  280. const updateFee = toNano("0.1"); // Use a reasonable amount
  281. const result = await pythTest.sendUpdatePriceFeeds(
  282. deployer.getSender(),
  283. invalidUpdateData,
  284. updateFee
  285. );
  286. expect(result.transactions).toHaveTransaction({
  287. from: deployer.address,
  288. to: pythTest.address,
  289. success: false,
  290. exitCode: 2002, // ERROR_INVALID_MAGIC
  291. });
  292. });
  293. it("should fail to update price feeds with outdated guardian set", async () => {
  294. await deployContract();
  295. // Don't update guardian sets
  296. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  297. const updateFee = await pythTest.getUpdateFee(updateData);
  298. const result = await pythTest.sendUpdatePriceFeeds(
  299. deployer.getSender(),
  300. updateData,
  301. toNano(updateFee)
  302. );
  303. expect(result.transactions).toHaveTransaction({
  304. from: deployer.address,
  305. to: pythTest.address,
  306. success: false,
  307. exitCode: 1002, // ERROR_GUARDIAN_SET_NOT_FOUND
  308. });
  309. });
  310. it("should fail to update price feeds with invalid data source", async () => {
  311. await deployContract(
  312. BTC_PRICE_FEED_ID,
  313. PRICE,
  314. EMA_PRICE,
  315. SINGLE_UPDATE_FEE,
  316. [] // Empty data sources
  317. );
  318. await updateGuardianSets(pythTest, deployer);
  319. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  320. const updateFee = await pythTest.getUpdateFee(updateData);
  321. const result = await pythTest.sendUpdatePriceFeeds(
  322. deployer.getSender(),
  323. updateData,
  324. toNano(updateFee)
  325. );
  326. expect(result.transactions).toHaveTransaction({
  327. from: deployer.address,
  328. to: pythTest.address,
  329. success: false,
  330. exitCode: 2005, // ERROR_UPDATE_DATA_SOURCE_NOT_FOUND
  331. });
  332. });
  333. it("should correctly handle stale prices", async () => {
  334. const staleTime = Math.floor(Date.now() / 1000) - TIME_PERIOD - 10; // 10 seconds past the allowed period
  335. const stalePrice = new Price({
  336. price: "1",
  337. conf: "2",
  338. expo: 3,
  339. publishTime: staleTime,
  340. });
  341. await deployContract(BTC_PRICE_FEED_ID, stalePrice, EMA_PRICE);
  342. await expect(
  343. pythTest.getPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  344. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  345. });
  346. it("should fail to update price feeds with insufficient gas", async () => {
  347. await deployContract();
  348. await updateGuardianSets(pythTest, deployer);
  349. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  350. let result = await pythTest.sendUpdatePriceFeeds(
  351. deployer.getSender(),
  352. updateData,
  353. calculateUpdatePriceFeedsFee(1n) // Send enough gas for 1 update instead of 2
  354. );
  355. expect(result.transactions).toHaveTransaction({
  356. from: deployer.address,
  357. to: pythTest.address,
  358. success: false,
  359. exitCode: 3000, // ERROR_INSUFFICIENT_GAS
  360. });
  361. });
  362. it("should fail to update price feeds with insufficient fee", async () => {
  363. await deployContract();
  364. await updateGuardianSets(pythTest, deployer);
  365. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  366. const updateFee = await pythTest.getUpdateFee(updateData);
  367. // Send less than the required fee
  368. const insufficientFee = updateFee - 1;
  369. const result = await pythTest.sendUpdatePriceFeeds(
  370. deployer.getSender(),
  371. updateData,
  372. calculateUpdatePriceFeedsFee(2n) + BigInt(insufficientFee)
  373. );
  374. // Check that the transaction did not succeed
  375. expect(result.transactions).toHaveTransaction({
  376. from: deployer.address,
  377. to: pythTest.address,
  378. success: false,
  379. exitCode: 2011, // ERROR_INSUFFICIENT_FEE = 2011
  380. });
  381. });
  382. it("should fail to get prices for non-existent price feed", async () => {
  383. await deployContract();
  384. const nonExistentPriceFeedId =
  385. "0000000000000000000000000000000000000000000000000000000000000000";
  386. await expect(
  387. pythTest.getPriceUnsafe(nonExistentPriceFeedId)
  388. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND = 2000
  389. await expect(
  390. pythTest.getPriceNoOlderThan(TIME_PERIOD, nonExistentPriceFeedId)
  391. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND
  392. await expect(
  393. pythTest.getEmaPriceUnsafe(nonExistentPriceFeedId)
  394. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND
  395. });
  396. it("should correctly get chain ID", async () => {
  397. await deployContract();
  398. const result = await pythTest.getChainId();
  399. expect(result).toEqual(1);
  400. });
  401. it("should correctly get last executed governance sequence", async () => {
  402. await deployContract(
  403. BTC_PRICE_FEED_ID,
  404. PRICE,
  405. EMA_PRICE,
  406. SINGLE_UPDATE_FEE,
  407. DATA_SOURCES,
  408. 0,
  409. [TEST_GUARDIAN_ADDRESS1],
  410. 60051,
  411. 1,
  412. "0000000000000000000000000000000000000000000000000000000000000004",
  413. TEST_GOVERNANCE_DATA_SOURCES[0]
  414. );
  415. // Check initial value
  416. let result = await pythTest.getLastExecutedGovernanceSequence();
  417. expect(result).toEqual(0);
  418. // Execute a governance action (e.g., set fee)
  419. await pythTest.sendExecuteGovernanceAction(
  420. deployer.getSender(),
  421. Buffer.from(PYTH_SET_FEE, "hex")
  422. );
  423. // Check that the sequence has increased
  424. result = await pythTest.getLastExecutedGovernanceSequence();
  425. expect(result).toEqual(1);
  426. });
  427. it("should correctly get governance data source index", async () => {
  428. // Deploy contract with initial governance data source
  429. await deployContract(
  430. BTC_PRICE_FEED_ID,
  431. PRICE,
  432. EMA_PRICE,
  433. SINGLE_UPDATE_FEE,
  434. DATA_SOURCES,
  435. 0,
  436. [TEST_GUARDIAN_ADDRESS1],
  437. 60051,
  438. 1,
  439. "0000000000000000000000000000000000000000000000000000000000000004",
  440. TEST_GOVERNANCE_DATA_SOURCES[0]
  441. );
  442. // Check initial value
  443. let result = await pythTest.getGovernanceDataSourceIndex();
  444. expect(result).toEqual(0);
  445. // Execute governance action to change data source
  446. await pythTest.sendExecuteGovernanceAction(
  447. deployer.getSender(),
  448. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  449. );
  450. // Check that the index has increased
  451. result = await pythTest.getGovernanceDataSourceIndex();
  452. expect(result).toEqual(1);
  453. });
  454. it("should correctly get governance data source", async () => {
  455. // Deploy contract without initial governance data source
  456. await deployContract();
  457. // Check initial value (should be empty)
  458. let result = await pythTest.getGovernanceDataSource();
  459. expect(result).toEqual(null);
  460. // Deploy contract with initial governance data source
  461. await deployContract(
  462. BTC_PRICE_FEED_ID,
  463. PRICE,
  464. EMA_PRICE,
  465. SINGLE_UPDATE_FEE,
  466. DATA_SOURCES,
  467. 0,
  468. [TEST_GUARDIAN_ADDRESS1],
  469. 60051,
  470. 1,
  471. "0000000000000000000000000000000000000000000000000000000000000004",
  472. TEST_GOVERNANCE_DATA_SOURCES[0]
  473. );
  474. // Check that the governance data source is set
  475. result = await pythTest.getGovernanceDataSource();
  476. expect(result).toEqual(TEST_GOVERNANCE_DATA_SOURCES[0]);
  477. // Execute governance action to change data source
  478. await pythTest.sendExecuteGovernanceAction(
  479. deployer.getSender(),
  480. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  481. );
  482. // Check that the data source has changed
  483. result = await pythTest.getGovernanceDataSource();
  484. expect(result).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]);
  485. });
  486. it("should correctly get single update fee", async () => {
  487. await deployContract();
  488. // Get the initial fee
  489. const result = await pythTest.getSingleUpdateFee();
  490. expect(result).toBe(SINGLE_UPDATE_FEE);
  491. });
  492. it("should execute set data sources governance instruction", async () => {
  493. await deployContract(
  494. BTC_PRICE_FEED_ID,
  495. PRICE,
  496. EMA_PRICE,
  497. SINGLE_UPDATE_FEE,
  498. DATA_SOURCES,
  499. 0,
  500. [TEST_GUARDIAN_ADDRESS1],
  501. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  502. 1,
  503. "0000000000000000000000000000000000000000000000000000000000000004",
  504. TEST_GOVERNANCE_DATA_SOURCES[0]
  505. );
  506. // Execute the governance action
  507. const result = await pythTest.sendExecuteGovernanceAction(
  508. deployer.getSender(),
  509. Buffer.from(PYTH_SET_DATA_SOURCES, "hex")
  510. );
  511. expect(result.transactions).toHaveTransaction({
  512. from: deployer.address,
  513. to: pythTest.address,
  514. success: true,
  515. });
  516. // Verify that the new data sources are set correctly
  517. const newDataSources: DataSource[] = [
  518. {
  519. emitterChain: 1,
  520. emitterAddress:
  521. "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
  522. },
  523. {
  524. emitterChain: 3,
  525. emitterAddress:
  526. "000000000000000000000000000000000000000000000000000000000000012d",
  527. },
  528. ];
  529. for (const dataSource of newDataSources) {
  530. const isValid = await pythTest.getIsValidDataSource(dataSource);
  531. expect(isValid).toBe(true);
  532. }
  533. // Verify that the old data source is no longer valid
  534. const oldDataSource = DATA_SOURCES[0];
  535. const oldDataSourceIsValid = await pythTest.getIsValidDataSource(
  536. oldDataSource
  537. );
  538. expect(oldDataSourceIsValid).toBe(false);
  539. });
  540. it("should execute set fee governance instruction", async () => {
  541. await deployContract(
  542. BTC_PRICE_FEED_ID,
  543. PRICE,
  544. EMA_PRICE,
  545. SINGLE_UPDATE_FEE,
  546. DATA_SOURCES,
  547. 0,
  548. [TEST_GUARDIAN_ADDRESS1],
  549. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  550. 1,
  551. "0000000000000000000000000000000000000000000000000000000000000004",
  552. TEST_GOVERNANCE_DATA_SOURCES[0]
  553. );
  554. // Get the initial fee
  555. const initialFee = await pythTest.getSingleUpdateFee();
  556. expect(initialFee).toBe(SINGLE_UPDATE_FEE);
  557. // Execute the governance action
  558. const result = await pythTest.sendExecuteGovernanceAction(
  559. deployer.getSender(),
  560. Buffer.from(PYTH_SET_FEE, "hex")
  561. );
  562. expect(result.transactions).toHaveTransaction({
  563. from: deployer.address,
  564. to: pythTest.address,
  565. success: true,
  566. });
  567. // Get the new fee
  568. const newFee = await pythTest.getSingleUpdateFee();
  569. expect(newFee).toBe(4200); // The new fee value is 4200 in the PYTH_SET_FEE payload
  570. // Verify that the new fee is used for updates
  571. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  572. const updateFee = await pythTest.getUpdateFee(updateData);
  573. expect(updateFee).toBe(8400); // There are two price updates in HERMES_BTC_ETH_UPDATE
  574. });
  575. it("should execute authorize governance data source transfer", async () => {
  576. await deployContract(
  577. BTC_PRICE_FEED_ID,
  578. PRICE,
  579. EMA_PRICE,
  580. SINGLE_UPDATE_FEE,
  581. DATA_SOURCES,
  582. 0,
  583. [TEST_GUARDIAN_ADDRESS1],
  584. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  585. 1,
  586. "0000000000000000000000000000000000000000000000000000000000000004",
  587. TEST_GOVERNANCE_DATA_SOURCES[0]
  588. );
  589. // Get the initial governance data source index
  590. const initialIndex = await pythTest.getGovernanceDataSourceIndex();
  591. expect(initialIndex).toEqual(0); // Initial value should be 0
  592. // Get the initial governance data source
  593. const initialDataSource = await pythTest.getGovernanceDataSource();
  594. expect(initialDataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[0]);
  595. // Get the initial last executed governance sequence
  596. const initialSequence = await pythTest.getLastExecutedGovernanceSequence();
  597. expect(initialSequence).toEqual(0); // Initial value should be 0
  598. // Execute the governance action
  599. const result = await pythTest.sendExecuteGovernanceAction(
  600. deployer.getSender(),
  601. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  602. );
  603. expect(result.transactions).toHaveTransaction({
  604. from: deployer.address,
  605. to: pythTest.address,
  606. success: true,
  607. });
  608. // Get the new governance data source index
  609. const newIndex = await pythTest.getGovernanceDataSourceIndex();
  610. expect(newIndex).toEqual(1); // The new index value should match the one in the test payload
  611. // Get the new governance data source
  612. const newDataSource = await pythTest.getGovernanceDataSource();
  613. expect(newDataSource).not.toEqual(initialDataSource); // The data source should have changed
  614. expect(newDataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]); // The data source should have changed
  615. // Get the new last executed governance sequence
  616. const newSequence = await pythTest.getLastExecutedGovernanceSequence();
  617. expect(newSequence).toBeGreaterThan(initialSequence); // The sequence should have increased
  618. expect(newSequence).toBe(1);
  619. });
  620. it("should fail when executing request governance data source transfer directly", async () => {
  621. await deployContract(
  622. BTC_PRICE_FEED_ID,
  623. PRICE,
  624. EMA_PRICE,
  625. SINGLE_UPDATE_FEE,
  626. DATA_SOURCES,
  627. 0,
  628. [TEST_GUARDIAN_ADDRESS1],
  629. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  630. 1,
  631. "0000000000000000000000000000000000000000000000000000000000000004",
  632. TEST_GOVERNANCE_DATA_SOURCES[1]
  633. );
  634. const result = await pythTest.sendExecuteGovernanceAction(
  635. deployer.getSender(),
  636. Buffer.from(PYTH_REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  637. );
  638. // Check that the transaction did not succeed
  639. expect(result.transactions).toHaveTransaction({
  640. from: deployer.address,
  641. to: pythTest.address,
  642. success: false,
  643. exitCode: 1012, // ERROR_INVALID_GOVERNANCE_ACTION
  644. });
  645. // Verify that the governance data source index hasn't changed
  646. const index = await pythTest.getGovernanceDataSourceIndex();
  647. expect(index).toEqual(0); // Should still be the initial value
  648. // Verify that the governance data source hasn't changed
  649. const dataSource = await pythTest.getGovernanceDataSource();
  650. expect(dataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]); // Should still be the initial value
  651. });
  652. it("should fail to execute governance action with invalid governance data source", async () => {
  653. await deployContract(
  654. BTC_PRICE_FEED_ID,
  655. PRICE,
  656. EMA_PRICE,
  657. SINGLE_UPDATE_FEE,
  658. DATA_SOURCES,
  659. 0,
  660. [TEST_GUARDIAN_ADDRESS1],
  661. 60051,
  662. 1,
  663. "0000000000000000000000000000000000000000000000000000000000000004",
  664. TEST_GOVERNANCE_DATA_SOURCES[1]
  665. );
  666. const result = await pythTest.sendExecuteGovernanceAction(
  667. deployer.getSender(),
  668. Buffer.from(PYTH_SET_FEE, "hex")
  669. );
  670. expect(result.transactions).toHaveTransaction({
  671. from: deployer.address,
  672. to: pythTest.address,
  673. success: false,
  674. exitCode: 2013, // ERROR_INVALID_GOVERNANCE_DATA_SOURCE
  675. });
  676. });
  677. it("should fail to execute governance action with old sequence number", async () => {
  678. await deployContract(
  679. BTC_PRICE_FEED_ID,
  680. PRICE,
  681. EMA_PRICE,
  682. SINGLE_UPDATE_FEE,
  683. DATA_SOURCES,
  684. 0,
  685. [TEST_GUARDIAN_ADDRESS1],
  686. 60051,
  687. 1,
  688. "0000000000000000000000000000000000000000000000000000000000000004",
  689. TEST_GOVERNANCE_DATA_SOURCES[0]
  690. );
  691. // Execute a governance action to increase the sequence number
  692. await pythTest.sendExecuteGovernanceAction(
  693. deployer.getSender(),
  694. Buffer.from(PYTH_SET_FEE, "hex")
  695. );
  696. // Try to execute the same governance action again
  697. const result = await pythTest.sendExecuteGovernanceAction(
  698. deployer.getSender(),
  699. Buffer.from(PYTH_SET_FEE, "hex")
  700. );
  701. expect(result.transactions).toHaveTransaction({
  702. from: deployer.address,
  703. to: pythTest.address,
  704. success: false,
  705. exitCode: 2014, // ERROR_OLD_GOVERNANCE_MESSAGE
  706. });
  707. });
  708. it("should fail to execute governance action with invalid chain ID", async () => {
  709. const invalidChainId = 999;
  710. await deployContract(
  711. BTC_PRICE_FEED_ID,
  712. PRICE,
  713. EMA_PRICE,
  714. SINGLE_UPDATE_FEE,
  715. DATA_SOURCES,
  716. 0,
  717. [TEST_GUARDIAN_ADDRESS1],
  718. invalidChainId,
  719. 1,
  720. "0000000000000000000000000000000000000000000000000000000000000004",
  721. TEST_GOVERNANCE_DATA_SOURCES[0]
  722. );
  723. const result = await pythTest.sendExecuteGovernanceAction(
  724. deployer.getSender(),
  725. Buffer.from(PYTH_SET_FEE, "hex")
  726. );
  727. expect(result.transactions).toHaveTransaction({
  728. from: deployer.address,
  729. to: pythTest.address,
  730. success: false,
  731. exitCode: 2015, // ERROR_INVALID_GOVERNANCE_TARGET
  732. });
  733. });
  734. it("should successfully upgrade the contract", async () => {
  735. // Compile the upgraded contract
  736. const upgradedCode = await compile("PythTestUpgraded");
  737. const upgradedCodeHash = upgradedCode.hash();
  738. // Create the authorize upgrade payload
  739. const authorizeUpgradePayload =
  740. createAuthorizeUpgradePayload(upgradedCodeHash);
  741. const authorizeUpgradeVaa = createVAA("Uint8Array", {
  742. guardianSet: 0,
  743. timestamp: 0,
  744. nonce: 0,
  745. emitterChain: "Solana",
  746. emitterAddress: new UniversalAddress(new Uint8Array(32)),
  747. sequence: 1n,
  748. consistencyLevel: 0,
  749. signatures: [],
  750. payload: authorizeUpgradePayload,
  751. });
  752. const guardianSet = mocks.devnetGuardianSet();
  753. guardianSet.setSignatures(authorizeUpgradeVaa);
  754. await deployContract(
  755. BTC_PRICE_FEED_ID,
  756. PRICE,
  757. EMA_PRICE,
  758. SINGLE_UPDATE_FEE,
  759. DATA_SOURCES,
  760. 0,
  761. [TEST_GUARDIAN_ADDRESS2],
  762. 1,
  763. 1,
  764. "0000000000000000000000000000000000000000000000000000000000000000",
  765. TEST_GOVERNANCE_DATA_SOURCES[2]
  766. );
  767. // Execute the upgrade
  768. const sendExecuteGovernanceActionResult =
  769. await pythTest.sendExecuteGovernanceAction(
  770. deployer.getSender(),
  771. Buffer.from(serialize(authorizeUpgradeVaa))
  772. );
  773. expect(sendExecuteGovernanceActionResult.transactions).toHaveTransaction({
  774. from: deployer.address,
  775. to: pythTest.address,
  776. success: true,
  777. });
  778. // Execute the upgrade
  779. const sendUpgradeContractResult = await pythTest.sendUpgradeContract(
  780. deployer.getSender(),
  781. upgradedCode
  782. );
  783. expect(sendUpgradeContractResult.transactions).toHaveTransaction({
  784. from: deployer.address,
  785. to: pythTest.address,
  786. success: true,
  787. });
  788. // Verify that the contract has been upgraded by calling a new method
  789. const newMethodResult = await pythTest.getNewFunction();
  790. expect(newMethodResult).toBe(1);
  791. });
  792. it("should fail to upgrade the contract with modified code", async () => {
  793. // Compile the upgraded contract
  794. const upgradedCode = await compile("PythTestUpgraded");
  795. const upgradedCodeHash = upgradedCode.hash();
  796. // Create the authorize upgrade payload
  797. const authorizeUpgradePayload =
  798. createAuthorizeUpgradePayload(upgradedCodeHash);
  799. const authorizeUpgradeVaa = createVAA("Uint8Array", {
  800. guardianSet: 0,
  801. timestamp: 0,
  802. nonce: 0,
  803. emitterChain: "Solana",
  804. emitterAddress: new UniversalAddress(new Uint8Array(32)),
  805. sequence: 1n,
  806. consistencyLevel: 0,
  807. signatures: [],
  808. payload: authorizeUpgradePayload,
  809. });
  810. const guardianSet = mocks.devnetGuardianSet();
  811. guardianSet.setSignatures(authorizeUpgradeVaa);
  812. await deployContract(
  813. BTC_PRICE_FEED_ID,
  814. PRICE,
  815. EMA_PRICE,
  816. SINGLE_UPDATE_FEE,
  817. DATA_SOURCES,
  818. 0,
  819. [TEST_GUARDIAN_ADDRESS2],
  820. 1,
  821. 1,
  822. "0000000000000000000000000000000000000000000000000000000000000000",
  823. TEST_GOVERNANCE_DATA_SOURCES[2]
  824. );
  825. // Execute the upgrade authorization
  826. const sendExecuteGovernanceActionResult =
  827. await pythTest.sendExecuteGovernanceAction(
  828. deployer.getSender(),
  829. Buffer.from(serialize(authorizeUpgradeVaa))
  830. );
  831. expect(sendExecuteGovernanceActionResult.transactions).toHaveTransaction({
  832. from: deployer.address,
  833. to: pythTest.address,
  834. success: true,
  835. });
  836. // Attempt to execute the upgrade with a different code
  837. const wormholeTestCode = await compile("WormholeTest");
  838. const sendUpgradeContractResult = await pythTest.sendUpgradeContract(
  839. deployer.getSender(),
  840. wormholeTestCode
  841. );
  842. // Expect the transaction to fail
  843. expect(sendUpgradeContractResult.transactions).toHaveTransaction({
  844. from: deployer.address,
  845. to: pythTest.address,
  846. success: false,
  847. exitCode: 2018, // ERROR_INVALID_CODE_HASH
  848. });
  849. // Verify that the contract has not been upgraded by attempting to call the new method
  850. await expect(pythTest.getNewFunction()).rejects.toThrow();
  851. });
  852. it("should successfully parse price feed updates", async () => {
  853. await deployContract();
  854. await updateGuardianSets(pythTest, deployer);
  855. const sentValue = toNano("1");
  856. const result = await pythTest.sendParsePriceFeedUpdates(
  857. deployer.getSender(),
  858. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex"),
  859. sentValue,
  860. [BTC_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
  861. HERMES_BTC_PUBLISH_TIME,
  862. HERMES_BTC_PUBLISH_TIME
  863. );
  864. // Verify transaction success and message count
  865. expect(result.transactions).toHaveTransaction({
  866. from: deployer.address,
  867. to: pythTest.address,
  868. success: true,
  869. outMessagesCount: 1,
  870. });
  871. // Get the output message
  872. const outMessage = result.transactions[1].outMessages.values()[0];
  873. // Verify excess value is returned
  874. expect(
  875. (outMessage.info as CommonMessageInfoInternal).value.coins
  876. ).toBeGreaterThan(0);
  877. const cs = outMessage.body.beginParse();
  878. // Verify message header
  879. const op = cs.loadUint(32);
  880. expect(op).toBe(5); // OP_PARSE_PRICE_FEED_UPDATES
  881. // Verify number of price feeds
  882. const numPriceFeeds = cs.loadUint(8);
  883. expect(numPriceFeeds).toBe(2); // We expect BTC and ETH price feeds
  884. // Load and verify price feeds
  885. const priceFeedsCell = cs.loadRef();
  886. let currentCell = priceFeedsCell;
  887. // First price feed (BTC)
  888. const btcCs = currentCell.beginParse();
  889. const btcPriceId =
  890. "0x" + btcCs.loadUintBig(256).toString(16).padStart(64, "0");
  891. expect(btcPriceId).toBe(BTC_PRICE_FEED_ID);
  892. const btcPriceFeedCell = btcCs.loadRef();
  893. const btcPriceFeedSlice = btcPriceFeedCell.beginParse();
  894. // Verify BTC current price
  895. const btcCurrentPriceCell = btcPriceFeedSlice.loadRef();
  896. const btcCurrentPrice = btcCurrentPriceCell.beginParse();
  897. expect(btcCurrentPrice.loadInt(64)).toBe(HERMES_BTC_PRICE);
  898. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_CONF);
  899. expect(btcCurrentPrice.loadInt(32)).toBe(HERMES_BTC_EXPO);
  900. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_PUBLISH_TIME);
  901. // Verify BTC EMA price
  902. const btcEmaPriceCell = btcPriceFeedSlice.loadRef();
  903. const btcEmaPrice = btcEmaPriceCell.beginParse();
  904. expect(btcEmaPrice.loadInt(64)).toBe(HERMES_BTC_EMA_PRICE);
  905. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_EMA_CONF);
  906. expect(btcEmaPrice.loadInt(32)).toBe(HERMES_BTC_EMA_EXPO);
  907. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_PUBLISH_TIME);
  908. // Move to ETH price feed
  909. currentCell = btcCs.loadRef();
  910. // Second price feed (ETH)
  911. const ethCs = currentCell.beginParse();
  912. const ethPriceId =
  913. "0x" + ethCs.loadUintBig(256).toString(16).padStart(64, "0");
  914. expect(ethPriceId).toBe(ETH_PRICE_FEED_ID);
  915. const ethPriceFeedCell = ethCs.loadRef();
  916. const ethPriceFeedSlice = ethPriceFeedCell.beginParse();
  917. // Verify ETH current price
  918. const ethCurrentPriceCell = ethPriceFeedSlice.loadRef();
  919. const ethCurrentPrice = ethCurrentPriceCell.beginParse();
  920. expect(ethCurrentPrice.loadInt(64)).toBe(HERMES_ETH_PRICE);
  921. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_CONF);
  922. expect(ethCurrentPrice.loadInt(32)).toBe(HERMES_ETH_EXPO);
  923. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_PUBLISH_TIME);
  924. // Verify ETH EMA price
  925. const ethEmaPriceCell = ethPriceFeedSlice.loadRef();
  926. const ethEmaPrice = ethEmaPriceCell.beginParse();
  927. expect(ethEmaPrice.loadInt(64)).toBe(HERMES_ETH_EMA_PRICE);
  928. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_EMA_CONF);
  929. expect(ethEmaPrice.loadInt(32)).toBe(HERMES_ETH_EMA_EXPO);
  930. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_PUBLISH_TIME);
  931. // Verify this is the end of the chain
  932. expect(ethCs.remainingRefs).toBe(0);
  933. });
  934. it("should successfully parse unique price feed updates", async () => {
  935. await deployContract();
  936. await updateGuardianSets(pythTest, deployer);
  937. const sentValue = toNano("1");
  938. const result = await pythTest.sendParseUniquePriceFeedUpdates(
  939. deployer.getSender(),
  940. Buffer.from(HERMES_BTC_ETH_UNIQUE_UPDATE, "hex"),
  941. sentValue,
  942. [BTC_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
  943. HERMES_BTC_PUBLISH_TIME,
  944. 60
  945. );
  946. // Verify transaction success and message count
  947. expect(result.transactions).toHaveTransaction({
  948. from: deployer.address,
  949. to: pythTest.address,
  950. success: true,
  951. outMessagesCount: 1,
  952. });
  953. // Get the output message
  954. const outMessage = result.transactions[1].outMessages.values()[0];
  955. // Verify excess value is returned
  956. expect(
  957. (outMessage.info as CommonMessageInfoInternal).value.coins
  958. ).toBeGreaterThan(0);
  959. const cs = outMessage.body.beginParse();
  960. // Verify message header
  961. const op = cs.loadUint(32);
  962. expect(op).toBe(6); // OP_PARSE_UNIQUE_PRICE_FEED_UPDATES
  963. // Verify number of price feeds
  964. const numPriceFeeds = cs.loadUint(8);
  965. expect(numPriceFeeds).toBe(2); // We expect BTC and ETH price feeds
  966. // Load and verify price feeds
  967. const priceFeedsCell = cs.loadRef();
  968. let currentCell = priceFeedsCell;
  969. // First price feed (BTC)
  970. const btcCs = currentCell.beginParse();
  971. const btcPriceId =
  972. "0x" + btcCs.loadUintBig(256).toString(16).padStart(64, "0");
  973. expect(btcPriceId).toBe(BTC_PRICE_FEED_ID);
  974. const btcPriceFeedCell = btcCs.loadRef();
  975. const btcPriceFeedSlice = btcPriceFeedCell.beginParse();
  976. // Verify BTC current price
  977. const btcCurrentPriceCell = btcPriceFeedSlice.loadRef();
  978. const btcCurrentPrice = btcCurrentPriceCell.beginParse();
  979. expect(btcCurrentPrice.loadInt(64)).toBe(HERMES_BTC_UNIQUE_PRICE);
  980. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_CONF);
  981. expect(btcCurrentPrice.loadInt(32)).toBe(HERMES_BTC_UNIQUE_EXPO);
  982. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_PUBLISH_TIME);
  983. // Verify BTC EMA price
  984. const btcEmaPriceCell = btcPriceFeedSlice.loadRef();
  985. const btcEmaPrice = btcEmaPriceCell.beginParse();
  986. expect(btcEmaPrice.loadInt(64)).toBe(HERMES_BTC_UNIQUE_EMA_PRICE);
  987. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_EMA_CONF);
  988. expect(btcEmaPrice.loadInt(32)).toBe(HERMES_BTC_UNIQUE_EMA_EXPO);
  989. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_EMA_PUBLISH_TIME);
  990. // Move to ETH price feed
  991. currentCell = btcCs.loadRef();
  992. // Second price feed (ETH)
  993. const ethCs = currentCell.beginParse();
  994. const ethPriceId =
  995. "0x" + ethCs.loadUintBig(256).toString(16).padStart(64, "0");
  996. expect(ethPriceId).toBe(ETH_PRICE_FEED_ID);
  997. const ethPriceFeedCell = ethCs.loadRef();
  998. const ethPriceFeedSlice = ethPriceFeedCell.beginParse();
  999. // Verify ETH current price
  1000. const ethCurrentPriceCell = ethPriceFeedSlice.loadRef();
  1001. const ethCurrentPrice = ethCurrentPriceCell.beginParse();
  1002. expect(ethCurrentPrice.loadInt(64)).toBe(HERMES_ETH_UNIQUE_PRICE);
  1003. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_CONF);
  1004. expect(ethCurrentPrice.loadInt(32)).toBe(HERMES_ETH_UNIQUE_EXPO);
  1005. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_PUBLISH_TIME);
  1006. // Verify ETH EMA price
  1007. const ethEmaPriceCell = ethPriceFeedSlice.loadRef();
  1008. const ethEmaPrice = ethEmaPriceCell.beginParse();
  1009. expect(ethEmaPrice.loadInt(64)).toBe(HERMES_ETH_UNIQUE_EMA_PRICE);
  1010. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_EMA_CONF);
  1011. expect(ethEmaPrice.loadInt(32)).toBe(HERMES_ETH_UNIQUE_EMA_EXPO);
  1012. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_EMA_PUBLISH_TIME);
  1013. // Verify this is the end of the chain
  1014. expect(ethCs.remainingRefs).toBe(0);
  1015. });
  1016. it("should fail to parse invalid price feed updates", async () => {
  1017. await deployContract();
  1018. await updateGuardianSets(pythTest, deployer);
  1019. const invalidUpdateData = Buffer.from("invalid data");
  1020. const result = await pythTest.sendParsePriceFeedUpdates(
  1021. deployer.getSender(),
  1022. invalidUpdateData,
  1023. toNano("1"),
  1024. [BTC_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
  1025. HERMES_BTC_PUBLISH_TIME,
  1026. HERMES_BTC_PUBLISH_TIME
  1027. );
  1028. // Verify transaction success and message count
  1029. expect(result.transactions).toHaveTransaction({
  1030. from: deployer.address,
  1031. to: pythTest.address,
  1032. success: false,
  1033. exitCode: 2002, // ERROR_INVALID_MAGIC
  1034. });
  1035. });
  1036. it("should fail to parse price feed updates within range", async () => {
  1037. await deployContract();
  1038. await updateGuardianSets(pythTest, deployer);
  1039. const sentValue = toNano("1");
  1040. const result = await pythTest.sendParseUniquePriceFeedUpdates(
  1041. deployer.getSender(),
  1042. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex"),
  1043. sentValue,
  1044. [BTC_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
  1045. HERMES_BTC_PUBLISH_TIME + 1,
  1046. HERMES_BTC_PUBLISH_TIME + 1
  1047. );
  1048. // Verify transaction success and message count
  1049. expect(result.transactions).toHaveTransaction({
  1050. from: deployer.address,
  1051. to: pythTest.address,
  1052. success: false,
  1053. exitCode: 2020, // ERROR_PRICE_FEED_NOT_FOUND_WITHIN_RANGE
  1054. });
  1055. });
  1056. it("should fail to parse unique price feed updates", async () => {
  1057. await deployContract();
  1058. await updateGuardianSets(pythTest, deployer);
  1059. const sentValue = toNano("1");
  1060. const result = await pythTest.sendParseUniquePriceFeedUpdates(
  1061. deployer.getSender(),
  1062. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex"),
  1063. sentValue,
  1064. [BTC_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
  1065. HERMES_BTC_PUBLISH_TIME,
  1066. 60
  1067. );
  1068. // Verify transaction success and message count
  1069. expect(result.transactions).toHaveTransaction({
  1070. from: deployer.address,
  1071. to: pythTest.address,
  1072. success: false,
  1073. exitCode: 2020, // ERROR_PRICE_FEED_NOT_FOUND_WITHIN_RANGE
  1074. });
  1075. });
  1076. it("should successfully parse price feed updates in price ids order", async () => {
  1077. await deployContract();
  1078. await updateGuardianSets(pythTest, deployer);
  1079. const sentValue = toNano("1");
  1080. const result = await pythTest.sendParsePriceFeedUpdates(
  1081. deployer.getSender(),
  1082. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex"),
  1083. sentValue,
  1084. [ETH_PRICE_FEED_ID, BTC_PRICE_FEED_ID],
  1085. HERMES_BTC_PUBLISH_TIME,
  1086. HERMES_BTC_PUBLISH_TIME
  1087. );
  1088. // Verify transaction success and message count
  1089. expect(result.transactions).toHaveTransaction({
  1090. from: deployer.address,
  1091. to: pythTest.address,
  1092. success: true,
  1093. outMessagesCount: 1,
  1094. });
  1095. // Get the output message
  1096. const outMessage = result.transactions[1].outMessages.values()[0];
  1097. // Verify excess value is returned
  1098. expect(
  1099. (outMessage.info as CommonMessageInfoInternal).value.coins
  1100. ).toBeGreaterThan(0);
  1101. const cs = outMessage.body.beginParse();
  1102. // Verify message header
  1103. const op = cs.loadUint(32);
  1104. expect(op).toBe(5); // OP_PARSE_PRICE_FEED_UPDATES
  1105. // Verify number of price feeds
  1106. const numPriceFeeds = cs.loadUint(8);
  1107. expect(numPriceFeeds).toBe(2); // We expect BTC and ETH price feeds
  1108. // Load and verify price feeds
  1109. const priceFeedsCell = cs.loadRef();
  1110. let currentCell = priceFeedsCell;
  1111. // First price feed (ETH)
  1112. const ethCs = currentCell.beginParse();
  1113. const ethPriceId =
  1114. "0x" + ethCs.loadUintBig(256).toString(16).padStart(64, "0");
  1115. expect(ethPriceId).toBe(ETH_PRICE_FEED_ID);
  1116. const ethPriceFeedCell = ethCs.loadRef();
  1117. const ethPriceFeedSlice = ethPriceFeedCell.beginParse();
  1118. // Verify ETH current price
  1119. const ethCurrentPriceCell = ethPriceFeedSlice.loadRef();
  1120. const ethCurrentPrice = ethCurrentPriceCell.beginParse();
  1121. expect(ethCurrentPrice.loadInt(64)).toBe(HERMES_ETH_PRICE);
  1122. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_CONF);
  1123. expect(ethCurrentPrice.loadInt(32)).toBe(HERMES_ETH_EXPO);
  1124. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_PUBLISH_TIME);
  1125. // Verify ETH EMA price
  1126. const ethEmaPriceCell = ethPriceFeedSlice.loadRef();
  1127. const ethEmaPrice = ethEmaPriceCell.beginParse();
  1128. expect(ethEmaPrice.loadInt(64)).toBe(HERMES_ETH_EMA_PRICE);
  1129. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_EMA_CONF);
  1130. expect(ethEmaPrice.loadInt(32)).toBe(HERMES_ETH_EMA_EXPO);
  1131. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_PUBLISH_TIME);
  1132. // Move to ETH price feed
  1133. currentCell = ethCs.loadRef();
  1134. // Second price feed (BTC)
  1135. const btcCs = currentCell.beginParse();
  1136. const btcPriceId =
  1137. "0x" + btcCs.loadUintBig(256).toString(16).padStart(64, "0");
  1138. expect(btcPriceId).toBe(BTC_PRICE_FEED_ID);
  1139. const btcPriceFeedCell = btcCs.loadRef();
  1140. const btcPriceFeedSlice = btcPriceFeedCell.beginParse();
  1141. // Verify BTC current price
  1142. const btcCurrentPriceCell = btcPriceFeedSlice.loadRef();
  1143. const btcCurrentPrice = btcCurrentPriceCell.beginParse();
  1144. expect(btcCurrentPrice.loadInt(64)).toBe(HERMES_BTC_PRICE);
  1145. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_CONF);
  1146. expect(btcCurrentPrice.loadInt(32)).toBe(HERMES_BTC_EXPO);
  1147. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_PUBLISH_TIME);
  1148. // Verify BTC EMA price
  1149. const btcEmaPriceCell = btcPriceFeedSlice.loadRef();
  1150. const btcEmaPrice = btcEmaPriceCell.beginParse();
  1151. expect(btcEmaPrice.loadInt(64)).toBe(HERMES_BTC_EMA_PRICE);
  1152. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_EMA_CONF);
  1153. expect(btcEmaPrice.loadInt(32)).toBe(HERMES_BTC_EMA_EXPO);
  1154. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_PUBLISH_TIME);
  1155. // Verify this is the end of the chain
  1156. expect(ethCs.remainingRefs).toBe(0);
  1157. });
  1158. it("should successfully parse unique price feed updates in price ids order", async () => {
  1159. await deployContract();
  1160. await updateGuardianSets(pythTest, deployer);
  1161. const sentValue = toNano("1");
  1162. const result = await pythTest.sendParseUniquePriceFeedUpdates(
  1163. deployer.getSender(),
  1164. Buffer.from(HERMES_BTC_ETH_UNIQUE_UPDATE, "hex"),
  1165. sentValue,
  1166. [ETH_PRICE_FEED_ID, BTC_PRICE_FEED_ID],
  1167. HERMES_BTC_PUBLISH_TIME,
  1168. 60
  1169. );
  1170. // Verify transaction success and message count
  1171. expect(result.transactions).toHaveTransaction({
  1172. from: deployer.address,
  1173. to: pythTest.address,
  1174. success: true,
  1175. outMessagesCount: 1,
  1176. });
  1177. // Get the output message
  1178. const outMessage = result.transactions[1].outMessages.values()[0];
  1179. // Verify excess value is returned
  1180. expect(
  1181. (outMessage.info as CommonMessageInfoInternal).value.coins
  1182. ).toBeGreaterThan(0);
  1183. const cs = outMessage.body.beginParse();
  1184. // Verify message header
  1185. const op = cs.loadUint(32);
  1186. expect(op).toBe(6); // OP_PARSE_UNIQUE_PRICE_FEED_UPDATES
  1187. // Verify number of price feeds
  1188. const numPriceFeeds = cs.loadUint(8);
  1189. expect(numPriceFeeds).toBe(2); // We expect BTC and ETH price feeds
  1190. // Load and verify price feeds
  1191. const priceFeedsCell = cs.loadRef();
  1192. let currentCell = priceFeedsCell;
  1193. // First price feed (ETH)
  1194. const ethCs = currentCell.beginParse();
  1195. const ethPriceId =
  1196. "0x" + ethCs.loadUintBig(256).toString(16).padStart(64, "0");
  1197. expect(ethPriceId).toBe(ETH_PRICE_FEED_ID);
  1198. const ethPriceFeedCell = ethCs.loadRef();
  1199. const ethPriceFeedSlice = ethPriceFeedCell.beginParse();
  1200. // Verify ETH current price
  1201. const ethCurrentPriceCell = ethPriceFeedSlice.loadRef();
  1202. const ethCurrentPrice = ethCurrentPriceCell.beginParse();
  1203. expect(ethCurrentPrice.loadInt(64)).toBe(HERMES_ETH_UNIQUE_PRICE);
  1204. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_CONF);
  1205. expect(ethCurrentPrice.loadInt(32)).toBe(HERMES_ETH_UNIQUE_EXPO);
  1206. expect(ethCurrentPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_PUBLISH_TIME);
  1207. // Verify ETH EMA price
  1208. const ethEmaPriceCell = ethPriceFeedSlice.loadRef();
  1209. const ethEmaPrice = ethEmaPriceCell.beginParse();
  1210. expect(ethEmaPrice.loadInt(64)).toBe(HERMES_ETH_UNIQUE_EMA_PRICE);
  1211. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_EMA_CONF);
  1212. expect(ethEmaPrice.loadInt(32)).toBe(HERMES_ETH_UNIQUE_EMA_EXPO);
  1213. expect(ethEmaPrice.loadUint(64)).toBe(HERMES_ETH_UNIQUE_EMA_PUBLISH_TIME);
  1214. currentCell = ethCs.loadRef();
  1215. // Second price feed (BTC)
  1216. const btcCs = currentCell.beginParse();
  1217. const btcPriceId =
  1218. "0x" + btcCs.loadUintBig(256).toString(16).padStart(64, "0");
  1219. expect(btcPriceId).toBe(BTC_PRICE_FEED_ID);
  1220. const btcPriceFeedCell = btcCs.loadRef();
  1221. const btcPriceFeedSlice = btcPriceFeedCell.beginParse();
  1222. // Verify BTC current price
  1223. const btcCurrentPriceCell = btcPriceFeedSlice.loadRef();
  1224. const btcCurrentPrice = btcCurrentPriceCell.beginParse();
  1225. expect(btcCurrentPrice.loadInt(64)).toBe(HERMES_BTC_UNIQUE_PRICE);
  1226. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_CONF);
  1227. expect(btcCurrentPrice.loadInt(32)).toBe(HERMES_BTC_UNIQUE_EXPO);
  1228. expect(btcCurrentPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_PUBLISH_TIME);
  1229. // Verify BTC EMA price
  1230. const btcEmaPriceCell = btcPriceFeedSlice.loadRef();
  1231. const btcEmaPrice = btcEmaPriceCell.beginParse();
  1232. expect(btcEmaPrice.loadInt(64)).toBe(HERMES_BTC_UNIQUE_EMA_PRICE);
  1233. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_EMA_CONF);
  1234. expect(btcEmaPrice.loadInt(32)).toBe(HERMES_BTC_UNIQUE_EMA_EXPO);
  1235. expect(btcEmaPrice.loadUint(64)).toBe(HERMES_BTC_UNIQUE_EMA_PUBLISH_TIME);
  1236. // Verify this is the end of the chain
  1237. expect(btcCs.remainingRefs).toBe(0);
  1238. });
  1239. });