pyth.js 51 KB

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