PythTest.spec.ts 55 KB

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