deploy.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import "dotenv/config";
  2. import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
  3. import {
  4. StdFee,
  5. MsgInstantiateContract,
  6. MsgExecuteContract,
  7. MsgStoreCode,
  8. } from "@terra-money/terra.js";
  9. import { readFileSync, readdirSync } from "fs";
  10. import { Bech32, toHex } from "@cosmjs/encoding";
  11. import { zeroPad } from "ethers/lib/utils.js";
  12. function sleep(ms) {
  13. return new Promise((resolve) => setTimeout(resolve, ms));
  14. }
  15. async function broadcastAndWait(terra, tx) {
  16. const response = await terra.tx.broadcast(tx);
  17. let currentHeight = (await terra.tendermint.blockInfo()).block.header.height;
  18. while (currentHeight <= response.height) {
  19. await sleep(100);
  20. currentHeight = (await terra.tendermint.blockInfo()).block.header.height;
  21. }
  22. return response;
  23. }
  24. /*
  25. NOTE: Only append to this array: keeping the ordering is crucial, as the
  26. contracts must be imported in a deterministic order so their addresses remain
  27. deterministic.
  28. */
  29. const artifacts = [
  30. "wormhole.wasm",
  31. "token_bridge_terra.wasm",
  32. "cw20_wrapped.wasm",
  33. "cw20_base.wasm",
  34. "nft_bridge.wasm",
  35. "cw721_wrapped.wasm",
  36. "cw721_base.wasm",
  37. "mock_bridge_integration.wasm",
  38. ];
  39. /* Check that the artifact folder contains all the wasm files we expect and nothing else */
  40. const actual_artifacts = readdirSync("../artifacts/").filter((a) =>
  41. a.endsWith(".wasm")
  42. );
  43. const missing_artifacts = artifacts.filter(
  44. (a) => !actual_artifacts.includes(a)
  45. );
  46. if (missing_artifacts.length) {
  47. console.log(
  48. "Error during terra deployment. The following files are expected to be in the artifacts folder:"
  49. );
  50. missing_artifacts.forEach((file) => console.log(` - ${file}`));
  51. console.log(
  52. "Hint: the deploy script needs to run after the contracts have been built."
  53. );
  54. console.log(
  55. "External binary blobs need to be manually added in tools/Dockerfile."
  56. );
  57. process.exit(1);
  58. }
  59. const unexpected_artifacts = actual_artifacts.filter(
  60. (a) => !artifacts.includes(a)
  61. );
  62. if (unexpected_artifacts.length) {
  63. console.log(
  64. "Error during terra deployment. The following files are not expected to be in the artifacts folder:"
  65. );
  66. unexpected_artifacts.forEach((file) => console.log(` - ${file}`));
  67. console.log("Hint: you might need to modify tools/deploy.js");
  68. process.exit(1);
  69. }
  70. /* Set up terra client & wallet */
  71. const terra = new LCDClient({
  72. URL: "http://localhost:1317",
  73. chainID: "localterra",
  74. });
  75. const wallet = terra.wallet(
  76. new MnemonicKey({
  77. mnemonic:
  78. "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius",
  79. })
  80. );
  81. await wallet.sequence();
  82. /* Deploy artifacts */
  83. const codeIds = {};
  84. for (const file of artifacts) {
  85. const contract_bytes = readFileSync(`../artifacts/${file}`);
  86. console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
  87. const store_code = new MsgStoreCode(
  88. wallet.key.accAddress,
  89. contract_bytes.toString("base64")
  90. );
  91. try {
  92. const tx = await wallet.createAndSignTx({
  93. msgs: [store_code],
  94. memo: "",
  95. });
  96. const rs = await broadcastAndWait(terra, tx);
  97. const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  98. codeIds[file] = parseInt(ci);
  99. } catch (e) {
  100. console.log(`${e}`);
  101. }
  102. }
  103. console.log(codeIds);
  104. /* Instantiate contracts.
  105. *
  106. * We instantiate the core contracts here (i.e. wormhole itself and the bridge contracts).
  107. * The wrapped asset contracts don't need to be instantiated here, because those
  108. * will be instantiated by the on-chain bridge contracts on demand.
  109. * */
  110. // Governance constants defined by the Wormhole spec.
  111. const govChain = 1;
  112. const govAddress =
  113. "0000000000000000000000000000000000000000000000000000000000000004";
  114. async function instantiate(contract, inst_msg) {
  115. var address;
  116. await wallet
  117. .createAndSignTx({
  118. msgs: [
  119. new MsgInstantiateContract(
  120. wallet.key.accAddress,
  121. wallet.key.accAddress,
  122. codeIds[contract],
  123. inst_msg
  124. ),
  125. ],
  126. memo: "",
  127. })
  128. .then((tx) => broadcastAndWait(terra, tx))
  129. .then((rs) => {
  130. address = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  131. });
  132. console.log(
  133. `Instantiated ${contract} at ${address} (${convert_terra_address_to_hex(
  134. address
  135. )})`
  136. );
  137. return address;
  138. }
  139. // Instantiate contracts. NOTE: Only append at the end, the ordering must be
  140. // deterministic for the addresses to work
  141. const addresses = {};
  142. const init_guardians = JSON.parse(process.env.INIT_SIGNERS);
  143. if (!init_guardians || init_guardians.length === 0) {
  144. throw "failed to get initial guardians from .env file.";
  145. }
  146. addresses["wormhole.wasm"] = await instantiate("wormhole.wasm", {
  147. gov_chain: govChain,
  148. gov_address: Buffer.from(govAddress, "hex").toString("base64"),
  149. guardian_set_expirity: 86400,
  150. initial_guardian_set: {
  151. addresses: init_guardians.map((hex) => {
  152. return {
  153. bytes: Buffer.from(hex, "hex").toString("base64"),
  154. };
  155. }),
  156. expiration_time: 0,
  157. },
  158. });
  159. addresses["token_bridge_terra.wasm"] = await instantiate(
  160. "token_bridge_terra.wasm",
  161. {
  162. gov_chain: govChain,
  163. gov_address: Buffer.from(govAddress, "hex").toString("base64"),
  164. wormhole_contract: addresses["wormhole.wasm"],
  165. wrapped_asset_code_id: codeIds["cw20_wrapped.wasm"],
  166. }
  167. );
  168. addresses["mock.wasm"] = await instantiate("cw20_base.wasm", {
  169. name: "MOCK",
  170. symbol: "MCK",
  171. decimals: 6,
  172. initial_balances: [
  173. {
  174. address: wallet.key.accAddress,
  175. amount: "100000000",
  176. },
  177. ],
  178. mint: null,
  179. });
  180. addresses["nft_bridge.wasm"] = await instantiate("nft_bridge.wasm", {
  181. gov_chain: govChain,
  182. gov_address: Buffer.from(govAddress, "hex").toString("base64"),
  183. wormhole_contract: addresses["wormhole.wasm"],
  184. wrapped_asset_code_id: codeIds["cw721_wrapped.wasm"],
  185. });
  186. addresses["cw721_base.wasm"] = await instantiate("cw721_base.wasm", {
  187. name: "MOCK",
  188. symbol: "MCK",
  189. minter: wallet.key.accAddress,
  190. });
  191. async function mint_cw721(token_id, token_uri) {
  192. await wallet
  193. .createAndSignTx({
  194. msgs: [
  195. new MsgExecuteContract(
  196. wallet.key.accAddress,
  197. addresses["cw721_base.wasm"],
  198. {
  199. mint: {
  200. token_id: token_id.toString(),
  201. owner: wallet.key.accAddress,
  202. token_uri: token_uri,
  203. },
  204. },
  205. { uluna: 1000 }
  206. ),
  207. ],
  208. memo: "",
  209. fee: new StdFee(2000000, {
  210. uluna: "100000",
  211. }),
  212. })
  213. .then((tx) => broadcastAndWait(terra, tx));
  214. console.log(
  215. `Minted NFT with token_id ${token_id} at ${addresses["cw721_base.wasm"]}`
  216. );
  217. }
  218. await mint_cw721(
  219. 0,
  220. "https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/"
  221. );
  222. await mint_cw721(
  223. 1,
  224. "https://portal.neondistrict.io/api/getNft/158456327500392944014123206890"
  225. );
  226. /* Registrations: tell the bridge contracts to know about each other */
  227. const contract_registrations = {
  228. "token_bridge_terra.wasm": [
  229. // Solana
  230. process.env.REGISTER_SOL_TOKEN_BRIDGE_VAA,
  231. // Ethereum
  232. process.env.REGISTER_ETH_TOKEN_BRIDGE_VAA,
  233. // BSC
  234. process.env.REGISTER_BSC_TOKEN_BRIDGE_VAA,
  235. // ALGO
  236. process.env.REGISTER_ALGO_TOKEN_BRIDGE_VAA,
  237. // TERRA2
  238. process.env.REGISTER_TERRA2_TOKEN_BRIDGE_VAA,
  239. // NEAR
  240. process.env.REGISTER_NEAR_TOKEN_BRIDGE_VAA,
  241. // Wormhole Chain
  242. process.env.REGISTER_WORMCHAIN_TOKEN_BRIDGE_VAA,
  243. ],
  244. "nft_bridge.wasm": [
  245. // Solana
  246. process.env.REGISTER_SOL_NFT_BRIDGE_VAA,
  247. // Ethereum
  248. process.env.REGISTER_ETH_NFT_BRIDGE_VAA,
  249. ],
  250. };
  251. for (const [contract, registrations] of Object.entries(
  252. contract_registrations
  253. )) {
  254. console.log(`Registering chains for ${contract}:`);
  255. for (const registration of registrations) {
  256. await wallet
  257. .createAndSignTx({
  258. msgs: [
  259. new MsgExecuteContract(
  260. wallet.key.accAddress,
  261. addresses[contract],
  262. {
  263. submit_vaa: {
  264. data: Buffer.from(registration, "hex").toString("base64"),
  265. },
  266. },
  267. { uluna: 1000 }
  268. ),
  269. ],
  270. memo: "",
  271. fee: new StdFee(2000000, {
  272. uluna: "100000",
  273. }),
  274. })
  275. .then((tx) => broadcastAndWait(terra, tx))
  276. .then((rs) => console.log(rs));
  277. }
  278. }
  279. // Terra addresses are "human-readable", but for cross-chain registrations, we
  280. // want the "canonical" version
  281. function convert_terra_address_to_hex(human_addr) {
  282. return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
  283. }