nft.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. const jsonfile = require('jsonfile');
  2. const elliptic = require('elliptic');
  3. const BigNumber = require('bignumber.js');
  4. const Wormhole = artifacts.require("Wormhole");
  5. const NFTBridge = artifacts.require("NFTBridgeEntrypoint");
  6. const NFTBridgeImplementation = artifacts.require("NFTBridgeImplementation");
  7. const NFTImplementation = artifacts.require("NFTImplementation");
  8. const MockBridgeImplementation = artifacts.require("MockNFTBridgeImplementation");
  9. const testSigner1PK = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
  10. const testSigner2PK = "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
  11. const WormholeImplementationFullABI = jsonfile.readFileSync("build/contracts/Implementation.json").abi
  12. const BridgeImplementationFullABI = jsonfile.readFileSync("build/contracts/NFTBridgeImplementation.json").abi
  13. const NFTImplementationFullABI = jsonfile.readFileSync("build/contracts/NFTImplementation.json").abi
  14. contract("NFT", function () {
  15. const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
  16. const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
  17. const testChainId = "2";
  18. const testGovernanceChainId = "1";
  19. const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
  20. let WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
  21. const testForeignChainId = "1";
  22. const testForeignBridgeContract = "0x000000000000000000000000000000000000000000000000000000000000ffff";
  23. const testBridgedAssetChain = "0003";
  24. const testBridgedAssetAddress = "000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e";
  25. it("should be initialized with the correct signers and values", async function () {
  26. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  27. const tokenImplentation = await initialized.methods.tokenImplementation().call();
  28. assert.equal(tokenImplentation, NFTImplementation.address);
  29. // test beacon functionality
  30. const beaconImplementation = await initialized.methods.implementation().call();
  31. assert.equal(beaconImplementation, NFTImplementation.address);
  32. // chain id
  33. const chainId = await initialized.methods.chainId().call();
  34. assert.equal(chainId, testChainId);
  35. // governance
  36. const governanceChainId = await initialized.methods.governanceChainId().call();
  37. assert.equal(governanceChainId, testGovernanceChainId);
  38. const governanceContract = await initialized.methods.governanceContract().call();
  39. assert.equal(governanceContract, testGovernanceContract);
  40. })
  41. it("should register a foreign bridge implementation correctly", async function () {
  42. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  43. const accounts = await web3.eth.getAccounts();
  44. let data = [
  45. "0x",
  46. "00000000000000000000000000000000000000000000004e4654427269646765",
  47. "01",
  48. "0000",
  49. web3.eth.abi.encodeParameter("uint16", testForeignChainId).substring(2 + (64 - 4)),
  50. web3.eth.abi.encodeParameter("bytes32", testForeignBridgeContract).substring(2),
  51. ].join('')
  52. const vm = await signAndEncodeVM(
  53. 1,
  54. 1,
  55. testGovernanceChainId,
  56. testGovernanceContract,
  57. 0,
  58. data,
  59. [
  60. testSigner1PK
  61. ],
  62. 0,
  63. 0
  64. );
  65. let before = await initialized.methods.bridgeContracts(testForeignChainId).call();
  66. assert.equal(before, "0x0000000000000000000000000000000000000000000000000000000000000000");
  67. await initialized.methods.registerChain("0x" + vm).send({
  68. value: 0,
  69. from: accounts[0],
  70. gasLimit: 2000000
  71. });
  72. let after = await initialized.methods.bridgeContracts(testForeignChainId).call();
  73. assert.equal(after, testForeignBridgeContract);
  74. })
  75. it("should accept a valid upgrade", async function () {
  76. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  77. const accounts = await web3.eth.getAccounts();
  78. const mock = await MockBridgeImplementation.new();
  79. let data = [
  80. "0x",
  81. "00000000000000000000000000000000000000000000004e4654427269646765",
  82. "02",
  83. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
  84. web3.eth.abi.encodeParameter("address", mock.address).substring(2),
  85. ].join('')
  86. const vm = await signAndEncodeVM(
  87. 1,
  88. 1,
  89. testGovernanceChainId,
  90. testGovernanceContract,
  91. 0,
  92. data,
  93. [
  94. testSigner1PK
  95. ],
  96. 0,
  97. 0
  98. );
  99. let before = await web3.eth.getStorageAt(NFTBridge.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
  100. assert.equal(before.toLowerCase(), NFTBridgeImplementation.address.toLowerCase());
  101. await initialized.methods.upgrade("0x" + vm).send({
  102. value: 0,
  103. from: accounts[0],
  104. gasLimit: 2000000
  105. });
  106. let after = await web3.eth.getStorageAt(NFTBridge.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
  107. assert.equal(after.toLowerCase(), mock.address.toLowerCase());
  108. const mockImpl = new web3.eth.Contract(MockBridgeImplementation.abi, NFTBridge.address);
  109. let isUpgraded = await mockImpl.methods.testNewImplementationActive().call();
  110. assert.ok(isUpgraded);
  111. })
  112. it("bridged tokens should only be mint- and burn-able by owner", async function () {
  113. const accounts = await web3.eth.getAccounts();
  114. // initialize our template token contract
  115. const token = new web3.eth.Contract(NFTImplementation.abi, NFTImplementation.address);
  116. await token.methods.initialize(
  117. "TestToken",
  118. "TT",
  119. accounts[0],
  120. 0,
  121. "0x0"
  122. ).send({
  123. value: 0,
  124. from: accounts[0],
  125. gasLimit: 2000000
  126. });
  127. await token.methods.mint(accounts[0], 10, "").send({
  128. from: accounts[0],
  129. gasLimit: 2000000
  130. });
  131. let failed = false
  132. try {
  133. await token.methods.mint(accounts[0], 11, "").send({
  134. from: accounts[1],
  135. gasLimit: 2000000
  136. });
  137. } catch (e) {
  138. failed = true
  139. }
  140. assert.ok(failed)
  141. failed = false
  142. try {
  143. await token.methods.burn(10).send({
  144. from: accounts[1],
  145. gasLimit: 2000000
  146. });
  147. } catch (e) {
  148. failed = true
  149. }
  150. assert.ok(failed)
  151. await token.methods.burn(10).send({
  152. from: accounts[0],
  153. gasLimit: 2000000
  154. });
  155. })
  156. it("should deposit and log transfers correctly", async function () {
  157. const accounts = await web3.eth.getAccounts();
  158. const tokenId = "1000000000000000000";
  159. // mint and approve tokens
  160. const token = new web3.eth.Contract(NFTImplementation.abi, NFTImplementation.address);
  161. await token.methods.mint(accounts[0], tokenId, "abcd").send({
  162. value: 0,
  163. from: accounts[0],
  164. gasLimit: 2000000
  165. });
  166. await token.methods.approve(NFTBridge.address, tokenId).send({
  167. value: 0,
  168. from: accounts[0],
  169. gasLimit: 2000000
  170. });
  171. // deposit tokens
  172. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  173. const ownerBefore = await token.methods.ownerOf(tokenId).call();
  174. assert.equal(ownerBefore, accounts[0]);
  175. await initialized.methods.transferNFT(
  176. NFTImplementation.address,
  177. tokenId,
  178. "10",
  179. "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
  180. "234"
  181. ).send({
  182. value: 0,
  183. from: accounts[0],
  184. gasLimit: 2000000
  185. });
  186. const ownerAfter = await token.methods.ownerOf(tokenId).call();
  187. assert.equal(ownerAfter, NFTBridge.address);
  188. // check transfer log
  189. const wormhole = new web3.eth.Contract(WormholeImplementationFullABI, Wormhole.address);
  190. const log = (await wormhole.getPastEvents('LogMessagePublished', {
  191. fromBlock: 'latest'
  192. }))[0].returnValues
  193. assert.equal(log.sender, NFTBridge.address)
  194. assert.equal(log.payload.length - 2, 340);
  195. // payload id
  196. assert.equal(log.payload.substr(2, 2), "01");
  197. // token
  198. assert.equal(log.payload.substr(4, 64), web3.eth.abi.encodeParameter("address", NFTImplementation.address).substring(2));
  199. // chain id
  200. assert.equal(log.payload.substr(68, 4), web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + 64 - 4))
  201. // symbol (TT)
  202. assert.equal(log.payload.substr(72, 64), "5454000000000000000000000000000000000000000000000000000000000000")
  203. // name (TestToken (Wormhole))
  204. assert.equal(log.payload.substr(136, 64), "54657374546f6b656e0000000000000000000000000000000000000000000000")
  205. // tokenID
  206. assert.equal(log.payload.substr(200, 64), web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId).toString()).substring(2));
  207. // url length
  208. assert.equal(log.payload.substr(264, 2), web3.eth.abi.encodeParameter("uint8", 4).substring(2 + 64 - 2))
  209. // url
  210. assert.equal(log.payload.substr(266, 8), "61626364")
  211. // to
  212. assert.equal(log.payload.substr(274, 64), "000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e");
  213. // to chain id
  214. assert.equal(log.payload.substr(338, 4), web3.eth.abi.encodeParameter("uint16", 10).substring(2 + 64 - 4))
  215. })
  216. it("should transfer out locked assets for a valid transfer vm", async function () {
  217. const accounts = await web3.eth.getAccounts();
  218. const tokenId = "1000000000000000000";
  219. const token = new web3.eth.Contract(NFTImplementation.abi, NFTImplementation.address);
  220. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  221. const ownerBefore = await token.methods.ownerOf(tokenId).call();
  222. assert.equal(ownerBefore, NFTBridge.address);
  223. // PayloadID uint8 = 1
  224. // // Address of the NFT. Left-zero-padded if shorter than 32 bytes
  225. // NFTAddress [32]uint8
  226. // // Chain ID of the NFT
  227. // NFTChain uint16
  228. // // Name of the NFT
  229. // Name [32]uint8
  230. // // Symbol of the NFT
  231. // Symbol [10]uint8
  232. // // ID of the token (big-endian uint256)
  233. // TokenID [32]uint8
  234. // // URL of the NFT
  235. // URLLength u8
  236. // URL [n]uint8
  237. // // Address of the recipient. Left-zero-padded if shorter than 32 bytes
  238. // To [32]uint8
  239. // // Chain ID of the recipient
  240. // ToChain uint16
  241. const data = "0x" +
  242. "01" +
  243. // tokenaddress
  244. web3.eth.abi.encodeParameter("address", NFTImplementation.address).substr(2) +
  245. // tokenchain
  246. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)) +
  247. // symbol
  248. "0000000000000000000000000000000000000000000000000000000000000000" +
  249. // name
  250. "0000000000000000000000000000000000000000000000000000000000000000" +
  251. // tokenID
  252. web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId).toString()).substring(2) +
  253. // url length
  254. "00" +
  255. // no URL
  256. "" +
  257. // receiver
  258. web3.eth.abi.encodeParameter("address", accounts[0]).substr(2) +
  259. // receiving chain
  260. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4));
  261. const vm = await signAndEncodeVM(
  262. 0,
  263. 0,
  264. testForeignChainId,
  265. testForeignBridgeContract,
  266. 0,
  267. data,
  268. [
  269. testSigner1PK
  270. ],
  271. 0,
  272. 0
  273. );
  274. await initialized.methods.completeTransfer("0x" + vm).send({
  275. value: 0,
  276. from: accounts[0],
  277. gasLimit: 2000000
  278. });
  279. const ownerAfter = await token.methods.ownerOf(tokenId).call();
  280. assert.equal(ownerAfter, accounts[0]);
  281. })
  282. it("should mint bridged assets wrappers on transfer from another chain and handle fees correctly", async function () {
  283. const accounts = await web3.eth.getAccounts();
  284. let tokenId = "1000000000000000001";
  285. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  286. // we are using the asset where we created a wrapper in the previous test
  287. let data = "0x" +
  288. "01" +
  289. // tokenaddress
  290. testBridgedAssetAddress +
  291. // tokenchain
  292. testBridgedAssetChain +
  293. // symbol
  294. "464f520000000000000000000000000000000000000000000000000000000000" +
  295. // name
  296. "466f726569676e20436861696e204e4654000000000000000000000000000000" +
  297. // tokenID
  298. web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId).toString()).substring(2) +
  299. // url length
  300. "00" +
  301. // no URL
  302. "" +
  303. // receiver
  304. web3.eth.abi.encodeParameter("address", accounts[0]).substr(2) +
  305. // receiving chain
  306. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4));
  307. let vm = await signAndEncodeVM(
  308. 0,
  309. 0,
  310. testForeignChainId,
  311. testForeignBridgeContract,
  312. 0,
  313. data,
  314. [
  315. testSigner1PK
  316. ],
  317. 0,
  318. 0
  319. );
  320. await initialized.methods.completeTransfer("0x" + vm).send({
  321. value: 0,
  322. from: accounts[1],
  323. gasLimit: 2000000
  324. });
  325. const wrappedAddress = await initialized.methods.wrappedAsset("0x" + testBridgedAssetChain, "0x" + testBridgedAssetAddress).call();
  326. assert.ok(await initialized.methods.isWrappedAsset(wrappedAddress).call())
  327. const wrappedAsset = new web3.eth.Contract(NFTImplementation.abi, wrappedAddress);
  328. let ownerAfter = await wrappedAsset.methods.ownerOf(tokenId).call();
  329. assert.equal(ownerAfter, accounts[0]);
  330. const symbol = await wrappedAsset.methods.symbol().call();
  331. assert.equal(symbol, "FOR");
  332. const name = await wrappedAsset.methods.name().call();
  333. assert.equal(name, "Foreign Chain NFT");
  334. const chainId = await wrappedAsset.methods.chainId().call();
  335. assert.equal(chainId, Number(testBridgedAssetChain));
  336. const nativeContract = await wrappedAsset.methods.nativeContract().call();
  337. assert.equal(nativeContract, "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e");
  338. // Transfer another tokenID of the same token address
  339. tokenId = "1000000000000000002"
  340. data = "0x" +
  341. "01" +
  342. // tokenaddress
  343. testBridgedAssetAddress +
  344. // tokenchain
  345. testBridgedAssetChain +
  346. // symbol
  347. "464f520000000000000000000000000000000000000000000000000000000000" +
  348. // name
  349. "466f726569676e20436861696e204e4654000000000000000000000000000000" +
  350. // tokenID
  351. web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId + 1).toString()).substring(2) +
  352. // url length
  353. "00" +
  354. // no URL
  355. "" +
  356. // receiver
  357. web3.eth.abi.encodeParameter("address", accounts[0]).substr(2) +
  358. // receiving chain
  359. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4));
  360. vm = await signAndEncodeVM(
  361. 0,
  362. 0,
  363. testForeignChainId,
  364. testForeignBridgeContract,
  365. 1,
  366. data,
  367. [
  368. testSigner1PK
  369. ],
  370. 0,
  371. 0
  372. );
  373. await initialized.methods.completeTransfer("0x" + vm).send({
  374. value: 0,
  375. from: accounts[1],
  376. gasLimit: 2000000
  377. });
  378. ownerAfter = await wrappedAsset.methods.ownerOf(tokenId + 1).call();
  379. assert.equal(ownerAfter, accounts[0]);
  380. })
  381. it("should mint bridged assets from solana under unified name, caching the original", async function () {
  382. const accounts = await web3.eth.getAccounts();
  383. let tokenId = "1000000000000000001";
  384. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  385. // we are using the asset where we created a wrapper in the previous test
  386. let data = "0x" +
  387. "01" +
  388. // tokenaddress
  389. testBridgedAssetAddress +
  390. // tokenchain
  391. "0001" +
  392. // symbol
  393. "464f520000000000000000000000000000000000000000000000000000000000" +
  394. // name
  395. "466f726569676e20436861696e204e4654000000000000000000000000000000" +
  396. // tokenID
  397. web3.eth.abi.encodeParameter("uint256", new BigNumber(tokenId).toString()).substring(2) +
  398. // url length
  399. "00" +
  400. // no URL
  401. "" +
  402. // receiver
  403. web3.eth.abi.encodeParameter("address", accounts[0]).substr(2) +
  404. // receiving chain
  405. web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4));
  406. let vm = await signAndEncodeVM(
  407. 0,
  408. 0,
  409. testForeignChainId,
  410. testForeignBridgeContract,
  411. 0,
  412. data,
  413. [
  414. testSigner1PK
  415. ],
  416. 0,
  417. 0
  418. );
  419. await initialized.methods.completeTransfer("0x" + vm).send({
  420. value: 0,
  421. from: accounts[1],
  422. gasLimit: 2000000
  423. });
  424. const cache = await initialized.methods.splCache(tokenId).call()
  425. assert.equal(cache.symbol, "0x464f520000000000000000000000000000000000000000000000000000000000");
  426. assert.equal(cache.name, "0x466f726569676e20436861696e204e4654000000000000000000000000000000");
  427. const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x" + testBridgedAssetAddress).call();
  428. const wrappedAsset = new web3.eth.Contract(NFTImplementation.abi, wrappedAddress);
  429. const symbol = await wrappedAsset.methods.symbol().call();
  430. assert.equal(symbol, "WORMSPLNFT");
  431. const name = await wrappedAsset.methods.name().call();
  432. assert.equal(name, "Wormhole Bridged Solana-NFT");
  433. })
  434. it("cached SPL names are loaded when transferring out, cache is cleared", async function () {
  435. const accounts = await web3.eth.getAccounts();
  436. let tokenId = "1000000000000000001";
  437. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  438. const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x" + testBridgedAssetAddress).call();
  439. const transfer = await initialized.methods.transferNFT(
  440. wrappedAddress,
  441. tokenId,
  442. "10",
  443. "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
  444. "2345"
  445. ).send({
  446. value: 0,
  447. from: accounts[0],
  448. gasLimit: 2000000
  449. });
  450. // symbol
  451. assert.ok(transfer.events[2].raw.data.includes('464f520000000000000000000000000000000000000000000000000000000000'))
  452. // name
  453. assert.ok(transfer.events[2].raw.data.includes('466f726569676e20436861696e204e4654000000000000000000000000000000'))
  454. // check if cache is cleared
  455. const cache = await initialized.methods.splCache(tokenId).call()
  456. assert.equal(cache.symbol, "0x0000000000000000000000000000000000000000000000000000000000000000");
  457. assert.equal(cache.name, "0x0000000000000000000000000000000000000000000000000000000000000000");
  458. })
  459. it("should burn bridged assets wrappers on transfer to another chain", async function () {
  460. const accounts = await web3.eth.getAccounts();
  461. const initialized = new web3.eth.Contract(BridgeImplementationFullABI, NFTBridge.address);
  462. const tokenId = "1000000000000000001";
  463. const wrappedAddress = await initialized.methods.wrappedAsset("0x" + testBridgedAssetChain, "0x" + testBridgedAssetAddress).call();
  464. const wrappedAsset = new web3.eth.Contract(NFTImplementation.abi, wrappedAddress);
  465. await wrappedAsset.methods.approve(NFTBridge.address, tokenId).send({
  466. value: 0,
  467. from: accounts[0],
  468. gasLimit: 2000000
  469. });
  470. // deposit tokens
  471. const ownerBefore = await wrappedAsset.methods.ownerOf(tokenId).call();
  472. assert.equal(ownerBefore, accounts[0]);
  473. await initialized.methods.transferNFT(
  474. wrappedAddress,
  475. tokenId,
  476. "10",
  477. "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
  478. "234"
  479. ).send({
  480. value: 0,
  481. from: accounts[0],
  482. gasLimit: 2000000
  483. });
  484. try {
  485. await wrappedAsset.methods.ownerOf(tokenId).call();
  486. assert.fail("burned token still exists")
  487. } catch (e) {
  488. assert.equal(e.data[Object.keys(e.data)[0]].reason, "ERC721: owner query for nonexistent token")
  489. }
  490. })
  491. });
  492. const signAndEncodeVM = async function (
  493. timestamp,
  494. nonce,
  495. emitterChainId,
  496. emitterAddress,
  497. sequence,
  498. data,
  499. signers,
  500. guardianSetIndex,
  501. consistencyLevel
  502. ) {
  503. const body = [
  504. web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
  505. web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
  506. web3.eth.abi.encodeParameter("uint16", emitterChainId).substring(2 + (64 - 4)),
  507. web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
  508. web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
  509. web3.eth.abi.encodeParameter("uint8", consistencyLevel).substring(2 + (64 - 2)),
  510. data.substr(2)
  511. ]
  512. const hash = web3.utils.soliditySha3(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. web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
  520. zeroPadBytes(signature.r.toString(16), 32),
  521. zeroPadBytes(signature.s.toString(16), 32),
  522. web3.eth.abi.encodeParameter("uint8", signature.recoveryParam).substr(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. }