deploy-pyth-bridge.js 7.4 KB

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