wormhole.js 22 KB

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