diff --git a/Tiltfile b/Tiltfile index 4a825ea69..959e1c69a 100644 --- a/Tiltfile +++ b/Tiltfile @@ -85,7 +85,7 @@ ci_tests = cfg.get("ci_tests", ci) guardiand_debug = cfg.get("guardiand_debug", False) node_metrics = cfg.get("node_metrics", False) guardiand_governor = cfg.get("guardiand_governor", False) -ibc_relayer = cfg.get("ibc_relayer", False) +ibc_relayer = cfg.get("ibc_relayer", ci) btc = cfg.get("btc", False) if cfg.get("manual", False): @@ -287,7 +287,13 @@ def build_node_yaml(): "--accountantContract", "wormhole14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9srrg465", "--accountantCheckEnabled", - "true" + "true", + "--ibcWS", + "ws://wormchain:26657/websocket", + "--ibcLCD", + "http://wormchain:1317", + "--ibcContract", + "wormhole1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq0kdhcj" ] return encode_yaml_stream(node_yaml_with_replicas) @@ -854,7 +860,7 @@ if ibc_relayer: port_forwards = [ port_forward(7597, name = "HTTPDEBUG [:7597]", host = webHost), ], - resource_deps = ["wormchain", "terra2-terrad"], + resource_deps = ["wormchain-deploy", "terra2-terrad"], labels = ["ibc-relayer"], trigger_mode = trigger_mode, ) diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/ibc.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/ibc.rs index 9b15c3ea5..76f9d2b0d 100644 --- a/cosmwasm/contracts/wormchain-ibc-receiver/src/ibc.rs +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/ibc.rs @@ -101,15 +101,13 @@ fn handle_packet_receive(msg: IbcPacketReceiveMsg) -> Result res.add_attribute("message.tx_index", index.to_string()), - None => res, - }; - res = res.add_attribute("message.block_height", block_height); + let res = core_execute(deps, env, info, msg).context("wormhole core execution failed")?; // Send the result attributes over IBC on this channel let packet = WormholeIbcPacketMsg::Publish { diff --git a/cosmwasm/deployment/terra2/tools/deploy.js b/cosmwasm/deployment/terra2/tools/deploy.js index 5947b791a..e385c58c3 100644 --- a/cosmwasm/deployment/terra2/tools/deploy.js +++ b/cosmwasm/deployment/terra2/tools/deploy.js @@ -9,6 +9,12 @@ import { readFileSync, readdirSync } from "fs"; import { Bech32, toHex } from "@cosmjs/encoding"; import { zeroPad } from "ethers/lib/utils.js"; +// Generated using +// `guardiand template ibc-receiver-update-channel-chain --channel-id channel-0 --chain-id 3104 --target-chain-id 32 > terra2.prototxt` +// `guardiand admin governance-vaa-verify terra2.prototxt` +const WORMHOLE_IBC_WHITELIST_VAA = + "0100000000010025e55ab23c8d0a7fddd4686f41801792cdce1ff7335a2b9436192bd552fa0f9b5c18016057b0d4b3f24c759eafe3e5fedd7fce76fe6f21cec815ffbaf4ec3ad801000000009b9a6b2d0001000000000000000000000000000000000000000000000000000000000000000460efd4405060ac0c200000000000000000000000000000000000000000004962635265636569766572010020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006368616e6e656c2d300c20"; + /* 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 @@ -19,11 +25,6 @@ const artifacts = [ "cw_token_bridge.wasm", "cw20_wrapped_2.wasm", "cw20_base.wasm", - "mock_bridge_integration_2.wasm", - "shutdown_core_bridge_cosmwasm.wasm", - "shutdown_token_bridge_cosmwasm.wasm", - "global_accountant.wasm", - "wormchain_ibc_receiver.wasm", "wormhole_ibc.wasm", ]; @@ -50,18 +51,6 @@ if (missing_artifacts.length) { 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({ @@ -208,6 +197,28 @@ addresses["mock.wasm"] = await instantiate( "mock" ); +addresses["wormhole_ibc.wasm"] = await instantiate( + "wormhole_ibc.wasm", + { + gov_chain: govChain, + gov_address: Buffer.from(govAddress, "hex").toString("base64"), + guardian_set_expirity: 86400, + initial_guardian_set: { + // This is using one guardian so the above registration can be hard-coded + // TODO: instantiate with the correct guardian set and dynamically generate the registration + addresses: [ + { + bytes: Buffer.from(init_guardians[0], "hex").toString("base64"), + }, + ], + expiration_time: 0, + }, + chain_id: 32, + fee_denom: "uluna", + }, + "wormholeIbc" +); + /* Registrations: tell the bridge contracts to know about each other */ const contract_registrations = { @@ -253,10 +264,49 @@ for (const [contract, registrations] of Object.entries( memo: "", }) .then((tx) => terra.tx.broadcast(tx)) - .then((rs) => console.log(rs)); + .then((rs) => console.log(rs)) + .catch((error) => { + if (error.response) { + // Request made and server responded + console.error( + error.response.data, + error.response.status, + error.response.headers + ); + } else if (error.request) { + // The request was made but no response was received + console.error(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.error("Error", error.message); + } + + throw new Error(`Registering chain failed: ${registration}`); + }); } } +// submit wormchain channel ID whitelist to the wormhole_ibc contract +const ibc_whitelist_tx = await wallet.createAndSignTx({ + msgs: [ + new MsgExecuteContract( + wallet.key.accAddress, + addresses["wormhole_ibc.wasm"], + { + submit_update_channel_chain: { + vaa: Buffer.from(WORMHOLE_IBC_WHITELIST_VAA, "hex").toString( + "base64" + ), + }, + }, + { uluna: 1000 } + ), + ], + memo: "", +}); +const ibc_whitelist_res = await terra.tx.broadcast(ibc_whitelist_tx); +console.log("updated wormhole_ibc channel whitelist", ibc_whitelist_res.txhash); + // Terra addresses are "human-readable", but for cross-chain registrations, we // want the "canonical" version function convert_terra_address_to_hex(human_addr) { diff --git a/devnet/ibc-relayer.yaml b/devnet/ibc-relayer.yaml index 269664162..8dc65d5af 100644 --- a/devnet/ibc-relayer.yaml +++ b/devnet/ibc-relayer.yaml @@ -37,7 +37,14 @@ spec: - link-then-start - terra-wormchain - --debug-addr - - localhost:7597 + - 0.0.0.0:7597 + - --src-port + - wasm.terra1436kxs0w2es6xlqpp9rd35e3d0cjnw4sv8j3a7483sgks29jqwgsnyey7t + - --dst-port + - wasm.wormhole1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq0kdhcj + - --version + - ibc-wormhole-v1 + - --override ports: - containerPort: 7597 name: rest @@ -48,4 +55,4 @@ spec: path: / periodSeconds: 1 restartPolicy: Always - serviceName: ibc-relayer \ No newline at end of file + serviceName: ibc-relayer diff --git a/sdk/js/src/bridge/__tests__/wormhole_ibc_e2e.ts b/sdk/js/src/bridge/__tests__/wormhole_ibc_e2e.ts new file mode 100644 index 000000000..fe72fa900 --- /dev/null +++ b/sdk/js/src/bridge/__tests__/wormhole_ibc_e2e.ts @@ -0,0 +1,84 @@ +import { describe, test } from "@jest/globals"; +import { + LCDClient, + MnemonicKey, + Msg, + MsgExecuteContract, + Wallet, + isTxError, +} from "@terra-money/terra.js"; +import { getEmitterAddressTerra, parseSequenceFromLogTerra } from "../.."; +import { + TERRA2_NODE_URL, + TERRA_CHAIN_ID, +} from "../../token_bridge/__tests__/utils/consts"; +import { + getSignedVAABySequence, + waitForTerraExecution, +} from "../../token_bridge/__tests__/utils/helpers"; +import { CHAIN_ID_SEI, CHAIN_ID_TERRA2 } from "../../utils/consts"; + +const TERRA2_PRIVATE_KEY_4 = + "bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty"; + +const lcd = new LCDClient({ + URL: TERRA2_NODE_URL, + chainID: TERRA_CHAIN_ID, +}); +const terraWallet = lcd.wallet( + new MnemonicKey({ mnemonic: TERRA2_PRIVATE_KEY_4 }) +); +const terraWalletAddress = terraWallet.key.accAddress; + +const terraBroadcastAndWaitForExecution = async ( + msgs: Msg[], + wallet: Wallet +) => { + const tx = await wallet.createAndSignTx({ + msgs, + }); + const txResult = await lcd.tx.broadcast(tx); + if (isTxError(txResult)) { + throw new Error("tx error"); + } + const txInfo = await waitForTerraExecution(txResult.txhash, lcd); + if (!txInfo) { + throw new Error("tx info not found"); + } + return txInfo; +}; + +const terraBroadcastTxAndGetSignedVaa = async ( + msgs: Msg[], + wallet: Wallet, + emitter: string +) => { + const txInfo = await terraBroadcastAndWaitForExecution(msgs, wallet); + const txSequence = parseSequenceFromLogTerra(txInfo); + if (!txSequence) { + throw new Error("tx sequence not found"); + } + console.log(`${CHAIN_ID_SEI}/${emitter}/${txSequence}`); + return await getSignedVAABySequence(CHAIN_ID_SEI, txSequence, emitter); +}; + +describe("IBC Watcher Integration Tests", () => { + test('Send a message from "Sei" (Terra2) via IBC', async () => { + const postMsg = new MsgExecuteContract( + terraWalletAddress, + "terra1436kxs0w2es6xlqpp9rd35e3d0cjnw4sv8j3a7483sgks29jqwgsnyey7t", + { + post_message: { + message: Buffer.from("Hello World").toString("base64"), + nonce: 1, + }, + } + ); + const postedVaa = await terraBroadcastTxAndGetSignedVaa( + [postMsg], + terraWallet, + await getEmitterAddressTerra(terraWalletAddress) + ); + console.log(postedVaa); + }); +}); diff --git a/testing/sdk.sh b/testing/sdk.sh index 1371b8569..51a3ab232 100644 --- a/testing/sdk.sh +++ b/testing/sdk.sh @@ -2,4 +2,5 @@ set -e while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' guardian:6060/readyz)" != "200" ]]; do sleep 5; done while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' spy:6060/metrics)" != "200" ]]; do sleep 5; done +while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ibc-relayer:7597/debug/pprof/)" != "200" ]]; do sleep 5; done CI=true npm --prefix ../sdk/js run test-ci diff --git a/wormchain/contracts/tools/deploy_wormchain.ts b/wormchain/contracts/tools/deploy_wormchain.ts index 8ea0c19c5..a73a2d1f3 100644 --- a/wormchain/contracts/tools/deploy_wormchain.ts +++ b/wormchain/contracts/tools/deploy_wormchain.ts @@ -1,17 +1,17 @@ import { - CHAIN_ID_WORMCHAIN, - hexToUint8Array, - Other, - Payload, - serialiseVAA, - sign, - VAA, + CHAIN_ID_WORMCHAIN, + hexToUint8Array, + Other, + Payload, + serialiseVAA, + sign, + VAA, } from "@certusone/wormhole-sdk"; import { toBinary } from "@cosmjs/cosmwasm-stargate"; -import { fromBase64, toUtf8 } from "@cosmjs/encoding"; +import { fromBase64, toUtf8, fromBech32 } from "@cosmjs/encoding"; import { - getWallet, - getWormchainSigningClient, + getWallet, + getWormchainSigningClient, } from "@wormhole-foundation/wormchain-sdk"; import { ZERO_FEE } from "@wormhole-foundation/wormchain-sdk/lib/core/consts"; import "dotenv/config"; @@ -23,9 +23,9 @@ import * as util from "util"; import * as devnetConsts from "./devnet-consts.json"; if (process.env.INIT_SIGNERS_KEYS_CSV === "undefined") { - let msg = `.env is missing. run "make contracts-tools-deps" to fetch.`; - console.error(msg); - throw msg; + let msg = `.env is missing. run "make contracts-tools-deps" to fetch.`; + console.error(msg); + throw msg; } const VAA_SIGNERS = process.env.INIT_SIGNERS_KEYS_CSV.split(","); @@ -40,236 +40,306 @@ const readFileAsync = util.promisify(fs.readFile); deterministic. */ type ContractName = string; -const artifacts: ContractName[] = ["global_accountant.wasm"]; +const artifacts: ContractName[] = [ + "global_accountant.wasm", + "wormchain_ibc_receiver.wasm", +]; const ARTIFACTS_PATH = "../artifacts/"; /* Check that the artifact folder contains all the wasm files we expect and nothing else */ try { - const actual_artifacts = readdirSync(ARTIFACTS_PATH).filter((a) => - a.endsWith(".wasm") - ); + const actual_artifacts = readdirSync(ARTIFACTS_PATH).filter((a) => + a.endsWith(".wasm") + ); - const missing_artifacts = artifacts.filter( - (a) => !actual_artifacts.includes(a) + const missing_artifacts = artifacts.filter( + (a) => !actual_artifacts.includes(a) + ); + if (missing_artifacts.length) { + console.log( + "Error during wormchain deployment. The following files are expected to be in the artifacts folder:" ); - if (missing_artifacts.length) { - console.log( - "Error during wormchain 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); - } -} catch (err) { - console.error( - `${ARTIFACTS_PATH} cannot be read. Do you need to run "make contracts-deploy-setup"?` + 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); + } +} catch (err) { + console.error( + `${ARTIFACTS_PATH} cannot be read. Do you need to run "make contracts-deploy-setup"?` + ); + process.exit(1); } async function main() { - /* Set up cosmos client & wallet */ + /* Set up cosmos client & wallet */ - let host = devnetConsts.chains[3104].tendermintUrlLocal; - if (os.hostname().includes("wormchain-deploy")) { - // running in tilt devnet - host = devnetConsts.chains[3104].tendermintUrlTilt; - } + let host = devnetConsts.chains[3104].tendermintUrlLocal; + if (os.hostname().includes("wormchain-deploy")) { + // running in tilt devnet + host = devnetConsts.chains[3104].tendermintUrlTilt; + } - const mnemonic = - devnetConsts.chains[3104].accounts.wormchainNodeOfGuardian0.mnemonic; + const mnemonic = + devnetConsts.chains[3104].accounts.wormchainNodeOfGuardian0.mnemonic; - const wallet = await getWallet(mnemonic); - const client = await getWormchainSigningClient(host, wallet); + const wallet = await getWallet(mnemonic); + const client = await getWormchainSigningClient(host, wallet); - // there are several Cosmos chains in devnet, so check the config is as expected - let id = await client.getChainId(); - if (id !== "wormchain") { - throw new Error( - `Wormchain CosmWasmClient connection produced an unexpected chainID: ${id}` - ); - } + // there are several Cosmos chains in devnet, so check the config is as expected + let id = await client.getChainId(); + if (id !== "wormchain") { + throw new Error( + `Wormchain CosmWasmClient connection produced an unexpected chainID: ${id}` + ); + } - const signers = await wallet.getAccounts(); - const signer = signers[0].address; - console.log("wormchain contract deployer is: ", signer); + const signers = await wallet.getAccounts(); + const signer = signers[0].address; + console.log("wormchain contract deployer is: ", signer); - /* Deploy artifacts */ + /* Deploy artifacts */ - const codeIds: { [name: ContractName]: number } = await artifacts.reduce( - async (prev, file) => { - // wait for the previous to finish, to avoid the race condition of wallet sequence mismatch. - const accum = await prev; + const codeIds: { [name: ContractName]: number } = await artifacts.reduce( + async (prev, file) => { + // wait for the previous to finish, to avoid the race condition of wallet sequence mismatch. + const accum = await prev; - const contract_bytes = await readFileAsync(`${ARTIFACTS_PATH}${file}`); + const contract_bytes = await readFileAsync(`${ARTIFACTS_PATH}${file}`); - const payload = keccak256(contract_bytes); - let vaa: VAA = { - version: 1, - guardianSetIndex: 0, - signatures: [], - timestamp: 0, - nonce: 0, - emitterChain: GOVERNANCE_CHAIN, - emitterAddress: GOVERNANCE_EMITTER, - sequence: BigInt(Math.floor(Math.random() * 100000000)), - consistencyLevel: 0, - payload: { - type: "Other", - hex: `0000000000000000000000000000000000000000005761736D644D6F64756C65010${CHAIN_ID_WORMCHAIN.toString( - 16 - )}${payload}`, - }, - }; - vaa.signatures = sign(VAA_SIGNERS, vaa as unknown as VAA); - console.log("uploading", file); - const msg = client.core.msgStoreCode({ - signer, - wasm_byte_code: new Uint8Array(contract_bytes), - vaa: hexToUint8Array(serialiseVAA(vaa as unknown as VAA)), - }); - const result = await client.signAndBroadcast(signer, [msg], { - ...ZERO_FEE, - gas: "10000000", - }); - const codeId = Number( - JSON.parse(result.rawLog)[0] - .events.find(({ type }) => type === "store_code") - .attributes.find(({ key }) => key === "code_id").value - ); - console.log( - `uploaded ${file}, codeID: ${codeId}, tx: ${result.transactionHash}` - ); - - accum[file] = codeId; - return accum; + const payload = keccak256(contract_bytes); + let vaa: VAA = { + version: 1, + guardianSetIndex: 0, + signatures: [], + timestamp: 0, + nonce: 0, + emitterChain: GOVERNANCE_CHAIN, + emitterAddress: GOVERNANCE_EMITTER, + sequence: BigInt(Math.floor(Math.random() * 100000000)), + consistencyLevel: 0, + payload: { + type: "Other", + hex: `0000000000000000000000000000000000000000005761736D644D6F64756C65010${CHAIN_ID_WORMCHAIN.toString( + 16 + )}${payload}`, }, - Object() - ); - - // Instantiate contracts. - - async function instantiate(code_id: number, inst_msg: any, label: string) { - const instMsgBinary = toBinary(inst_msg); - const instMsgBytes = fromBase64(instMsgBinary); - - // see /sdk/vaa/governance.go - const codeIdBuf = Buffer.alloc(8); - codeIdBuf.writeBigInt64BE(BigInt(code_id)); - const codeIdHash = keccak256(codeIdBuf); - const codeIdLabelHash = keccak256( - Buffer.concat([ - Buffer.from(codeIdHash, "hex"), - Buffer.from(label, "utf8"), - ]) - ); - const fullHash = keccak256( - Buffer.concat([Buffer.from(codeIdLabelHash, "hex"), instMsgBytes]) - ); - - console.log(fullHash); - - let vaa: VAA = { - version: 1, - guardianSetIndex: 0, - signatures: [], - timestamp: 0, - nonce: 0, - emitterChain: GOVERNANCE_CHAIN, - emitterAddress: GOVERNANCE_EMITTER, - sequence: BigInt(Math.floor(Math.random() * 100000000)), - consistencyLevel: 0, - payload: { - type: "Other", - hex: `0000000000000000000000000000000000000000005761736D644D6F64756C65020${CHAIN_ID_WORMCHAIN.toString( - 16 - )}${fullHash}`, - }, - }; - // TODO: check for number of guardians in set and use the corresponding keys - vaa.signatures = sign(VAA_SIGNERS, vaa as unknown as VAA); - const msg = client.core.msgInstantiateContract({ - signer, - code_id, - label, - msg: instMsgBytes, - vaa: hexToUint8Array(serialiseVAA(vaa as unknown as VAA)), - }); - const result = await client.signAndBroadcast(signer, [msg], { - ...ZERO_FEE, - gas: "10000000", - }); - const addr = JSON.parse(result.rawLog)[0] - .events.find(({ type }) => type === "instantiate") - .attributes.find(({ key }) => key === "_contract_address").value; - console.log( - `deployed contract ${label}, codeID: ${code_id}, address: ${addr}, txHash: ${result.transactionHash}` - ); - - return addr; - } - - // Instantiate contracts. - // NOTE: Only append at the end, the ordering must be deterministic. - - const addresses: { - [contractName: string]: string; - } = {}; - - const registrations: { [chainName: string]: string } = { - // keys are only used for logging success/failure - solana: String(process.env.REGISTER_SOL_TOKEN_BRIDGE_VAA), - ethereum: String(process.env.REGISTER_ETH_TOKEN_BRIDGE_VAA), - bsc: String(process.env.REGISTER_BSC_TOKEN_BRIDGE_VAA), - algo: String(process.env.REGISTER_ALGO_TOKEN_BRIDGE_VAA), - terra: String(process.env.REGISTER_TERRA_TOKEN_BRIDGE_VAA), - near: String(process.env.REGISTER_NEAR_TOKEN_BRIDGE_VAA), - terra2: String(process.env.REGISTER_TERRA2_TOKEN_BRIDGE_VAA), - aptos: String(process.env.REGISTER_APTOS_TOKEN_BRIDGE_VAA), - sui: String(process.env.REGISTER_SUI_TOKEN_BRIDGE_VAA), - }; - - const instantiateMsg = {}; - addresses["global_accountant.wasm"] = await instantiate( - codeIds["global_accountant.wasm"], - instantiateMsg, - "wormchainAccounting" - ); - console.log("instantiated accounting: ", addresses["global_accountant.wasm"]); - - const accountingRegistrations = Object.values(registrations).map((r) => - Buffer.from(r, "hex").toString("base64") - ); - const msg = client.wasm.msgExecuteContract({ - sender: signer, - contract: addresses["global_accountant.wasm"], - msg: toUtf8( - JSON.stringify({ - submit_vaas: { - vaas: accountingRegistrations, - }, - }) - ), - funds: [], - }); - const res = await client.signAndBroadcast(signer, [msg], { + }; + vaa.signatures = sign(VAA_SIGNERS, vaa as unknown as VAA); + console.log("uploading", file); + const msg = client.core.msgStoreCode({ + signer, + wasm_byte_code: new Uint8Array(contract_bytes), + vaa: hexToUint8Array(serialiseVAA(vaa as unknown as VAA)), + }); + const result = await client.signAndBroadcast(signer, [msg], { ...ZERO_FEE, gas: "10000000", + }); + const codeId = Number( + JSON.parse(result.rawLog)[0] + .events.find(({ type }) => type === "store_code") + .attributes.find(({ key }) => key === "code_id").value + ); + console.log( + `uploaded ${file}, codeID: ${codeId}, tx: ${result.transactionHash}` + ); + + accum[file] = codeId; + return accum; + }, + Object() + ); + + // Instantiate contracts. + + async function instantiate(code_id: number, inst_msg: any, label: string) { + const instMsgBinary = toBinary(inst_msg); + const instMsgBytes = fromBase64(instMsgBinary); + + // see /sdk/vaa/governance.go + const codeIdBuf = Buffer.alloc(8); + codeIdBuf.writeBigInt64BE(BigInt(code_id)); + const codeIdHash = keccak256(codeIdBuf); + const codeIdLabelHash = keccak256( + Buffer.concat([ + Buffer.from(codeIdHash, "hex"), + Buffer.from(label, "utf8"), + ]) + ); + const fullHash = keccak256( + Buffer.concat([Buffer.from(codeIdLabelHash, "hex"), instMsgBytes]) + ); + + console.log(fullHash); + + let vaa: VAA = { + version: 1, + guardianSetIndex: 0, + signatures: [], + timestamp: 0, + nonce: 0, + emitterChain: GOVERNANCE_CHAIN, + emitterAddress: GOVERNANCE_EMITTER, + sequence: BigInt(Math.floor(Math.random() * 100000000)), + consistencyLevel: 0, + payload: { + type: "Other", + hex: `0000000000000000000000000000000000000000005761736D644D6F64756C65020${CHAIN_ID_WORMCHAIN.toString( + 16 + )}${fullHash}`, + }, + }; + // TODO: check for number of guardians in set and use the corresponding keys + vaa.signatures = sign(VAA_SIGNERS, vaa as unknown as VAA); + const msg = client.core.msgInstantiateContract({ + signer, + code_id, + label, + msg: instMsgBytes, + vaa: hexToUint8Array(serialiseVAA(vaa as unknown as VAA)), }); - console.log(`sent accounting chain registrations, tx: `, res.transactionHash); + const result = await client.signAndBroadcast(signer, [msg], { + ...ZERO_FEE, + gas: "10000000", + }); + console.log("contract instantiation msg: ", msg); + console.log("contract instantiation result: ", result); + const addr = JSON.parse(result.rawLog)[0] + .events.find(({ type }) => type === "instantiate") + .attributes.find(({ key }) => key === "_contract_address").value; + console.log( + `deployed contract ${label}, codeID: ${code_id}, address: ${addr}, txHash: ${result.transactionHash}` + ); + + return addr; + } + + // Instantiate contracts. + // NOTE: Only append at the end, the ordering must be deterministic. + + const addresses: { + [contractName: string]: string; + } = {}; + + const registrations: { [chainName: string]: string } = { + // keys are only used for logging success/failure + solana: String(process.env.REGISTER_SOL_TOKEN_BRIDGE_VAA), + ethereum: String(process.env.REGISTER_ETH_TOKEN_BRIDGE_VAA), + bsc: String(process.env.REGISTER_BSC_TOKEN_BRIDGE_VAA), + algo: String(process.env.REGISTER_ALGO_TOKEN_BRIDGE_VAA), + terra: String(process.env.REGISTER_TERRA_TOKEN_BRIDGE_VAA), + near: String(process.env.REGISTER_NEAR_TOKEN_BRIDGE_VAA), + terra2: String(process.env.REGISTER_TERRA2_TOKEN_BRIDGE_VAA), + aptos: String(process.env.REGISTER_APTOS_TOKEN_BRIDGE_VAA), + sui: String(process.env.REGISTER_SUI_TOKEN_BRIDGE_VAA), + }; + + const instantiateMsg = {}; + addresses["global_accountant.wasm"] = await instantiate( + codeIds["global_accountant.wasm"], + instantiateMsg, + "wormchainAccounting" + ); + console.log("instantiated accounting: ", addresses["global_accountant.wasm"]); + + const accountingRegistrations = Object.values(registrations).map((r) => + Buffer.from(r, "hex").toString("base64") + ); + const msg = client.wasm.msgExecuteContract({ + sender: signer, + contract: addresses["global_accountant.wasm"], + msg: toUtf8( + JSON.stringify({ + submit_vaas: { + vaas: accountingRegistrations, + }, + }) + ), + funds: [], + }); + const res = await client.signAndBroadcast(signer, [msg], { + ...ZERO_FEE, + gas: "10000000", + }); + console.log(`sent accounting chain registrations, tx: `, res.transactionHash); + + const wormchainIbcReceiverInstantiateMsg = {}; + addresses["wormchain_ibc_receiver.wasm"] = await instantiate( + codeIds["wormchain_ibc_receiver.wasm"], + wormchainIbcReceiverInstantiateMsg, + "wormchainIbcReceiver" + ); + console.log( + "instantiated wormchain ibc receiver contract: ", + addresses["wormchain_ibc_receiver.wasm"] + ); + + // Generated VAA using + // `guardiand template ibc-receiver-update-channel-chain --channel-id channel-0 --chain-id 32 --target-chain-id 3104 > wormchain.prototxt` + // `guardiand admin governance-vaa-verify wormchain.prototxt` + let wormchainIbcReceiverWhitelistVaa: VAA = { + version: 1, + guardianSetIndex: 0, + signatures: [], + timestamp: 0, + nonce: 0, + emitterChain: GOVERNANCE_CHAIN, + emitterAddress: GOVERNANCE_EMITTER, + sequence: BigInt(Math.floor(Math.random() * 100000000)), + consistencyLevel: 0, + payload: { + type: "Other", + hex: `0000000000000000000000000000000000000000004962635265636569766572010c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006368616e6e656c2d300020`, + }, + }; + wormchainIbcReceiverWhitelistVaa.signatures = sign( + VAA_SIGNERS, + wormchainIbcReceiverWhitelistVaa as unknown as VAA + ); + const wormchainIbcReceiverUpdateWhitelistMsg = { + submit_update_channel_chain: { + vaas: [ + Buffer.from( + serialiseVAA( + wormchainIbcReceiverWhitelistVaa as unknown as VAA + ), + "hex" + ).toString("base64"), + ], + }, + }; + const executeMsg = client.wasm.msgExecuteContract({ + sender: signer, + contract: addresses["wormchain_ibc_receiver.wasm"], + msg: toUtf8(JSON.stringify(wormchainIbcReceiverUpdateWhitelistMsg)), + funds: [], + }); + const updateIbcWhitelistRes = await client.signAndBroadcast( + signer, + [executeMsg], + { + ...ZERO_FEE, + gas: "10000000", + } + ); + console.log( + "updated wormchain_ibc_receiver whitelist: ", + updateIbcWhitelistRes.transactionHash, + updateIbcWhitelistRes.code + ); } try { - main(); + main(); } catch (e: any) { - if (e?.message) { - console.error(e.message); - } - throw e; + if (e?.message) { + console.error(e.message); + } + throw e; } diff --git a/wormchain/ts-sdk/package-lock.json b/wormchain/ts-sdk/package-lock.json index b5e9caef3..b6136b5ae 100644 --- a/wormchain/ts-sdk/package-lock.json +++ b/wormchain/ts-sdk/package-lock.json @@ -1,12 +1,12 @@ { - "name": "wormhole-chain-sdk", - "version": "0.0.0", + "name": "@wormhole-foundation/wormchain-sdk", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "wormhole-chain-sdk", - "version": "0.0.0", + "name": "@wormhole-foundation/wormchain-sdk", + "version": "0.0.1", "license": "ISC", "dependencies": { "@certusone/wormhole-sdk": "^0.2.0",