|
|
@@ -0,0 +1,248 @@
|
|
|
+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 axios from "axios";
|
|
|
+import yargs from "yargs";
|
|
|
+import {hideBin} from "yargs/helpers";
|
|
|
+import assert from "assert";
|
|
|
+
|
|
|
+export const TERRA_GAS_PRICES_URL = "https://fcd.terra.dev/v1/txs/gas_prices";
|
|
|
+
|
|
|
+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://lcd.terra.dev",
|
|
|
+ chainID: "columbus-5",
|
|
|
+ name: "mainnet",
|
|
|
+ },
|
|
|
+ wormholeContract: "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5",
|
|
|
+ pythEmitterAddress: "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25"
|
|
|
+ },
|
|
|
+ testnet: {
|
|
|
+ terraHost: {
|
|
|
+ URL: "https://bombay-lcd.terra.dev",
|
|
|
+ chainID: "bombay-12",
|
|
|
+ name: "testnet",
|
|
|
+ },
|
|
|
+ wormholeContract: "terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v",
|
|
|
+ pythEmitterAddress: "f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const terraHost = CONFIG[argv.network].terraHost;
|
|
|
+const wormholeContract = CONFIG[argv.network].wormholeContract;
|
|
|
+const pythEmitterAddress = CONFIG[argv.network].pythEmitterAddress;
|
|
|
+
|
|
|
+const lcd = new LCDClient(terraHost);
|
|
|
+
|
|
|
+const feeDenoms = ["uluna"];
|
|
|
+
|
|
|
+const gasPrices = await axios
|
|
|
+ .get(TERRA_GAS_PRICES_URL)
|
|
|
+ .then((result) => result.data);
|
|
|
+
|
|
|
+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 feeEstimate = await lcd.tx.estimateFee(
|
|
|
+ wallet.key.accAddress,
|
|
|
+ [store_code],
|
|
|
+ {
|
|
|
+ feeDenoms,
|
|
|
+ gasPrices,
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log("Deploy fee: ", feeEstimate.amount.toString());
|
|
|
+
|
|
|
+ const tx = await wallet.createAndSignTx({
|
|
|
+ msgs: [store_code],
|
|
|
+ feeDenoms,
|
|
|
+ gasPrices,
|
|
|
+ fee: feeEstimate,
|
|
|
+ });
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ var address;
|
|
|
+ await wallet
|
|
|
+ .createAndSignTx({
|
|
|
+ msgs: [
|
|
|
+ new MsgInstantiateContract(
|
|
|
+ wallet.key.accAddress,
|
|
|
+ wallet.key.accAddress,
|
|
|
+ codeId,
|
|
|
+ inst_msg
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ })
|
|
|
+ .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 Bridge at ${address} (${convert_terra_address_to_hex(address)})`);
|
|
|
+ return address;
|
|
|
+ }
|
|
|
+
|
|
|
+ const pythChain = 1;
|
|
|
+
|
|
|
+ const contractAddress = await instantiate(codeId, {
|
|
|
+ wormhole_contract: wormholeContract,
|
|
|
+ pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString(
|
|
|
+ "base64"
|
|
|
+ ),
|
|
|
+ pyth_emitter_chain: pythChain,
|
|
|
+ });
|
|
|
+
|
|
|
+ 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,
|
|
|
+ gasPrices,
|
|
|
+ });
|
|
|
+
|
|
|
+ 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));
|
|
|
+}
|