pyth.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. const elliptic = require("elliptic");
  2. const governance = require("@pythnetwork/xc-admin-common");
  3. const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  4. const {
  5. expectRevert,
  6. expectEvent,
  7. time,
  8. } = require("@openzeppelin/test-helpers");
  9. const { assert, expect } = require("chai");
  10. const { EvmSetWormholeAddress } = require("@pythnetwork/xc-admin-common");
  11. // Use "WormholeReceiver" if you are testing with Wormhole Receiver
  12. const Setup = artifacts.require("Setup");
  13. const Implementation = artifacts.require("Implementation");
  14. const Wormhole = artifacts.require("Wormhole");
  15. const ReceiverSetup = artifacts.require("ReceiverSetup");
  16. const ReceiverImplementation = artifacts.require("ReceiverImplementation");
  17. const WormholeReceiver = artifacts.require("WormholeReceiver");
  18. const wormholeGovernanceChainId = governance.CHAINS.solana;
  19. const wormholeGovernanceContract =
  20. "0x0000000000000000000000000000000000000000000000000000000000000004";
  21. const PythUpgradable = artifacts.require("PythUpgradable");
  22. const MockPythUpgrade = artifacts.require("MockPythUpgrade");
  23. const MockUpgradeableProxy = artifacts.require("MockUpgradeableProxy");
  24. const testSigner1PK =
  25. "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
  26. const testSigner2PK =
  27. "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
  28. contract("Pyth", function () {
  29. const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
  30. const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
  31. const testGovernanceChainId = "1";
  32. const testGovernanceEmitter =
  33. "0x0000000000000000000000000000000000000000000000000000000000001234";
  34. const testPyth2WormholeChainId = "1";
  35. const testPyth2WormholeEmitter =
  36. "0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
  37. // Place all atomic operations that are done within migrations here.
  38. beforeEach(async function () {
  39. this.pythProxy = await deployProxy(PythUpgradable, [
  40. (await Wormhole.deployed()).address,
  41. [testPyth2WormholeChainId],
  42. [testPyth2WormholeEmitter],
  43. testGovernanceChainId,
  44. testGovernanceEmitter,
  45. 0, // Initial governance sequence
  46. 60, // Validity time in seconds
  47. 0, // single update fee in wei
  48. ]);
  49. });
  50. it("should be initialized with the correct signers and values", async function () {
  51. await this.pythProxy.isValidDataSource(
  52. testPyth2WormholeChainId,
  53. testPyth2WormholeEmitter
  54. );
  55. });
  56. it("there should be no owner", async function () {
  57. // Check that the ownership is renounced.
  58. const owner = await this.pythProxy.owner();
  59. assert.equal(owner, "0x0000000000000000000000000000000000000000");
  60. });
  61. it("deployer cannot upgrade the contract", async function () {
  62. // upgrade proxy should fail
  63. await expectRevert(
  64. upgradeProxy(this.pythProxy.address, MockPythUpgrade),
  65. "Ownable: caller is not the owner."
  66. );
  67. });
  68. async function updatePriceFeeds(
  69. contract,
  70. batches,
  71. valueInWei,
  72. chainId,
  73. emitter
  74. ) {
  75. let updateData = [];
  76. for (let data of batches) {
  77. const vm = await signAndEncodeVM(
  78. 1,
  79. 1,
  80. chainId || testPyth2WormholeChainId,
  81. emitter || testPyth2WormholeEmitter,
  82. 0,
  83. data,
  84. [testSigner1PK],
  85. 0,
  86. 0
  87. );
  88. updateData.push("0x" + vm);
  89. }
  90. return await contract.updatePriceFeeds(updateData, { value: valueInWei });
  91. }
  92. /**
  93. * Create a governance instruction VAA from the Instruction object. Then
  94. * Submit and execute it on the contract.
  95. * @param contract Pyth contract
  96. * @param {governance.PythGovernanceAction} governanceInstruction
  97. * @param {number} sequence
  98. */
  99. async function createAndThenSubmitGovernanceInstructionVaa(
  100. contract,
  101. governanceInstruction,
  102. sequence
  103. ) {
  104. await contract.executeGovernanceInstruction(
  105. await createVAAFromUint8Array(
  106. governanceInstruction.encode(),
  107. testGovernanceChainId,
  108. testGovernanceEmitter,
  109. sequence
  110. )
  111. );
  112. }
  113. it("should attest price updates empty", async function () {
  114. const receipt = await updatePriceFeeds(this.pythProxy, []);
  115. expectEvent.notEmitted(receipt, "PriceFeedUpdate");
  116. });
  117. /**
  118. * Set fee to `newFee` by creating and submitting a governance instruction for it.
  119. * @param contarct Pyth contract
  120. * @param {number} newFee
  121. * @param {number=} governanceSequence Sequence number of the governance instruction. Defaults to 1.
  122. */
  123. async function setFeeTo(contract, newFee, governanceSequence) {
  124. await createAndThenSubmitGovernanceInstructionVaa(
  125. contract,
  126. new governance.SetFee("ethereum", BigInt(newFee), BigInt(0)),
  127. governanceSequence ?? 1
  128. );
  129. }
  130. it("should fail transaction if a price is not found", async function () {
  131. await expectRevertCustomError(
  132. this.pythProxy.queryPriceFeed(
  133. "0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed"
  134. ),
  135. "PriceFeedNotFound"
  136. );
  137. });
  138. /**
  139. * Set valid time period to `newValidPeriod` by creating and submitting a
  140. * governance instruction for it.
  141. * @param contract Pyth contract
  142. * @param {number} newValidPeriod
  143. * @param {number=} governanceSequence Sequence number of the governance instruction. Defaults to 1.
  144. */
  145. async function setValidPeriodTo(
  146. contract,
  147. newValidPeriod,
  148. governanceSequence
  149. ) {
  150. await createAndThenSubmitGovernanceInstructionVaa(
  151. contract,
  152. new governance.SetValidPeriod("ethereum", BigInt(newValidPeriod)),
  153. governanceSequence ?? 1
  154. );
  155. }
  156. // Governance
  157. // Logics that apply to all governance messages
  158. it("Make sure invalid magic and module won't work", async function () {
  159. // First 4 bytes of data are magic and the second byte after that is module
  160. const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
  161. const wrongMagic = Buffer.from(data);
  162. wrongMagic[1] = 0;
  163. const vaaWrongMagic = await createVAAFromUint8Array(
  164. wrongMagic,
  165. testGovernanceChainId,
  166. testGovernanceEmitter,
  167. 1
  168. );
  169. await expectRevertCustomError(
  170. this.pythProxy.executeGovernanceInstruction(vaaWrongMagic),
  171. "InvalidGovernanceMessage"
  172. );
  173. const wrongModule = Buffer.from(data);
  174. wrongModule[4] = 0;
  175. const vaaWrongModule = await createVAAFromUint8Array(
  176. wrongModule,
  177. testGovernanceChainId,
  178. testGovernanceEmitter,
  179. 1
  180. );
  181. await expectRevertCustomError(
  182. this.pythProxy.executeGovernanceInstruction(vaaWrongModule),
  183. "InvalidGovernanceTarget"
  184. );
  185. const outOfBoundModule = Buffer.from(data);
  186. outOfBoundModule[4] = 20;
  187. const vaaOutOfBoundModule = await createVAAFromUint8Array(
  188. outOfBoundModule,
  189. testGovernanceChainId,
  190. testGovernanceEmitter,
  191. 1
  192. );
  193. await expectRevert(
  194. this.pythProxy.executeGovernanceInstruction(vaaOutOfBoundModule),
  195. "Panic: Enum value out of bounds."
  196. );
  197. });
  198. it("Make sure governance with wrong sender won't work", async function () {
  199. const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
  200. const vaaWrongEmitter = await createVAAFromUint8Array(
  201. data,
  202. testGovernanceChainId,
  203. "0x0000000000000000000000000000000000000000000000000000000000001111",
  204. 1
  205. );
  206. await expectRevertCustomError(
  207. this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter),
  208. "InvalidGovernanceDataSource"
  209. );
  210. const vaaWrongChain = await createVAAFromUint8Array(
  211. data,
  212. governance.CHAINS.karura,
  213. testGovernanceEmitter,
  214. 1
  215. );
  216. await expectRevertCustomError(
  217. this.pythProxy.executeGovernanceInstruction(vaaWrongChain),
  218. "InvalidGovernanceDataSource"
  219. );
  220. });
  221. it("Make sure governance with only target chain id and 0 work", async function () {
  222. const wrongChainData = new governance.SetValidPeriod(
  223. "solana",
  224. BigInt(10)
  225. ).encode();
  226. const wrongChainVaa = await createVAAFromUint8Array(
  227. wrongChainData,
  228. testGovernanceChainId,
  229. testGovernanceEmitter,
  230. 1
  231. );
  232. await expectRevertCustomError(
  233. this.pythProxy.executeGovernanceInstruction(wrongChainVaa),
  234. "InvalidGovernanceTarget"
  235. );
  236. const dataForAllChains = new governance.SetValidPeriod(
  237. "unset",
  238. BigInt(10)
  239. ).encode();
  240. const vaaForAllChains = await createVAAFromUint8Array(
  241. dataForAllChains,
  242. testGovernanceChainId,
  243. testGovernanceEmitter,
  244. 1
  245. );
  246. await this.pythProxy.executeGovernanceInstruction(vaaForAllChains);
  247. const dataForEth = new governance.SetValidPeriod(
  248. "ethereum",
  249. BigInt(10)
  250. ).encode();
  251. const vaaForEth = await createVAAFromUint8Array(
  252. dataForEth,
  253. testGovernanceChainId,
  254. testGovernanceEmitter,
  255. 2
  256. );
  257. await this.pythProxy.executeGovernanceInstruction(vaaForEth);
  258. });
  259. it("Make sure that governance messages are executed in order and cannot be reused", async function () {
  260. const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
  261. const vaaSeq1 = await createVAAFromUint8Array(
  262. data,
  263. testGovernanceChainId,
  264. testGovernanceEmitter,
  265. 1
  266. );
  267. await this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  268. // Replaying shouldn't work
  269. await expectRevertCustomError(
  270. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  271. "OldGovernanceMessage"
  272. );
  273. const vaaSeq2 = await createVAAFromUint8Array(
  274. data,
  275. testGovernanceChainId,
  276. testGovernanceEmitter,
  277. 2
  278. );
  279. await this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  280. // Replaying shouldn't work
  281. await expectRevertCustomError(
  282. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  283. "OldGovernanceMessage"
  284. );
  285. await expectRevertCustomError(
  286. this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  287. "OldGovernanceMessage"
  288. );
  289. });
  290. // Per governance type logic
  291. it("Upgrading the contract with chain id 0 is invalid", async function () {
  292. const newImplementation = await PythUpgradable.new();
  293. const data = new governance.EvmUpgradeContract(
  294. "unset", // 0
  295. newImplementation.address.replace("0x", "")
  296. ).encode();
  297. const vaa = await createVAAFromUint8Array(
  298. data,
  299. testGovernanceChainId,
  300. testGovernanceEmitter,
  301. 1
  302. );
  303. await expectRevertCustomError(
  304. this.pythProxy.executeGovernanceInstruction(vaa),
  305. "InvalidGovernanceTarget"
  306. );
  307. });
  308. it("Upgrading the contract should work", async function () {
  309. const newImplementation = await PythUpgradable.new();
  310. const data = new governance.EvmUpgradeContract(
  311. "ethereum",
  312. newImplementation.address.replace("0x", "")
  313. ).encode();
  314. const vaa = await createVAAFromUint8Array(
  315. data,
  316. testGovernanceChainId,
  317. testGovernanceEmitter,
  318. 1
  319. );
  320. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  321. // Couldn't get the oldImplementation address.
  322. expectEvent(receipt, "ContractUpgraded", {
  323. newImplementation: newImplementation.address,
  324. });
  325. expectEvent(receipt, "Upgraded", {
  326. implementation: newImplementation.address,
  327. });
  328. });
  329. it("Upgrading the contract to a non-pyth contract won't work", async function () {
  330. const newImplementation = await MockUpgradeableProxy.new();
  331. const data = new governance.EvmUpgradeContract(
  332. "ethereum",
  333. newImplementation.address.replace("0x", "")
  334. ).encode();
  335. const vaa = await createVAAFromUint8Array(
  336. data,
  337. testGovernanceChainId,
  338. testGovernanceEmitter,
  339. 1
  340. );
  341. // Calling a non-existing method will cause a revert with no explanation.
  342. await expectRevert(
  343. this.pythProxy.executeGovernanceInstruction(vaa),
  344. "revert"
  345. );
  346. });
  347. it("Transferring governance data source should work", async function () {
  348. const newEmitterAddress =
  349. "0x0000000000000000000000000000000000000000000000000000000000001111";
  350. const newEmitterChain = governance.CHAINS.acala;
  351. const claimInstructionData =
  352. new governance.RequestGovernanceDataSourceTransfer("unset", 1).encode();
  353. const claimVaaHexString = await createVAAFromUint8Array(
  354. claimInstructionData,
  355. newEmitterChain,
  356. newEmitterAddress,
  357. 1
  358. );
  359. await expectRevertCustomError(
  360. this.pythProxy.executeGovernanceInstruction(claimVaaHexString),
  361. "InvalidGovernanceDataSource"
  362. );
  363. const claimVaa = Buffer.from(claimVaaHexString.substring(2), "hex");
  364. const data = new governance.AuthorizeGovernanceDataSourceTransfer(
  365. "unset",
  366. claimVaa
  367. ).encode();
  368. const vaa = await createVAAFromUint8Array(
  369. data,
  370. testGovernanceChainId,
  371. testGovernanceEmitter,
  372. 1
  373. );
  374. const oldGovernanceDataSource = await this.pythProxy.governanceDataSource();
  375. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  376. const newGovernanceDataSource = await this.pythProxy.governanceDataSource();
  377. expectEvent(receipt, "GovernanceDataSourceSet", {
  378. oldDataSource: oldGovernanceDataSource,
  379. newDataSource: newGovernanceDataSource,
  380. });
  381. expect(newGovernanceDataSource.chainId).equal(newEmitterChain.toString());
  382. expect(newGovernanceDataSource.emitterAddress).equal(newEmitterAddress);
  383. // Verifies the data source has changed.
  384. await expectRevertCustomError(
  385. this.pythProxy.executeGovernanceInstruction(vaa),
  386. "InvalidGovernanceDataSource"
  387. );
  388. // Make sure a claim vaa does not get executed
  389. const claimLonely = new governance.RequestGovernanceDataSourceTransfer(
  390. "unset",
  391. 2
  392. ).encode();
  393. const claimLonelyVaa = await createVAAFromUint8Array(
  394. claimLonely,
  395. newEmitterChain,
  396. newEmitterAddress,
  397. 2
  398. );
  399. await expectRevertCustomError(
  400. this.pythProxy.executeGovernanceInstruction(claimLonelyVaa),
  401. "InvalidGovernanceMessage"
  402. );
  403. // Transfer back the ownership to the old governance data source without increasing
  404. // the governance index should not work
  405. // A wrong vaa that does not move the governance index
  406. const transferBackClaimInstructionDataWrong =
  407. new governance.RequestGovernanceDataSourceTransfer(
  408. "unset",
  409. 1 // The same governance data source index => Should fail
  410. ).encode();
  411. const transferBackClaimVaaHexStringWrong = await createVAAFromUint8Array(
  412. transferBackClaimInstructionDataWrong,
  413. testGovernanceChainId,
  414. testGovernanceEmitter,
  415. 2
  416. );
  417. const transferBackClaimVaaWrong = Buffer.from(
  418. transferBackClaimVaaHexStringWrong.substring(2),
  419. "hex"
  420. );
  421. const transferBackDataWrong =
  422. new governance.AuthorizeGovernanceDataSourceTransfer(
  423. "unset",
  424. transferBackClaimVaaWrong
  425. ).encode();
  426. const transferBackVaaWrong = await createVAAFromUint8Array(
  427. transferBackDataWrong,
  428. newEmitterChain,
  429. newEmitterAddress,
  430. 2
  431. );
  432. await expectRevertCustomError(
  433. this.pythProxy.executeGovernanceInstruction(transferBackVaaWrong),
  434. "OldGovernanceMessage"
  435. );
  436. });
  437. it("Setting data sources should work", async function () {
  438. const data = new governance.SetDataSources("ethereum", [
  439. {
  440. emitterChain: governance.CHAINS.acala,
  441. emitterAddress:
  442. "0000000000000000000000000000000000000000000000000000000000001111",
  443. },
  444. ]).encode();
  445. const vaa = await createVAAFromUint8Array(
  446. data,
  447. testGovernanceChainId,
  448. testGovernanceEmitter,
  449. 1
  450. );
  451. const oldDataSources = await this.pythProxy.validDataSources();
  452. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  453. expectEvent(receipt, "DataSourcesSet", {
  454. oldDataSources: oldDataSources,
  455. newDataSources: await this.pythProxy.validDataSources(),
  456. });
  457. assert.isTrue(
  458. await this.pythProxy.isValidDataSource(
  459. governance.CHAINS.acala,
  460. "0x0000000000000000000000000000000000000000000000000000000000001111"
  461. )
  462. );
  463. assert.isFalse(
  464. await this.pythProxy.isValidDataSource(
  465. testPyth2WormholeChainId,
  466. testPyth2WormholeEmitter
  467. )
  468. );
  469. // TODO: try to publish prices
  470. });
  471. it("Setting fee should work", async function () {
  472. const data = new governance.SetFee(
  473. "ethereum",
  474. BigInt(5),
  475. BigInt(3) // 5*10**3 = 5000
  476. ).encode();
  477. const vaa = await createVAAFromUint8Array(
  478. data,
  479. testGovernanceChainId,
  480. testGovernanceEmitter,
  481. 1
  482. );
  483. const oldFee = await this.pythProxy.singleUpdateFeeInWei();
  484. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  485. expectEvent(receipt, "FeeSet", {
  486. oldFee: oldFee,
  487. newFee: await this.pythProxy.singleUpdateFeeInWei(),
  488. });
  489. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000");
  490. // TODO: check that fee is applied
  491. });
  492. it("Setting valid period should work", async function () {
  493. const data = new governance.SetValidPeriod("ethereum", BigInt(0)).encode();
  494. const vaa = await createVAAFromUint8Array(
  495. data,
  496. testGovernanceChainId,
  497. testGovernanceEmitter,
  498. 1
  499. );
  500. const oldValidPeriod = await this.pythProxy.validTimePeriodSeconds();
  501. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  502. expectEvent(receipt, "ValidPeriodSet", {
  503. oldValidPeriod: oldValidPeriod,
  504. newValidPeriod: await this.pythProxy.validTimePeriodSeconds(),
  505. });
  506. assert.equal(await this.pythProxy.validTimePeriodSeconds(), "0");
  507. // The behaviour of valid time period is extensively tested before,
  508. // and adding it here will cause more complexity (and is not so short).
  509. });
  510. it("Setting wormhole address should work", async function () {
  511. // Deploy a new wormhole contract
  512. const newSetup = await Setup.new();
  513. const newImpl = await Implementation.new();
  514. // encode initialisation data
  515. const initData = newSetup.contract.methods
  516. .setup(
  517. newImpl.address,
  518. [testSigner1.address],
  519. governance.CHAINS.polygon, // changing the chain id to polygon
  520. wormholeGovernanceChainId,
  521. wormholeGovernanceContract
  522. )
  523. .encodeABI();
  524. const newWormhole = await Wormhole.new(newSetup.address, initData);
  525. // Creating the vaa to set the new wormhole address
  526. const data = new governance.EvmSetWormholeAddress(
  527. "ethereum",
  528. newWormhole.address.replace("0x", "")
  529. ).encode();
  530. const vaa = await createVAAFromUint8Array(
  531. data,
  532. testGovernanceChainId,
  533. testGovernanceEmitter,
  534. 1
  535. );
  536. assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
  537. const oldWormholeAddress = await this.pythProxy.wormhole();
  538. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  539. expectEvent(receipt, "WormholeAddressSet", {
  540. oldWormholeAddress: oldWormholeAddress,
  541. newWormholeAddress: newWormhole.address,
  542. });
  543. assert.equal(await this.pythProxy.wormhole(), newWormhole.address);
  544. assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
  545. });
  546. it("Setting wormhole address to WormholeReceiver should work", async function () {
  547. // Deploy a new wormhole receiver contract
  548. const newReceiverSetup = await ReceiverSetup.new();
  549. const newReceiverImpl = await ReceiverImplementation.new();
  550. // encode initialisation data
  551. const initData = newReceiverSetup.contract.methods
  552. .setup(
  553. newReceiverImpl.address,
  554. [testSigner1.address],
  555. governance.CHAINS.polygon, // changing the chain id to polygon
  556. wormholeGovernanceChainId,
  557. wormholeGovernanceContract
  558. )
  559. .encodeABI();
  560. const newWormholeReceiver = await WormholeReceiver.new(
  561. newReceiverSetup.address,
  562. initData
  563. );
  564. // Creating the vaa to set the new wormhole address
  565. const data = new governance.EvmSetWormholeAddress(
  566. "ethereum",
  567. newWormholeReceiver.address.replace("0x", "")
  568. ).encode();
  569. const vaa = await createVAAFromUint8Array(
  570. data,
  571. testGovernanceChainId,
  572. testGovernanceEmitter,
  573. 1
  574. );
  575. assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
  576. const oldWormholeAddress = await this.pythProxy.wormhole();
  577. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  578. expectEvent(receipt, "WormholeAddressSet", {
  579. oldWormholeAddress: oldWormholeAddress,
  580. newWormholeAddress: newWormholeReceiver.address,
  581. });
  582. assert.equal(await this.pythProxy.wormhole(), newWormholeReceiver.address);
  583. assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
  584. });
  585. it("Setting wormhole address to a wrong contract should reject", async function () {
  586. // Deploy a new wormhole contract
  587. const newSetup = await Setup.new();
  588. const newImpl = await Implementation.new();
  589. // encode initialisation data
  590. const initData = newSetup.contract.methods
  591. .setup(
  592. newImpl.address,
  593. [testSigner2.address], // A wrong signer
  594. governance.CHAINS.ethereum,
  595. wormholeGovernanceChainId,
  596. wormholeGovernanceContract
  597. )
  598. .encodeABI();
  599. const newWormhole = await Wormhole.new(newSetup.address, initData);
  600. // Creating the vaa to set the new wormhole address
  601. const data = new governance.EvmSetWormholeAddress(
  602. "ethereum",
  603. newWormhole.address.replace("0x", "")
  604. ).encode();
  605. const wrongVaa = await createVAAFromUint8Array(
  606. data,
  607. testGovernanceChainId,
  608. testGovernanceEmitter,
  609. 1
  610. );
  611. await expectRevertCustomError(
  612. this.pythProxy.executeGovernanceInstruction(wrongVaa),
  613. "InvalidGovernanceMessage"
  614. );
  615. });
  616. // Version
  617. it("Make sure version is the npm package version", async function () {
  618. const contractVersion = await this.pythProxy.version();
  619. const { version } = require("../package.json");
  620. expect(contractVersion).equal(version);
  621. });
  622. });
  623. const signAndEncodeVM = async function (
  624. timestamp,
  625. nonce,
  626. emitterChainId,
  627. emitterAddress,
  628. sequence,
  629. data,
  630. signers,
  631. guardianSetIndex,
  632. consistencyLevel
  633. ) {
  634. const body = [
  635. web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
  636. web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
  637. web3.eth.abi
  638. .encodeParameter("uint16", emitterChainId)
  639. .substring(2 + (64 - 4)),
  640. web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
  641. web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
  642. web3.eth.abi
  643. .encodeParameter("uint8", consistencyLevel)
  644. .substring(2 + (64 - 2)),
  645. data.substr(2),
  646. ];
  647. const hash = web3.utils.soliditySha3(
  648. web3.utils.soliditySha3("0x" + body.join(""))
  649. );
  650. let signatures = "";
  651. for (let i in signers) {
  652. const ec = new elliptic.ec("secp256k1");
  653. const key = ec.keyFromPrivate(signers[i]);
  654. const signature = key.sign(hash.substr(2), { canonical: true });
  655. const packSig = [
  656. web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
  657. zeroPadBytes(signature.r.toString(16), 32),
  658. zeroPadBytes(signature.s.toString(16), 32),
  659. web3.eth.abi
  660. .encodeParameter("uint8", signature.recoveryParam)
  661. .substr(2 + (64 - 2)),
  662. ];
  663. signatures += packSig.join("");
  664. }
  665. const vm = [
  666. web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
  667. web3.eth.abi
  668. .encodeParameter("uint32", guardianSetIndex)
  669. .substring(2 + (64 - 8)),
  670. web3.eth.abi
  671. .encodeParameter("uint8", signers.length)
  672. .substring(2 + (64 - 2)),
  673. signatures,
  674. body.join(""),
  675. ].join("");
  676. return vm;
  677. };
  678. function zeroPadBytes(value, length) {
  679. while (value.length < 2 * length) {
  680. value = "0" + value;
  681. }
  682. return value;
  683. }
  684. async function createVAAFromUint8Array(
  685. dataBuffer,
  686. emitterChainId,
  687. emitterAddress,
  688. sequence
  689. ) {
  690. const dataHex = "0x" + dataBuffer.toString("hex");
  691. return (
  692. "0x" +
  693. (await signAndEncodeVM(
  694. 0,
  695. 0,
  696. emitterChainId.toString(),
  697. emitterAddress,
  698. sequence,
  699. dataHex,
  700. [testSigner1PK],
  701. 0,
  702. 0
  703. ))
  704. );
  705. }
  706. // There is no way to check event with given args has not emitted with expectEvent
  707. // or how many times an event was emitted. This function is implemented to count
  708. // the matching events and is used for the mentioned purposes.
  709. function getNumMatchingEvents(receipt, eventName, args) {
  710. let matchCnt = 0;
  711. for (let log of receipt.logs) {
  712. if (log.event === eventName) {
  713. let match = true;
  714. for (let argKey in args) {
  715. if (log.args[argKey].toString() !== args[argKey].toString()) {
  716. match = false;
  717. break;
  718. }
  719. }
  720. if (match) {
  721. matchCnt++;
  722. }
  723. }
  724. }
  725. return matchCnt;
  726. }
  727. function expectEventNotEmittedWithArgs(receipt, eventName, args) {
  728. const matches = getNumMatchingEvents(receipt, eventName, args);
  729. assert(
  730. matches === 0,
  731. `Expected no matching emitted event. But found ${matches}.`
  732. );
  733. }
  734. function expectEventMultipleTimes(receipt, eventName, args, cnt) {
  735. const matches = getNumMatchingEvents(receipt, eventName, args);
  736. assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`);
  737. }
  738. async function expectRevertCustomError(promise, reason) {
  739. try {
  740. await promise;
  741. expect.fail("Expected promise to throw but it didn't");
  742. } catch (revert) {
  743. if (reason) {
  744. const reasonId = web3.utils.keccak256(reason + "()").substr(0, 10);
  745. expect(
  746. JSON.stringify(revert),
  747. `Expected custom error ${reason} (${reasonId})`
  748. ).to.include(reasonId);
  749. }
  750. }
  751. }