PythTest.spec.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
  2. import { Cell, 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. } from "./utils/pyth";
  22. import { GUARDIAN_SET_0, MAINNET_UPGRADE_VAAS } from "./utils/wormhole";
  23. import { DataSource } from "@pythnetwork/xc-admin-common";
  24. import { createAuthorizeUpgradePayload } from "./utils";
  25. import {
  26. UniversalAddress,
  27. createVAA,
  28. serialize,
  29. } from "@wormhole-foundation/sdk-definitions";
  30. import { mocks } from "@wormhole-foundation/sdk-definitions/testing";
  31. import { calculateUpdatePriceFeedsFee } from "@pythnetwork/pyth-ton-js";
  32. const TIME_PERIOD = 60;
  33. const PRICE = new Price({
  34. price: "1",
  35. conf: "2",
  36. expo: 3,
  37. publishTime: 4,
  38. });
  39. const EMA_PRICE = new Price({
  40. price: "5",
  41. conf: "6",
  42. expo: 7,
  43. publishTime: 8,
  44. });
  45. const SINGLE_UPDATE_FEE = 1;
  46. const DATA_SOURCES: DataSource[] = [
  47. {
  48. emitterChain: 26,
  49. emitterAddress:
  50. "e101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71",
  51. },
  52. ];
  53. const TEST_GOVERNANCE_DATA_SOURCES: DataSource[] = [
  54. {
  55. emitterChain: 1,
  56. emitterAddress:
  57. "0000000000000000000000000000000000000000000000000000000000000029",
  58. },
  59. {
  60. emitterChain: 2,
  61. emitterAddress:
  62. "000000000000000000000000000000000000000000000000000000000000002b",
  63. },
  64. {
  65. emitterChain: 1,
  66. emitterAddress:
  67. "0000000000000000000000000000000000000000000000000000000000000000",
  68. },
  69. ];
  70. describe("PythTest", () => {
  71. let code: Cell;
  72. beforeAll(async () => {
  73. code = await compile("PythTest");
  74. });
  75. let blockchain: Blockchain;
  76. let deployer: SandboxContract<TreasuryContract>;
  77. let pythTest: SandboxContract<PythTest>;
  78. beforeEach(async () => {
  79. blockchain = await Blockchain.create();
  80. deployer = await blockchain.treasury("deployer");
  81. });
  82. async function deployContract(
  83. priceFeedId: HexString = BTC_PRICE_FEED_ID,
  84. price: Price = PRICE,
  85. emaPrice: Price = EMA_PRICE,
  86. singleUpdateFee: number = SINGLE_UPDATE_FEE,
  87. dataSources: DataSource[] = DATA_SOURCES,
  88. guardianSetIndex: number = 0,
  89. guardianSet: string[] = GUARDIAN_SET_0,
  90. chainId: number = 1,
  91. governanceChainId: number = 1,
  92. governanceContract: string = "0000000000000000000000000000000000000000000000000000000000000004",
  93. governanceDataSource?: DataSource
  94. ) {
  95. const config: PythTestConfig = {
  96. priceFeedId,
  97. price,
  98. emaPrice,
  99. singleUpdateFee,
  100. dataSources,
  101. guardianSetIndex,
  102. guardianSet,
  103. chainId,
  104. governanceChainId,
  105. governanceContract,
  106. governanceDataSource,
  107. };
  108. pythTest = blockchain.openContract(PythTest.createFromConfig(config, code));
  109. const deployResult = await pythTest.sendDeploy(
  110. deployer.getSender(),
  111. toNano("0.05")
  112. );
  113. expect(deployResult.transactions).toHaveTransaction({
  114. from: deployer.address,
  115. to: pythTest.address,
  116. deploy: true,
  117. success: true,
  118. });
  119. }
  120. async function updateGuardianSets(
  121. pythTest: SandboxContract<PythTest>,
  122. deployer: SandboxContract<TreasuryContract>
  123. ) {
  124. for (const vaa of MAINNET_UPGRADE_VAAS) {
  125. const result = await pythTest.sendUpdateGuardianSet(
  126. deployer.getSender(),
  127. Buffer.from(vaa, "hex")
  128. );
  129. expect(result.transactions).toHaveTransaction({
  130. from: deployer.address,
  131. to: pythTest.address,
  132. success: true,
  133. });
  134. }
  135. }
  136. it("should correctly get price unsafe", async () => {
  137. await deployContract();
  138. const result = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  139. expect(result.price).toBe(1);
  140. expect(result.conf).toBe(2);
  141. expect(result.expo).toBe(3);
  142. expect(result.publishTime).toBe(4);
  143. });
  144. it("should correctly get price no older than", async () => {
  145. const timeNow = Math.floor(Date.now() / 1000) - TIME_PERIOD + 5; // 5 seconds buffer
  146. const price = new Price({
  147. price: "1",
  148. conf: "2",
  149. expo: 3,
  150. publishTime: timeNow,
  151. });
  152. await deployContract(BTC_PRICE_FEED_ID, price, EMA_PRICE);
  153. const result = await pythTest.getPriceNoOlderThan(
  154. TIME_PERIOD,
  155. BTC_PRICE_FEED_ID
  156. );
  157. expect(result.price).toBe(1);
  158. expect(result.conf).toBe(2);
  159. expect(result.expo).toBe(3);
  160. expect(result.publishTime).toBe(timeNow);
  161. });
  162. it("should fail to get price no older than", async () => {
  163. await deployContract();
  164. await expect(
  165. pythTest.getPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  166. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  167. });
  168. it("should correctly get ema price no older than", async () => {
  169. const timeNow = Math.floor(Date.now() / 1000) - TIME_PERIOD + 5; // 5 seconds buffer
  170. const emaPrice = new Price({
  171. price: "5",
  172. conf: "6",
  173. expo: 7,
  174. publishTime: timeNow,
  175. });
  176. await deployContract(BTC_PRICE_FEED_ID, PRICE, emaPrice);
  177. const result = await pythTest.getEmaPriceNoOlderThan(
  178. TIME_PERIOD,
  179. BTC_PRICE_FEED_ID
  180. );
  181. expect(result.price).toBe(5);
  182. expect(result.conf).toBe(6);
  183. expect(result.expo).toBe(7);
  184. expect(result.publishTime).toBe(timeNow);
  185. });
  186. it("should fail to get ema price no older than", async () => {
  187. await deployContract();
  188. await expect(
  189. pythTest.getEmaPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  190. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  191. });
  192. it("should correctly get ema price unsafe", async () => {
  193. await deployContract();
  194. const result = await pythTest.getEmaPriceUnsafe(BTC_PRICE_FEED_ID);
  195. expect(result.price).toBe(5);
  196. expect(result.conf).toBe(6);
  197. expect(result.expo).toBe(7);
  198. expect(result.publishTime).toBe(8);
  199. });
  200. it("should correctly get update fee", async () => {
  201. await deployContract();
  202. const result = await pythTest.getUpdateFee(
  203. Buffer.from(HERMES_BTC_ETH_UPDATE, "hex")
  204. );
  205. expect(result).toBe(2);
  206. });
  207. it("should correctly update price feeds", async () => {
  208. await deployContract();
  209. let result;
  210. await updateGuardianSets(pythTest, deployer);
  211. // Check initial prices
  212. const initialBtcPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  213. expect(initialBtcPrice.price).not.toBe(HERMES_BTC_PRICE);
  214. // Expect an error for ETH price feed as it doesn't exist initially
  215. await expect(pythTest.getPriceUnsafe(ETH_PRICE_FEED_ID)).rejects.toThrow(
  216. "Unable to execute get method. Got exit_code: 2000"
  217. ); // ERROR_PRICE_FEED_NOT_FOUND = 2000
  218. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  219. const updateFee = await pythTest.getUpdateFee(updateData);
  220. result = await pythTest.sendUpdatePriceFeeds(
  221. deployer.getSender(),
  222. updateData,
  223. toNano(updateFee)
  224. );
  225. expect(result.transactions).toHaveTransaction({
  226. from: deployer.address,
  227. to: pythTest.address,
  228. success: true,
  229. });
  230. // Check if both BTC and ETH prices have been updated
  231. const updatedBtcPrice = await pythTest.getPriceUnsafe(BTC_PRICE_FEED_ID);
  232. expect(updatedBtcPrice.price).toBe(HERMES_BTC_PRICE);
  233. expect(updatedBtcPrice.publishTime).toBe(HERMES_BTC_PUBLISH_TIME);
  234. const updatedEthPrice = await pythTest.getPriceUnsafe(ETH_PRICE_FEED_ID);
  235. expect(updatedEthPrice.price).toBe(HERMES_ETH_PRICE);
  236. expect(updatedEthPrice.publishTime).toBe(HERMES_ETH_PUBLISH_TIME);
  237. });
  238. it("should fail to get update fee with invalid data", async () => {
  239. await deployContract();
  240. await updateGuardianSets(pythTest, deployer);
  241. const invalidUpdateData = Buffer.from("invalid data");
  242. await expect(pythTest.getUpdateFee(invalidUpdateData)).rejects.toThrow(
  243. "Unable to execute get method. Got exit_code: 2002"
  244. ); // ERROR_INVALID_MAGIC = 2002
  245. });
  246. it("should fail to update price feeds with invalid data", async () => {
  247. await deployContract();
  248. await updateGuardianSets(pythTest, deployer);
  249. const invalidUpdateData = Buffer.from("invalid data");
  250. // Use a fixed value for updateFee since we can't get it from getUpdateFee
  251. const updateFee = toNano("0.1"); // Use a reasonable amount
  252. const result = await pythTest.sendUpdatePriceFeeds(
  253. deployer.getSender(),
  254. invalidUpdateData,
  255. updateFee
  256. );
  257. expect(result.transactions).toHaveTransaction({
  258. from: deployer.address,
  259. to: pythTest.address,
  260. success: false,
  261. exitCode: 2002, // ERROR_INVALID_MAGIC
  262. });
  263. });
  264. it("should fail to update price feeds with outdated guardian set", async () => {
  265. await deployContract();
  266. // Don't update guardian sets
  267. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  268. const updateFee = await pythTest.getUpdateFee(updateData);
  269. const result = await pythTest.sendUpdatePriceFeeds(
  270. deployer.getSender(),
  271. updateData,
  272. toNano(updateFee)
  273. );
  274. expect(result.transactions).toHaveTransaction({
  275. from: deployer.address,
  276. to: pythTest.address,
  277. success: false,
  278. exitCode: 1002, // ERROR_GUARDIAN_SET_NOT_FOUND
  279. });
  280. });
  281. it("should fail to update price feeds with invalid data source", async () => {
  282. await deployContract(
  283. BTC_PRICE_FEED_ID,
  284. PRICE,
  285. EMA_PRICE,
  286. SINGLE_UPDATE_FEE,
  287. [] // Empty data sources
  288. );
  289. await updateGuardianSets(pythTest, deployer);
  290. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  291. const updateFee = await pythTest.getUpdateFee(updateData);
  292. const result = await pythTest.sendUpdatePriceFeeds(
  293. deployer.getSender(),
  294. updateData,
  295. toNano(updateFee)
  296. );
  297. expect(result.transactions).toHaveTransaction({
  298. from: deployer.address,
  299. to: pythTest.address,
  300. success: false,
  301. exitCode: 2005, // ERROR_UPDATE_DATA_SOURCE_NOT_FOUND
  302. });
  303. });
  304. it("should correctly handle stale prices", async () => {
  305. const staleTime = Math.floor(Date.now() / 1000) - TIME_PERIOD - 10; // 10 seconds past the allowed period
  306. const stalePrice = new Price({
  307. price: "1",
  308. conf: "2",
  309. expo: 3,
  310. publishTime: staleTime,
  311. });
  312. await deployContract(BTC_PRICE_FEED_ID, stalePrice, EMA_PRICE);
  313. await expect(
  314. pythTest.getPriceNoOlderThan(TIME_PERIOD, BTC_PRICE_FEED_ID)
  315. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2001"); // ERROR_OUTDATED_PRICE = 2001
  316. });
  317. it("should fail to update price feeds with insufficient gas", async () => {
  318. await deployContract();
  319. await updateGuardianSets(pythTest, deployer);
  320. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  321. let result = await pythTest.sendUpdatePriceFeeds(
  322. deployer.getSender(),
  323. updateData,
  324. toNano("0.1") // Insufficient gas
  325. );
  326. expect(result.transactions).toHaveTransaction({
  327. from: deployer.address,
  328. to: pythTest.address,
  329. success: false,
  330. exitCode: 3000, // ERROR_INSUFFICIENT_GAS
  331. });
  332. result = await pythTest.sendUpdatePriceFeeds(
  333. deployer.getSender(),
  334. updateData,
  335. calculateUpdatePriceFeedsFee(1n) // Send enough gas for 1 update instead of 2
  336. );
  337. expect(result.transactions).toHaveTransaction({
  338. from: deployer.address,
  339. to: pythTest.address,
  340. success: false,
  341. exitCode: 3000, // ERROR_INSUFFICIENT_GAS
  342. });
  343. });
  344. it("should fail to update price feeds with insufficient fee", async () => {
  345. await deployContract();
  346. await updateGuardianSets(pythTest, deployer);
  347. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  348. const updateFee = await pythTest.getUpdateFee(updateData);
  349. // Send less than the required fee
  350. const insufficientFee = updateFee - 1;
  351. const result = await pythTest.sendUpdatePriceFeeds(
  352. deployer.getSender(),
  353. updateData,
  354. calculateUpdatePriceFeedsFee(2n) + BigInt(insufficientFee)
  355. );
  356. // Check that the transaction did not succeed
  357. expect(result.transactions).toHaveTransaction({
  358. from: deployer.address,
  359. to: pythTest.address,
  360. success: false,
  361. exitCode: 2011, // ERROR_INSUFFICIENT_FEE = 2011
  362. });
  363. });
  364. it("should fail to get prices for non-existent price feed", async () => {
  365. await deployContract();
  366. const nonExistentPriceFeedId =
  367. "0000000000000000000000000000000000000000000000000000000000000000";
  368. await expect(
  369. pythTest.getPriceUnsafe(nonExistentPriceFeedId)
  370. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND = 2000
  371. await expect(
  372. pythTest.getPriceNoOlderThan(TIME_PERIOD, nonExistentPriceFeedId)
  373. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND
  374. await expect(
  375. pythTest.getEmaPriceUnsafe(nonExistentPriceFeedId)
  376. ).rejects.toThrow("Unable to execute get method. Got exit_code: 2000"); // ERROR_PRICE_FEED_NOT_FOUND
  377. });
  378. it("should correctly get chain ID", async () => {
  379. await deployContract();
  380. const result = await pythTest.getChainId();
  381. expect(result).toEqual(1);
  382. });
  383. it("should correctly get last executed governance sequence", async () => {
  384. await deployContract(
  385. BTC_PRICE_FEED_ID,
  386. PRICE,
  387. EMA_PRICE,
  388. SINGLE_UPDATE_FEE,
  389. DATA_SOURCES,
  390. 0,
  391. [TEST_GUARDIAN_ADDRESS1],
  392. 60051,
  393. 1,
  394. "0000000000000000000000000000000000000000000000000000000000000004",
  395. TEST_GOVERNANCE_DATA_SOURCES[0]
  396. );
  397. // Check initial value
  398. let result = await pythTest.getLastExecutedGovernanceSequence();
  399. expect(result).toEqual(0);
  400. // Execute a governance action (e.g., set fee)
  401. await pythTest.sendExecuteGovernanceAction(
  402. deployer.getSender(),
  403. Buffer.from(PYTH_SET_FEE, "hex")
  404. );
  405. // Check that the sequence has increased
  406. result = await pythTest.getLastExecutedGovernanceSequence();
  407. expect(result).toEqual(1);
  408. });
  409. it("should correctly get governance data source index", async () => {
  410. // Deploy contract with initial governance data source
  411. await deployContract(
  412. BTC_PRICE_FEED_ID,
  413. PRICE,
  414. EMA_PRICE,
  415. SINGLE_UPDATE_FEE,
  416. DATA_SOURCES,
  417. 0,
  418. [TEST_GUARDIAN_ADDRESS1],
  419. 60051,
  420. 1,
  421. "0000000000000000000000000000000000000000000000000000000000000004",
  422. TEST_GOVERNANCE_DATA_SOURCES[0]
  423. );
  424. // Check initial value
  425. let result = await pythTest.getGovernanceDataSourceIndex();
  426. expect(result).toEqual(0);
  427. // Execute governance action to change data source
  428. await pythTest.sendExecuteGovernanceAction(
  429. deployer.getSender(),
  430. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  431. );
  432. // Check that the index has increased
  433. result = await pythTest.getGovernanceDataSourceIndex();
  434. expect(result).toEqual(1);
  435. });
  436. it("should correctly get governance data source", async () => {
  437. // Deploy contract without initial governance data source
  438. await deployContract();
  439. // Check initial value (should be empty)
  440. let result = await pythTest.getGovernanceDataSource();
  441. expect(result).toEqual(null);
  442. // Deploy contract with initial governance data source
  443. await deployContract(
  444. BTC_PRICE_FEED_ID,
  445. PRICE,
  446. EMA_PRICE,
  447. SINGLE_UPDATE_FEE,
  448. DATA_SOURCES,
  449. 0,
  450. [TEST_GUARDIAN_ADDRESS1],
  451. 60051,
  452. 1,
  453. "0000000000000000000000000000000000000000000000000000000000000004",
  454. TEST_GOVERNANCE_DATA_SOURCES[0]
  455. );
  456. // Check that the governance data source is set
  457. result = await pythTest.getGovernanceDataSource();
  458. expect(result).toEqual(TEST_GOVERNANCE_DATA_SOURCES[0]);
  459. // Execute governance action to change data source
  460. await pythTest.sendExecuteGovernanceAction(
  461. deployer.getSender(),
  462. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  463. );
  464. // Check that the data source has changed
  465. result = await pythTest.getGovernanceDataSource();
  466. expect(result).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]);
  467. });
  468. it("should correctly get single update fee", async () => {
  469. await deployContract();
  470. // Get the initial fee
  471. const result = await pythTest.getSingleUpdateFee();
  472. expect(result).toBe(SINGLE_UPDATE_FEE);
  473. });
  474. it("should execute set data sources governance instruction", async () => {
  475. await deployContract(
  476. BTC_PRICE_FEED_ID,
  477. PRICE,
  478. EMA_PRICE,
  479. SINGLE_UPDATE_FEE,
  480. DATA_SOURCES,
  481. 0,
  482. [TEST_GUARDIAN_ADDRESS1],
  483. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  484. 1,
  485. "0000000000000000000000000000000000000000000000000000000000000004",
  486. TEST_GOVERNANCE_DATA_SOURCES[0]
  487. );
  488. // Execute the governance action
  489. const result = await pythTest.sendExecuteGovernanceAction(
  490. deployer.getSender(),
  491. Buffer.from(PYTH_SET_DATA_SOURCES, "hex")
  492. );
  493. expect(result.transactions).toHaveTransaction({
  494. from: deployer.address,
  495. to: pythTest.address,
  496. success: true,
  497. });
  498. // Verify that the new data sources are set correctly
  499. const newDataSources: DataSource[] = [
  500. {
  501. emitterChain: 1,
  502. emitterAddress:
  503. "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
  504. },
  505. {
  506. emitterChain: 3,
  507. emitterAddress:
  508. "000000000000000000000000000000000000000000000000000000000000012d",
  509. },
  510. ];
  511. for (const dataSource of newDataSources) {
  512. const isValid = await pythTest.getIsValidDataSource(dataSource);
  513. expect(isValid).toBe(true);
  514. }
  515. // Verify that the old data source is no longer valid
  516. const oldDataSource = DATA_SOURCES[0];
  517. const oldDataSourceIsValid = await pythTest.getIsValidDataSource(
  518. oldDataSource
  519. );
  520. expect(oldDataSourceIsValid).toBe(false);
  521. });
  522. it("should execute set fee governance instruction", async () => {
  523. await deployContract(
  524. BTC_PRICE_FEED_ID,
  525. PRICE,
  526. EMA_PRICE,
  527. SINGLE_UPDATE_FEE,
  528. DATA_SOURCES,
  529. 0,
  530. [TEST_GUARDIAN_ADDRESS1],
  531. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  532. 1,
  533. "0000000000000000000000000000000000000000000000000000000000000004",
  534. TEST_GOVERNANCE_DATA_SOURCES[0]
  535. );
  536. // Get the initial fee
  537. const initialFee = await pythTest.getSingleUpdateFee();
  538. expect(initialFee).toBe(SINGLE_UPDATE_FEE);
  539. // Execute the governance action
  540. const result = await pythTest.sendExecuteGovernanceAction(
  541. deployer.getSender(),
  542. Buffer.from(PYTH_SET_FEE, "hex")
  543. );
  544. expect(result.transactions).toHaveTransaction({
  545. from: deployer.address,
  546. to: pythTest.address,
  547. success: true,
  548. });
  549. // Get the new fee
  550. const newFee = await pythTest.getSingleUpdateFee();
  551. expect(newFee).toBe(4200); // The new fee value is 4200 in the PYTH_SET_FEE payload
  552. // Verify that the new fee is used for updates
  553. const updateData = Buffer.from(HERMES_BTC_ETH_UPDATE, "hex");
  554. const updateFee = await pythTest.getUpdateFee(updateData);
  555. expect(updateFee).toBe(8400); // There are two price updates in HERMES_BTC_ETH_UPDATE
  556. });
  557. it("should execute authorize governance data source transfer", async () => {
  558. await deployContract(
  559. BTC_PRICE_FEED_ID,
  560. PRICE,
  561. EMA_PRICE,
  562. SINGLE_UPDATE_FEE,
  563. DATA_SOURCES,
  564. 0,
  565. [TEST_GUARDIAN_ADDRESS1],
  566. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  567. 1,
  568. "0000000000000000000000000000000000000000000000000000000000000004",
  569. TEST_GOVERNANCE_DATA_SOURCES[0]
  570. );
  571. // Get the initial governance data source index
  572. const initialIndex = await pythTest.getGovernanceDataSourceIndex();
  573. expect(initialIndex).toEqual(0); // Initial value should be 0
  574. // Get the initial governance data source
  575. const initialDataSource = await pythTest.getGovernanceDataSource();
  576. expect(initialDataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[0]);
  577. // Get the initial last executed governance sequence
  578. const initialSequence = await pythTest.getLastExecutedGovernanceSequence();
  579. expect(initialSequence).toEqual(0); // Initial value should be 0
  580. // Execute the governance action
  581. const result = await pythTest.sendExecuteGovernanceAction(
  582. deployer.getSender(),
  583. Buffer.from(PYTH_AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  584. );
  585. expect(result.transactions).toHaveTransaction({
  586. from: deployer.address,
  587. to: pythTest.address,
  588. success: true,
  589. });
  590. // Get the new governance data source index
  591. const newIndex = await pythTest.getGovernanceDataSourceIndex();
  592. expect(newIndex).toEqual(1); // The new index value should match the one in the test payload
  593. // Get the new governance data source
  594. const newDataSource = await pythTest.getGovernanceDataSource();
  595. expect(newDataSource).not.toEqual(initialDataSource); // The data source should have changed
  596. expect(newDataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]); // The data source should have changed
  597. // Get the new last executed governance sequence
  598. const newSequence = await pythTest.getLastExecutedGovernanceSequence();
  599. expect(newSequence).toBeGreaterThan(initialSequence); // The sequence should have increased
  600. expect(newSequence).toBe(1);
  601. });
  602. it("should fail when executing request governance data source transfer directly", async () => {
  603. await deployContract(
  604. BTC_PRICE_FEED_ID,
  605. PRICE,
  606. EMA_PRICE,
  607. SINGLE_UPDATE_FEE,
  608. DATA_SOURCES,
  609. 0,
  610. [TEST_GUARDIAN_ADDRESS1],
  611. 60051, // CHAIN_ID of starknet since we are using the test payload for starknet
  612. 1,
  613. "0000000000000000000000000000000000000000000000000000000000000004",
  614. TEST_GOVERNANCE_DATA_SOURCES[1]
  615. );
  616. const result = await pythTest.sendExecuteGovernanceAction(
  617. deployer.getSender(),
  618. Buffer.from(PYTH_REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER, "hex")
  619. );
  620. // Check that the transaction did not succeed
  621. expect(result.transactions).toHaveTransaction({
  622. from: deployer.address,
  623. to: pythTest.address,
  624. success: false,
  625. exitCode: 1012, // ERROR_INVALID_GOVERNANCE_ACTION
  626. });
  627. // Verify that the governance data source index hasn't changed
  628. const index = await pythTest.getGovernanceDataSourceIndex();
  629. expect(index).toEqual(0); // Should still be the initial value
  630. // Verify that the governance data source hasn't changed
  631. const dataSource = await pythTest.getGovernanceDataSource();
  632. expect(dataSource).toEqual(TEST_GOVERNANCE_DATA_SOURCES[1]); // Should still be the initial value
  633. });
  634. it("should fail to execute governance action with invalid governance data source", async () => {
  635. await deployContract(
  636. BTC_PRICE_FEED_ID,
  637. PRICE,
  638. EMA_PRICE,
  639. SINGLE_UPDATE_FEE,
  640. DATA_SOURCES,
  641. 0,
  642. [TEST_GUARDIAN_ADDRESS1],
  643. 60051,
  644. 1,
  645. "0000000000000000000000000000000000000000000000000000000000000004",
  646. TEST_GOVERNANCE_DATA_SOURCES[1]
  647. );
  648. const result = await pythTest.sendExecuteGovernanceAction(
  649. deployer.getSender(),
  650. Buffer.from(PYTH_SET_FEE, "hex")
  651. );
  652. expect(result.transactions).toHaveTransaction({
  653. from: deployer.address,
  654. to: pythTest.address,
  655. success: false,
  656. exitCode: 2013, // ERROR_INVALID_GOVERNANCE_DATA_SOURCE
  657. });
  658. });
  659. it("should fail to execute governance action with old sequence number", async () => {
  660. await deployContract(
  661. BTC_PRICE_FEED_ID,
  662. PRICE,
  663. EMA_PRICE,
  664. SINGLE_UPDATE_FEE,
  665. DATA_SOURCES,
  666. 0,
  667. [TEST_GUARDIAN_ADDRESS1],
  668. 60051,
  669. 1,
  670. "0000000000000000000000000000000000000000000000000000000000000004",
  671. TEST_GOVERNANCE_DATA_SOURCES[0]
  672. );
  673. // Execute a governance action to increase the sequence number
  674. await pythTest.sendExecuteGovernanceAction(
  675. deployer.getSender(),
  676. Buffer.from(PYTH_SET_FEE, "hex")
  677. );
  678. // Try to execute the same governance action again
  679. const result = await pythTest.sendExecuteGovernanceAction(
  680. deployer.getSender(),
  681. Buffer.from(PYTH_SET_FEE, "hex")
  682. );
  683. expect(result.transactions).toHaveTransaction({
  684. from: deployer.address,
  685. to: pythTest.address,
  686. success: false,
  687. exitCode: 2014, // ERROR_OLD_GOVERNANCE_MESSAGE
  688. });
  689. });
  690. it("should fail to execute governance action with invalid chain ID", async () => {
  691. const invalidChainId = 999;
  692. await deployContract(
  693. BTC_PRICE_FEED_ID,
  694. PRICE,
  695. EMA_PRICE,
  696. SINGLE_UPDATE_FEE,
  697. DATA_SOURCES,
  698. 0,
  699. [TEST_GUARDIAN_ADDRESS1],
  700. invalidChainId,
  701. 1,
  702. "0000000000000000000000000000000000000000000000000000000000000004",
  703. TEST_GOVERNANCE_DATA_SOURCES[0]
  704. );
  705. const result = await pythTest.sendExecuteGovernanceAction(
  706. deployer.getSender(),
  707. Buffer.from(PYTH_SET_FEE, "hex")
  708. );
  709. expect(result.transactions).toHaveTransaction({
  710. from: deployer.address,
  711. to: pythTest.address,
  712. success: false,
  713. exitCode: 2015, // ERROR_INVALID_GOVERNANCE_TARGET
  714. });
  715. });
  716. it("should successfully upgrade the contract", async () => {
  717. // Compile the upgraded contract
  718. const upgradedCode = await compile("PythTestUpgraded");
  719. const upgradedCodeHash = upgradedCode.hash();
  720. // Create the authorize upgrade payload
  721. const authorizeUpgradePayload =
  722. createAuthorizeUpgradePayload(upgradedCodeHash);
  723. const authorizeUpgradeVaa = createVAA("Uint8Array", {
  724. guardianSet: 0,
  725. timestamp: 0,
  726. nonce: 0,
  727. emitterChain: "Solana",
  728. emitterAddress: new UniversalAddress(new Uint8Array(32)),
  729. sequence: 1n,
  730. consistencyLevel: 0,
  731. signatures: [],
  732. payload: authorizeUpgradePayload,
  733. });
  734. const guardianSet = mocks.devnetGuardianSet();
  735. guardianSet.setSignatures(authorizeUpgradeVaa);
  736. await deployContract(
  737. BTC_PRICE_FEED_ID,
  738. PRICE,
  739. EMA_PRICE,
  740. SINGLE_UPDATE_FEE,
  741. DATA_SOURCES,
  742. 0,
  743. [TEST_GUARDIAN_ADDRESS2],
  744. 1,
  745. 1,
  746. "0000000000000000000000000000000000000000000000000000000000000000",
  747. TEST_GOVERNANCE_DATA_SOURCES[2]
  748. );
  749. // Execute the upgrade
  750. const sendExecuteGovernanceActionResult =
  751. await pythTest.sendExecuteGovernanceAction(
  752. deployer.getSender(),
  753. Buffer.from(serialize(authorizeUpgradeVaa))
  754. );
  755. expect(sendExecuteGovernanceActionResult.transactions).toHaveTransaction({
  756. from: deployer.address,
  757. to: pythTest.address,
  758. success: true,
  759. });
  760. // Execute the upgrade
  761. const sendUpgradeContractResult = await pythTest.sendUpgradeContract(
  762. deployer.getSender(),
  763. upgradedCode
  764. );
  765. expect(sendUpgradeContractResult.transactions).toHaveTransaction({
  766. from: deployer.address,
  767. to: pythTest.address,
  768. success: true,
  769. });
  770. // Verify that the contract has been upgraded by calling a new method
  771. const newMethodResult = await pythTest.getNewFunction();
  772. expect(newMethodResult).toBe(1);
  773. });
  774. it("should fail to upgrade the contract with modified code", async () => {
  775. // Compile the upgraded contract
  776. const upgradedCode = await compile("PythTestUpgraded");
  777. const upgradedCodeHash = upgradedCode.hash();
  778. // Create the authorize upgrade payload
  779. const authorizeUpgradePayload =
  780. createAuthorizeUpgradePayload(upgradedCodeHash);
  781. const authorizeUpgradeVaa = createVAA("Uint8Array", {
  782. guardianSet: 0,
  783. timestamp: 0,
  784. nonce: 0,
  785. emitterChain: "Solana",
  786. emitterAddress: new UniversalAddress(new Uint8Array(32)),
  787. sequence: 1n,
  788. consistencyLevel: 0,
  789. signatures: [],
  790. payload: authorizeUpgradePayload,
  791. });
  792. const guardianSet = mocks.devnetGuardianSet();
  793. guardianSet.setSignatures(authorizeUpgradeVaa);
  794. await deployContract(
  795. BTC_PRICE_FEED_ID,
  796. PRICE,
  797. EMA_PRICE,
  798. SINGLE_UPDATE_FEE,
  799. DATA_SOURCES,
  800. 0,
  801. [TEST_GUARDIAN_ADDRESS2],
  802. 1,
  803. 1,
  804. "0000000000000000000000000000000000000000000000000000000000000000",
  805. TEST_GOVERNANCE_DATA_SOURCES[2]
  806. );
  807. // Execute the upgrade authorization
  808. const sendExecuteGovernanceActionResult =
  809. await pythTest.sendExecuteGovernanceAction(
  810. deployer.getSender(),
  811. Buffer.from(serialize(authorizeUpgradeVaa))
  812. );
  813. expect(sendExecuteGovernanceActionResult.transactions).toHaveTransaction({
  814. from: deployer.address,
  815. to: pythTest.address,
  816. success: true,
  817. });
  818. // Attempt to execute the upgrade with a different code
  819. const wormholeTestCode = await compile("WormholeTest");
  820. const sendUpgradeContractResult = await pythTest.sendUpgradeContract(
  821. deployer.getSender(),
  822. wormholeTestCode
  823. );
  824. // Expect the transaction to fail
  825. expect(sendUpgradeContractResult.transactions).toHaveTransaction({
  826. from: deployer.address,
  827. to: pythTest.address,
  828. success: false,
  829. exitCode: 2018, // ERROR_INVALID_CODE_HASH
  830. });
  831. // Verify that the contract has not been upgraded by attempting to call the new method
  832. await expect(pythTest.getNewFunction()).rejects.toThrow();
  833. });
  834. });