wormhole.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. const jsonfile = require('jsonfile');
  2. const elliptic = require('elliptic');
  3. const path = require('path');
  4. const Wormhole = artifacts.require("Wormhole");
  5. const MockImplementation = artifacts.require("MockImplementation");
  6. const Implementation = artifacts.require("Implementation");
  7. const testSigner1PK = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
  8. const testSigner2PK = "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
  9. const testSigner3PK = "87b45997ea577b93073568f06fc4838cffc1d01f90fc4d57f936957f3c4d99fb";
  10. const ImplementationFullABI = jsonfile.readFileSync("build/contracts/Implementation.json").abi
  11. // Taken from https://medium.com/fluidity/standing-the-time-of-test-b906fcc374a9
  12. advanceTimeAndBlock = async (time) => {
  13. await advanceTime(time);
  14. await advanceBlock();
  15. return Promise.resolve(web3.eth.getBlock('latest'));
  16. }
  17. advanceTime = (time) => {
  18. return new Promise((resolve, reject) => {
  19. web3.currentProvider.send({
  20. jsonrpc: "2.0",
  21. method: "evm_increaseTime",
  22. params: [time],
  23. id: new Date().getTime()
  24. }, (err, result) => {
  25. if (err) {
  26. return reject(err);
  27. }
  28. return resolve(result);
  29. });
  30. });
  31. }
  32. advanceBlock = () => {
  33. return new Promise((resolve, reject) => {
  34. web3.currentProvider.send({
  35. jsonrpc: "2.0",
  36. method: "evm_mine",
  37. id: new Date().getTime()
  38. }, (err, result) => {
  39. if (err) {
  40. return reject(err);
  41. }
  42. const newBlockHash = web3.eth.getBlock('latest').hash;
  43. return resolve(newBlockHash)
  44. });
  45. });
  46. }
  47. contract("Wormhole", function () {
  48. const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
  49. const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
  50. const testSigner3 = web3.eth.accounts.privateKeyToAccount(testSigner3PK);
  51. const testChainId = "2";
  52. const testGovernanceChainId = "1";
  53. const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
  54. it("should be initialized with the correct signers and values", async function () {
  55. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  56. const index = await initialized.methods.getCurrentGuardianSetIndex().call();
  57. const set = (await initialized.methods.getGuardianSet(index).call());
  58. // check set
  59. assert.lengthOf(set[0], 1);
  60. assert.equal(set[0][0], testSigner1.address);
  61. // check expiration
  62. assert.equal(set.expirationTime, "0");
  63. // chain id
  64. const chainId = await initialized.methods.chainId().call();
  65. assert.equal(chainId, testChainId);
  66. // governance
  67. const governanceChainId = await initialized.methods.governanceChainId().call();
  68. assert.equal(governanceChainId, testGovernanceChainId);
  69. const governanceContract = await initialized.methods.governanceContract().call();
  70. assert.equal(governanceContract, testGovernanceContract);
  71. })
  72. it("should log a published message correctly", async function () {
  73. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  74. const accounts = await web3.eth.getAccounts();
  75. const log = await initialized.methods.publishMessage(
  76. "0x123",
  77. "0x123321",
  78. 32
  79. ).send({
  80. value: 0, // fees are set to 0 initially
  81. from: accounts[0]
  82. })
  83. assert.equal(log.events.LogMessagePublished.returnValues.sender.toString(), accounts[0]);
  84. assert.equal(log.events.LogMessagePublished.returnValues.sequence.toString(), "0");
  85. assert.equal(log.events.LogMessagePublished.returnValues.nonce, 291);
  86. assert.equal(log.events.LogMessagePublished.returnValues.payload.toString(), "0x123321");
  87. assert.equal(log.events.LogMessagePublished.returnValues.consistencyLevel, 32);
  88. })
  89. it("should increase the sequence for an account", async function () {
  90. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  91. const accounts = await web3.eth.getAccounts();
  92. const log = await initialized.methods.publishMessage(
  93. "0x1",
  94. "0x1",
  95. 32
  96. ).send({
  97. value: 0, // fees are set to 0 initially
  98. from: accounts[0]
  99. })
  100. assert.equal(log.events.LogMessagePublished.returnValues.sequence.toString(), "1");
  101. })
  102. it("parses VMs correctly", async function () {
  103. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  104. const timestamp = 1000;
  105. const nonce = 1001;
  106. const emitterChainId = 11;
  107. const emitterAddress = "0x0000000000000000000000000000000000000000000000000000000000000eee"
  108. const data = "0xaaaaaa";
  109. const vm = await signAndEncodeVM(
  110. timestamp,
  111. nonce,
  112. emitterChainId,
  113. emitterAddress,
  114. 1337,
  115. data,
  116. [
  117. testSigner1PK,
  118. ],
  119. 0,
  120. 2
  121. );
  122. let result
  123. try {
  124. result = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
  125. } catch (err) {
  126. console.log(err)
  127. assert.fail("parseAndVerifyVM failed")
  128. }
  129. assert.equal(result.vm.version, 1);
  130. assert.equal(result.vm.timestamp, timestamp);
  131. assert.equal(result.vm.nonce, nonce);
  132. assert.equal(result.vm.emitterChainId, emitterChainId);
  133. assert.equal(result.vm.emitterAddress, emitterAddress);
  134. assert.equal(result.vm.payload, data);
  135. assert.equal(result.vm.guardianSetIndex, 0);
  136. assert.equal(result.vm.sequence, 1337);
  137. assert.equal(result.vm.consistencyLevel, 2);
  138. assert.equal(result.valid, true);
  139. assert.equal(result.reason, "");
  140. })
  141. it("should set and enforce fees", async function () {
  142. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  143. const accounts = await web3.eth.getAccounts();
  144. const timestamp = 1000;
  145. const nonce = 1001;
  146. const emitterChainId = testGovernanceChainId;
  147. const emitterAddress = testGovernanceContract
  148. let data = "0x00000000000000000000000000000000000000000000000000000000436f726503";
  149. data += [
  150. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  151. web3.eth.abi.encodeParameter("uint256", 1111).substring(2),
  152. ].join('')
  153. const vm = await signAndEncodeVM(
  154. timestamp,
  155. nonce,
  156. emitterChainId,
  157. emitterAddress,
  158. 0,
  159. data,
  160. [
  161. testSigner1PK,
  162. ],
  163. 0,
  164. 2
  165. );
  166. let before = await initialized.methods.messageFee().call();
  167. let set = await initialized.methods.submitSetMessageFee("0x" + vm).send({
  168. value: 0,
  169. from: accounts[0],
  170. gasLimit: 1000000
  171. });
  172. let after = await initialized.methods.messageFee().call();
  173. assert.notEqual(before, after);
  174. assert.equal(after, 1111);
  175. // test message publishing
  176. await initialized.methods.publishMessage(
  177. "0x123",
  178. "0x123321",
  179. 32
  180. ).send({
  181. from: accounts[0],
  182. value: 1111
  183. })
  184. let failed = false;
  185. try {
  186. await initialized.methods.publishMessage(
  187. "0x123",
  188. "0x123321",
  189. 32
  190. ).send({
  191. value: 1110,
  192. from: accounts[0]
  193. })
  194. } catch (e) {
  195. failed = true
  196. }
  197. assert.equal(failed, true);
  198. })
  199. it("should transfer out collected fees", async function () {
  200. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  201. const accounts = await web3.eth.getAccounts();
  202. const receiver = "0x" + zeroPadBytes(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16), 20);
  203. const timestamp = 1000;
  204. const nonce = 1001;
  205. const emitterChainId = testGovernanceChainId;
  206. const emitterAddress = testGovernanceContract
  207. let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
  208. data += [
  209. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  210. web3.eth.abi.encodeParameter("uint256", 11).substring(2),
  211. web3.eth.abi.encodeParameter("address", receiver).substring(2),
  212. ].join('')
  213. const vm = await signAndEncodeVM(
  214. timestamp,
  215. nonce,
  216. emitterChainId,
  217. emitterAddress,
  218. 0,
  219. data,
  220. [
  221. testSigner1PK,
  222. ],
  223. 0,
  224. 2
  225. );
  226. let WHBefore = await web3.eth.getBalance(Wormhole.address);
  227. let receiverBefore = await web3.eth.getBalance(receiver);
  228. let set = await initialized.methods.submitTransferFees("0x" + vm).send({
  229. value: 0,
  230. from: accounts[0],
  231. gasLimit: 1000000
  232. });
  233. let WHAfter = await web3.eth.getBalance(Wormhole.address);
  234. let receiverAfter = await web3.eth.getBalance(receiver);
  235. assert.equal(WHBefore - WHAfter, 11);
  236. assert.equal(receiverAfter - receiverBefore, 11);
  237. })
  238. it("should accept a new guardian set", async function () {
  239. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  240. const accounts = await web3.eth.getAccounts();
  241. const timestamp = 1000;
  242. const nonce = 1001;
  243. const emitterChainId = testGovernanceChainId;
  244. const emitterAddress = testGovernanceContract
  245. let data = "0x00000000000000000000000000000000000000000000000000000000436f726502";
  246. let oldIndex = Number(await initialized.methods.getCurrentGuardianSetIndex().call());
  247. data += [
  248. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  249. web3.eth.abi.encodeParameter("uint32", oldIndex + 1).substring(2 + (64 - 8)),
  250. web3.eth.abi.encodeParameter("uint8", 3).substring(2 + (64 - 2)),
  251. web3.eth.abi.encodeParameter("address", testSigner1.address).substring(2 + (64 - 40)),
  252. web3.eth.abi.encodeParameter("address", testSigner2.address).substring(2 + (64 - 40)),
  253. web3.eth.abi.encodeParameter("address", testSigner3.address).substring(2 + (64 - 40)),
  254. ].join('')
  255. const vm = await signAndEncodeVM(
  256. timestamp,
  257. nonce,
  258. emitterChainId,
  259. emitterAddress,
  260. 0,
  261. data,
  262. [
  263. testSigner1PK,
  264. ],
  265. 0,
  266. 2
  267. );
  268. let set = await initialized.methods.submitNewGuardianSet("0x" + vm).send({
  269. value: 0,
  270. from: accounts[0],
  271. gasLimit: 1000000
  272. });
  273. let index = await initialized.methods.getCurrentGuardianSetIndex().call();
  274. assert.equal(oldIndex + 1, index);
  275. assert.equal(index, 1);
  276. let guardians = await initialized.methods.getGuardianSet(index).call();
  277. assert.equal(guardians.expirationTime, 0);
  278. assert.lengthOf(guardians[0], 3);
  279. assert.equal(guardians[0][0], testSigner1.address);
  280. assert.equal(guardians[0][1], testSigner2.address);
  281. assert.equal(guardians[0][2], testSigner3.address);
  282. let oldGuardians = await initialized.methods.getGuardianSet(oldIndex).call();
  283. const time = (await web3.eth.getBlock("latest")).timestamp;
  284. // old guardian set expiry is set
  285. assert.ok(
  286. oldGuardians.expirationTime > Number(time) + 86000
  287. && oldGuardians.expirationTime < Number(time) + 88000
  288. );
  289. })
  290. it("should accept smart contract upgrades", async function () {
  291. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  292. const accounts = await web3.eth.getAccounts();
  293. const mock = await MockImplementation.new();
  294. const timestamp = 1000;
  295. const nonce = 1001;
  296. const emitterChainId = testGovernanceChainId;
  297. const emitterAddress = testGovernanceContract
  298. let data = "0x00000000000000000000000000000000000000000000000000000000436f726501";
  299. data += [
  300. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  301. web3.eth.abi.encodeParameter("address", mock.address).substring(2),
  302. ].join('')
  303. const vm = await signAndEncodeVM(
  304. timestamp,
  305. nonce,
  306. emitterChainId,
  307. emitterAddress,
  308. 0,
  309. data,
  310. [
  311. testSigner1PK,
  312. testSigner2PK,
  313. testSigner3PK
  314. ],
  315. 1,
  316. 2
  317. );
  318. let before = await web3.eth.getStorageAt(Wormhole.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
  319. assert.equal(before.toLowerCase(), Implementation.address.toLowerCase());
  320. let set = await initialized.methods.submitContractUpgrade("0x" + vm).send({
  321. value: 0,
  322. from: accounts[0],
  323. gasLimit: 1000000
  324. });
  325. let after = await web3.eth.getStorageAt(Wormhole.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
  326. assert.equal(after.toLowerCase(), mock.address.toLowerCase());
  327. const mockImpl = new web3.eth.Contract(MockImplementation.abi, Wormhole.address);
  328. let isUpgraded = await mockImpl.methods.testNewImplementationActive().call();
  329. assert.ok(isUpgraded);
  330. })
  331. it("should revert governance packets from old guardian set", async function () {
  332. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  333. const accounts = await web3.eth.getAccounts();
  334. let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
  335. data += [
  336. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  337. web3.eth.abi.encodeParameter("uint256", 1).substring(2),
  338. web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
  339. ].join('')
  340. const vm = await signAndEncodeVM(
  341. 0,
  342. 0,
  343. testGovernanceChainId,
  344. testGovernanceContract,
  345. 0,
  346. data,
  347. [
  348. testSigner1PK,
  349. ],
  350. 0,
  351. 2
  352. );
  353. let failed = false;
  354. try {
  355. await initialized.methods.submitTransferFees("0x" + vm).send({
  356. value: 0,
  357. from: accounts[0],
  358. gasLimit: 1000000
  359. });
  360. asset.fail("governance packet of old guardian set accepted")
  361. } catch (e) {
  362. assert.equal(e.data[Object.keys(e.data)[0]].reason, "not signed by current guardian set")
  363. }
  364. })
  365. it("should time out old gardians", async function () {
  366. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  367. const timestamp = 1000;
  368. const nonce = 1001;
  369. const emitterChainId = 11;
  370. const emitterAddress = "0x0000000000000000000000000000000000000000000000000000000000000eee"
  371. const data = "0xaaaaaa";
  372. const vm = await signAndEncodeVM(
  373. timestamp,
  374. nonce,
  375. emitterChainId,
  376. emitterAddress,
  377. 0,
  378. data,
  379. [
  380. testSigner1PK,
  381. ],
  382. 0,
  383. 2
  384. );
  385. // this should pass
  386. const current = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
  387. assert.equal(current.valid, true)
  388. await advanceTimeAndBlock(100000);
  389. const expired = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
  390. assert.equal(expired.valid, false)
  391. })
  392. it("should revert governance packets from wrong governance chain", async function () {
  393. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  394. const accounts = await web3.eth.getAccounts();
  395. let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
  396. data += [
  397. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  398. web3.eth.abi.encodeParameter("uint256", 1).substring(2),
  399. web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
  400. ].join('')
  401. const vm = await signAndEncodeVM(
  402. 0,
  403. 0,
  404. 999,
  405. testGovernanceContract,
  406. 0,
  407. data,
  408. [
  409. testSigner1PK,
  410. testSigner2PK,
  411. testSigner3PK,
  412. ],
  413. 1,
  414. 2
  415. );
  416. try {
  417. await initialized.methods.submitTransferFees("0x" + vm).send({
  418. value: 0,
  419. from: accounts[0],
  420. gasLimit: 1000000
  421. });
  422. asset.fail("governance packet from wrong governance chain accepted")
  423. } catch (e) {
  424. assert.equal(e.data[Object.keys(e.data)[0]].reason, "wrong governance chain")
  425. }
  426. })
  427. it("should revert governance packets from wrong governance contract", async function () {
  428. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  429. const accounts = await web3.eth.getAccounts();
  430. let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
  431. data += [
  432. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  433. web3.eth.abi.encodeParameter("uint256", 1).substring(2),
  434. web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
  435. ].join('')
  436. const vm = await signAndEncodeVM(
  437. 0,
  438. 0,
  439. testGovernanceChainId,
  440. "0x00000000000000000000000000000000000000000000000000000000436f7265",
  441. 0,
  442. data,
  443. [
  444. testSigner1PK,
  445. testSigner2PK,
  446. testSigner3PK,
  447. ],
  448. 1,
  449. 2
  450. );
  451. try {
  452. await initialized.methods.submitTransferFees("0x" + vm).send({
  453. value: 0,
  454. from: accounts[0],
  455. gasLimit: 1000000
  456. });
  457. asset.fail("governance packet from wrong governance contract accepted")
  458. } catch (e) {
  459. assert.equal(e.data[Object.keys(e.data)[0]].reason, "wrong governance contract")
  460. }
  461. })
  462. it("should revert on governance packets that already have been applied", async function () {
  463. const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
  464. const accounts = await web3.eth.getAccounts();
  465. let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
  466. data += [
  467. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  468. web3.eth.abi.encodeParameter("uint256", 1).substring(2),
  469. web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
  470. ].join('')
  471. const vm = await signAndEncodeVM(
  472. 0,
  473. 0,
  474. testGovernanceChainId,
  475. testGovernanceContract,
  476. 0,
  477. data,
  478. [
  479. testSigner1PK,
  480. testSigner2PK,
  481. testSigner3PK,
  482. ],
  483. 1,
  484. 2
  485. );
  486. await initialized.methods.submitTransferFees("0x" + vm).send({
  487. value: 0,
  488. from: accounts[0],
  489. gasLimit: 1000000
  490. });
  491. try {
  492. await initialized.methods.submitTransferFees("0x" + vm).send({
  493. value: 0,
  494. from: accounts[0],
  495. gasLimit: 1000000
  496. });
  497. asset.fail("governance packet accepted twice")
  498. } catch (e) {
  499. assert.equal(e.data[Object.keys(e.data)[0]].reason, "governance action already consumed")
  500. }
  501. })
  502. });
  503. const signAndEncodeVM = async function (
  504. timestamp,
  505. nonce,
  506. emitterChainId,
  507. emitterAddress,
  508. sequence,
  509. data,
  510. signers,
  511. guardianSetIndex,
  512. consistencyLevel
  513. ) {
  514. const body = [
  515. web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
  516. web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
  517. web3.eth.abi.encodeParameter("uint16", emitterChainId).substring(2 + (64 - 4)),
  518. web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
  519. web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
  520. web3.eth.abi.encodeParameter("uint8", consistencyLevel).substring(2 + (64 - 2)),
  521. data.substr(2)
  522. ]
  523. const hash = web3.utils.soliditySha3(web3.utils.soliditySha3("0x" + body.join("")))
  524. let signatures = "";
  525. for (let i in signers) {
  526. const ec = new elliptic.ec("secp256k1");
  527. const key = ec.keyFromPrivate(signers[i]);
  528. const signature = key.sign(hash.substr(2), {canonical: true});
  529. const packSig = [
  530. web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
  531. zeroPadBytes(signature.r.toString(16), 32),
  532. zeroPadBytes(signature.s.toString(16), 32),
  533. web3.eth.abi.encodeParameter("uint8", signature.recoveryParam).substr(2 + (64 - 2)),
  534. ]
  535. signatures += packSig.join("")
  536. }
  537. const vm = [
  538. web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
  539. web3.eth.abi.encodeParameter("uint32", guardianSetIndex).substring(2 + (64 - 8)),
  540. web3.eth.abi.encodeParameter("uint8", signers.length).substring(2 + (64 - 2)),
  541. signatures,
  542. body.join("")
  543. ].join("");
  544. return vm
  545. }
  546. function zeroPadBytes(value, length) {
  547. while (value.length < 2 * length) {
  548. value = "0" + value;
  549. }
  550. return value;
  551. }