diff --git a/terra/Dockerfile.build b/terra/Dockerfile.build new file mode 100644 index 00000000..fd3a3931 --- /dev/null +++ b/terra/Dockerfile.build @@ -0,0 +1,11 @@ +# Run with: +# docker build -f Dockerfile.build -o artifacts . +FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder +ADD Cargo.lock /code/ +ADD Cargo.toml /code/ +ADD contracts /code/contracts +ADD packages /code/packages +RUN optimize_workspace.sh + +FROM scratch AS export-stage +COPY --from=builder /code/artifacts / diff --git a/terra/README.md b/terra/README.md new file mode 100644 index 00000000..67f02826 --- /dev/null +++ b/terra/README.md @@ -0,0 +1,23 @@ +# Deploy + +First build the contracts + + +``` sh +docker build -f Dockerfile.build -o artifacts . +``` + +Then, for example, to deploy `token_bridge.wasm`, run in the `tools` directory + +``` sh +npm ci +node deploy_single.js --network mainnet --artifact ../artifacts/token_bridge.wasm --mnemonic "..." +``` + +which will print something along the lines of + +``` sh +Storing WASM: ../artifacts/token_bridge.wasm (367689 bytes) +Deploy fee: 88446uluna +Code ID: 2435 +``` diff --git a/terra/tools/deploy_single.js b/terra/tools/deploy_single.js new file mode 100644 index 00000000..c79ab553 --- /dev/null +++ b/terra/tools/deploy_single.js @@ -0,0 +1,162 @@ +import { LCDClient, MnemonicKey } from "@terra-money/terra.js"; +import { + MsgInstantiateContract, + 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"; + +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', 'localterra'], + required: true + }) + .option('artifact', { + description: 'Which WASM file to deploy', + type: 'string', + required: true + }) + .option('mnemonic', { + description: 'Mnemonic (private key)', + type: 'string', + required: true + }) + .help() + .alias('help', 'h').argv; + +const artifact = argv.artifact; + +/* Set up terra client & wallet */ + +const terra_host = + argv.network === "mainnet" + ? { + URL: "https://lcd.terra.dev", + chainID: "columbus-5", + name: "mainnet", + } + : argv.network === "testnet" + ? { + URL: "https://bombay-lcd.terra.dev", + chainID: "bombay-12", + name: "testnet", + } + : { + URL: "http://localhost:1317", + chainID: "columbus-5", + name: "localterra", + }; + +const lcd = new LCDClient(terra_host); + +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 + }) +); + +await wallet.sequence(); + +/* Deploy artifacts */ + +let codeId; +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], + { + memo: "", + feeDenoms, + gasPrices, + } +); + +console.log("Deploy fee: ", feeEstimate.amount.toString()); + +const tx = await wallet.createAndSignTx({ + msgs: [store_code], + memo: "", + feeDenoms, + gasPrices, + fee: feeEstimate, +}); + +const rs = await lcd.tx.broadcast(tx); +const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1]; +codeId = parseInt(ci); + +console.log("Code ID: ", codeId); + +/* 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. + * */ +async function instantiate(codeId, inst_msg) { + var address; + await wallet + .createAndSignTx({ + msgs: [ + new MsgInstantiateContract( + wallet.key.accAddress, + wallet.key.accAddress, + codeId, + inst_msg + ), + ], + memo: "", + }) + .then((tx) => lcd.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; +} + +// example usage of instantiate: + +// const contractAddress = 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, +// }, +// }); + + +// 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)); +} diff --git a/terra/tools/package.json b/terra/tools/package.json index c443a5d4..88bee5e4 100644 --- a/terra/tools/package.json +++ b/terra/tools/package.json @@ -12,6 +12,7 @@ "dependencies": { "@terra-money/terra.js": "^2.0.11", "@cosmjs/encoding": "^0.26.2", - "ethers": "^5.4.4" + "ethers": "^5.4.4", + "yargs": "^17.0.1" } }