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>
This commit is contained in:
Dev Kalra 2023-01-10 15:45:35 +05:30 committed by GitHub
parent 2a961d5853
commit 627edaa62a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 7597 additions and 1096 deletions

View File

@ -27,7 +27,7 @@ Then, to deploy the Pyth contract (`pyth_cosmwasm.wasm`), run the following comm
```sh ```sh
npm ci # Do it only once to install the required packages 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: If successful, this command will print something along the lines of:

View File

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

View File

@ -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));
}

View File

@ -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));
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -2,18 +2,31 @@
"name": "tools", "name": "tools",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "deploy.js", "main": "deploy-pyth-bridge.ts",
"type": "module",
"scripts": { "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": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@cosmjs/encoding": "^0.26.2", "@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", "@terra-money/terra.js": "^3.1.3",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"ethers": "^5.4.4", "ethers": "^5.4.4",
"yargs": "^17.0.1" "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"
} }
} }

View File

@ -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();

View File

@ -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();

View File

@ -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;
};

View File

@ -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");
}
}
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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,
];

View File

@ -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",
},
},
};

View File

@ -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"]
}

View File

@ -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 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 - name: TERRA_PYTH_CONTRACT_ADDRESS
value: terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6 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 - name: TERRA_CHAIN_ID
value: localterra value: localterra
- name: TERRA_NAME - name: TERRA_NAME