deploy.js 9.6 KB

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