pyth.js 47 KB

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