pyth.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. const jsonfile = require("jsonfile");
  2. const elliptic = require("elliptic");
  3. const BigNumber = require("bignumber.js");
  4. const governance = require("@pythnetwork/xc-governance-sdk");
  5. const PythStructs = artifacts.require("PythStructs");
  6. const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  7. const { expectRevert, expectEvent, time } = require("@openzeppelin/test-helpers");
  8. const { assert, expect } = require("chai");
  9. const { deployProxyImpl } = require("@openzeppelin/truffle-upgrades/dist/utils");
  10. // Use "WormholeReceiver" if you are testing with Wormhole Receiver
  11. const Wormhole = artifacts.require("Wormhole");
  12. const PythUpgradable = artifacts.require("PythUpgradable");
  13. const MockPythUpgrade = artifacts.require("MockPythUpgrade");
  14. const testSigner1PK =
  15. "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
  16. const testSigner2PK =
  17. "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
  18. contract("Pyth", function () {
  19. const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
  20. const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
  21. const testGovernanceChainId = "1";
  22. const testGovernanceEmitter =
  23. "0x0000000000000000000000000000000000000000000000000000000000001234";
  24. const testPyth2WormholeChainId = "1";
  25. const testPyth2WormholeEmitter =
  26. "0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
  27. const notOwnerError =
  28. "Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.";
  29. const insufficientFeeError =
  30. "insufficient paid fee amount";
  31. // Place all atomic operations that are done within migrations here.
  32. beforeEach(async function () {
  33. this.pythProxy = await deployProxy(PythUpgradable, [
  34. (await Wormhole.deployed()).address,
  35. testPyth2WormholeChainId,
  36. testPyth2WormholeEmitter,
  37. ]);
  38. await this.pythProxy.addDataSource(
  39. testPyth2WormholeChainId,
  40. testPyth2WormholeEmitter
  41. );
  42. // Setting the validity time to 60 seconds
  43. await this.pythProxy.updateValidTimePeriodSeconds(60);
  44. // Setting the governance data source to 0x1 (solana) and some random emitter address
  45. await this.pythProxy.updateGovernanceDataSource(testGovernanceChainId, testGovernanceEmitter, 0);
  46. });
  47. it("should be initialized with the correct signers and values", async function () {
  48. await this.pythProxy.isValidDataSource(testPyth2WormholeChainId, testPyth2WormholeEmitter);
  49. });
  50. it("should allow upgrades from the owner", async function () {
  51. // Check that the owner is the default account Truffle
  52. // has configured for the network. upgradeProxy will send
  53. // transactions from the default account.
  54. const accounts = await web3.eth.getAccounts();
  55. const defaultAccount = accounts[0];
  56. const owner = await this.pythProxy.owner();
  57. assert.equal(owner, defaultAccount);
  58. // Try and upgrade the proxy
  59. const newImplementation = await upgradeProxy(
  60. this.pythProxy.address,
  61. MockPythUpgrade
  62. );
  63. // Check that the new upgrade is successful
  64. assert.equal(await newImplementation.isUpgradeActive(), true);
  65. assert.equal(this.pythProxy.address, newImplementation.address);
  66. });
  67. it("should allow ownership transfer", async function () {
  68. // Check that the owner is the default account Truffle
  69. // has configured for the network.
  70. const accounts = await web3.eth.getAccounts();
  71. const defaultAccount = accounts[0];
  72. assert.equal(await this.pythProxy.owner(), defaultAccount);
  73. // Check that another account can't transfer the ownership
  74. await expectRevert(
  75. this.pythProxy.transferOwnership(accounts[1], {
  76. from: accounts[1],
  77. }),
  78. notOwnerError
  79. );
  80. // Transfer the ownership to another account
  81. await this.pythProxy.transferOwnership(accounts[2], {
  82. from: defaultAccount,
  83. });
  84. assert.equal(await this.pythProxy.owner(), accounts[2]);
  85. // Check that the original account can't transfer the ownership back to itself
  86. await expectRevert(
  87. this.pythProxy.transferOwnership(defaultAccount, {
  88. from: defaultAccount,
  89. }),
  90. notOwnerError
  91. );
  92. // Check that the new owner can transfer the ownership back to the original account
  93. await this.pythProxy.transferOwnership(defaultAccount, {
  94. from: accounts[2],
  95. });
  96. assert.equal(await this.pythProxy.owner(), defaultAccount);
  97. });
  98. it("should not allow upgrades from the another account", async function () {
  99. // This test is slightly convoluted as, due to a limitation of Truffle,
  100. // we cannot specify which account upgradeProxy send transactions from:
  101. // it will always use the default account.
  102. //
  103. // Therefore, we transfer the ownership to another account first,
  104. // and then attempt an upgrade using the default account.
  105. // Check that the owner is the default account Truffle
  106. // has configured for the network.
  107. const accounts = await web3.eth.getAccounts();
  108. const defaultAccount = accounts[0];
  109. assert.equal(await this.pythProxy.owner(), defaultAccount);
  110. // Transfer the ownership to another account
  111. const newOwnerAccount = accounts[1];
  112. await this.pythProxy.transferOwnership(newOwnerAccount, {
  113. from: defaultAccount,
  114. });
  115. assert.equal(await this.pythProxy.owner(), newOwnerAccount);
  116. // Try and upgrade using the default account, which will fail
  117. // because we are no longer the owner.
  118. await expectRevert(
  119. upgradeProxy(this.pythProxy.address, MockPythUpgrade),
  120. notOwnerError
  121. );
  122. });
  123. it("should allow updating singleUpdateFeeInWei by owner", async function () {
  124. // Check that the owner is the default account Truffle
  125. // has configured for the network.
  126. const accounts = await web3.eth.getAccounts();
  127. const defaultAccount = accounts[0];
  128. assert.equal(await this.pythProxy.owner(), defaultAccount);
  129. // Check initial fee is zero
  130. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  131. // Set fee
  132. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  133. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  134. });
  135. it("should not allow updating singleUpdateFeeInWei by another account", async function () {
  136. // Check that the owner is the default account Truffle
  137. // has configured for the network.
  138. const accounts = await web3.eth.getAccounts();
  139. const defaultAccount = accounts[0];
  140. assert.equal(await this.pythProxy.owner(), defaultAccount);
  141. // Check initial valid time period is zero
  142. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  143. // Checks setting valid time period using another account reverts.
  144. await expectRevert(
  145. this.pythProxy.updateSingleUpdateFeeInWei(10, {from: accounts[1]}),
  146. notOwnerError,
  147. );
  148. });
  149. it("should allow updating validTimePeriodSeconds by owner", async function () {
  150. // Check that the owner is the default account Truffle
  151. // has configured for the network.
  152. const accounts = await web3.eth.getAccounts();
  153. const defaultAccount = accounts[0];
  154. assert.equal(await this.pythProxy.owner(), defaultAccount);
  155. // Check valid time period is 60 (set in beforeEach)
  156. assert.equal(await this.pythProxy.validTimePeriodSeconds(), 60);
  157. // Set valid time period
  158. await this.pythProxy.updateValidTimePeriodSeconds(30);
  159. assert.equal(await this.pythProxy.validTimePeriodSeconds(), 30);
  160. });
  161. it("should not allow updating validTimePeriodSeconds by another account", async function () {
  162. // Check that the owner is the default account Truffle
  163. // has configured for the network.
  164. const accounts = await web3.eth.getAccounts();
  165. const defaultAccount = accounts[0];
  166. assert.equal(await this.pythProxy.owner(), defaultAccount);
  167. // Check valid time period is 60 (set in beforeEach)
  168. assert.equal(await this.pythProxy.validTimePeriodSeconds(), 60);
  169. // Checks setting validity time using another account reverts.
  170. await expectRevert(
  171. this.pythProxy.updateValidTimePeriodSeconds(30, {from: accounts[1]}),
  172. notOwnerError,
  173. );
  174. });
  175. // NOTE(2022-05-02): Raw hex payload obtained from format serialization unit tests in `p2w-sdk/rust`
  176. // Latest known addition: wire format v3
  177. //
  178. // Tests rely on a p2w-sdk mock price/prod ID generation rule:
  179. // nthProdByte(n) = n % 256, starting with n=1
  180. // nthPriceByte(n) = 255 - (n % 256), starting with n=1
  181. //
  182. // Examples:
  183. // 1st prod = "0x010101[...]"
  184. // 1st price = "0xFEFEFE[...]"
  185. // 2nd prod = "0x020202[...]"
  186. // 2nd price = "0xFDFDFD[...]"
  187. // 3rd prod = "0x030303[...]"
  188. // 3rd price = "0xFCFCFC[...]"
  189. const RAW_BATCH_ATTESTATION_TIME_REGEX = /DEADBEEFFADEDEED/g;
  190. const RAW_BATCH_PUBLISH_TIME_REGEX = /00000000DADEBEEF/g;
  191. const RAW_BATCH_PRICE_REGEX = /0000002BAD2FEED7/g;
  192. const RAW_PRICE_ATTESTATION_SIZE = 149;
  193. const RAW_BATCH_ATTESTATION_COUNT = 10;
  194. const RAW_BATCH =
  195. "0x" +
  196. "5032574800030000000102000A00950101010101010101010101010101010101010101010101010101010101010101FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0202020202020202020202020202020202020202020202020202020202020202FDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFD0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0303030303030303030303030303030303030303030303030303030303030303FCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0404040404040404040404040404040404040404040404040404040404040404FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFB0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0505050505050505050505050505050505050505050505050505050505050505FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFA0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0606060606060606060606060606060606060606060606060606060606060606F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F90000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0707070707070707070707070707070707070707070707070707070707070707F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F80000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0808080808080808080808080808080808080808080808080808080808080808F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F70000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0909090909090909090909090909090909090909090909090909090909090909F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F60000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0AF5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F50000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF";
  197. // Takes an unsigned 64-bit integer, converts it to hex with 0-padding
  198. function u64ToHex(timestamp) {
  199. // u64 -> 8 bytes -> 16 hex bytes
  200. return timestamp.toString(16).padStart(16, "0");
  201. }
  202. function generateRawBatchAttestation(
  203. publishTime,
  204. attestationTime,
  205. priceVal
  206. ) {
  207. const pubTs = u64ToHex(publishTime);
  208. const attTs = u64ToHex(attestationTime);
  209. const price = u64ToHex(priceVal);
  210. const replaced = RAW_BATCH.replace(RAW_BATCH_PUBLISH_TIME_REGEX, pubTs)
  211. .replace(RAW_BATCH_ATTESTATION_TIME_REGEX, attTs)
  212. .replace(RAW_BATCH_PRICE_REGEX, price);
  213. return replaced;
  214. }
  215. it("should parse batch price attestation correctly", async function () {
  216. const magic = 0x50325748;
  217. const versionMajor = 3;
  218. const versionMinor = 0;
  219. let attestationTime = 1647273460; // re-used for publishTime
  220. let publishTime = 1647273465; // re-used for publishTime
  221. let priceVal = 1337;
  222. let rawBatch = generateRawBatchAttestation(
  223. publishTime,
  224. attestationTime,
  225. priceVal
  226. );
  227. let parsed = await this.pythProxy.parseBatchPriceAttestation(rawBatch);
  228. // Check the header
  229. assert.equal(parsed.header.magic, magic);
  230. assert.equal(parsed.header.versionMajor, versionMajor);
  231. assert.equal(parsed.header.versionMinor, versionMinor);
  232. assert.equal(parsed.header.payloadId, 2);
  233. assert.equal(parsed.nAttestations, RAW_BATCH_ATTESTATION_COUNT);
  234. assert.equal(parsed.attestationSize, RAW_PRICE_ATTESTATION_SIZE);
  235. assert.equal(parsed.attestations.length, parsed.nAttestations);
  236. for (var i = 0; i < parsed.attestations.length; ++i) {
  237. const prodId =
  238. "0x" + (i + 1).toString(16).padStart(2, "0").repeat(32);
  239. const priceByte = 255 - ((i + 1) % 256);
  240. const priceId =
  241. "0x" + priceByte.toString(16).padStart(2, "0").repeat(32);
  242. assert.equal(parsed.attestations[i].productId, prodId);
  243. assert.equal(parsed.attestations[i].priceId, priceId);
  244. assert.equal(parsed.attestations[i].price, priceVal);
  245. assert.equal(parsed.attestations[i].conf, 101);
  246. assert.equal(parsed.attestations[i].expo, -3);
  247. assert.equal(parsed.attestations[i].emaPrice, -42);
  248. assert.equal(parsed.attestations[i].emaConf, 42);
  249. assert.equal(parsed.attestations[i].status, 1);
  250. assert.equal(parsed.attestations[i].numPublishers, 123212);
  251. assert.equal(parsed.attestations[i].maxNumPublishers, 321232);
  252. assert.equal(
  253. parsed.attestations[i].attestationTime,
  254. attestationTime
  255. );
  256. assert.equal(parsed.attestations[i].publishTime, publishTime);
  257. assert.equal(parsed.attestations[i].prevPublishTime, 0xdeadbabe);
  258. assert.equal(parsed.attestations[i].prevPrice, 0xdeadfacebeef);
  259. assert.equal(parsed.attestations[i].prevConf, 0xbadbadbeef);
  260. console.debug(
  261. `attestation ${i + 1}/${parsed.attestations.length} parsed OK`
  262. );
  263. }
  264. });
  265. async function updatePriceFeeds(contract, batches, valueInWei, chainId, emitter) {
  266. let updateData = [];
  267. for (let data of batches) {
  268. const vm = await signAndEncodeVM(
  269. 1,
  270. 1,
  271. chainId || testPyth2WormholeChainId,
  272. emitter || testPyth2WormholeEmitter,
  273. 0,
  274. data,
  275. [testSigner1PK],
  276. 0,
  277. 0
  278. );
  279. updateData.push("0x" + vm);
  280. }
  281. return await contract.updatePriceFeeds(updateData, {value: valueInWei});
  282. }
  283. it("should attest price updates over wormhole", async function () {
  284. let ts = 1647273460;
  285. let rawBatch = generateRawBatchAttestation(ts - 5, ts, 1337);
  286. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  287. });
  288. it("should attest price updates empty", async function () {
  289. const receipt = await updatePriceFeeds(this.pythProxy, []);
  290. expectEvent.notEmitted(receipt, 'PriceFeedUpdate');
  291. expectEvent.notEmitted(receipt, 'BatchPriceFeedUpdate');
  292. expectEvent(receipt, 'UpdatePriceFeeds', {
  293. batchCount: '0',
  294. });
  295. });
  296. it("should attest price updates with multiple batches of correct order", async function () {
  297. let ts = 1647273460;
  298. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  299. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  300. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2]);
  301. expectEvent(receipt, 'PriceFeedUpdate', {
  302. fresh: true,
  303. });
  304. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  305. batchSize: '10',
  306. freshPricesInBatch: '10',
  307. });
  308. expectEvent(receipt, 'UpdatePriceFeeds', {
  309. batchCount: '2',
  310. });
  311. });
  312. it("should attest price updates with multiple batches of wrong order", async function () {
  313. let ts = 1647273460;
  314. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  315. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  316. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2, rawBatch1]);
  317. expectEvent(receipt, 'PriceFeedUpdate', {
  318. fresh: true,
  319. });
  320. expectEvent(receipt, 'PriceFeedUpdate', {
  321. fresh: false,
  322. });
  323. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  324. batchSize: '10',
  325. freshPricesInBatch: '10',
  326. });
  327. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  328. batchSize: '10',
  329. freshPricesInBatch: '0',
  330. });
  331. expectEvent(receipt, 'UpdatePriceFeeds', {
  332. batchCount: '2',
  333. });
  334. });
  335. it("should not attest price updates with when required fee is not given", async function () {
  336. // Check that the owner is the default account Truffle
  337. // has configured for the network.
  338. const accounts = await web3.eth.getAccounts();
  339. const defaultAccount = accounts[0];
  340. assert.equal(await this.pythProxy.owner(), defaultAccount);
  341. // Check initial fee is zero
  342. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  343. // Set fee
  344. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  345. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  346. let ts = 1647273460;
  347. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  348. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  349. // Getting the fee from the contract
  350. let feeInWei = await this.pythProxy.getUpdateFee(2);
  351. assert.equal(feeInWei, 20);
  352. // When a smaller fee is payed it reverts
  353. await expectRevert(
  354. updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei - 1),
  355. insufficientFeeError
  356. );
  357. });
  358. it("should attest price updates with when required fee is given", async function () {
  359. // Check that the owner is the default account Truffle
  360. // has configured for the network.
  361. const accounts = await web3.eth.getAccounts();
  362. const defaultAccount = accounts[0];
  363. assert.equal(await this.pythProxy.owner(), defaultAccount);
  364. // Check initial fee is zero
  365. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  366. // Set fee
  367. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  368. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  369. let ts = 1647273460;
  370. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  371. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  372. // Getting the fee from the contract
  373. let feeInWei = await this.pythProxy.getUpdateFee(2);
  374. assert.equal(feeInWei, 20);
  375. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei);
  376. expectEvent(receipt, 'UpdatePriceFeeds', {
  377. fee: feeInWei
  378. });
  379. const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
  380. assert.equal(pythBalance, feeInWei);
  381. });
  382. it("should attest price updates with required fee even if more fee is given", async function () {
  383. // Check that the owner is the default account Truffle
  384. // has configured for the network.
  385. const accounts = await web3.eth.getAccounts();
  386. const defaultAccount = accounts[0];
  387. assert.equal(await this.pythProxy.owner(), defaultAccount);
  388. // Check initial fee is zero
  389. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  390. // Set fee
  391. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  392. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  393. let ts = 1647273460;
  394. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  395. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  396. // Getting the fee from the contract
  397. let feeInWei = await this.pythProxy.getUpdateFee(2);
  398. assert.equal(feeInWei, 20);
  399. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10);
  400. expectEvent(receipt, 'UpdatePriceFeeds', {
  401. fee: feeInWei
  402. });
  403. const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
  404. assert.equal(pythBalance, feeInWei);
  405. });
  406. it("should cache price updates", async function () {
  407. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  408. let priceVal = 521;
  409. let rawBatch = generateRawBatchAttestation(
  410. currentTimestamp - 5,
  411. currentTimestamp,
  412. priceVal
  413. );
  414. let receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
  415. expectEvent(receipt, 'PriceFeedUpdate', {
  416. fresh: true,
  417. });
  418. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  419. batchSize: '10',
  420. freshPricesInBatch: '10',
  421. });
  422. expectEvent(receipt, 'UpdatePriceFeeds', {
  423. batchCount: '1',
  424. });
  425. let first_prod_id = "0x" + "01".repeat(32);
  426. let first_price_id = "0x" + "fe".repeat(32);
  427. let second_prod_id = "0x" + "02".repeat(32);
  428. let second_price_id = "0x" + "fd".repeat(32);
  429. // Confirm that previously non-existent feeds are created
  430. let first = await this.pythProxy.queryPriceFeed(first_price_id);
  431. console.debug(`first is ${JSON.stringify(first)}`);
  432. assert.equal(first.price, priceVal);
  433. let second = await this.pythProxy.queryPriceFeed(second_price_id);
  434. assert.equal(second.price, priceVal);
  435. // Confirm the price is bumped after a new attestation updates each record
  436. let nextTimestamp = currentTimestamp + 1;
  437. let rawBatch2 = generateRawBatchAttestation(
  438. nextTimestamp - 5,
  439. nextTimestamp,
  440. priceVal + 5
  441. );
  442. receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2]);
  443. expectEvent(receipt, 'PriceFeedUpdate', {
  444. fresh: true,
  445. });
  446. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  447. batchSize: '10',
  448. freshPricesInBatch: '10',
  449. });
  450. expectEvent(receipt, 'UpdatePriceFeeds', {
  451. batchCount: '1',
  452. });
  453. first = await this.pythProxy.queryPriceFeed(first_price_id);
  454. assert.equal(first.price, priceVal + 5);
  455. second = await this.pythProxy.queryPriceFeed(second_price_id);
  456. assert.equal(second.price, priceVal + 5);
  457. // Confirm that only strictly larger timestamps trigger updates
  458. let rawBatch3 = generateRawBatchAttestation(
  459. nextTimestamp - 5,
  460. nextTimestamp,
  461. priceVal + 10
  462. );
  463. receipt = await updatePriceFeeds(this.pythProxy, [rawBatch3]);
  464. expectEvent(receipt, 'PriceFeedUpdate', {
  465. fresh: false,
  466. });
  467. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  468. batchSize: '10',
  469. freshPricesInBatch: '0',
  470. });
  471. expectEvent(receipt, 'UpdatePriceFeeds', {
  472. batchCount: '1',
  473. });
  474. first = await this.pythProxy.queryPriceFeed(first_price_id);
  475. assert.equal(first.price, priceVal + 5);
  476. assert.notEqual(first.price, priceVal + 10);
  477. second = await this.pythProxy.queryPriceFeed(second_price_id);
  478. assert.equal(second.price, priceVal + 5);
  479. assert.notEqual(second.price, priceVal + 10);
  480. });
  481. it("should fail transaction if a price is not found", async function () {
  482. await expectRevert(
  483. this.pythProxy.queryPriceFeed(
  484. "0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed"
  485. ),
  486. "no price feed found for the given price id"
  487. );
  488. });
  489. it("should revert on getting stale current prices", async function () {
  490. let smallestTimestamp = 1;
  491. let rawBatch = generateRawBatchAttestation(
  492. smallestTimestamp,
  493. smallestTimestamp + 5,
  494. 1337
  495. );
  496. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  497. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  498. const price_id =
  499. "0x" +
  500. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  501. expectRevert(
  502. this.pythProxy.getCurrentPrice(price_id),
  503. "current price unavailable"
  504. );
  505. }
  506. });
  507. it("should revert on getting current prices too far into the future as they are considered unknown", async function () {
  508. let largestTimestamp = 4294967295;
  509. let rawBatch = generateRawBatchAttestation(
  510. largestTimestamp - 5,
  511. largestTimestamp,
  512. 1337
  513. );
  514. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  515. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  516. const price_id =
  517. "0x" +
  518. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  519. expectRevert(
  520. this.pythProxy.getCurrentPrice(price_id),
  521. "current price unavailable"
  522. );
  523. }
  524. });
  525. it("changing validity time works", async function() {
  526. const latestTime = await time.latest();
  527. let rawBatch = generateRawBatchAttestation(
  528. latestTime,
  529. latestTime,
  530. 1337
  531. );
  532. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  533. // Setting the validity time to 30 seconds
  534. await this.pythProxy.updateValidTimePeriodSeconds(30);
  535. // Then prices should be available
  536. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  537. const price_id =
  538. "0x" +
  539. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  540. // Expect getCurrentPrice to work (not revert)
  541. await this.pythProxy.getCurrentPrice(price_id);
  542. }
  543. // One minute passes
  544. await time.increase(time.duration.minutes(1));
  545. // The prices should become unavailable now.
  546. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  547. const price_id =
  548. "0x" +
  549. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  550. expectRevert(
  551. this.pythProxy.getCurrentPrice(price_id),
  552. "current price unavailable"
  553. );
  554. }
  555. // Setting the validity time to 120 seconds
  556. await this.pythProxy.updateValidTimePeriodSeconds(120);
  557. // Then prices should be available because the valid period is now 120 seconds
  558. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  559. const price_id =
  560. "0x" +
  561. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  562. let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
  563. // Expect getCurrentPrice to work (not revert)
  564. await this.pythProxy.getCurrentPrice(price_id);
  565. }
  566. });
  567. it("should accept a VM after adding its data source", async function () {
  568. let newChainId = "42424";
  569. let newEmitter = testPyth2WormholeEmitter.replace("a", "f");
  570. await this.pythProxy.addDataSource(newChainId, newEmitter);
  571. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  572. let rawBatch = generateRawBatchAttestation(
  573. currentTimestamp - 5,
  574. currentTimestamp,
  575. 1337
  576. );
  577. let vm = await signAndEncodeVM(
  578. 1,
  579. 1,
  580. newChainId,
  581. newEmitter,
  582. 0,
  583. rawBatch,
  584. [testSigner1PK],
  585. 0,
  586. 0
  587. );
  588. await this.pythProxy.updatePriceFeeds(["0x" + vm]);
  589. });
  590. it("should reject a VM after removing its data source", async function () {
  591. // Add 2 new data sources to produce a non-trivial data source state.
  592. let newChainId = "42424";
  593. let newEmitter = testPyth2WormholeEmitter.replace("a", "f");
  594. await this.pythProxy.addDataSource(newChainId, newEmitter);
  595. let newChainId2 = "42425";
  596. let newEmitter2 = testPyth2WormholeEmitter.replace("a", "e");
  597. await this.pythProxy.addDataSource(newChainId2, newEmitter2);
  598. // Remove the first one added
  599. await this.pythProxy.removeDataSource(newChainId, newEmitter);
  600. // Sign a batch with the removed data source
  601. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  602. let rawBatch = generateRawBatchAttestation(
  603. currentTimestamp - 5,
  604. currentTimestamp,
  605. 1337
  606. );
  607. let vm = await signAndEncodeVM(
  608. 1,
  609. 1,
  610. newChainId,
  611. newEmitter,
  612. 0,
  613. rawBatch,
  614. [testSigner1PK],
  615. 0,
  616. 0
  617. );
  618. await expectRevert(
  619. this.pythProxy.updatePriceFeeds(["0x" + vm]),
  620. "invalid data source chain/emitter ID"
  621. );
  622. });
  623. // Governance
  624. // Logics that apply to all governance messages
  625. it("Make sure invalid magic and module won't work", async function () {
  626. // First 4 bytes of data are magic and the second byte after that is module
  627. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  628. const wrongMagic = Buffer.from(data);
  629. wrongMagic[1] = 0;
  630. const vaaWrongMagic = await createVAAFromUint8Array(wrongMagic,
  631. testGovernanceChainId,
  632. testGovernanceEmitter,
  633. 1
  634. );
  635. await expectRevert(
  636. this.pythProxy.executeGovernanceInstruction(vaaWrongMagic),
  637. "invalid magic for GovernanceInstruction"
  638. );
  639. const wrongModule = Buffer.from(data);
  640. wrongModule[4] = 0;
  641. const vaaWrongModule = await createVAAFromUint8Array(wrongModule,
  642. testGovernanceChainId,
  643. testGovernanceEmitter,
  644. 1
  645. );
  646. await expectRevert(
  647. this.pythProxy.executeGovernanceInstruction(vaaWrongModule),
  648. "invalid module for GovernanceInstruction"
  649. );
  650. const outOfBoundModule = Buffer.from(data);
  651. outOfBoundModule[4] = 20;
  652. const vaaOutOfBoundModule = await createVAAFromUint8Array(outOfBoundModule,
  653. testGovernanceChainId,
  654. testGovernanceEmitter,
  655. 1
  656. );
  657. await expectRevert(
  658. this.pythProxy.executeGovernanceInstruction(vaaOutOfBoundModule),
  659. "Panic: Enum value out of bounds.",
  660. );
  661. });
  662. it("Make sure governance with wrong sender won't work", async function () {
  663. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  664. const vaaWrongEmitter = await createVAAFromUint8Array(data,
  665. testGovernanceChainId,
  666. "0x0000000000000000000000000000000000000000000000000000000000001111",
  667. 1
  668. );
  669. await expectRevert(
  670. this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter),
  671. "VAA is not coming from the governance data source"
  672. );
  673. const vaaWrongChain = await createVAAFromUint8Array(data,
  674. governance.CHAINS.karura,
  675. testGovernanceEmitter,
  676. 1
  677. );
  678. await expectRevert(
  679. this.pythProxy.executeGovernanceInstruction(vaaWrongChain),
  680. "VAA is not coming from the governance data source"
  681. );
  682. });
  683. it("Make sure governance with only target chain id and 0 work", async function () {
  684. const wrongChainData = new governance.SetValidPeriodInstruction(governance.CHAINS.solana, BigInt(10)).serialize();
  685. const wrongChainVaa = await createVAAFromUint8Array(wrongChainData,
  686. testGovernanceChainId,
  687. testGovernanceEmitter,
  688. 1
  689. );
  690. await expectRevert(
  691. this.pythProxy.executeGovernanceInstruction(wrongChainVaa),
  692. "invalid target chain for this governance instruction"
  693. );
  694. const dataForAllChains = new governance.SetValidPeriodInstruction(governance.CHAINS.unset, BigInt(10)).serialize();
  695. const vaaForAllChains = await createVAAFromUint8Array(dataForAllChains,
  696. testGovernanceChainId,
  697. testGovernanceEmitter,
  698. 1
  699. );
  700. await this.pythProxy.executeGovernanceInstruction(vaaForAllChains);
  701. const dataForEth = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  702. const vaaForEth = await createVAAFromUint8Array(dataForEth,
  703. testGovernanceChainId,
  704. testGovernanceEmitter,
  705. 2,
  706. );
  707. await this.pythProxy.executeGovernanceInstruction(vaaForEth);
  708. });
  709. it("Make sure that governance messages are executed in order and cannot be reused", async function () {
  710. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  711. const vaaSeq1 = await createVAAFromUint8Array(data,
  712. testGovernanceChainId,
  713. testGovernanceEmitter,
  714. 1
  715. );
  716. await this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  717. // Replaying shouldn't work
  718. await expectRevert(
  719. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  720. "VAA is older than the last executed governance VAA",
  721. )
  722. const vaaSeq2 = await createVAAFromUint8Array(data,
  723. testGovernanceChainId,
  724. testGovernanceEmitter,
  725. 2
  726. );
  727. await this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  728. // Replaying shouldn't work
  729. await expectRevert(
  730. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  731. "VAA is older than the last executed governance VAA",
  732. )
  733. await expectRevert(
  734. this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  735. "VAA is older than the last executed governance VAA",
  736. )
  737. });
  738. // Per governance type logic
  739. it("Upgrading the contract with chain id 0 is invalid", async function () {
  740. const newImplementation = await PythUpgradable.new();
  741. const data = new governance.EthereumUpgradeContractInstruction(
  742. governance.CHAINS.unset, // 0
  743. new governance.HexString20Bytes(newImplementation.address),
  744. ).serialize();
  745. const vaa = await createVAAFromUint8Array(data,
  746. testGovernanceChainId,
  747. testGovernanceEmitter,
  748. 1
  749. );
  750. await expectRevert(
  751. this.pythProxy.executeGovernanceInstruction(vaa),
  752. "upgrade with chain id 0 is not possible"
  753. );
  754. });
  755. it("Upgrading the contract should work", async function () {
  756. const newImplementation = await PythUpgradable.new();
  757. const data = new governance.EthereumUpgradeContractInstruction(
  758. governance.CHAINS.ethereum,
  759. new governance.HexString20Bytes(newImplementation.address),
  760. ).serialize();
  761. const vaa = await createVAAFromUint8Array(data,
  762. testGovernanceChainId,
  763. testGovernanceEmitter,
  764. 1
  765. );
  766. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  767. // Couldn't get the oldImplementation address.
  768. expectEvent(receipt, 'ContractUpgraded', {
  769. newImplementation: newImplementation.address,
  770. });
  771. expectEvent(receipt, 'Upgraded', {
  772. implementation: newImplementation.address
  773. });
  774. });
  775. it("Setting governance data source should work", async function () {
  776. const data = new governance.SetGovernanceDataSourceInstruction(
  777. governance.CHAINS.ethereum,
  778. new governance.DataSource(
  779. governance.CHAINS.acala,
  780. new governance.HexString32Bytes(
  781. "0x0000000000000000000000000000000000000000000000000000000000001111",
  782. )
  783. ),
  784. BigInt(10)
  785. ).serialize();
  786. const vaa = await createVAAFromUint8Array(data,
  787. testGovernanceChainId,
  788. testGovernanceEmitter,
  789. 1
  790. );
  791. const oldGovernanceDataSource = await this.pythProxy.governanceDataSource();
  792. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  793. expectEvent(receipt, 'GovernanceDataSourceSet', {
  794. oldDataSource: oldGovernanceDataSource,
  795. newDataSource: await this.pythProxy.governanceDataSource(),
  796. });
  797. const newVaaFromOldGovernanceSource = await createVAAFromUint8Array(data,
  798. testGovernanceChainId,
  799. testGovernanceEmitter,
  800. 2
  801. );
  802. await expectRevert(
  803. this.pythProxy.executeGovernanceInstruction(newVaaFromOldGovernanceSource),
  804. "VAA is not coming from the governance data source"
  805. );
  806. const newVaaFromNewGovernanceOldSequence = await createVAAFromUint8Array(data,
  807. governance.CHAINS.acala,
  808. "0x0000000000000000000000000000000000000000000000000000000000001111",
  809. 2
  810. );
  811. await expectRevert(
  812. this.pythProxy.executeGovernanceInstruction(newVaaFromNewGovernanceOldSequence),
  813. "VAA is older than the last executed governance VAA"
  814. );
  815. const newVaaFromNewGovernanceGood = await createVAAFromUint8Array(data,
  816. governance.CHAINS.acala,
  817. "0x0000000000000000000000000000000000000000000000000000000000001111",
  818. 20
  819. );
  820. await this.pythProxy.executeGovernanceInstruction(newVaaFromNewGovernanceGood);
  821. });
  822. it("Setting data sources should work", async function () {
  823. const data = new governance.SetDataSourcesInstruction(
  824. governance.CHAINS.ethereum,
  825. [new governance.DataSource(
  826. governance.CHAINS.acala,
  827. new governance.HexString32Bytes(
  828. "0x0000000000000000000000000000000000000000000000000000000000001111",
  829. )
  830. )],
  831. ).serialize();
  832. const vaa = await createVAAFromUint8Array(data,
  833. testGovernanceChainId,
  834. testGovernanceEmitter,
  835. 1
  836. );
  837. const oldDataSources = await this.pythProxy.validDataSources();
  838. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  839. expectEvent(receipt, 'DataSourcesSet', {
  840. oldDataSources: oldDataSources,
  841. newDataSources: await this.pythProxy.validDataSources(),
  842. });
  843. assert.isTrue(await this.pythProxy.isValidDataSource(governance.CHAINS.acala,
  844. "0x0000000000000000000000000000000000000000000000000000000000001111"));
  845. assert.isFalse(await this.pythProxy.isValidDataSource(testPyth2WormholeChainId,
  846. testPyth2WormholeEmitter));
  847. let rawBatch = generateRawBatchAttestation(
  848. 100,
  849. 100,
  850. 1337
  851. );
  852. await expectRevert(
  853. updatePriceFeeds(this.pythProxy, [rawBatch]),
  854. "invalid data source chain/emitter ID"
  855. );
  856. await updatePriceFeeds(this.pythProxy, [rawBatch], 0, governance.CHAINS.acala,
  857. "0x0000000000000000000000000000000000000000000000000000000000001111");
  858. });
  859. it("Setting fee should work", async function () {
  860. const data = new governance.SetFeeInstruction(
  861. governance.CHAINS.ethereum,
  862. BigInt(5), BigInt(3) // 5*10**3 = 5000
  863. ).serialize();
  864. const vaa = await createVAAFromUint8Array(data,
  865. testGovernanceChainId,
  866. testGovernanceEmitter,
  867. 1
  868. );
  869. const oldFee = await this.pythProxy.singleUpdateFeeInWei();
  870. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  871. expectEvent(receipt, 'FeeSet', {
  872. oldFee: oldFee,
  873. newFee: await this.pythProxy.singleUpdateFeeInWei(),
  874. });
  875. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000");
  876. let rawBatch = generateRawBatchAttestation(
  877. 100,
  878. 100,
  879. 1337
  880. );
  881. await expectRevert(
  882. updatePriceFeeds(this.pythProxy, [rawBatch], 0),
  883. insufficientFeeError
  884. );
  885. const receiptUpdateFeeds = await updatePriceFeeds(this.pythProxy, [rawBatch], 5000);
  886. expectEvent(receiptUpdateFeeds, 'UpdatePriceFeeds', {
  887. fee: "5000"
  888. });
  889. });
  890. it("Setting valid period should work", async function () {
  891. const data = new governance.SetValidPeriodInstruction(
  892. governance.CHAINS.ethereum,
  893. BigInt(0),
  894. ).serialize();
  895. const vaa = await createVAAFromUint8Array(data,
  896. testGovernanceChainId,
  897. testGovernanceEmitter,
  898. 1
  899. );
  900. const oldValidPeriod = await this.pythProxy.validTimePeriodSeconds();
  901. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  902. expectEvent(receipt, 'ValidPeriodSet', {
  903. oldValidPeriod: oldValidPeriod,
  904. newValidPeriod: await this.pythProxy.validTimePeriodSeconds(),
  905. });
  906. assert.equal(await this.pythProxy.validTimePeriodSeconds(), "0");
  907. // The behaviour of valid time period is extensively tested before,
  908. // and adding it here will cause more complexity (and is not so short).
  909. });
  910. // Renounce ownership works
  911. it("Renouncing ownership should work", async function () {
  912. await this.pythProxy.updateValidTimePeriodSeconds(100);
  913. await this.pythProxy.renounceOwnership();
  914. await expectRevert(
  915. this.pythProxy.updateValidTimePeriodSeconds(60),
  916. "Ownable: caller is not the owner",
  917. )
  918. });
  919. // Version
  920. it("Make sure version is the npm package version", async function () {
  921. const contractVersion = await this.pythProxy.version();
  922. const { version } = require('../package.json');
  923. expect(contractVersion).equal(version);
  924. });
  925. });
  926. const signAndEncodeVM = async function (
  927. timestamp,
  928. nonce,
  929. emitterChainId,
  930. emitterAddress,
  931. sequence,
  932. data,
  933. signers,
  934. guardianSetIndex,
  935. consistencyLevel
  936. ) {
  937. const body = [
  938. web3.eth.abi
  939. .encodeParameter("uint32", timestamp)
  940. .substring(2 + (64 - 8)),
  941. web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
  942. web3.eth.abi
  943. .encodeParameter("uint16", emitterChainId)
  944. .substring(2 + (64 - 4)),
  945. web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
  946. web3.eth.abi
  947. .encodeParameter("uint64", sequence)
  948. .substring(2 + (64 - 16)),
  949. web3.eth.abi
  950. .encodeParameter("uint8", consistencyLevel)
  951. .substring(2 + (64 - 2)),
  952. data.substr(2),
  953. ];
  954. const hash = web3.utils.soliditySha3(
  955. web3.utils.soliditySha3("0x" + body.join(""))
  956. );
  957. let signatures = "";
  958. for (let i in signers) {
  959. const ec = new elliptic.ec("secp256k1");
  960. const key = ec.keyFromPrivate(signers[i]);
  961. const signature = key.sign(hash.substr(2), { canonical: true });
  962. const packSig = [
  963. web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
  964. zeroPadBytes(signature.r.toString(16), 32),
  965. zeroPadBytes(signature.s.toString(16), 32),
  966. web3.eth.abi
  967. .encodeParameter("uint8", signature.recoveryParam)
  968. .substr(2 + (64 - 2)),
  969. ];
  970. signatures += packSig.join("");
  971. }
  972. const vm = [
  973. web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
  974. web3.eth.abi
  975. .encodeParameter("uint32", guardianSetIndex)
  976. .substring(2 + (64 - 8)),
  977. web3.eth.abi
  978. .encodeParameter("uint8", signers.length)
  979. .substring(2 + (64 - 2)),
  980. signatures,
  981. body.join(""),
  982. ].join("");
  983. return vm;
  984. };
  985. function zeroPadBytes(value, length) {
  986. while (value.length < 2 * length) {
  987. value = "0" + value;
  988. }
  989. return value;
  990. }
  991. async function createVAAFromUint8Array(
  992. dataBuffer,
  993. emitterChainId,
  994. emitterAddress,
  995. sequence,
  996. ) {
  997. const dataHex = "0x" + dataBuffer.toString("hex");
  998. return "0x" + await signAndEncodeVM(
  999. 0,
  1000. 0,
  1001. emitterChainId.toString(),
  1002. emitterAddress,
  1003. sequence,
  1004. dataHex,
  1005. [testSigner1PK],
  1006. 0,
  1007. 0
  1008. );
  1009. }