deploy-pyth-bridge.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
  2. import {
  3. MsgInstantiateContract,
  4. MsgMigrateContract,
  5. MsgStoreCode,
  6. } from "@terra-money/terra.js";
  7. import { readFileSync } from "fs";
  8. import { Bech32, toHex } from "@cosmjs/encoding";
  9. import { zeroPad } from "ethers/lib/utils.js";
  10. import axios from "axios";
  11. import yargs from "yargs";
  12. import { hideBin } from "yargs/helpers";
  13. import assert from "assert";
  14. export const TERRA_GAS_PRICES_URL = "https://fcd.terra.dev/v1/txs/gas_prices";
  15. const argv = yargs(hideBin(process.argv))
  16. .option("network", {
  17. description: "Which network to deploy to",
  18. choices: ["mainnet", "testnet"],
  19. required: true,
  20. })
  21. .option("artifact", {
  22. description: "Path to Pyth artifact",
  23. type: "string",
  24. required: false,
  25. })
  26. .option("mnemonic", {
  27. description: "Mnemonic (private key)",
  28. type: "string",
  29. required: true,
  30. })
  31. .option("instantiate", {
  32. description: "Instantiate contract if set (default: disabled)",
  33. type: "boolean",
  34. default: false,
  35. required: false,
  36. })
  37. .option("migrate", {
  38. description: "Migrate an existing contract if set (default: disabled)",
  39. type: "boolean",
  40. default: false,
  41. required: false,
  42. })
  43. .option("contract", {
  44. description: "Contract address, used only for migration",
  45. type: "string",
  46. required: false,
  47. default: "",
  48. })
  49. .option("code-id", {
  50. description:
  51. "Code Id, if provided this will be used for migrate/instantiate and no code will be uploaded",
  52. type: "number",
  53. requred: false,
  54. })
  55. .help()
  56. .alias("help", "h").argv;
  57. const artifact = argv.artifact;
  58. /* Set up terra client & wallet. It won't fail because inputs are validated with yargs */
  59. const CONFIG = {
  60. mainnet: {
  61. terraHost: {
  62. URL: "https://phoenix-lcd.terra.dev",
  63. chainID: "phoenix-1",
  64. name: "mainnet",
  65. },
  66. wormholeContract:
  67. "terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnh",
  68. pythEmitterAddress:
  69. "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
  70. },
  71. testnet: {
  72. terraHost: {
  73. URL: "https://pisco-lcd.terra.dev",
  74. chainID: "pisco-1",
  75. name: "testnet",
  76. },
  77. pyth_config: {
  78. wormhole_contract: "terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0",
  79. data_sources: [
  80. {
  81. emitter: Buffer.from("f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0", "hex").toString("base64"),
  82. chain_id: 1,
  83. },
  84. ],
  85. governance_source: {
  86. emitter: Buffer.from("63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", "hex").toString(
  87. "base64"
  88. ),
  89. chain_id: 1,
  90. },
  91. governance_source_index: 0,
  92. governance_sequence_number: 0,
  93. chain_id: 18,
  94. valid_time_period_secs: 60,
  95. fee: {
  96. amount: "1",
  97. denom: "uluna",
  98. },
  99. },
  100. },
  101. };
  102. const terraHost = CONFIG[argv.network].terraHost;
  103. const pythConfig = CONFIG[argv.network].pyth_config;
  104. const lcd = new LCDClient(terraHost);
  105. const feeDenoms = ["uluna"];
  106. const gasPrices = await axios
  107. .get(TERRA_GAS_PRICES_URL)
  108. .then((result) => result.data);
  109. const wallet = lcd.wallet(
  110. new MnemonicKey({
  111. mnemonic: argv.mnemonic,
  112. })
  113. );
  114. /* Deploy artifacts */
  115. var codeId;
  116. if (argv.codeId !== undefined) {
  117. codeId = argv.codeId;
  118. } else {
  119. if (argv.artifact === undefined) {
  120. console.error(
  121. "Artifact is not provided. Please at least provide artifact or code id"
  122. );
  123. process.exit(1);
  124. }
  125. const contract_bytes = readFileSync(artifact);
  126. console.log(`Storing WASM: ${artifact} (${contract_bytes.length} bytes)`);
  127. const store_code = new MsgStoreCode(
  128. wallet.key.accAddress,
  129. contract_bytes.toString("base64")
  130. );
  131. /*
  132. const feeEstimate = await lcd.tx.estimateFee([wallet.key.accAddress], {
  133. msgs: [store_code],
  134. feeDenoms,
  135. // gasPrices,
  136. });
  137. */
  138. // console.log("Deploy fee: ", feeEstimate.amount.toString());
  139. const tx = await wallet.createAndSignTx({
  140. msgs: [store_code],
  141. feeDenoms,
  142. // gasPrices,
  143. // fee: feeEstimate,
  144. });
  145. const rs = await lcd.tx.broadcast(tx);
  146. try {
  147. const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  148. codeId = parseInt(ci);
  149. } catch (e) {
  150. console.error(
  151. "Encountered an error in parsing deploy code result. Printing raw log"
  152. );
  153. console.error(rs.raw_log);
  154. throw e;
  155. }
  156. console.log("Code ID: ", codeId);
  157. if (argv.instantiate || argv.migrate) {
  158. console.log("Sleeping for 10 seconds for store transaction to finalize.");
  159. await sleep(10000);
  160. }
  161. }
  162. if (argv.instantiate) {
  163. console.log("Instantiating a contract");
  164. async function instantiate(codeId, inst_msg, label) {
  165. var address;
  166. await wallet
  167. .createAndSignTx({
  168. msgs: [
  169. new MsgInstantiateContract(
  170. wallet.key.accAddress,
  171. wallet.key.accAddress,
  172. codeId,
  173. inst_msg,
  174. undefined,
  175. label
  176. ),
  177. ],
  178. })
  179. .then((tx) => lcd.tx.broadcast(tx))
  180. .then((rs) => {
  181. try {
  182. address = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  183. } catch (e) {
  184. console.error(
  185. "Encountered an error in parsing instantiation result. Printing raw log"
  186. );
  187. console.error(rs.raw_log);
  188. throw e;
  189. }
  190. });
  191. console.log(
  192. `Instantiated Pyth at ${address} (${convert_terra_address_to_hex(
  193. address
  194. )})`
  195. );
  196. return address;
  197. }
  198. const contractAddress = await instantiate(codeId, pythConfig, "pyth");
  199. console.log(`Deployed Pyth contract at ${contractAddress}`);
  200. }
  201. if (argv.migrate) {
  202. if (argv.contract === "") {
  203. console.error(
  204. "Contract address is not provided. Provide it using --contract"
  205. );
  206. process.exit(1);
  207. }
  208. console.log(`Migrating contract ${argv.contract} to ${codeId}`);
  209. const tx = await wallet.createAndSignTx({
  210. msgs: [
  211. new MsgMigrateContract(
  212. wallet.key.accAddress,
  213. argv.contract,
  214. codeId,
  215. {
  216. action: "",
  217. },
  218. { uluna: 1000 }
  219. ),
  220. ],
  221. feeDenoms,
  222. // gasPrices,
  223. });
  224. const rs = await lcd.tx.broadcast(tx);
  225. var resultCodeId;
  226. try {
  227. resultCodeId = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  228. assert.equal(codeId, resultCodeId);
  229. } catch (e) {
  230. console.error(
  231. "Encountered an error in parsing migration result. Printing raw log"
  232. );
  233. console.error(rs.raw_log);
  234. throw e;
  235. }
  236. console.log(
  237. `Contract ${argv.contract} code_id successfully updated to ${resultCodeId}`
  238. );
  239. }
  240. // Terra addresses are "human-readable", but for cross-chain registrations, we
  241. // want the "canonical" version
  242. function convert_terra_address_to_hex(human_addr) {
  243. return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
  244. }
  245. function sleep(ms) {
  246. return new Promise((resolve) => setTimeout(resolve, ms));
  247. }