瀏覽代碼

Injective deployment code (#465)

* make it all typescript

* what the hell

* injective - store, instantiate, and migrate works

* terra, injective refactored

* update compiler in build.sh

* update package.json

* deploy.ts

* pre-commit run

* replace helper functions call with actual call

* correct case of RaiseCLError

* update deployer factory to take in config

* add comment to gas for injective store code

* extract raw log logic

* remove comment from injective config

* update deploy script for tilt

Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com>
Dev Kalra 2 年之前
父節點
當前提交
627edaa62a

+ 1 - 1
target-chains/cosmwasm/README.md

@@ -27,7 +27,7 @@ Then, to deploy the Pyth contract (`pyth_cosmwasm.wasm`), run the following comm
 
 ```sh
 npm ci # Do it only once to install the required packages
-npm run deploy-pyth -- --network testnet --artifact ../artifacts/pyth_cosmwasm.wasm --mnemonic "..."
+npm run deploy-pyth -- --network terra_testnet --artifact ../artifacts/pyth_cosmwasm.wasm --mnemonic "..."
 ```
 
 If successful, this command will print something along the lines of:

+ 2 - 2
target-chains/cosmwasm/build.sh

@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 docker run --rm -v "$(pwd)":/code \
-  -v $(cd ../third_party; pwd):/third_party \
+  -v $(cd ../../third_party; pwd):/third_party \
   --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
   --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
-  cosmwasm/workspace-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6

+ 0 - 302
target-chains/cosmwasm/tools/deploy-pyth-bridge.js

@@ -1,302 +0,0 @@
-import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
-import {
-  MsgInstantiateContract,
-  MsgMigrateContract,
-  MsgStoreCode,
-} from "@terra-money/terra.js";
-import { readFileSync } from "fs";
-import { Bech32, toHex } from "@cosmjs/encoding";
-import { zeroPad } from "ethers/lib/utils.js";
-import yargs from "yargs";
-import { hideBin } from "yargs/helpers";
-import assert from "assert";
-
-const argv = yargs(hideBin(process.argv))
-  .option("network", {
-    description: "Which network to deploy to",
-    choices: ["mainnet", "testnet"],
-    required: true,
-  })
-  .option("artifact", {
-    description: "Path to Pyth artifact",
-    type: "string",
-    required: false,
-  })
-  .option("mnemonic", {
-    description: "Mnemonic (private key)",
-    type: "string",
-    required: true,
-  })
-  .option("instantiate", {
-    description: "Instantiate contract if set (default: disabled)",
-    type: "boolean",
-    default: false,
-    required: false,
-  })
-  .option("migrate", {
-    description: "Migrate an existing contract if set (default: disabled)",
-    type: "boolean",
-    default: false,
-    required: false,
-  })
-  .option("contract", {
-    description: "Contract address, used only for migration",
-    type: "string",
-    required: false,
-    default: "",
-  })
-  .option("code-id", {
-    description:
-      "Code Id, if provided this will be used for migrate/instantiate and no code will be uploaded",
-    type: "number",
-    requred: false,
-  })
-  .help()
-  .alias("help", "h").argv;
-
-const artifact = argv.artifact;
-
-/* Set up terra client & wallet. It won't fail because inputs are validated with yargs */
-
-const CONFIG = {
-  mainnet: {
-    terraHost: {
-      URL: "https://phoenix-lcd.terra.dev",
-      chainID: "phoenix-1",
-      name: "mainnet",
-    },
-    pyth_config: {
-      wormhole_contract:
-        "terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnh",
-      data_sources: [
-        {
-          emitter: Buffer.from(
-            "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
-            "hex"
-          ).toString("base64"),
-          chain_id: 1,
-        },
-        {
-          emitter: Buffer.from(
-            "f8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0",
-            "hex"
-          ).toString("base64"),
-          chain_id: 26,
-        },
-      ],
-      governance_source: {
-        emitter: Buffer.from(
-          "5635979a221c34931e32620b9293a463065555ea71fe97cd6237ade875b12e9e",
-          "hex"
-        ).toString("base64"),
-        chain_id: 1,
-      },
-      governance_source_index: 0,
-      governance_sequence_number: 0,
-      chain_id: 18,
-      valid_time_period_secs: 60,
-      fee: {
-        amount: "1",
-        denom: "uluna",
-      },
-    },
-  },
-  testnet: {
-    terraHost: {
-      URL: "https://pisco-lcd.terra.dev",
-      chainID: "pisco-1",
-      name: "testnet",
-    },
-    pyth_config: {
-      wormhole_contract:
-        "terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0",
-      data_sources: [
-        {
-          emitter: Buffer.from(
-            "f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0",
-            "hex"
-          ).toString("base64"),
-          chain_id: 1,
-        },
-        {
-          emitter: Buffer.from(
-            "a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6",
-            "hex"
-          ).toString("base64"),
-          chain_id: 26,
-        },
-      ],
-      governance_source: {
-        emitter: Buffer.from(
-          "63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385",
-          "hex"
-        ).toString("base64"),
-        chain_id: 1,
-      },
-      governance_source_index: 0,
-      governance_sequence_number: 0,
-      chain_id: 18,
-      valid_time_period_secs: 60,
-      fee: {
-        amount: "1",
-        denom: "uluna",
-      },
-    },
-  },
-};
-
-const terraHost = CONFIG[argv.network].terraHost;
-const pythConfig = CONFIG[argv.network].pyth_config;
-const lcd = new LCDClient(terraHost);
-
-const feeDenoms = ["uluna"];
-
-const wallet = lcd.wallet(
-  new MnemonicKey({
-    mnemonic: argv.mnemonic,
-  })
-);
-
-/* Deploy artifacts */
-
-var codeId;
-
-if (argv.codeId !== undefined) {
-  codeId = argv.codeId;
-} else {
-  if (argv.artifact === undefined) {
-    console.error(
-      "Artifact is not provided. Please at least provide artifact or code id"
-    );
-    process.exit(1);
-  }
-
-  const contract_bytes = readFileSync(artifact);
-  console.log(`Storing WASM: ${artifact} (${contract_bytes.length} bytes)`);
-
-  const store_code = new MsgStoreCode(
-    wallet.key.accAddress,
-    contract_bytes.toString("base64")
-  );
-
-  const tx = await wallet.createAndSignTx({
-    msgs: [store_code],
-    feeDenoms,
-  });
-
-  const rs = await lcd.tx.broadcast(tx);
-
-  try {
-    const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
-    codeId = parseInt(ci);
-  } catch (e) {
-    console.error(
-      "Encountered an error in parsing deploy code result. Printing raw log"
-    );
-    console.error(rs.raw_log);
-    throw e;
-  }
-
-  console.log("Code ID: ", codeId);
-
-  if (argv.instantiate || argv.migrate) {
-    console.log("Sleeping for 10 seconds for store transaction to finalize.");
-    await sleep(10000);
-  }
-}
-
-if (argv.instantiate) {
-  console.log("Instantiating a contract");
-
-  async function instantiate(codeId, inst_msg, label) {
-    var address;
-    await wallet
-      .createAndSignTx({
-        msgs: [
-          new MsgInstantiateContract(
-            wallet.key.accAddress,
-            wallet.key.accAddress,
-            codeId,
-            inst_msg,
-            undefined,
-            label
-          ),
-        ],
-      })
-      .then((tx) => lcd.tx.broadcast(tx))
-      .then((rs) => {
-        try {
-          address = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
-        } catch (e) {
-          console.error(
-            "Encountered an error in parsing instantiation result. Printing raw log"
-          );
-          console.error(rs.raw_log);
-          throw e;
-        }
-      });
-    console.log(
-      `Instantiated Pyth at ${address} (${convert_terra_address_to_hex(
-        address
-      )})`
-    );
-    return address;
-  }
-
-  const contractAddress = await instantiate(codeId, pythConfig, "pyth");
-
-  console.log(`Deployed Pyth contract at ${contractAddress}`);
-}
-
-if (argv.migrate) {
-  if (argv.contract === "") {
-    console.error(
-      "Contract address is not provided. Provide it using --contract"
-    );
-    process.exit(1);
-  }
-
-  console.log(`Migrating contract ${argv.contract} to ${codeId}`);
-
-  const tx = await wallet.createAndSignTx({
-    msgs: [
-      new MsgMigrateContract(
-        wallet.key.accAddress,
-        argv.contract,
-        codeId,
-        {
-          action: "",
-        },
-        { uluna: 1000 }
-      ),
-    ],
-    feeDenoms,
-  });
-
-  const rs = await lcd.tx.broadcast(tx);
-  var resultCodeId;
-  try {
-    resultCodeId = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
-    assert.equal(codeId, resultCodeId);
-  } catch (e) {
-    console.error(
-      "Encountered an error in parsing migration result. Printing raw log"
-    );
-    console.error(rs.raw_log);
-    throw e;
-  }
-
-  console.log(
-    `Contract ${argv.contract} code_id successfully updated to ${resultCodeId}`
-  );
-}
-
-// Terra addresses are "human-readable", but for cross-chain registrations, we
-// want the "canonical" version
-function convert_terra_address_to_hex(human_addr) {
-  return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
-}
-
-function sleep(ms) {
-  return new Promise((resolve) => setTimeout(resolve, ms));
-}

+ 0 - 204
target-chains/cosmwasm/tools/deploy.js

@@ -1,204 +0,0 @@
-// Deploy Wormhole and Pyth contract to Tilt. If you want to
-// test the contracts locally you need to build the wormhole contract
-// as well. You can use Dockerfile.cosmwasm in the root of this repo
-// to do that.
-
-import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
-import { MsgInstantiateContract, MsgStoreCode } from "@terra-money/terra.js";
-import { readFileSync, readdirSync } from "fs";
-import { Bech32, toHex } from "@cosmjs/encoding";
-import { zeroPad } from "ethers/lib/utils.js";
-
-/*
-  NOTE: Only append to this array: keeping the ordering is crucial, as the
-  contracts must be imported in a deterministic order so their addresses remain
-  deterministic.
-*/
-const artifacts = ["wormhole.wasm", "pyth_cosmwasm.wasm"];
-
-/* Check that the artifact folder contains all the wasm files we expect and nothing else */
-
-const actual_artifacts = readdirSync("../artifacts/").filter((a) =>
-  a.endsWith(".wasm")
-);
-
-const missing_artifacts = artifacts.filter(
-  (a) => !actual_artifacts.includes(a)
-);
-if (missing_artifacts.length) {
-  console.log(
-    "Error during terra deployment. The following files are expected to be in the artifacts folder:"
-  );
-  missing_artifacts.forEach((file) => console.log(`  - ${file}`));
-  console.log(
-    "Hint: the deploy script needs to run after the contracts have been built."
-  );
-  console.log(
-    "External binary blobs need to be manually added in tools/Dockerfile."
-  );
-  process.exit(1);
-}
-
-const unexpected_artifacts = actual_artifacts.filter(
-  (a) => !artifacts.includes(a)
-);
-if (unexpected_artifacts.length) {
-  console.log(
-    "Error during terra deployment. The following files are not expected to be in the artifacts folder:"
-  );
-  unexpected_artifacts.forEach((file) => console.log(`  - ${file}`));
-  console.log("Hint: you might need to modify tools/deploy.js");
-  process.exit(1);
-}
-
-/* Set up terra client & wallet */
-
-const terra = new LCDClient({
-  URL: "http://localhost:1317",
-  chainID: "localterra",
-});
-
-const wallet = terra.wallet(
-  new MnemonicKey({
-    mnemonic:
-      "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius",
-  })
-);
-
-await wallet.sequence();
-
-/* Deploy artifacts */
-
-const codeIds = {};
-for (const file of artifacts) {
-  const contract_bytes = readFileSync(`../artifacts/${file}`);
-  console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
-
-  const store_code = new MsgStoreCode(
-    wallet.key.accAddress,
-    contract_bytes.toString("base64")
-  );
-
-  try {
-    const tx = await wallet.createAndSignTx({
-      msgs: [store_code],
-      memo: "",
-    });
-
-    const rs = await terra.tx.broadcast(tx);
-    const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
-    codeIds[file] = parseInt(ci);
-  } catch (e) {
-    console.log(`${e}`);
-  }
-}
-
-console.log(codeIds);
-
-/* Instantiate contracts.
- *
- * We instantiate the core contracts here (i.e. wormhole itself and the bridge contracts).
- * The wrapped asset contracts don't need to be instantiated here, because those
- * will be instantiated by the on-chain bridge contracts on demand.
- * */
-
-// Governance constants defined by the Wormhole spec.
-const govChain = 1;
-const govAddress =
-  "0000000000000000000000000000000000000000000000000000000000000004";
-
-async function instantiate(contract, inst_msg, label) {
-  var address;
-  await wallet
-    .createAndSignTx({
-      msgs: [
-        new MsgInstantiateContract(
-          wallet.key.accAddress,
-          wallet.key.accAddress,
-          codeIds[contract],
-          inst_msg,
-          undefined,
-          label
-        ),
-      ],
-      memo: "",
-    })
-    .then((tx) => terra.tx.broadcast(tx))
-    .then((rs) => {
-      address = /"_contract_address","value":"([^"]+)/gm.exec(rs.raw_log)[1];
-    });
-  console.log(
-    `Instantiated ${contract} at ${address} (${convert_terra_address_to_hex(
-      address
-    )})`
-  );
-  return address;
-}
-
-// Instantiate contracts.  NOTE: Only append at the end, the ordering must be
-// deterministic for the addresses to work
-
-const addresses = {};
-
-addresses["wormhole.wasm"] = await instantiate(
-  "wormhole.wasm",
-  {
-    gov_chain: govChain,
-    gov_address: Buffer.from(govAddress, "hex").toString("base64"),
-    guardian_set_expirity: 86400,
-    initial_guardian_set: {
-      addresses: [
-        {
-          bytes: Buffer.from(
-            "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
-            "hex"
-          ).toString("base64"),
-        },
-      ],
-      expiration_time: 0,
-    },
-    chain_id: 18,
-    fee_denom: "uluna",
-  },
-  "wormhole"
-);
-
-const pythEmitterAddress =
-  "71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
-const pythGovernanceEmitterAddress =
-  "0000000000000000000000000000000000000000000000000000000000001234";
-const pythChain = 1;
-
-addresses["pyth_cosmwasm.wasm"] = await instantiate(
-  "pyth_cosmwasm.wasm",
-  {
-    wormhole_contract: addresses["wormhole.wasm"],
-    data_sources: [
-      {
-        emitter: Buffer.from(pythEmitterAddress, "hex").toString("base64"),
-        chain_id: pythChain,
-      },
-    ],
-    governance_source: {
-      emitter: Buffer.from(pythGovernanceEmitterAddress, "hex").toString(
-        "base64"
-      ),
-      chain_id: pythChain,
-    },
-    governance_source_index: 0,
-    governance_sequence_number: 0,
-    chain_id: 3,
-    valid_time_period_secs: 60,
-    fee: {
-      amount: "1",
-      denom: "uluna",
-    },
-  },
-  "pyth"
-);
-
-// Terra addresses are "human-readable", but for cross-chain registrations, we
-// want the "canonical" version
-function convert_terra_address_to_hex(human_addr) {
-  return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
-}

+ 1 - 1
target-chains/cosmwasm/tools/deploy.sh

@@ -10,4 +10,4 @@ done
 
 sleep 2
 
-node deploy.js
+npm run deploy

File diff suppressed because it is too large
+ 559 - 188
target-chains/cosmwasm/tools/package-lock.json


+ 16 - 3
target-chains/cosmwasm/tools/package.json

@@ -2,18 +2,31 @@
   "name": "tools",
   "version": "1.0.0",
   "description": "",
-  "main": "deploy.js",
-  "type": "module",
+  "main": "deploy-pyth-bridge.ts",
   "scripts": {
-    "deploy-pyth": "node deploy-pyth-bridge.js"
+    "build": "tsc",
+    "deploy-pyth": "ts-node ./src/deploy-pyth-bridge.ts",
+    "deploy": "ts-node ./src/deploy.ts"
   },
   "author": "",
   "license": "ISC",
   "dependencies": {
     "@cosmjs/encoding": "^0.26.2",
+    "@injectivelabs/networks": "^1.0.55",
+    "@injectivelabs/sdk-ts": "^1.0.330",
+    "@injectivelabs/utils": "^1.0.47",
     "@terra-money/terra.js": "^3.1.3",
     "dotenv": "^16.0.0",
     "ethers": "^5.4.4",
     "yargs": "^17.0.1"
+  },
+  "devDependencies": {
+    "@types/yargs": "^17.0.18",
+    "@typescript-eslint/eslint-plugin": "^5.43.0",
+    "@typescript-eslint/parser": "^5.43.0",
+    "eslint": "^8.27.0",
+    "eslint-config-prettier": "^8.5.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^4.9.3"
   }
 }

+ 115 - 0
target-chains/cosmwasm/tools/src/deploy-pyth-bridge.ts

@@ -0,0 +1,115 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { Deployer, DeployerFactory } from "./deployer";
+import { NETWORKS_OPTIONS } from "./network";
+import { CONFIG as PythConfig } from "./pyth_config";
+import { CONFIG as NetworkConfig } from "./deployer/config";
+
+const argv = yargs(hideBin(process.argv))
+  .option("network", {
+    description: "Which network to deploy to",
+    choices: NETWORKS_OPTIONS,
+    required: true,
+  })
+  .option("artifact", {
+    description: "Path to Pyth artifact",
+    type: "string",
+    required: false,
+  })
+  .option("mnemonic", {
+    description: "Mnemonic (private key)",
+    type: "string",
+    required: true,
+  })
+  .option("instantiate", {
+    description: "Instantiate contract if set (default: disabled)",
+    type: "boolean",
+    default: false,
+    required: false,
+  })
+  .option("migrate", {
+    description: "Migrate an existing contract if set (default: disabled)",
+    type: "boolean",
+    default: false,
+    required: false,
+  })
+  .option("contract", {
+    description: "Contract address, used only for migration",
+    type: "string",
+    required: false,
+    default: "",
+  })
+  .option("code-id", {
+    description:
+      "Code Id, if provided this will be used for migrate/instantiate and no code will be uploaded",
+    type: "number",
+    requred: false,
+  })
+  .help()
+  .alias("help", "h")
+  .parseSync();
+
+const {
+  artifact,
+  network,
+  mnemonic,
+  codeId: inputCodeId,
+  instantiate,
+  migrate,
+  contract,
+} = argv;
+const pythConfig = PythConfig[network];
+const networkConfig = NetworkConfig[network];
+const deployer: Deployer = DeployerFactory.create(networkConfig, mnemonic);
+
+// checks
+if (inputCodeId === undefined && artifact === undefined)
+  raiseCLError("Please provide either artifact or code id");
+
+function sleep(ms: number) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+function raiseCLError(message: string) {
+  console.error(message);
+  process.exit(1);
+}
+
+async function run() {
+  let codeId: number;
+  if (inputCodeId === undefined) {
+    console.log("Deploying artifact");
+    codeId = await deployer.deployArtifact(artifact!);
+    console.log("Deployed Code ID: ", codeId);
+
+    // sleep only when a new artifact is deployed
+    if (instantiate || migrate) {
+      console.log("Sleeping for 10 seconds for store transaction to finalize.");
+      await sleep(10000);
+    }
+  } else codeId = inputCodeId;
+
+  if (instantiate) {
+    console.log("Instantiating a contract");
+    const contractAddress = await deployer.instantiate(
+      codeId,
+      pythConfig,
+      "pyth"
+    );
+    console.log(`Deployed Pyth contract at ${contractAddress}`);
+  }
+  if (migrate) {
+    if (contract === "")
+      raiseCLError(
+        "Contract address is not provided. Provide it using --contract"
+      );
+
+    console.log(`Migrating contract ${contract} to ${codeId}`);
+    await deployer.migrate(contract, codeId);
+    console.log(
+      `Contract ${contract} code_id successfully updated to ${codeId}`
+    );
+  }
+}
+
+run();

+ 161 - 0
target-chains/cosmwasm/tools/src/deploy.ts

@@ -0,0 +1,161 @@
+// Deploy Wormhole and Pyth contract to Tilt. If you want to
+// test the contracts locally you need to build the wormhole contract
+// as well. You can use Dockerfile.cosmwasm in the root of this repo
+// to do that.
+
+import { readdirSync } from "fs";
+import { DeployerFactory } from "./deployer";
+import { CONFIG_TYPE, NetworkConfig } from "./deployer/config";
+
+const ARTIFACT_DIR = "../artifacts/";
+
+async function deploy() {
+  /*
+  NOTE: Only append to this array: keeping the ordering is crucial, as the
+  contracts must be imported in a deterministic order so their addresses remain
+  deterministic.
+*/
+  const artifacts = ["wormhole.wasm", "pyth_cosmwasm.wasm"];
+
+  /* Check that the artifact folder contains all the wasm files we expect and nothing else */
+
+  const actual_artifacts = readdirSync("../artifacts/").filter((a) =>
+    a.endsWith(".wasm")
+  );
+
+  const missing_artifacts = artifacts.filter(
+    (a) => !actual_artifacts.includes(a)
+  );
+  if (missing_artifacts.length) {
+    console.log(
+      "Error during terra deployment. The following files are expected to be in the artifacts folder:"
+    );
+    missing_artifacts.forEach((file) => console.log(`  - ${file}`));
+    console.log(
+      "Hint: the deploy script needs to run after the contracts have been built."
+    );
+    console.log(
+      "External binary blobs need to be manually added in tools/Dockerfile."
+    );
+    process.exit(1);
+  }
+
+  const unexpected_artifacts = actual_artifacts.filter(
+    (a) => !artifacts.includes(a)
+  );
+  if (unexpected_artifacts.length) {
+    console.log(
+      "Error during terra deployment. The following files are not expected to be in the artifacts folder:"
+    );
+    unexpected_artifacts.forEach((file) => console.log(`  - ${file}`));
+    console.log("Hint: you might need to modify tools/deploy.js");
+    process.exit(1);
+  }
+
+  /* Set up terra deployer */
+  const networkConfig: NetworkConfig = {
+    type: CONFIG_TYPE.TERRA,
+    host: {
+      URL: "http://localhost:1317",
+      chainID: "localterra",
+      name: "localterra",
+    },
+  };
+  const deployer = DeployerFactory.create(
+    networkConfig,
+    "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
+  );
+
+  /* Deploy artifacts */
+  const codeIds: Record<string, number> = {};
+  for (const file of artifacts) {
+    const codeId = await deployer.deployArtifact(`../artifacts/${file}`);
+    codeIds[file] = codeId;
+  }
+  console.log(codeIds);
+
+  /* Instantiate contracts.
+   *
+   * We instantiate the core contracts here (i.e. wormhole itself and the bridge contracts).
+   * The wrapped asset contracts don't need to be instantiated here, because those
+   * will be instantiated by the on-chain bridge contracts on demand.
+   * */
+
+  // Instantiate contracts.  NOTE: Only append at the end, the ordering must be
+  // deterministic for the addresses to work
+
+  const addresses: Record<string, string> = {};
+
+  let contract = "wormhole.wasm";
+
+  // Governance constants defined by the Wormhole spec.
+  const govChain = 1;
+  const govAddress =
+    "0000000000000000000000000000000000000000000000000000000000000004";
+
+  let inst_msg: Object = {
+    gov_chain: govChain,
+    gov_address: Buffer.from(govAddress, "hex").toString("base64"),
+    guardian_set_expirity: 86400,
+    initial_guardian_set: {
+      addresses: [
+        {
+          bytes: Buffer.from(
+            "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
+            "hex"
+          ).toString("base64"),
+        },
+      ],
+      expiration_time: 0,
+    },
+    chain_id: 18,
+    fee_denom: "uluna",
+  };
+  console.log("Instantiating Wormhole contract");
+  addresses[contract] = await deployer.instantiate(
+    codeIds[contract],
+    inst_msg,
+    "wormhole"
+  );
+
+  contract = "pyth_cosmwasm.wasm";
+
+  const pythEmitterAddress =
+    "71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
+  const pythGovernanceEmitterAddress =
+    "0000000000000000000000000000000000000000000000000000000000001234";
+  const pythChain = 1;
+
+  inst_msg = {
+    wormhole_contract: addresses["wormhole.wasm"],
+    data_sources: [
+      {
+        emitter: Buffer.from(pythEmitterAddress, "hex").toString("base64"),
+        chain_id: pythChain,
+      },
+    ],
+    governance_source: {
+      emitter: Buffer.from(pythGovernanceEmitterAddress, "hex").toString(
+        "base64"
+      ),
+      chain_id: pythChain,
+    },
+    governance_source_index: 0,
+    governance_sequence_number: 0,
+    chain_id: 3,
+    valid_time_period_secs: 60,
+    fee: {
+      amount: "1",
+      denom: "uluna",
+    },
+  };
+
+  console.log("Instantiating Pyth contract");
+  addresses[contract] = await deployer.instantiate(
+    codeIds[contract],
+    inst_msg,
+    "pyth"
+  );
+}
+
+deploy();

+ 46 - 0
target-chains/cosmwasm/tools/src/deployer/config.ts

@@ -0,0 +1,46 @@
+import { Network } from "@injectivelabs/networks";
+import { TerraHost } from "./terra";
+import { InjectiveHost } from "./injective";
+import { NETWORKS } from "../network";
+
+export enum CONFIG_TYPE {
+  TERRA = "terra",
+  INJECTIVE = "injective",
+}
+
+export const CONFIG: Config = {
+  [NETWORKS.TERRA_MAINNET]: {
+    type: CONFIG_TYPE.TERRA,
+    host: {
+      URL: "https://phoenix-lcd.terra.dev",
+      chainID: "phoenix-1",
+      name: "mainnet",
+    },
+  },
+  [NETWORKS.TERRA_TESTNET]: {
+    type: CONFIG_TYPE.TERRA,
+    host: {
+      URL: "https://pisco-lcd.terra.dev",
+      chainID: "pisco-1",
+      name: "testnet",
+    },
+  },
+  [NETWORKS.INJECTIVE_TESTNET]: {
+    type: CONFIG_TYPE.INJECTIVE,
+    host: {
+      network: Network.Testnet,
+    },
+  },
+};
+
+export type Config = Record<NETWORKS, NetworkConfig>;
+
+export type NetworkConfig =
+  | {
+      type: CONFIG_TYPE.TERRA;
+      host: TerraHost;
+    }
+  | {
+      type: CONFIG_TYPE.INJECTIVE;
+      host: InjectiveHost;
+    };

+ 29 - 0
target-chains/cosmwasm/tools/src/deployer/index.ts

@@ -0,0 +1,29 @@
+import { CONFIG, CONFIG_TYPE, NetworkConfig } from "./config";
+import { TerraDeployer } from "./terra";
+import { InjectiveDeployer } from "./injective";
+import { NETWORKS } from "../network";
+
+export interface Deployer {
+  deployArtifact(artifact: string): Promise<number>;
+  instantiate(
+    codeId: number,
+    inst_msg: string | object,
+    label: string
+  ): Promise<string>;
+  migrate(contract: string, codeId: number): Promise<void>;
+}
+
+export class DeployerFactory {
+  static create(config: NetworkConfig, mnemonic: string): Deployer {
+    switch (config.type) {
+      case CONFIG_TYPE.TERRA:
+        return TerraDeployer.fromHostAndMnemonic(config.host, mnemonic);
+
+      case CONFIG_TYPE.INJECTIVE:
+        return InjectiveDeployer.fromHostAndMnemonic(config.host, mnemonic);
+
+      default:
+        throw new Error("Invalid config type");
+    }
+  }
+}

+ 188 - 0
target-chains/cosmwasm/tools/src/deployer/injective.ts

@@ -0,0 +1,188 @@
+import { readFileSync } from "fs";
+import { Bech32, toHex } from "@cosmjs/encoding";
+import { zeroPad } from "ethers/lib/utils.js";
+import assert from "assert";
+import { getNetworkInfo, Network } from "@injectivelabs/networks";
+import {
+  DEFAULT_STD_FEE,
+  MsgStoreCode,
+  MsgInstantiateContract,
+  PrivateKey,
+  TxGrpcClient,
+  TxResponse,
+  Msgs,
+  MsgMigrateContract,
+  createTransactionForAddressAndMsg,
+} from "@injectivelabs/sdk-ts";
+import { Deployer } from ".";
+
+export type InjectiveHost = {
+  network: Network;
+};
+
+export class InjectiveDeployer implements Deployer {
+  network: Network;
+  wallet: PrivateKey;
+
+  constructor(network: Network, wallet: PrivateKey) {
+    this.network = network;
+    this.wallet = wallet;
+  }
+
+  private injectiveAddress(): string {
+    return this.wallet.toBech32();
+  }
+
+  private async signAndBroadcastMsg(
+    msg: Msgs | MsgMigrateContract,
+    fee = DEFAULT_STD_FEE
+  ): Promise<TxResponse> {
+    const networkInfo = getNetworkInfo(this.network);
+
+    const { signBytes, txRaw } = await createTransactionForAddressAndMsg({
+      // @ts-ignore
+      message: msg,
+      address: this.injectiveAddress(),
+      endpoint: networkInfo.rest,
+      chainId: networkInfo.chainId,
+      fee,
+      pubKey: this.wallet.toPublicKey().toBase64(),
+    });
+
+    const sig = await this.wallet.sign(Buffer.from(signBytes));
+
+    /** Append Signatures */
+    txRaw.setSignaturesList([sig]);
+
+    const txService = new TxGrpcClient(networkInfo.grpc);
+    const txResponse = await txService.broadcast(txRaw);
+
+    if (txResponse.code !== 0) {
+      console.error(`Transaction failed: ${txResponse.rawLog}`);
+    } else {
+      console.log(
+        `Broadcasted transaction hash: ${JSON.stringify(txResponse.txHash)}`
+      );
+    }
+
+    return txResponse;
+  }
+
+  async deployArtifact(artifact: string): Promise<number> {
+    const contract_bytes = readFileSync(artifact);
+    console.log(`Storing WASM: ${artifact} (${contract_bytes.length} bytes)`);
+
+    const store_code = MsgStoreCode.fromJSON({
+      sender: this.injectiveAddress(),
+      wasmBytes: contract_bytes,
+    });
+
+    const txResponse = await this.signAndBroadcastMsg(store_code, {
+      amount: [
+        {
+          // gas = 5000000 & gasPrice = 500000000
+          amount: String(500000000 * 5000000),
+          denom: "inj",
+        },
+      ],
+      // DEFAULT STD FEE that we use has gas = 400000 and gasPrice = 500000000
+      // But this transaction was taking gas around 3000000. Which is a lot more
+      // Keeping the gasPrice same as in default std fee as seen above in amount.
+      // Changing the gasLimit to 5000000
+      // If similar issue arise saying gas not enough, we can increase it more.
+      gas: "5000000",
+    });
+
+    var codeId: number;
+    try {
+      // {"key":"code_id","value":"\"14\""}
+      const ci = extractFromRawLog(txResponse.rawLog, "code_id");
+      codeId = parseInt(ci);
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing deploy code result. Printing raw log"
+      );
+      console.error(txResponse.rawLog);
+      throw e;
+    }
+
+    return codeId;
+  }
+
+  async instantiate(
+    codeId: number,
+    inst_msg: object,
+    label: string
+  ): Promise<string> {
+    const instantiate_msg = MsgInstantiateContract.fromJSON({
+      sender: this.injectiveAddress(),
+      admin: this.injectiveAddress(),
+      codeId,
+      label,
+      msg: inst_msg,
+    });
+
+    const txResponse = await this.signAndBroadcastMsg(instantiate_msg);
+
+    let address: string = "";
+    try {
+      address = extractFromRawLog(txResponse.rawLog, "contract_address");
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing instantiation result. Printing raw log"
+      );
+      console.error(txResponse.rawLog);
+      throw e;
+    }
+
+    console.log(
+      `Instantiated Pyth at ${address} (${convert_injective_address_to_hex(
+        address
+      )})`
+    );
+
+    return address;
+  }
+
+  async migrate(contract: string, codeId: number): Promise<void> {
+    const migrate_msg = MsgMigrateContract.fromJSON({
+      sender: this.injectiveAddress(),
+      contract,
+      codeId,
+      msg: {
+        action: "",
+      },
+    });
+
+    const txResponse = await this.signAndBroadcastMsg(migrate_msg);
+
+    let resultCodeId: number;
+    try {
+      resultCodeId = parseInt(extractFromRawLog(txResponse.rawLog, "code_id"));
+      assert.strictEqual(codeId, resultCodeId);
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing migration result. Printing raw log"
+      );
+      console.error(txResponse.rawLog);
+      throw e;
+    }
+  }
+
+  static fromHostAndMnemonic(host: InjectiveHost, mnemonic: string) {
+    const wallet = PrivateKey.fromMnemonic(mnemonic);
+    return new InjectiveDeployer(host.network, wallet);
+  }
+}
+
+// Injective addresses are "human-readable", but for cross-chain registrations, we
+// want the "canonical" version
+function convert_injective_address_to_hex(human_addr: string) {
+  return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
+}
+
+// enter key of what to extract
+function extractFromRawLog(rawLog: string, key: string): string {
+  const rx = new RegExp(`"${key}","value":"\\\\"([^\\\\"]+)`, "gm");
+  return rx.exec(rawLog)![1];
+}

+ 160 - 0
target-chains/cosmwasm/tools/src/deployer/terra.ts

@@ -0,0 +1,160 @@
+import {
+  LCDClient,
+  MnemonicKey,
+  Msg,
+  MsgInstantiateContract,
+  MsgMigrateContract,
+  MsgStoreCode,
+  WaitTxBroadcastResult,
+  Wallet,
+  isTxError,
+} from "@terra-money/terra.js";
+import { readFileSync } from "fs";
+import { Bech32, toHex } from "@cosmjs/encoding";
+import { zeroPad } from "ethers/lib/utils.js";
+import assert from "assert";
+import { Deployer } from ".";
+
+export type TerraHost = {
+  URL: string;
+  chainID: string;
+  name: string;
+};
+
+export class TerraDeployer implements Deployer {
+  wallet: Wallet;
+  feeDenoms: [string];
+
+  constructor(wallet: Wallet) {
+    this.wallet = wallet;
+    this.feeDenoms = ["uluna"];
+  }
+
+  private async signAndBroadcastMsg(msg: Msg): Promise<WaitTxBroadcastResult> {
+    const tx = await this.wallet.createAndSignTx({
+      msgs: [msg],
+      feeDenoms: this.feeDenoms,
+    });
+    const res = await this.wallet.lcd.tx.broadcast(tx);
+
+    if (isTxError(res)) {
+      console.error(`Transaction failed: ${res.raw_log}`);
+    } else {
+      console.log(
+        `Broadcasted transaction hash: ${JSON.stringify(res.txhash)}`
+      );
+    }
+
+    return res;
+  }
+
+  async deployArtifact(artifact: string): Promise<number> {
+    const contract_bytes = readFileSync(artifact);
+    console.log(`Storing WASM: ${artifact} (${contract_bytes.length} bytes)`);
+
+    const store_code = new MsgStoreCode(
+      this.wallet.key.accAddress,
+      contract_bytes.toString("base64")
+    );
+
+    const rs = await this.signAndBroadcastMsg(store_code);
+
+    var codeId: number;
+    try {
+      // {"key":"code_id","value":"14"}
+      const ci = extractFromRawLog(rs.raw_log, "code_id");
+      codeId = parseInt(ci);
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing deploy code result. Printing raw log"
+      );
+      console.error(rs.raw_log);
+      throw e;
+    }
+
+    return codeId;
+  }
+
+  async instantiate(
+    codeId: number,
+    inst_msg: string | object,
+    label: string
+  ): Promise<string> {
+    const instMsg = new MsgInstantiateContract(
+      this.wallet.key.accAddress,
+      this.wallet.key.accAddress,
+      codeId,
+      inst_msg,
+      undefined,
+      label
+    );
+    const rs = await this.signAndBroadcastMsg(instMsg);
+
+    var address: string = "";
+
+    try {
+      // {"key":"_contract_address","value":"terra1xxx3ps3gm3wceg4g300hvggdv7ga0hmsk64srccffmfy4wvcrugqnlvt8w"}
+      address = extractFromRawLog(rs.raw_log, "_contract_address");
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing instantiation result. Printing raw log"
+      );
+      console.error(rs.raw_log);
+      throw e;
+    }
+
+    console.log(
+      `Instantiated ${label} at ${address} (${convert_terra_address_to_hex(
+        address
+      )})`
+    );
+    return address;
+  }
+
+  async migrate(contract: string, codeId: number): Promise<void> {
+    const migrateMsg = new MsgMigrateContract(
+      this.wallet.key.accAddress,
+      contract,
+      codeId,
+      {
+        action: "",
+      }
+    );
+
+    const rs = await this.signAndBroadcastMsg(migrateMsg);
+    try {
+      // {"key":"code_id","value":"13"}
+      let resultCodeId = parseInt(extractFromRawLog(rs.raw_log, "code_id"));
+      assert.strictEqual(codeId, resultCodeId);
+    } catch (e) {
+      console.error(
+        "Encountered an error in parsing migration result. Printing raw log"
+      );
+      console.error(rs.raw_log);
+      throw e;
+    }
+  }
+
+  static fromHostAndMnemonic(host: TerraHost, mnemonic: string) {
+    const lcd = new LCDClient(host);
+    const wallet = lcd.wallet(
+      new MnemonicKey({
+        mnemonic,
+      })
+    );
+
+    return new TerraDeployer(wallet);
+  }
+}
+
+// Terra addresses are "human-readable", but for cross-chain registrations, we
+// want the "canonical" version
+function convert_terra_address_to_hex(human_addr: string) {
+  return "0x" + toHex(zeroPad(Bech32.decode(human_addr).data, 32));
+}
+
+// enter key of what to extract
+function extractFromRawLog(rawLog: string, key: string): string {
+  const rx = new RegExp(`"${key}","value":"([^"]+)`, "gm");
+  return rx.exec(rawLog)![1];
+}

+ 11 - 0
target-chains/cosmwasm/tools/src/network.ts

@@ -0,0 +1,11 @@
+export enum NETWORKS {
+  TERRA_MAINNET = "terra_mainnet",
+  TERRA_TESTNET = "terra_testnet",
+  INJECTIVE_TESTNET = "injective_testnet",
+}
+
+export const NETWORKS_OPTIONS = [
+  NETWORKS.TERRA_MAINNET,
+  NETWORKS.TERRA_TESTNET,
+  NETWORKS.INJECTIVE_TESTNET,
+];

+ 131 - 0
target-chains/cosmwasm/tools/src/pyth_config.ts

@@ -0,0 +1,131 @@
+import { NETWORKS } from "./network";
+
+export type PythConfig = {
+  wormhole_contract: string;
+  data_sources: DataSource[];
+  governance_source: DataSource;
+  governance_source_index: number;
+  governance_sequence_number: number;
+  chain_id: number;
+  valid_time_period_secs: number;
+  fee: Fee;
+};
+
+export type DataSource = {
+  emitter: string;
+  chain_id: number;
+};
+
+export type Fee = {
+  amount: string;
+  denom: string;
+};
+
+type Config = Record<NETWORKS, PythConfig>;
+
+export const CONFIG: Config = {
+  [NETWORKS.TERRA_MAINNET]: {
+    wormhole_contract:
+      "terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnh",
+    data_sources: [
+      {
+        emitter: Buffer.from(
+          "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
+          "hex"
+        ).toString("base64"),
+        chain_id: 1,
+      },
+      {
+        emitter: Buffer.from(
+          "f8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0",
+          "hex"
+        ).toString("base64"),
+        chain_id: 26,
+      },
+    ],
+    governance_source: {
+      emitter: Buffer.from(
+        "5635979a221c34931e32620b9293a463065555ea71fe97cd6237ade875b12e9e",
+        "hex"
+      ).toString("base64"),
+      chain_id: 1,
+    },
+    governance_source_index: 0,
+    governance_sequence_number: 0,
+    chain_id: 18,
+    valid_time_period_secs: 60,
+    fee: {
+      amount: "1",
+      denom: "uluna",
+    },
+  },
+  [NETWORKS.TERRA_TESTNET]: {
+    wormhole_contract:
+      "terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0",
+    data_sources: [
+      {
+        emitter: Buffer.from(
+          "f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0",
+          "hex"
+        ).toString("base64"),
+        chain_id: 1,
+      },
+      {
+        emitter: Buffer.from(
+          "a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6",
+          "hex"
+        ).toString("base64"),
+        chain_id: 26,
+      },
+    ],
+    governance_source: {
+      emitter: Buffer.from(
+        "63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385",
+        "hex"
+      ).toString("base64"),
+      chain_id: 1,
+    },
+    governance_source_index: 0,
+    governance_sequence_number: 0,
+    chain_id: 18,
+    valid_time_period_secs: 60,
+    fee: {
+      amount: "1",
+      denom: "uluna",
+    },
+  },
+  [NETWORKS.INJECTIVE_TESTNET]: {
+    wormhole_contract: "inj1xx3aupmgv3ce537c0yce8zzd3sz567syuyedpg",
+    data_sources: [
+      {
+        emitter: Buffer.from(
+          "f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0",
+          "hex"
+        ).toString("base64"),
+        chain_id: 1,
+      },
+      {
+        emitter: Buffer.from(
+          "a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6",
+          "hex"
+        ).toString("base64"),
+        chain_id: 26,
+      },
+    ],
+    governance_source: {
+      emitter: Buffer.from(
+        "63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385",
+        "hex"
+      ).toString("base64"),
+      chain_id: 1,
+    },
+    governance_source_index: 0,
+    governance_sequence_number: 0,
+    chain_id: 19,
+    valid_time_period_secs: 60,
+    fee: {
+      amount: "1",
+      denom: "inj",
+    },
+  },
+};

+ 17 - 0
target-chains/cosmwasm/tools/tsconfig.json

@@ -0,0 +1,17 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "target": "es2020",
+    "module": "CommonJS",
+    "moduleResolution": "node",
+    "outDir": "lib",
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true,
+    "noErrorTruncation": true,
+    "sourceMap": true,
+    "lib": ["es2021"]
+  },
+  "include": ["src/**/*.ts"]
+}

+ 1 - 1
tilt-devnet/k8s/p2w-terra-relay.yaml

@@ -63,7 +63,7 @@ spec:
               value: notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius
             - name: TERRA_PYTH_CONTRACT_ADDRESS
               value: terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6
-              # ^^ It can change if order of terra contract creation changes or anything is added/removed in terra/tools/deploy.js
+              # ^^ It can change if order of terra contract creation changes or anything is added/removed in terra/tools/deploy.ts
             - name: TERRA_CHAIN_ID
               value: localterra
             - name: TERRA_NAME

Some files were not shown because too many files changed in this diff