pyth.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  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_BATCH_PREV_PRICE_REGEX = /0000DEADFACEBEEF/g;
  193. const RAW_BATCH_PREV_PUBLISH_TIME_REGEX = /00000000DEADBABE/g;
  194. const RAW_BATCH_EMA_PRICE_REGEX = /FFFFFFFFFFFFFFD6/g;
  195. const RAW_PRICE_ATTESTATION_SIZE = 149;
  196. const RAW_BATCH_ATTESTATION_COUNT = 10;
  197. const RAW_BATCH =
  198. "0x" +
  199. "5032574800030000000102000A00950101010101010101010101010101010101010101010101010101010101010101FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0202020202020202020202020202020202020202020202020202020202020202FDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFD0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0303030303030303030303030303030303030303030303030303030303030303FCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0404040404040404040404040404040404040404040404040404040404040404FBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFBFB0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0505050505050505050505050505050505050505050505050505050505050505FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFA0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0606060606060606060606060606060606060606060606060606060606060606F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F9F90000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0707070707070707070707070707070707070707070707070707070707070707F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F80000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0808080808080808080808080808080808080808080808080808080808080808F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F70000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0909090909090909090909090909090909090909090909090909090909090909F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F6F60000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0AF5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F5F50000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF";
  200. const RAW_UNKNOWN_BATCH_ATTESTATION_COUNT = 3;
  201. const RAW_UNKNOWN_BATCH =
  202. "0x" +
  203. "5032574800030000000102000300950101010101010101010101010101010101010101010101010101010101010101FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A000001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0202020202020202020202020202020202020202020202020202020202020202FDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFD0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A000001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0303030303030303030303030303030303030303030303030303030303030303FCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC0000002BAD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A000001E14C0004E6D0DEADBEEFFADEDEED00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF";
  204. // Takes an unsigned 64-bit integer, converts it to hex with 0-padding
  205. function u64ToHex(timestamp) {
  206. // u64 -> 8 bytes -> 16 hex bytes
  207. return timestamp.toString(16).padStart(16, "0");
  208. }
  209. function generateRawBatchAttestation(
  210. publishTime,
  211. attestationTime,
  212. priceVal
  213. ) {
  214. const pubTs = u64ToHex(publishTime);
  215. const attTs = u64ToHex(attestationTime);
  216. const price = u64ToHex(priceVal);
  217. const replaced = RAW_BATCH.replace(RAW_BATCH_PUBLISH_TIME_REGEX, pubTs)
  218. .replace(RAW_BATCH_ATTESTATION_TIME_REGEX, attTs)
  219. .replace(RAW_BATCH_PRICE_REGEX, price);
  220. return replaced;
  221. }
  222. function generateRawUnknownBatchAttestation(
  223. publishTime,
  224. attestationTime,
  225. priceVal,
  226. emaPriceVal,
  227. prevPublishTime,
  228. prevPriceVal,
  229. ) {
  230. const pubTs = u64ToHex(publishTime);
  231. const attTs = u64ToHex(attestationTime);
  232. const price = u64ToHex(priceVal);
  233. const emaPrice = u64ToHex(emaPriceVal);
  234. const prevPubTs = u64ToHex(prevPublishTime);
  235. const prevPrice = u64ToHex(prevPriceVal);
  236. const replaced = RAW_UNKNOWN_BATCH.replace(RAW_BATCH_PUBLISH_TIME_REGEX, pubTs)
  237. .replace(RAW_BATCH_ATTESTATION_TIME_REGEX, attTs)
  238. .replace(RAW_BATCH_PRICE_REGEX, price)
  239. .replace(RAW_BATCH_EMA_PRICE_REGEX, emaPrice)
  240. .replace(RAW_BATCH_PREV_PUBLISH_TIME_REGEX, prevPubTs)
  241. .replace(RAW_BATCH_PREV_PRICE_REGEX, prevPrice);
  242. return replaced;
  243. }
  244. it("should parse batch price attestation correctly", async function () {
  245. const magic = 0x50325748;
  246. const versionMajor = 3;
  247. const versionMinor = 0;
  248. let attestationTime = 1647273460; // re-used for publishTime
  249. let publishTime = 1647273465; // re-used for publishTime
  250. let priceVal = 1337;
  251. let rawBatch = generateRawBatchAttestation(
  252. publishTime,
  253. attestationTime,
  254. priceVal
  255. );
  256. let parsed = await this.pythProxy.parseBatchPriceAttestation(rawBatch);
  257. // Check the header
  258. assert.equal(parsed.header.magic, magic);
  259. assert.equal(parsed.header.versionMajor, versionMajor);
  260. assert.equal(parsed.header.versionMinor, versionMinor);
  261. assert.equal(parsed.header.payloadId, 2);
  262. assert.equal(parsed.nAttestations, RAW_BATCH_ATTESTATION_COUNT);
  263. assert.equal(parsed.attestationSize, RAW_PRICE_ATTESTATION_SIZE);
  264. assert.equal(parsed.attestations.length, parsed.nAttestations);
  265. for (var i = 0; i < parsed.attestations.length; ++i) {
  266. const prodId =
  267. "0x" + (i + 1).toString(16).padStart(2, "0").repeat(32);
  268. const priceByte = 255 - ((i + 1) % 256);
  269. const priceId =
  270. "0x" + priceByte.toString(16).padStart(2, "0").repeat(32);
  271. assert.equal(parsed.attestations[i].productId, prodId);
  272. assert.equal(parsed.attestations[i].priceId, priceId);
  273. assert.equal(parsed.attestations[i].price, priceVal);
  274. assert.equal(parsed.attestations[i].conf, 101);
  275. assert.equal(parsed.attestations[i].expo, -3);
  276. assert.equal(parsed.attestations[i].emaPrice, -42);
  277. assert.equal(parsed.attestations[i].emaConf, 42);
  278. assert.equal(parsed.attestations[i].status, 1);
  279. assert.equal(parsed.attestations[i].numPublishers, 123212);
  280. assert.equal(parsed.attestations[i].maxNumPublishers, 321232);
  281. assert.equal(
  282. parsed.attestations[i].attestationTime,
  283. attestationTime
  284. );
  285. assert.equal(parsed.attestations[i].publishTime, publishTime);
  286. assert.equal(parsed.attestations[i].prevPublishTime, 0xdeadbabe);
  287. assert.equal(parsed.attestations[i].prevPrice, 0xdeadfacebeef);
  288. assert.equal(parsed.attestations[i].prevConf, 0xbadbadbeef);
  289. console.debug(
  290. `attestation ${i + 1}/${parsed.attestations.length} parsed OK`
  291. );
  292. }
  293. });
  294. async function updatePriceFeeds(contract, batches, valueInWei, chainId, emitter) {
  295. let updateData = [];
  296. for (let data of batches) {
  297. const vm = await signAndEncodeVM(
  298. 1,
  299. 1,
  300. chainId || testPyth2WormholeChainId,
  301. emitter || testPyth2WormholeEmitter,
  302. 0,
  303. data,
  304. [testSigner1PK],
  305. 0,
  306. 0
  307. );
  308. updateData.push("0x" + vm);
  309. }
  310. return await contract.updatePriceFeeds(updateData, {value: valueInWei});
  311. }
  312. it("should attest price updates over wormhole", async function () {
  313. let ts = 1647273460;
  314. let rawBatch = generateRawBatchAttestation(ts - 5, ts, 1337);
  315. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  316. });
  317. it("should attest price updates empty", async function () {
  318. const receipt = await updatePriceFeeds(this.pythProxy, []);
  319. expectEvent.notEmitted(receipt, 'PriceFeedUpdate');
  320. expectEvent.notEmitted(receipt, 'BatchPriceFeedUpdate');
  321. expectEvent(receipt, 'UpdatePriceFeeds', {
  322. batchCount: '0',
  323. });
  324. });
  325. it("should attest price updates with multiple batches of correct order", async function () {
  326. let ts = 1647273460;
  327. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  328. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  329. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2]);
  330. expectEvent(receipt, 'PriceFeedUpdate', {
  331. fresh: true,
  332. });
  333. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  334. batchSize: '10',
  335. freshPricesInBatch: '10',
  336. });
  337. expectEvent(receipt, 'UpdatePriceFeeds', {
  338. batchCount: '2',
  339. });
  340. });
  341. it("should attest price updates with multiple batches of wrong order", async function () {
  342. let ts = 1647273460;
  343. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  344. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  345. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2, rawBatch1]);
  346. expectEvent(receipt, 'PriceFeedUpdate', {
  347. fresh: true,
  348. });
  349. expectEvent(receipt, 'PriceFeedUpdate', {
  350. fresh: false,
  351. });
  352. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  353. batchSize: '10',
  354. freshPricesInBatch: '10',
  355. });
  356. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  357. batchSize: '10',
  358. freshPricesInBatch: '0',
  359. });
  360. expectEvent(receipt, 'UpdatePriceFeeds', {
  361. batchCount: '2',
  362. });
  363. });
  364. it("should not attest price updates with when required fee is not given", async function () {
  365. // Check that the owner is the default account Truffle
  366. // has configured for the network.
  367. const accounts = await web3.eth.getAccounts();
  368. const defaultAccount = accounts[0];
  369. assert.equal(await this.pythProxy.owner(), defaultAccount);
  370. // Check initial fee is zero
  371. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  372. // Set fee
  373. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  374. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  375. let ts = 1647273460;
  376. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  377. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  378. // Getting the fee from the contract
  379. let feeInWei = await this.pythProxy.getUpdateFee(2);
  380. assert.equal(feeInWei, 20);
  381. // When a smaller fee is payed it reverts
  382. await expectRevert(
  383. updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei - 1),
  384. insufficientFeeError
  385. );
  386. });
  387. it("should attest price updates with when required fee is given", async function () {
  388. // Check that the owner is the default account Truffle
  389. // has configured for the network.
  390. const accounts = await web3.eth.getAccounts();
  391. const defaultAccount = accounts[0];
  392. assert.equal(await this.pythProxy.owner(), defaultAccount);
  393. // Check initial fee is zero
  394. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  395. // Set fee
  396. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  397. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  398. let ts = 1647273460;
  399. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  400. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  401. // Getting the fee from the contract
  402. let feeInWei = await this.pythProxy.getUpdateFee(2);
  403. assert.equal(feeInWei, 20);
  404. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei);
  405. expectEvent(receipt, 'UpdatePriceFeeds', {
  406. fee: feeInWei
  407. });
  408. const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
  409. assert.equal(pythBalance, feeInWei);
  410. });
  411. it("should attest price updates with required fee even if more fee is given", async function () {
  412. // Check that the owner is the default account Truffle
  413. // has configured for the network.
  414. const accounts = await web3.eth.getAccounts();
  415. const defaultAccount = accounts[0];
  416. assert.equal(await this.pythProxy.owner(), defaultAccount);
  417. // Check initial fee is zero
  418. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
  419. // Set fee
  420. await this.pythProxy.updateSingleUpdateFeeInWei(10);
  421. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
  422. let ts = 1647273460;
  423. let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
  424. let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
  425. // Getting the fee from the contract
  426. let feeInWei = await this.pythProxy.getUpdateFee(2);
  427. assert.equal(feeInWei, 20);
  428. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10);
  429. expectEvent(receipt, 'UpdatePriceFeeds', {
  430. fee: feeInWei
  431. });
  432. const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
  433. assert.equal(pythBalance, feeInWei);
  434. });
  435. it("should cache price updates", async function () {
  436. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  437. let priceVal = 521;
  438. let rawBatch = generateRawBatchAttestation(
  439. currentTimestamp - 5,
  440. currentTimestamp,
  441. priceVal
  442. );
  443. let receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
  444. expectEvent(receipt, 'PriceFeedUpdate', {
  445. fresh: true,
  446. });
  447. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  448. batchSize: '10',
  449. freshPricesInBatch: '10',
  450. });
  451. expectEvent(receipt, 'UpdatePriceFeeds', {
  452. batchCount: '1',
  453. });
  454. let first_prod_id = "0x" + "01".repeat(32);
  455. let first_price_id = "0x" + "fe".repeat(32);
  456. let second_prod_id = "0x" + "02".repeat(32);
  457. let second_price_id = "0x" + "fd".repeat(32);
  458. // Confirm that previously non-existent feeds are created
  459. let first = await this.pythProxy.queryPriceFeed(first_price_id);
  460. console.debug(`first is ${JSON.stringify(first)}`);
  461. assert.equal(first.price.price, priceVal);
  462. let second = await this.pythProxy.queryPriceFeed(second_price_id);
  463. assert.equal(second.price.price, priceVal);
  464. // Confirm the price is bumped after a new attestation updates each record
  465. let nextTimestamp = currentTimestamp + 1;
  466. let rawBatch2 = generateRawBatchAttestation(
  467. nextTimestamp - 5,
  468. nextTimestamp,
  469. priceVal + 5
  470. );
  471. receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2]);
  472. expectEvent(receipt, 'PriceFeedUpdate', {
  473. fresh: true,
  474. });
  475. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  476. batchSize: '10',
  477. freshPricesInBatch: '10',
  478. });
  479. expectEvent(receipt, 'UpdatePriceFeeds', {
  480. batchCount: '1',
  481. });
  482. first = await this.pythProxy.queryPriceFeed(first_price_id);
  483. assert.equal(first.price.price, priceVal + 5);
  484. second = await this.pythProxy.queryPriceFeed(second_price_id);
  485. assert.equal(second.price.price, priceVal + 5);
  486. // Confirm that only strictly larger timestamps trigger updates
  487. let rawBatch3 = generateRawBatchAttestation(
  488. nextTimestamp - 5,
  489. nextTimestamp,
  490. priceVal + 10
  491. );
  492. receipt = await updatePriceFeeds(this.pythProxy, [rawBatch3]);
  493. expectEvent(receipt, 'PriceFeedUpdate', {
  494. fresh: false,
  495. });
  496. expectEvent(receipt, 'BatchPriceFeedUpdate', {
  497. batchSize: '10',
  498. freshPricesInBatch: '0',
  499. });
  500. expectEvent(receipt, 'UpdatePriceFeeds', {
  501. batchCount: '1',
  502. });
  503. first = await this.pythProxy.queryPriceFeed(first_price_id);
  504. assert.equal(first.price.price, priceVal + 5);
  505. assert.notEqual(first.price.price, priceVal + 10);
  506. second = await this.pythProxy.queryPriceFeed(second_price_id);
  507. assert.equal(second.price.price, priceVal + 5);
  508. assert.notEqual(second.price.price, priceVal + 10);
  509. });
  510. it("should fail transaction if a price is not found", async function () {
  511. await expectRevert(
  512. this.pythProxy.queryPriceFeed(
  513. "0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed"
  514. ),
  515. "no price feed found for the given price id"
  516. );
  517. });
  518. it("should revert on getting stale current prices", async function () {
  519. let smallestTimestamp = 1;
  520. let rawBatch = generateRawBatchAttestation(
  521. smallestTimestamp,
  522. smallestTimestamp + 5,
  523. 1337
  524. );
  525. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  526. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  527. const price_id =
  528. "0x" +
  529. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  530. expectRevert(
  531. this.pythProxy.getPrice(price_id),
  532. "no price available which is recent enough"
  533. );
  534. }
  535. });
  536. it("should revert on getting current prices too far into the future as they are considered unknown", async function () {
  537. let largestTimestamp = 4294967295;
  538. let rawBatch = generateRawBatchAttestation(
  539. largestTimestamp - 5,
  540. largestTimestamp,
  541. 1337
  542. );
  543. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  544. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  545. const price_id =
  546. "0x" +
  547. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  548. expectRevert(
  549. this.pythProxy.getPrice(price_id),
  550. "no price available which is recent enough"
  551. );
  552. }
  553. });
  554. it("changing validity time works", async function() {
  555. const latestTime = await time.latest();
  556. let rawBatch = generateRawBatchAttestation(
  557. latestTime,
  558. latestTime,
  559. 1337
  560. );
  561. await updatePriceFeeds(this.pythProxy, [rawBatch]);
  562. // Setting the validity time to 30 seconds
  563. await this.pythProxy.updateValidTimePeriodSeconds(30);
  564. // Then prices should be available
  565. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  566. const price_id =
  567. "0x" +
  568. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  569. // Expect getPrice to work (not revert)
  570. await this.pythProxy.getPrice(price_id);
  571. }
  572. // One minute passes
  573. await time.increase(time.duration.minutes(1));
  574. // The prices should become unavailable now.
  575. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  576. const price_id =
  577. "0x" +
  578. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  579. expectRevert(
  580. this.pythProxy.getPrice(price_id),
  581. "no price available which is recent enough"
  582. );
  583. }
  584. // Setting the validity time to 120 seconds
  585. await this.pythProxy.updateValidTimePeriodSeconds(120);
  586. // Then prices should be available because the valid period is now 120 seconds
  587. for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
  588. const price_id =
  589. "0x" +
  590. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  591. let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
  592. // Expect getPrice to work (not revert)
  593. await this.pythProxy.getPrice(price_id);
  594. }
  595. });
  596. it("should use prev price and timestamp on unknown attestation status", async function () {
  597. const latestTime = await time.latest();
  598. let rawBatch = generateRawUnknownBatchAttestation(
  599. latestTime,
  600. latestTime,
  601. 1337, // price
  602. 1500, // ema price
  603. latestTime - 10,
  604. 1000, // prev price
  605. );
  606. const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
  607. expectEvent(receipt, 'PriceFeedUpdate', {
  608. price: "1000",
  609. });
  610. // Then prices should be available because the valid period is now 120 seconds
  611. for (var i = 1; i <= RAW_UNKNOWN_BATCH_ATTESTATION_COUNT; i++) {
  612. const price_id =
  613. "0x" +
  614. (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
  615. const price = await this.pythProxy.getPrice(price_id);
  616. assert.equal(price.price, "1000");
  617. assert.equal(price.publishTime, (latestTime - 10).toString());
  618. const emaPrice = await this.pythProxy.getEmaPrice(price_id);
  619. assert.equal(emaPrice.price, "1500");
  620. assert.equal(emaPrice.publishTime, (latestTime - 10).toString());
  621. }
  622. });
  623. it("should accept a VM after adding its data source", async function () {
  624. let newChainId = "42424";
  625. let newEmitter = testPyth2WormholeEmitter.replace("a", "f");
  626. await this.pythProxy.addDataSource(newChainId, newEmitter);
  627. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  628. let rawBatch = generateRawBatchAttestation(
  629. currentTimestamp - 5,
  630. currentTimestamp,
  631. 1337
  632. );
  633. let vm = await signAndEncodeVM(
  634. 1,
  635. 1,
  636. newChainId,
  637. newEmitter,
  638. 0,
  639. rawBatch,
  640. [testSigner1PK],
  641. 0,
  642. 0
  643. );
  644. await this.pythProxy.updatePriceFeeds(["0x" + vm]);
  645. });
  646. it("should reject a VM after removing its data source", async function () {
  647. // Add 2 new data sources to produce a non-trivial data source state.
  648. let newChainId = "42424";
  649. let newEmitter = testPyth2WormholeEmitter.replace("a", "f");
  650. await this.pythProxy.addDataSource(newChainId, newEmitter);
  651. let newChainId2 = "42425";
  652. let newEmitter2 = testPyth2WormholeEmitter.replace("a", "e");
  653. await this.pythProxy.addDataSource(newChainId2, newEmitter2);
  654. // Remove the first one added
  655. await this.pythProxy.removeDataSource(newChainId, newEmitter);
  656. // Sign a batch with the removed data source
  657. let currentTimestamp = (await web3.eth.getBlock("latest")).timestamp;
  658. let rawBatch = generateRawBatchAttestation(
  659. currentTimestamp - 5,
  660. currentTimestamp,
  661. 1337
  662. );
  663. let vm = await signAndEncodeVM(
  664. 1,
  665. 1,
  666. newChainId,
  667. newEmitter,
  668. 0,
  669. rawBatch,
  670. [testSigner1PK],
  671. 0,
  672. 0
  673. );
  674. await expectRevert(
  675. this.pythProxy.updatePriceFeeds(["0x" + vm]),
  676. "invalid data source chain/emitter ID"
  677. );
  678. });
  679. // Governance
  680. // Logics that apply to all governance messages
  681. it("Make sure invalid magic and module won't work", async function () {
  682. // First 4 bytes of data are magic and the second byte after that is module
  683. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  684. const wrongMagic = Buffer.from(data);
  685. wrongMagic[1] = 0;
  686. const vaaWrongMagic = await createVAAFromUint8Array(wrongMagic,
  687. testGovernanceChainId,
  688. testGovernanceEmitter,
  689. 1
  690. );
  691. await expectRevert(
  692. this.pythProxy.executeGovernanceInstruction(vaaWrongMagic),
  693. "invalid magic for GovernanceInstruction"
  694. );
  695. const wrongModule = Buffer.from(data);
  696. wrongModule[4] = 0;
  697. const vaaWrongModule = await createVAAFromUint8Array(wrongModule,
  698. testGovernanceChainId,
  699. testGovernanceEmitter,
  700. 1
  701. );
  702. await expectRevert(
  703. this.pythProxy.executeGovernanceInstruction(vaaWrongModule),
  704. "invalid module for GovernanceInstruction"
  705. );
  706. const outOfBoundModule = Buffer.from(data);
  707. outOfBoundModule[4] = 20;
  708. const vaaOutOfBoundModule = await createVAAFromUint8Array(outOfBoundModule,
  709. testGovernanceChainId,
  710. testGovernanceEmitter,
  711. 1
  712. );
  713. await expectRevert(
  714. this.pythProxy.executeGovernanceInstruction(vaaOutOfBoundModule),
  715. "Panic: Enum value out of bounds.",
  716. );
  717. });
  718. it("Make sure governance with wrong sender won't work", async function () {
  719. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  720. const vaaWrongEmitter = await createVAAFromUint8Array(data,
  721. testGovernanceChainId,
  722. "0x0000000000000000000000000000000000000000000000000000000000001111",
  723. 1
  724. );
  725. await expectRevert(
  726. this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter),
  727. "VAA is not coming from the governance data source"
  728. );
  729. const vaaWrongChain = await createVAAFromUint8Array(data,
  730. governance.CHAINS.karura,
  731. testGovernanceEmitter,
  732. 1
  733. );
  734. await expectRevert(
  735. this.pythProxy.executeGovernanceInstruction(vaaWrongChain),
  736. "VAA is not coming from the governance data source"
  737. );
  738. });
  739. it("Make sure governance with only target chain id and 0 work", async function () {
  740. const wrongChainData = new governance.SetValidPeriodInstruction(governance.CHAINS.solana, BigInt(10)).serialize();
  741. const wrongChainVaa = await createVAAFromUint8Array(wrongChainData,
  742. testGovernanceChainId,
  743. testGovernanceEmitter,
  744. 1
  745. );
  746. await expectRevert(
  747. this.pythProxy.executeGovernanceInstruction(wrongChainVaa),
  748. "invalid target chain for this governance instruction"
  749. );
  750. const dataForAllChains = new governance.SetValidPeriodInstruction(governance.CHAINS.unset, BigInt(10)).serialize();
  751. const vaaForAllChains = await createVAAFromUint8Array(dataForAllChains,
  752. testGovernanceChainId,
  753. testGovernanceEmitter,
  754. 1
  755. );
  756. await this.pythProxy.executeGovernanceInstruction(vaaForAllChains);
  757. const dataForEth = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  758. const vaaForEth = await createVAAFromUint8Array(dataForEth,
  759. testGovernanceChainId,
  760. testGovernanceEmitter,
  761. 2,
  762. );
  763. await this.pythProxy.executeGovernanceInstruction(vaaForEth);
  764. });
  765. it("Make sure that governance messages are executed in order and cannot be reused", async function () {
  766. const data = new governance.SetValidPeriodInstruction(governance.CHAINS.ethereum, BigInt(10)).serialize();
  767. const vaaSeq1 = await createVAAFromUint8Array(data,
  768. testGovernanceChainId,
  769. testGovernanceEmitter,
  770. 1
  771. );
  772. await this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  773. // Replaying shouldn't work
  774. await expectRevert(
  775. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  776. "VAA is older than the last executed governance VAA",
  777. )
  778. const vaaSeq2 = await createVAAFromUint8Array(data,
  779. testGovernanceChainId,
  780. testGovernanceEmitter,
  781. 2
  782. );
  783. await this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  784. // Replaying shouldn't work
  785. await expectRevert(
  786. this.pythProxy.executeGovernanceInstruction(vaaSeq1),
  787. "VAA is older than the last executed governance VAA",
  788. )
  789. await expectRevert(
  790. this.pythProxy.executeGovernanceInstruction(vaaSeq2),
  791. "VAA is older than the last executed governance VAA",
  792. )
  793. });
  794. // Per governance type logic
  795. it("Upgrading the contract with chain id 0 is invalid", async function () {
  796. const newImplementation = await PythUpgradable.new();
  797. const data = new governance.EthereumUpgradeContractInstruction(
  798. governance.CHAINS.unset, // 0
  799. new governance.HexString20Bytes(newImplementation.address),
  800. ).serialize();
  801. const vaa = await createVAAFromUint8Array(data,
  802. testGovernanceChainId,
  803. testGovernanceEmitter,
  804. 1
  805. );
  806. await expectRevert(
  807. this.pythProxy.executeGovernanceInstruction(vaa),
  808. "upgrade with chain id 0 is not possible"
  809. );
  810. });
  811. it("Upgrading the contract should work", async function () {
  812. const newImplementation = await PythUpgradable.new();
  813. const data = new governance.EthereumUpgradeContractInstruction(
  814. governance.CHAINS.ethereum,
  815. new governance.HexString20Bytes(newImplementation.address),
  816. ).serialize();
  817. const vaa = await createVAAFromUint8Array(data,
  818. testGovernanceChainId,
  819. testGovernanceEmitter,
  820. 1
  821. );
  822. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  823. // Couldn't get the oldImplementation address.
  824. expectEvent(receipt, 'ContractUpgraded', {
  825. newImplementation: newImplementation.address,
  826. });
  827. expectEvent(receipt, 'Upgraded', {
  828. implementation: newImplementation.address
  829. });
  830. });
  831. it("Setting governance data source should work", async function () {
  832. const data = new governance.SetGovernanceDataSourceInstruction(
  833. governance.CHAINS.ethereum,
  834. new governance.DataSource(
  835. governance.CHAINS.acala,
  836. new governance.HexString32Bytes(
  837. "0x0000000000000000000000000000000000000000000000000000000000001111",
  838. )
  839. ),
  840. BigInt(10)
  841. ).serialize();
  842. const vaa = await createVAAFromUint8Array(data,
  843. testGovernanceChainId,
  844. testGovernanceEmitter,
  845. 1
  846. );
  847. const oldGovernanceDataSource = await this.pythProxy.governanceDataSource();
  848. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  849. expectEvent(receipt, 'GovernanceDataSourceSet', {
  850. oldDataSource: oldGovernanceDataSource,
  851. newDataSource: await this.pythProxy.governanceDataSource(),
  852. });
  853. const newVaaFromOldGovernanceSource = await createVAAFromUint8Array(data,
  854. testGovernanceChainId,
  855. testGovernanceEmitter,
  856. 2
  857. );
  858. await expectRevert(
  859. this.pythProxy.executeGovernanceInstruction(newVaaFromOldGovernanceSource),
  860. "VAA is not coming from the governance data source"
  861. );
  862. const newVaaFromNewGovernanceOldSequence = await createVAAFromUint8Array(data,
  863. governance.CHAINS.acala,
  864. "0x0000000000000000000000000000000000000000000000000000000000001111",
  865. 2
  866. );
  867. await expectRevert(
  868. this.pythProxy.executeGovernanceInstruction(newVaaFromNewGovernanceOldSequence),
  869. "VAA is older than the last executed governance VAA"
  870. );
  871. const newVaaFromNewGovernanceGood = await createVAAFromUint8Array(data,
  872. governance.CHAINS.acala,
  873. "0x0000000000000000000000000000000000000000000000000000000000001111",
  874. 20
  875. );
  876. await this.pythProxy.executeGovernanceInstruction(newVaaFromNewGovernanceGood);
  877. });
  878. it("Setting data sources should work", async function () {
  879. const data = new governance.SetDataSourcesInstruction(
  880. governance.CHAINS.ethereum,
  881. [new governance.DataSource(
  882. governance.CHAINS.acala,
  883. new governance.HexString32Bytes(
  884. "0x0000000000000000000000000000000000000000000000000000000000001111",
  885. )
  886. )],
  887. ).serialize();
  888. const vaa = await createVAAFromUint8Array(data,
  889. testGovernanceChainId,
  890. testGovernanceEmitter,
  891. 1
  892. );
  893. const oldDataSources = await this.pythProxy.validDataSources();
  894. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  895. expectEvent(receipt, 'DataSourcesSet', {
  896. oldDataSources: oldDataSources,
  897. newDataSources: await this.pythProxy.validDataSources(),
  898. });
  899. assert.isTrue(await this.pythProxy.isValidDataSource(governance.CHAINS.acala,
  900. "0x0000000000000000000000000000000000000000000000000000000000001111"));
  901. assert.isFalse(await this.pythProxy.isValidDataSource(testPyth2WormholeChainId,
  902. testPyth2WormholeEmitter));
  903. let rawBatch = generateRawBatchAttestation(
  904. 100,
  905. 100,
  906. 1337
  907. );
  908. await expectRevert(
  909. updatePriceFeeds(this.pythProxy, [rawBatch]),
  910. "invalid data source chain/emitter ID"
  911. );
  912. await updatePriceFeeds(this.pythProxy, [rawBatch], 0, governance.CHAINS.acala,
  913. "0x0000000000000000000000000000000000000000000000000000000000001111");
  914. });
  915. it("Setting fee should work", async function () {
  916. const data = new governance.SetFeeInstruction(
  917. governance.CHAINS.ethereum,
  918. BigInt(5), BigInt(3) // 5*10**3 = 5000
  919. ).serialize();
  920. const vaa = await createVAAFromUint8Array(data,
  921. testGovernanceChainId,
  922. testGovernanceEmitter,
  923. 1
  924. );
  925. const oldFee = await this.pythProxy.singleUpdateFeeInWei();
  926. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  927. expectEvent(receipt, 'FeeSet', {
  928. oldFee: oldFee,
  929. newFee: await this.pythProxy.singleUpdateFeeInWei(),
  930. });
  931. assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000");
  932. let rawBatch = generateRawBatchAttestation(
  933. 100,
  934. 100,
  935. 1337
  936. );
  937. await expectRevert(
  938. updatePriceFeeds(this.pythProxy, [rawBatch], 0),
  939. insufficientFeeError
  940. );
  941. const receiptUpdateFeeds = await updatePriceFeeds(this.pythProxy, [rawBatch], 5000);
  942. expectEvent(receiptUpdateFeeds, 'UpdatePriceFeeds', {
  943. fee: "5000"
  944. });
  945. });
  946. it("Setting valid period should work", async function () {
  947. const data = new governance.SetValidPeriodInstruction(
  948. governance.CHAINS.ethereum,
  949. BigInt(0),
  950. ).serialize();
  951. const vaa = await createVAAFromUint8Array(data,
  952. testGovernanceChainId,
  953. testGovernanceEmitter,
  954. 1
  955. );
  956. const oldValidPeriod = await this.pythProxy.validTimePeriodSeconds();
  957. const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
  958. expectEvent(receipt, 'ValidPeriodSet', {
  959. oldValidPeriod: oldValidPeriod,
  960. newValidPeriod: await this.pythProxy.validTimePeriodSeconds(),
  961. });
  962. assert.equal(await this.pythProxy.validTimePeriodSeconds(), "0");
  963. // The behaviour of valid time period is extensively tested before,
  964. // and adding it here will cause more complexity (and is not so short).
  965. });
  966. // Renounce ownership works
  967. it("Renouncing ownership should work", async function () {
  968. await this.pythProxy.updateValidTimePeriodSeconds(100);
  969. await this.pythProxy.renounceOwnership();
  970. await expectRevert(
  971. this.pythProxy.updateValidTimePeriodSeconds(60),
  972. "Ownable: caller is not the owner",
  973. )
  974. });
  975. // Version
  976. it("Make sure version is the npm package version", async function () {
  977. const contractVersion = await this.pythProxy.version();
  978. const { version } = require('../package.json');
  979. expect(contractVersion).equal(version);
  980. });
  981. });
  982. const signAndEncodeVM = async function (
  983. timestamp,
  984. nonce,
  985. emitterChainId,
  986. emitterAddress,
  987. sequence,
  988. data,
  989. signers,
  990. guardianSetIndex,
  991. consistencyLevel
  992. ) {
  993. const body = [
  994. web3.eth.abi
  995. .encodeParameter("uint32", timestamp)
  996. .substring(2 + (64 - 8)),
  997. web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
  998. web3.eth.abi
  999. .encodeParameter("uint16", emitterChainId)
  1000. .substring(2 + (64 - 4)),
  1001. web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
  1002. web3.eth.abi
  1003. .encodeParameter("uint64", sequence)
  1004. .substring(2 + (64 - 16)),
  1005. web3.eth.abi
  1006. .encodeParameter("uint8", consistencyLevel)
  1007. .substring(2 + (64 - 2)),
  1008. data.substr(2),
  1009. ];
  1010. const hash = web3.utils.soliditySha3(
  1011. web3.utils.soliditySha3("0x" + body.join(""))
  1012. );
  1013. let signatures = "";
  1014. for (let i in signers) {
  1015. const ec = new elliptic.ec("secp256k1");
  1016. const key = ec.keyFromPrivate(signers[i]);
  1017. const signature = key.sign(hash.substr(2), { canonical: true });
  1018. const packSig = [
  1019. web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
  1020. zeroPadBytes(signature.r.toString(16), 32),
  1021. zeroPadBytes(signature.s.toString(16), 32),
  1022. web3.eth.abi
  1023. .encodeParameter("uint8", signature.recoveryParam)
  1024. .substr(2 + (64 - 2)),
  1025. ];
  1026. signatures += packSig.join("");
  1027. }
  1028. const vm = [
  1029. web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
  1030. web3.eth.abi
  1031. .encodeParameter("uint32", guardianSetIndex)
  1032. .substring(2 + (64 - 8)),
  1033. web3.eth.abi
  1034. .encodeParameter("uint8", signers.length)
  1035. .substring(2 + (64 - 2)),
  1036. signatures,
  1037. body.join(""),
  1038. ].join("");
  1039. return vm;
  1040. };
  1041. function zeroPadBytes(value, length) {
  1042. while (value.length < 2 * length) {
  1043. value = "0" + value;
  1044. }
  1045. return value;
  1046. }
  1047. async function createVAAFromUint8Array(
  1048. dataBuffer,
  1049. emitterChainId,
  1050. emitterAddress,
  1051. sequence,
  1052. ) {
  1053. const dataHex = "0x" + dataBuffer.toString("hex");
  1054. return "0x" + await signAndEncodeVM(
  1055. 0,
  1056. 0,
  1057. emitterChainId.toString(),
  1058. emitterAddress,
  1059. sequence,
  1060. dataHex,
  1061. [testSigner1PK],
  1062. 0,
  1063. 0
  1064. );
  1065. }