deploy-pyth-bridge.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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: 'Code Id, if provided this will be used for migrate/instantiate and no code will be uploaded',
  51. type: 'number',
  52. requred: false
  53. })
  54. .help()
  55. .alias('help', 'h').argv;
  56. const artifact = argv.artifact;
  57. /* Set up terra client & wallet. It won't fail because inputs are validated with yargs */
  58. const CONFIG = {
  59. mainnet: {
  60. terraHost: {
  61. URL: "https://lcd.terra.dev",
  62. chainID: "columbus-5",
  63. name: "mainnet",
  64. },
  65. wormholeContract: "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5",
  66. pythEmitterAddress: "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25"
  67. },
  68. testnet: {
  69. terraHost: {
  70. URL: "https://bombay-lcd.terra.dev",
  71. chainID: "bombay-12",
  72. name: "testnet",
  73. },
  74. wormholeContract: "terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v",
  75. pythEmitterAddress: "f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0"
  76. }
  77. }
  78. const terraHost = CONFIG[argv.network].terraHost;
  79. const wormholeContract = CONFIG[argv.network].wormholeContract;
  80. const pythEmitterAddress = CONFIG[argv.network].pythEmitterAddress;
  81. const lcd = new LCDClient(terraHost);
  82. const feeDenoms = ["uluna"];
  83. const gasPrices = await axios
  84. .get(TERRA_GAS_PRICES_URL)
  85. .then((result) => result.data);
  86. const wallet = lcd.wallet(
  87. new MnemonicKey({
  88. mnemonic: argv.mnemonic
  89. })
  90. );
  91. /* Deploy artifacts */
  92. var codeId;
  93. if (argv.codeId !== undefined) {
  94. codeId = argv.codeId;
  95. } else {
  96. if (argv.artifact === undefined) {
  97. console.error("Artifact is not provided. Please at least provide artifact or code id");
  98. process.exit(1);
  99. }
  100. const contract_bytes = readFileSync(artifact);
  101. console.log(`Storing WASM: ${artifact} (${contract_bytes.length} bytes)`);
  102. const store_code = new MsgStoreCode(
  103. wallet.key.accAddress,
  104. contract_bytes.toString("base64")
  105. );
  106. const feeEstimate = await lcd.tx.estimateFee(
  107. wallet.key.accAddress,
  108. [store_code],
  109. {
  110. feeDenoms,
  111. gasPrices,
  112. }
  113. );
  114. console.log("Deploy fee: ", feeEstimate.amount.toString());
  115. const tx = await wallet.createAndSignTx({
  116. msgs: [store_code],
  117. feeDenoms,
  118. gasPrices,
  119. fee: feeEstimate,
  120. });
  121. const rs = await lcd.tx.broadcast(tx);
  122. try {
  123. const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  124. codeId = parseInt(ci);
  125. } catch(e) {
  126. console.error("Encountered an error in parsing deploy code result. Printing raw log")
  127. console.error(rs.raw_log);
  128. throw(e);
  129. }
  130. console.log("Code ID: ", codeId);
  131. if (argv.instantiate || argv.migrate) {
  132. console.log("Sleeping for 10 seconds for store transaction to finalize.");
  133. await sleep(10000);
  134. }
  135. }
  136. if (argv.instantiate) {
  137. console.log("Instantiating a contract");
  138. async function instantiate(codeId, inst_msg) {
  139. var address;
  140. await wallet
  141. .createAndSignTx({
  142. msgs: [
  143. new MsgInstantiateContract(
  144. wallet.key.accAddress,
  145. wallet.key.accAddress,
  146. codeId,
  147. inst_msg
  148. ),
  149. ],
  150. })
  151. .then((tx) => lcd.tx.broadcast(tx))
  152. .then((rs) => {
  153. try {
  154. address = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  155. } catch (e) {
  156. console.error("Encountered an error in parsing instantiation result. Printing raw log")
  157. console.error(rs.raw_log);
  158. throw(e);
  159. }
  160. });
  161. console.log(`Instantiated Pyth Bridge at ${address} (${convert_terra_address_to_hex(address)})`);
  162. return address;
  163. }
  164. const pythChain = 1;
  165. const contractAddress = await instantiate(codeId, {
  166. wormhole_contract: wormholeContract,
  167. pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString(
  168. "base64"
  169. ),
  170. pyth_emitter_chain: pythChain,
  171. });
  172. console.log(`Deployed pyth contract at ${contractAddress}`);
  173. }
  174. if (argv.migrate) {
  175. if (argv.contract === '') {
  176. console.error("Contract address is not provided. Provide it using --contract");
  177. process.exit(1);
  178. }
  179. console.log(`Migrating contract ${argv.contract} to ${codeId}`);
  180. const tx = await wallet.createAndSignTx({
  181. msgs: [
  182. new MsgMigrateContract(
  183. wallet.key.accAddress,
  184. argv.contract,
  185. codeId,
  186. {
  187. "action": ""
  188. },
  189. { uluna: 1000 }
  190. ),
  191. ],
  192. feeDenoms,
  193. gasPrices,
  194. });
  195. const rs = await lcd.tx.broadcast(tx);
  196. var resultCodeId;
  197. try {
  198. resultCodeId = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
  199. assert.equal(codeId, resultCodeId)
  200. } catch (e) {
  201. console.error("Encountered an error in parsing migration result. Printing raw log")
  202. console.error(rs.raw_log);
  203. throw(e);
  204. }
  205. console.log(`Contract ${argv.contract} code_id successfully updated to ${resultCodeId}`);
  206. }
  207. // Terra addresses are "human-readable", but for cross-chain registrations, we
  208. // want the "canonical" version
  209. function convert_terra_address_to_hex(human_addr) {
  210. return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
  211. }
  212. function sleep(ms) {
  213. return new Promise(resolve => setTimeout(resolve, ms));
  214. }