pyth.js 41 KB

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