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_bridge.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, }, }, "wormhole"); const pythEmitterAddress = "71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"; const pythChain = 1; addresses["pyth_bridge.wasm"] = await instantiate("pyth_bridge.wasm", { wormhole_contract: addresses["wormhole.wasm"], pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString( "base64" ), pyth_emitter_chain: pythChain, }, "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)); }