From d3527755884dcad048ebb654181aeba2bdc1580f Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 16:51:43 -0400 Subject: [PATCH 01/10] refactor(orchestration): rename `makeICAConnectionAddress` -> `makeICAChannelAddress --- packages/orchestration/src/service.js | 4 ++-- packages/orchestration/src/utils/address.js | 2 +- .../orchestration/test/utils/address.test.js | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index 58baaa3a63a..7ac940699b7 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -7,7 +7,7 @@ import '@agoric/network/exported.js'; import { V as E } from '@agoric/vat-data/vow.js'; import { M } from '@endo/patterns'; import { prepareChainAccountKit } from './exos/chainAccountKit.js'; -import { makeICAConnectionAddress } from './utils/address.js'; +import { makeICAChannelAddress } from './utils/address.js'; /** * @import { PortAllocator} from '@agoric/network'; @@ -92,7 +92,7 @@ const prepareOrchestration = (zone, makeChainAccountKit) => async makeAccount(hostConnectionId, controllerConnectionId) { const port = await this.facets.self.bindPort(); - const remoteConnAddr = makeICAConnectionAddress( + const remoteConnAddr = makeICAChannelAddress( hostConnectionId, controllerConnectionId, ); diff --git a/packages/orchestration/src/utils/address.js b/packages/orchestration/src/utils/address.js index 082362d6485..bf0307d80d7 100644 --- a/packages/orchestration/src/utils/address.js +++ b/packages/orchestration/src/utils/address.js @@ -15,7 +15,7 @@ import { Fail } from '@agoric/assert'; * @param {string} [opts.txType] - default is `sdk_multi_msg` * @param {string} [opts.version] - default is `ics27-1` */ -export const makeICAConnectionAddress = ( +export const makeICAChannelAddress = ( hostConnectionId, controllerConnectionId, { diff --git a/packages/orchestration/test/utils/address.test.js b/packages/orchestration/test/utils/address.test.js index 9f30dfeb221..80b13e1b7b5 100644 --- a/packages/orchestration/test/utils/address.test.js +++ b/packages/orchestration/test/utils/address.test.js @@ -1,37 +1,37 @@ import test from '@endo/ses-ava/prepare-endo.js'; import { - makeICAConnectionAddress, + makeICAChannelAddress, parseAddress, } from '../../src/utils/address.js'; -test('makeICAConnectionAddress', t => { - t.throws(() => makeICAConnectionAddress(), { +test('makeICAChannelAddress', t => { + t.throws(() => makeICAChannelAddress(), { message: 'hostConnectionId is required', }); - t.throws(() => makeICAConnectionAddress('connection-0'), { + t.throws(() => makeICAChannelAddress('connection-0'), { message: 'controllerConnectionId is required', }); t.is( - makeICAConnectionAddress('connection-1', 'connection-0'), + makeICAChannelAddress('connection-1', 'connection-0'), '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}', 'returns connection string when controllerConnectionId and hostConnectionId are provided', ); t.is( - makeICAConnectionAddress('connection-1', 'connection-0', { + makeICAChannelAddress('connection-1', 'connection-0', { version: 'ics27-0', }), '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-0","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}', 'accepts custom version', ); t.is( - makeICAConnectionAddress('connection-1', 'connection-0', { + makeICAChannelAddress('connection-1', 'connection-0', { encoding: 'test', }), '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"test","txType":"sdk_multi_msg"}', 'accepts custom encoding', ); t.is( - makeICAConnectionAddress('connection-1', 'connection-0', { + makeICAChannelAddress('connection-1', 'connection-0', { ordering: 'unordered', }), '/ibc-hop/connection-0/ibc-port/icahost/unordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}', From 78179862b69e8c0101c13f75104cc7f141e22bf5 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 16:53:41 -0400 Subject: [PATCH 02/10] refactor(orchestration): rename parseAddress -> findAddressField --- packages/orchestration/src/exos/chainAccountKit.js | 6 +++--- packages/orchestration/src/utils/address.js | 9 +++++---- packages/orchestration/test/utils/address.test.js | 12 ++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js index 1013b21f769..900950124ba 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -10,7 +10,7 @@ import { V as E } from '@agoric/vat-data/vow.js'; import { M } from '@endo/patterns'; import { PaymentShape, PurseShape } from '@agoric/ertp'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; -import { parseAddress } from '../utils/address.js'; +import { findAddressField } from '../utils/address.js'; import { makeTxPacket, parsePacketAck } from '../utils/tx.js'; /** @@ -175,9 +175,9 @@ export const prepareChainAccountKit = zone => this.state.connection = connection; this.state.remoteAddress = remoteAddr; this.state.localAddress = localAddr; - // XXX parseAddress currently throws, should it return '' instead? + // XXX findAddressField currently throws, should it return '' instead? this.state.chainAddress = harden({ - address: parseAddress(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, + address: findAddressField(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, // TODO get this from `Chain` object #9063 // XXX how do we get a chainId for an unknown chain? seems it may need to be a user supplied arg chainId: 'FIXME', diff --git a/packages/orchestration/src/utils/address.js b/packages/orchestration/src/utils/address.js index bf0307d80d7..a7abd969524 100644 --- a/packages/orchestration/src/utils/address.js +++ b/packages/orchestration/src/utils/address.js @@ -3,6 +3,7 @@ import { Fail } from '@agoric/assert'; /** * @import { IBCConnectionID } from '@agoric/vats'; + * @import { RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; * @import { ChainAddress, CosmosValidatorAddress } from '../types.js'; */ @@ -42,10 +43,10 @@ export const makeICAChannelAddress = ( * Parse a chain address from a remote address string. * Assumes the address string is in a JSON format and contains an "address" field. * This function is designed to be safe against malformed inputs and unexpected data types, and will return `undefined` in those cases. - * @param {string} remoteAddressString - remote address string, including version - * @returns {string | undefined} returns undefined on error + * @param {RemoteIbcAddress} remoteAddressString - remote address string, including version + * @returns {ChainAddress['address'] | undefined} returns undefined on error */ -export const parseAddress = remoteAddressString => { +export const findAddressField = remoteAddressString => { try { // Extract JSON version string assuming it's always surrounded by {} const jsonStr = remoteAddressString?.match(/{.*?}/)?.[0]; @@ -55,4 +56,4 @@ export const parseAddress = remoteAddressString => { return undefined; } }; -harden(parseAddress); +harden(findAddressField); diff --git a/packages/orchestration/test/utils/address.test.js b/packages/orchestration/test/utils/address.test.js index 80b13e1b7b5..618ee16e407 100644 --- a/packages/orchestration/test/utils/address.test.js +++ b/packages/orchestration/test/utils/address.test.js @@ -1,7 +1,7 @@ import test from '@endo/ses-ava/prepare-endo.js'; import { makeICAChannelAddress, - parseAddress, + findAddressField, } from '../../src/utils/address.js'; test('makeICAChannelAddress', t => { @@ -39,28 +39,28 @@ test('makeICAChannelAddress', t => { ); }); -test('parseAddress', t => { +test('findAddressField', t => { t.is( - parseAddress('/ibc-hop/'), + findAddressField('/ibc-hop/'), undefined, 'returns undefined when version json is missing', ); t.is( - parseAddress( + findAddressField( '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"","encoding":"proto3","txType":"sdk_multi_msg"}', ), '', 'returns empty string if address is an empty string', ); t.is( - parseAddress( + findAddressField( '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-0","hostConnectionId":"connection-1","address":"osmo1m30khedzqy9msu4502u74ugmep30v69pzee370jkas57xhmjfgjqe67ayq","encoding":"proto3","txType":"sdk_multi_msg"}', ), 'osmo1m30khedzqy9msu4502u74ugmep30v69pzee370jkas57xhmjfgjqe67ayq', 'returns address', ); t.is( - parseAddress( + findAddressField( '/ibc-hop/connection-0/ibc-port/icahost/ordered/{"version":"ics27-1","controller_connection_id":"connection-0","host_connection_id":"connection-1","address":"osmo1m30khedzqy9msu4502u74ugmep30v69pzee370jkas57xhmjfgjqe67ayq","encoding":"proto3","tx_type":"sdk_multi_msg"}/ibc-channel/channel-1', ), 'osmo1m30khedzqy9msu4502u74ugmep30v69pzee370jkas57xhmjfgjqe67ayq', From 8d61c3f7362a2ecce50127c947d18a25974d449f Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 16:55:17 -0400 Subject: [PATCH 03/10] refactor(orchestration): rename parsePacketAck -> parseTxPacket --- packages/orchestration/index.js | 2 +- packages/orchestration/src/exos/chainAccountKit.js | 6 +++--- packages/orchestration/src/utils/{tx.js => packet.js} | 4 ++-- .../test/utils/{tx.test.js => packet.test.js} | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) rename packages/orchestration/src/utils/{tx.js => packet.js} (95%) rename packages/orchestration/test/utils/{tx.test.js => packet.test.js} (89%) diff --git a/packages/orchestration/index.js b/packages/orchestration/index.js index 4a1fef1666a..29a434ab341 100644 --- a/packages/orchestration/index.js +++ b/packages/orchestration/index.js @@ -1,5 +1,5 @@ // eslint-disable-next-line import/export export * from './src/types.js'; export * from './src/utils/address.js'; -export * from './src/utils/tx.js'; +export * from './src/utils/packet.js'; export * from './src/service.js'; diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js index 900950124ba..2ec5178aea8 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -11,7 +11,7 @@ import { M } from '@endo/patterns'; import { PaymentShape, PurseShape } from '@agoric/ertp'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { findAddressField } from '../utils/address.js'; -import { makeTxPacket, parsePacketAck } from '../utils/tx.js'; +import { makeTxPacket, parseTxPacket } from '../utils/packet.js'; /** * @import { Connection, Port } from '@agoric/network'; @@ -131,8 +131,8 @@ export const prepareChainAccountKit = zone => if (!connection) throw Fail`connection not available`; return E.when( E(connection).send(makeTxPacket(msgs, opts)), - // if parsePacketAck cannot find a `result` key, it throws - ack => parsePacketAck(ack), + // if parseTxPacket cannot find a `result` key, it throws + ack => parseTxPacket(ack), ); }, /** diff --git a/packages/orchestration/src/utils/tx.js b/packages/orchestration/src/utils/packet.js similarity index 95% rename from packages/orchestration/src/utils/tx.js rename to packages/orchestration/src/utils/packet.js index bd74fd15bcc..9d75f186b10 100644 --- a/packages/orchestration/src/utils/tx.js +++ b/packages/orchestration/src/utils/packet.js @@ -39,10 +39,10 @@ harden(makeTxPacket); * @returns {string} - base64 encoded bytes string * @throws {Error} if error key is detected in response string, or result key is not found */ -export function parsePacketAck(response) { +export function parseTxPacket(response) { const { result, error } = JSON.parse(response); if (result) return result; else if (error) throw Error(error); else throw Error(response); } -harden(parsePacketAck); +harden(parseTxPacket); diff --git a/packages/orchestration/test/utils/tx.test.js b/packages/orchestration/test/utils/packet.test.js similarity index 89% rename from packages/orchestration/test/utils/tx.test.js rename to packages/orchestration/test/utils/packet.test.js index 1b52310246f..76dae957585 100644 --- a/packages/orchestration/test/utils/tx.test.js +++ b/packages/orchestration/test/utils/packet.test.js @@ -1,6 +1,6 @@ import test from '@endo/ses-ava/prepare-endo.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import { makeTxPacket, parsePacketAck } from '../../src/utils/tx.js'; +import { makeTxPacket, parseTxPacket } from '../../src/utils/packet.js'; test('makeTxPacket', t => { const mockMsg = { @@ -49,16 +49,16 @@ test('txToBase64', t => { ); }); -test('parsePacketAck', t => { +test('parseTxPacket', t => { t.is( - parsePacketAck( + parseTxPacket( `{"result":"Ei0KKy9jb3Ntb3Muc3Rha2luZy52MWJldGExLk1zZ0RlbGVnYXRlUmVzcG9uc2U="}`, ), 'Ei0KKy9jb3Ntb3Muc3Rha2luZy52MWJldGExLk1zZ0RlbGVnYXRlUmVzcG9uc2U=', ); t.throws( () => - parsePacketAck( + parseTxPacket( `{"error":"ABCI code: 5: error handling packet: see events for details"}`, ), { @@ -66,7 +66,7 @@ test('parsePacketAck', t => { }, ); t.throws( - () => parsePacketAck('{"foo":"bar"}'), + () => parseTxPacket('{"foo":"bar"}'), { message: '{"foo":"bar"}', }, From 910e038069d01b333b5a536ea27f734cd64b3eae Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 17:00:27 -0400 Subject: [PATCH 04/10] feat(cosmic-proto): add icq/v1 proto + codegen for @agoric/orchestration - sourced from: https://github.com/cosmos/ibc-apps/tree/18248c35e913b3ccba99a2756612b1fcb3370a0d/modules/async-icq/proto/icq/v1 - refs: #9072 --- .../interchain_accounts/README.md | 1 + packages/cosmic-proto/proto/icq/README.md | 3 + .../cosmic-proto/proto/icq/v1/genesis.proto | 14 + packages/cosmic-proto/proto/icq/v1/icq.proto | 15 + .../cosmic-proto/proto/icq/v1/packet.proto | 32 ++ .../cosmic-proto/proto/icq/v1/query.proto | 25 ++ packages/cosmic-proto/proto/icq/v1/tx.proto | 41 ++ .../cosmic-proto/src/codegen/icq/bundle.ts | 15 + .../src/codegen/icq/v1/genesis.ts | 94 +++++ .../cosmic-proto/src/codegen/icq/v1/icq.ts | 100 +++++ .../cosmic-proto/src/codegen/icq/v1/packet.ts | 364 ++++++++++++++++++ .../cosmic-proto/src/codegen/icq/v1/query.ts | 147 +++++++ .../cosmic-proto/src/codegen/icq/v1/tx.ts | 183 +++++++++ .../cosmic-proto/src/codegen/ics23/bundle.ts | 4 +- packages/cosmic-proto/src/codegen/index.ts | 1 + .../src/codegen/tendermint/bundle.ts | 44 +-- .../test/snapshots/test-exports.js.md | 1 + .../test/snapshots/test-exports.js.snap | Bin 1196 -> 1201 bytes 18 files changed, 1060 insertions(+), 24 deletions(-) create mode 100644 packages/cosmic-proto/proto/ibc/applications/interchain_accounts/README.md create mode 100644 packages/cosmic-proto/proto/icq/README.md create mode 100644 packages/cosmic-proto/proto/icq/v1/genesis.proto create mode 100644 packages/cosmic-proto/proto/icq/v1/icq.proto create mode 100644 packages/cosmic-proto/proto/icq/v1/packet.proto create mode 100644 packages/cosmic-proto/proto/icq/v1/query.proto create mode 100644 packages/cosmic-proto/proto/icq/v1/tx.proto create mode 100644 packages/cosmic-proto/src/codegen/icq/bundle.ts create mode 100644 packages/cosmic-proto/src/codegen/icq/v1/genesis.ts create mode 100644 packages/cosmic-proto/src/codegen/icq/v1/icq.ts create mode 100644 packages/cosmic-proto/src/codegen/icq/v1/packet.ts create mode 100644 packages/cosmic-proto/src/codegen/icq/v1/query.ts create mode 100644 packages/cosmic-proto/src/codegen/icq/v1/tx.ts diff --git a/packages/cosmic-proto/proto/ibc/applications/interchain_accounts/README.md b/packages/cosmic-proto/proto/ibc/applications/interchain_accounts/README.md new file mode 100644 index 00000000000..a197f83216f --- /dev/null +++ b/packages/cosmic-proto/proto/ibc/applications/interchain_accounts/README.md @@ -0,0 +1 @@ +# interchain accounts diff --git a/packages/cosmic-proto/proto/icq/README.md b/packages/cosmic-proto/proto/icq/README.md new file mode 100644 index 00000000000..85789a7a96d --- /dev/null +++ b/packages/cosmic-proto/proto/icq/README.md @@ -0,0 +1,3 @@ +# icq (interchain queries) + +These protos were sourced from: [cosmos/ibc-apps/async-icq/v1#18248c3](https://github.com/cosmos/ibc-apps/tree/18248c35e913b3ccba99a2756612b1fcb3370a0d/modules/async-icq/proto/icq/v1) diff --git a/packages/cosmic-proto/proto/icq/v1/genesis.proto b/packages/cosmic-proto/proto/icq/v1/genesis.proto new file mode 100644 index 00000000000..d8a8fe055f1 --- /dev/null +++ b/packages/cosmic-proto/proto/icq/v1/genesis.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package icq.v1; + +option go_package = "github.com/cosmos/ibc-apps/modules/async-icq/v8/types"; + +import "gogoproto/gogo.proto"; +import "icq/v1/icq.proto"; + +// GenesisState defines the interchain query genesis state +message GenesisState { + string host_port = 1; + Params params = 4 [(gogoproto.nullable) = false]; +} \ No newline at end of file diff --git a/packages/cosmic-proto/proto/icq/v1/icq.proto b/packages/cosmic-proto/proto/icq/v1/icq.proto new file mode 100644 index 00000000000..3b63f05f63d --- /dev/null +++ b/packages/cosmic-proto/proto/icq/v1/icq.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package icq.v1; + +option go_package = "github.com/cosmos/ibc-apps/modules/async-icq/v8/types"; + +import "gogoproto/gogo.proto"; + +// Params defines the set of on-chain interchain query parameters. +message Params { + // host_enabled enables or disables the host submodule. + bool host_enabled = 2 [(gogoproto.moretags) = "yaml:\"host_enabled\""]; + // allow_queries defines a list of query paths allowed to be queried on a host chain. + repeated string allow_queries = 3 [(gogoproto.moretags) = "yaml:\"allow_queries\""]; +} diff --git a/packages/cosmic-proto/proto/icq/v1/packet.proto b/packages/cosmic-proto/proto/icq/v1/packet.proto new file mode 100644 index 00000000000..f16d8e76d8c --- /dev/null +++ b/packages/cosmic-proto/proto/icq/v1/packet.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package icq.v1; + +import "gogoproto/gogo.proto"; +import "tendermint/abci/types.proto"; + +option go_package = "github.com/cosmos/ibc-apps/modules/async-icq/v8/types"; + + + +// InterchainQueryPacketData is comprised of raw query. +message InterchainQueryPacketData { + bytes data = 1; + // optional memo + string memo = 2; +} + +// InterchainQueryPacketAck is comprised of an ABCI query response with non-deterministic fields left empty (e.g. Codespace, Log, Info and ...). +message InterchainQueryPacketAck { + bytes data = 1; +} + +// CosmosQuery contains a list of tendermint ABCI query requests. It should be used when sending queries to an SDK host chain. +message CosmosQuery { + repeated tendermint.abci.RequestQuery requests = 1 [(gogoproto.nullable) = false]; +} + +// CosmosResponse contains a list of tendermint ABCI query responses. It should be used when receiving responses from an SDK host chain. +message CosmosResponse { + repeated tendermint.abci.ResponseQuery responses = 1 [(gogoproto.nullable) = false]; +} \ No newline at end of file diff --git a/packages/cosmic-proto/proto/icq/v1/query.proto b/packages/cosmic-proto/proto/icq/v1/query.proto new file mode 100644 index 00000000000..f32dbd9aefd --- /dev/null +++ b/packages/cosmic-proto/proto/icq/v1/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package icq.v1; + +option go_package = "github.com/cosmos/ibc-apps/modules/async-icq/v8/types"; + +import "google/api/annotations.proto"; +import "icq/v1/icq.proto"; + +// Query provides defines the gRPC querier service. +service Query { + // Params queries all parameters of the ICQ module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/async-icq/v1/params"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} \ No newline at end of file diff --git a/packages/cosmic-proto/proto/icq/v1/tx.proto b/packages/cosmic-proto/proto/icq/v1/tx.proto new file mode 100644 index 00000000000..a618b4b4373 --- /dev/null +++ b/packages/cosmic-proto/proto/icq/v1/tx.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package icq.v1; + +import "cosmos/msg/v1/msg.proto"; +import "icq/v1/icq.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/cosmos/ibc-apps/modules/async-icq/v8/types"; + +// Msg defines the Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // UpdateParams defines a governance operation for updating the x/async-icq module + // parameters. The authority is hard-coded to the x/gov module account. + // + // Since: cosmos-sdk 0.47 + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgUpdateParams is the Msg/UpdateParams request type. +// +// Since: cosmos-sdk 0.47 +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority is the address of the governance account. + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the x/async-icq parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +// +// Since: cosmos-sdk 0.47 +message MsgUpdateParamsResponse {} \ No newline at end of file diff --git a/packages/cosmic-proto/src/codegen/icq/bundle.ts b/packages/cosmic-proto/src/codegen/icq/bundle.ts new file mode 100644 index 00000000000..262be354b2f --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/bundle.ts @@ -0,0 +1,15 @@ +//@ts-nocheck +import * as _118 from './v1/genesis.js'; +import * as _119 from './v1/icq.js'; +import * as _120 from './v1/packet.js'; +import * as _121 from './v1/query.js'; +import * as _122 from './v1/tx.js'; +export namespace icq { + export const v1 = { + ..._118, + ..._119, + ..._120, + ..._121, + ..._122, + }; +} diff --git a/packages/cosmic-proto/src/codegen/icq/v1/genesis.ts b/packages/cosmic-proto/src/codegen/icq/v1/genesis.ts new file mode 100644 index 00000000000..c110f465921 --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/v1/genesis.ts @@ -0,0 +1,94 @@ +//@ts-nocheck +import { Params, ParamsSDKType } from './icq.js'; +import { BinaryReader, BinaryWriter } from '../../binary.js'; +import { isSet } from '../../helpers.js'; +/** GenesisState defines the interchain query genesis state */ +export interface GenesisState { + hostPort: string; + params: Params; +} +export interface GenesisStateProtoMsg { + typeUrl: '/icq.v1.GenesisState'; + value: Uint8Array; +} +/** GenesisState defines the interchain query genesis state */ +export interface GenesisStateSDKType { + host_port: string; + params: ParamsSDKType; +} +function createBaseGenesisState(): GenesisState { + return { + hostPort: '', + params: Params.fromPartial({}), + }; +} +export const GenesisState = { + typeUrl: '/icq.v1.GenesisState', + encode( + message: GenesisState, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.hostPort !== '') { + writer.uint32(10).string(message.hostPort); + } + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(34).fork()).ldelim(); + } + return writer; + }, + decode(input: BinaryReader | Uint8Array, length?: number): GenesisState { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGenesisState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.hostPort = reader.string(); + break; + case 4: + message.params = Params.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): GenesisState { + return { + hostPort: isSet(object.hostPort) ? String(object.hostPort) : '', + params: isSet(object.params) ? Params.fromJSON(object.params) : undefined, + }; + }, + toJSON(message: GenesisState): unknown { + const obj: any = {}; + message.hostPort !== undefined && (obj.hostPort = message.hostPort); + message.params !== undefined && + (obj.params = message.params ? Params.toJSON(message.params) : undefined); + return obj; + }, + fromPartial(object: Partial): GenesisState { + const message = createBaseGenesisState(); + message.hostPort = object.hostPort ?? ''; + message.params = + object.params !== undefined && object.params !== null + ? Params.fromPartial(object.params) + : undefined; + return message; + }, + fromProtoMsg(message: GenesisStateProtoMsg): GenesisState { + return GenesisState.decode(message.value); + }, + toProto(message: GenesisState): Uint8Array { + return GenesisState.encode(message).finish(); + }, + toProtoMsg(message: GenesisState): GenesisStateProtoMsg { + return { + typeUrl: '/icq.v1.GenesisState', + value: GenesisState.encode(message).finish(), + }; + }, +}; diff --git a/packages/cosmic-proto/src/codegen/icq/v1/icq.ts b/packages/cosmic-proto/src/codegen/icq/v1/icq.ts new file mode 100644 index 00000000000..200842b91cc --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/v1/icq.ts @@ -0,0 +1,100 @@ +//@ts-nocheck +import { BinaryReader, BinaryWriter } from '../../binary.js'; +import { isSet } from '../../helpers.js'; +/** Params defines the set of on-chain interchain query parameters. */ +export interface Params { + /** host_enabled enables or disables the host submodule. */ + hostEnabled: boolean; + /** allow_queries defines a list of query paths allowed to be queried on a host chain. */ + allowQueries: string[]; +} +export interface ParamsProtoMsg { + typeUrl: '/icq.v1.Params'; + value: Uint8Array; +} +/** Params defines the set of on-chain interchain query parameters. */ +export interface ParamsSDKType { + host_enabled: boolean; + allow_queries: string[]; +} +function createBaseParams(): Params { + return { + hostEnabled: false, + allowQueries: [], + }; +} +export const Params = { + typeUrl: '/icq.v1.Params', + encode( + message: Params, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.hostEnabled === true) { + writer.uint32(16).bool(message.hostEnabled); + } + for (const v of message.allowQueries) { + writer.uint32(26).string(v!); + } + return writer; + }, + decode(input: BinaryReader | Uint8Array, length?: number): Params { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseParams(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 2: + message.hostEnabled = reader.bool(); + break; + case 3: + message.allowQueries.push(reader.string()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): Params { + return { + hostEnabled: isSet(object.hostEnabled) + ? Boolean(object.hostEnabled) + : false, + allowQueries: Array.isArray(object?.allowQueries) + ? object.allowQueries.map((e: any) => String(e)) + : [], + }; + }, + toJSON(message: Params): unknown { + const obj: any = {}; + message.hostEnabled !== undefined && + (obj.hostEnabled = message.hostEnabled); + if (message.allowQueries) { + obj.allowQueries = message.allowQueries.map(e => e); + } else { + obj.allowQueries = []; + } + return obj; + }, + fromPartial(object: Partial): Params { + const message = createBaseParams(); + message.hostEnabled = object.hostEnabled ?? false; + message.allowQueries = object.allowQueries?.map(e => e) || []; + return message; + }, + fromProtoMsg(message: ParamsProtoMsg): Params { + return Params.decode(message.value); + }, + toProto(message: Params): Uint8Array { + return Params.encode(message).finish(); + }, + toProtoMsg(message: Params): ParamsProtoMsg { + return { + typeUrl: '/icq.v1.Params', + value: Params.encode(message).finish(), + }; + }, +}; diff --git a/packages/cosmic-proto/src/codegen/icq/v1/packet.ts b/packages/cosmic-proto/src/codegen/icq/v1/packet.ts new file mode 100644 index 00000000000..e9293539421 --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/v1/packet.ts @@ -0,0 +1,364 @@ +//@ts-nocheck +import { + RequestQuery, + RequestQuerySDKType, + ResponseQuery, + ResponseQuerySDKType, +} from '../../tendermint/abci/types.js'; +import { BinaryReader, BinaryWriter } from '../../binary.js'; +import { isSet, bytesFromBase64, base64FromBytes } from '../../helpers.js'; +/** InterchainQueryPacketData is comprised of raw query. */ +export interface InterchainQueryPacketData { + data: Uint8Array; + /** optional memo */ + memo: string; +} +export interface InterchainQueryPacketDataProtoMsg { + typeUrl: '/icq.v1.InterchainQueryPacketData'; + value: Uint8Array; +} +/** InterchainQueryPacketData is comprised of raw query. */ +export interface InterchainQueryPacketDataSDKType { + data: Uint8Array; + memo: string; +} +/** InterchainQueryPacketAck is comprised of an ABCI query response with non-deterministic fields left empty (e.g. Codespace, Log, Info and ...). */ +export interface InterchainQueryPacketAck { + data: Uint8Array; +} +export interface InterchainQueryPacketAckProtoMsg { + typeUrl: '/icq.v1.InterchainQueryPacketAck'; + value: Uint8Array; +} +/** InterchainQueryPacketAck is comprised of an ABCI query response with non-deterministic fields left empty (e.g. Codespace, Log, Info and ...). */ +export interface InterchainQueryPacketAckSDKType { + data: Uint8Array; +} +/** CosmosQuery contains a list of tendermint ABCI query requests. It should be used when sending queries to an SDK host chain. */ +export interface CosmosQuery { + requests: RequestQuery[]; +} +export interface CosmosQueryProtoMsg { + typeUrl: '/icq.v1.CosmosQuery'; + value: Uint8Array; +} +/** CosmosQuery contains a list of tendermint ABCI query requests. It should be used when sending queries to an SDK host chain. */ +export interface CosmosQuerySDKType { + requests: RequestQuerySDKType[]; +} +/** CosmosResponse contains a list of tendermint ABCI query responses. It should be used when receiving responses from an SDK host chain. */ +export interface CosmosResponse { + responses: ResponseQuery[]; +} +export interface CosmosResponseProtoMsg { + typeUrl: '/icq.v1.CosmosResponse'; + value: Uint8Array; +} +/** CosmosResponse contains a list of tendermint ABCI query responses. It should be used when receiving responses from an SDK host chain. */ +export interface CosmosResponseSDKType { + responses: ResponseQuerySDKType[]; +} +function createBaseInterchainQueryPacketData(): InterchainQueryPacketData { + return { + data: new Uint8Array(), + memo: '', + }; +} +export const InterchainQueryPacketData = { + typeUrl: '/icq.v1.InterchainQueryPacketData', + encode( + message: InterchainQueryPacketData, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.data.length !== 0) { + writer.uint32(10).bytes(message.data); + } + if (message.memo !== '') { + writer.uint32(18).string(message.memo); + } + return writer; + }, + decode( + input: BinaryReader | Uint8Array, + length?: number, + ): InterchainQueryPacketData { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseInterchainQueryPacketData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.data = reader.bytes(); + break; + case 2: + message.memo = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): InterchainQueryPacketData { + return { + data: isSet(object.data) + ? bytesFromBase64(object.data) + : new Uint8Array(), + memo: isSet(object.memo) ? String(object.memo) : '', + }; + }, + toJSON(message: InterchainQueryPacketData): unknown { + const obj: any = {}; + message.data !== undefined && + (obj.data = base64FromBytes( + message.data !== undefined ? message.data : new Uint8Array(), + )); + message.memo !== undefined && (obj.memo = message.memo); + return obj; + }, + fromPartial( + object: Partial, + ): InterchainQueryPacketData { + const message = createBaseInterchainQueryPacketData(); + message.data = object.data ?? new Uint8Array(); + message.memo = object.memo ?? ''; + return message; + }, + fromProtoMsg( + message: InterchainQueryPacketDataProtoMsg, + ): InterchainQueryPacketData { + return InterchainQueryPacketData.decode(message.value); + }, + toProto(message: InterchainQueryPacketData): Uint8Array { + return InterchainQueryPacketData.encode(message).finish(); + }, + toProtoMsg( + message: InterchainQueryPacketData, + ): InterchainQueryPacketDataProtoMsg { + return { + typeUrl: '/icq.v1.InterchainQueryPacketData', + value: InterchainQueryPacketData.encode(message).finish(), + }; + }, +}; +function createBaseInterchainQueryPacketAck(): InterchainQueryPacketAck { + return { + data: new Uint8Array(), + }; +} +export const InterchainQueryPacketAck = { + typeUrl: '/icq.v1.InterchainQueryPacketAck', + encode( + message: InterchainQueryPacketAck, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.data.length !== 0) { + writer.uint32(10).bytes(message.data); + } + return writer; + }, + decode( + input: BinaryReader | Uint8Array, + length?: number, + ): InterchainQueryPacketAck { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseInterchainQueryPacketAck(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.data = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): InterchainQueryPacketAck { + return { + data: isSet(object.data) + ? bytesFromBase64(object.data) + : new Uint8Array(), + }; + }, + toJSON(message: InterchainQueryPacketAck): unknown { + const obj: any = {}; + message.data !== undefined && + (obj.data = base64FromBytes( + message.data !== undefined ? message.data : new Uint8Array(), + )); + return obj; + }, + fromPartial( + object: Partial, + ): InterchainQueryPacketAck { + const message = createBaseInterchainQueryPacketAck(); + message.data = object.data ?? new Uint8Array(); + return message; + }, + fromProtoMsg( + message: InterchainQueryPacketAckProtoMsg, + ): InterchainQueryPacketAck { + return InterchainQueryPacketAck.decode(message.value); + }, + toProto(message: InterchainQueryPacketAck): Uint8Array { + return InterchainQueryPacketAck.encode(message).finish(); + }, + toProtoMsg( + message: InterchainQueryPacketAck, + ): InterchainQueryPacketAckProtoMsg { + return { + typeUrl: '/icq.v1.InterchainQueryPacketAck', + value: InterchainQueryPacketAck.encode(message).finish(), + }; + }, +}; +function createBaseCosmosQuery(): CosmosQuery { + return { + requests: [], + }; +} +export const CosmosQuery = { + typeUrl: '/icq.v1.CosmosQuery', + encode( + message: CosmosQuery, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + for (const v of message.requests) { + RequestQuery.encode(v!, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + decode(input: BinaryReader | Uint8Array, length?: number): CosmosQuery { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCosmosQuery(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.requests.push(RequestQuery.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): CosmosQuery { + return { + requests: Array.isArray(object?.requests) + ? object.requests.map((e: any) => RequestQuery.fromJSON(e)) + : [], + }; + }, + toJSON(message: CosmosQuery): unknown { + const obj: any = {}; + if (message.requests) { + obj.requests = message.requests.map(e => + e ? RequestQuery.toJSON(e) : undefined, + ); + } else { + obj.requests = []; + } + return obj; + }, + fromPartial(object: Partial): CosmosQuery { + const message = createBaseCosmosQuery(); + message.requests = + object.requests?.map(e => RequestQuery.fromPartial(e)) || []; + return message; + }, + fromProtoMsg(message: CosmosQueryProtoMsg): CosmosQuery { + return CosmosQuery.decode(message.value); + }, + toProto(message: CosmosQuery): Uint8Array { + return CosmosQuery.encode(message).finish(); + }, + toProtoMsg(message: CosmosQuery): CosmosQueryProtoMsg { + return { + typeUrl: '/icq.v1.CosmosQuery', + value: CosmosQuery.encode(message).finish(), + }; + }, +}; +function createBaseCosmosResponse(): CosmosResponse { + return { + responses: [], + }; +} +export const CosmosResponse = { + typeUrl: '/icq.v1.CosmosResponse', + encode( + message: CosmosResponse, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + for (const v of message.responses) { + ResponseQuery.encode(v!, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + decode(input: BinaryReader | Uint8Array, length?: number): CosmosResponse { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCosmosResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.responses.push(ResponseQuery.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): CosmosResponse { + return { + responses: Array.isArray(object?.responses) + ? object.responses.map((e: any) => ResponseQuery.fromJSON(e)) + : [], + }; + }, + toJSON(message: CosmosResponse): unknown { + const obj: any = {}; + if (message.responses) { + obj.responses = message.responses.map(e => + e ? ResponseQuery.toJSON(e) : undefined, + ); + } else { + obj.responses = []; + } + return obj; + }, + fromPartial(object: Partial): CosmosResponse { + const message = createBaseCosmosResponse(); + message.responses = + object.responses?.map(e => ResponseQuery.fromPartial(e)) || []; + return message; + }, + fromProtoMsg(message: CosmosResponseProtoMsg): CosmosResponse { + return CosmosResponse.decode(message.value); + }, + toProto(message: CosmosResponse): Uint8Array { + return CosmosResponse.encode(message).finish(); + }, + toProtoMsg(message: CosmosResponse): CosmosResponseProtoMsg { + return { + typeUrl: '/icq.v1.CosmosResponse', + value: CosmosResponse.encode(message).finish(), + }; + }, +}; diff --git a/packages/cosmic-proto/src/codegen/icq/v1/query.ts b/packages/cosmic-proto/src/codegen/icq/v1/query.ts new file mode 100644 index 00000000000..be0bfe0a992 --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/v1/query.ts @@ -0,0 +1,147 @@ +//@ts-nocheck +import { Params, ParamsSDKType } from './icq.js'; +import { BinaryReader, BinaryWriter } from '../../binary.js'; +import { isSet } from '../../helpers.js'; +/** QueryParamsRequest is the request type for the Query/Params RPC method. */ +export interface QueryParamsRequest {} +export interface QueryParamsRequestProtoMsg { + typeUrl: '/icq.v1.QueryParamsRequest'; + value: Uint8Array; +} +/** QueryParamsRequest is the request type for the Query/Params RPC method. */ +export interface QueryParamsRequestSDKType {} +/** QueryParamsResponse is the response type for the Query/Params RPC method. */ +export interface QueryParamsResponse { + /** params defines the parameters of the module. */ + params?: Params; +} +export interface QueryParamsResponseProtoMsg { + typeUrl: '/icq.v1.QueryParamsResponse'; + value: Uint8Array; +} +/** QueryParamsResponse is the response type for the Query/Params RPC method. */ +export interface QueryParamsResponseSDKType { + params?: ParamsSDKType; +} +function createBaseQueryParamsRequest(): QueryParamsRequest { + return {}; +} +export const QueryParamsRequest = { + typeUrl: '/icq.v1.QueryParamsRequest', + encode( + _: QueryParamsRequest, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + return writer; + }, + decode( + input: BinaryReader | Uint8Array, + length?: number, + ): QueryParamsRequest { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParamsRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(_: any): QueryParamsRequest { + return {}; + }, + toJSON(_: QueryParamsRequest): unknown { + const obj: any = {}; + return obj; + }, + fromPartial(_: Partial): QueryParamsRequest { + const message = createBaseQueryParamsRequest(); + return message; + }, + fromProtoMsg(message: QueryParamsRequestProtoMsg): QueryParamsRequest { + return QueryParamsRequest.decode(message.value); + }, + toProto(message: QueryParamsRequest): Uint8Array { + return QueryParamsRequest.encode(message).finish(); + }, + toProtoMsg(message: QueryParamsRequest): QueryParamsRequestProtoMsg { + return { + typeUrl: '/icq.v1.QueryParamsRequest', + value: QueryParamsRequest.encode(message).finish(), + }; + }, +}; +function createBaseQueryParamsResponse(): QueryParamsResponse { + return { + params: undefined, + }; +} +export const QueryParamsResponse = { + typeUrl: '/icq.v1.QueryParamsResponse', + encode( + message: QueryParamsResponse, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + decode( + input: BinaryReader | Uint8Array, + length?: number, + ): QueryParamsResponse { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParamsResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.params = Params.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): QueryParamsResponse { + return { + params: isSet(object.params) ? Params.fromJSON(object.params) : undefined, + }; + }, + toJSON(message: QueryParamsResponse): unknown { + const obj: any = {}; + message.params !== undefined && + (obj.params = message.params ? Params.toJSON(message.params) : undefined); + return obj; + }, + fromPartial(object: Partial): QueryParamsResponse { + const message = createBaseQueryParamsResponse(); + message.params = + object.params !== undefined && object.params !== null + ? Params.fromPartial(object.params) + : undefined; + return message; + }, + fromProtoMsg(message: QueryParamsResponseProtoMsg): QueryParamsResponse { + return QueryParamsResponse.decode(message.value); + }, + toProto(message: QueryParamsResponse): Uint8Array { + return QueryParamsResponse.encode(message).finish(); + }, + toProtoMsg(message: QueryParamsResponse): QueryParamsResponseProtoMsg { + return { + typeUrl: '/icq.v1.QueryParamsResponse', + value: QueryParamsResponse.encode(message).finish(), + }; + }, +}; diff --git a/packages/cosmic-proto/src/codegen/icq/v1/tx.ts b/packages/cosmic-proto/src/codegen/icq/v1/tx.ts new file mode 100644 index 00000000000..531882b1e0c --- /dev/null +++ b/packages/cosmic-proto/src/codegen/icq/v1/tx.ts @@ -0,0 +1,183 @@ +//@ts-nocheck +import { Params, ParamsSDKType } from './icq.js'; +import { BinaryReader, BinaryWriter } from '../../binary.js'; +import { isSet } from '../../helpers.js'; +/** + * MsgUpdateParams is the Msg/UpdateParams request type. + * + * Since: cosmos-sdk 0.47 + */ +export interface MsgUpdateParams { + /** authority is the address of the governance account. */ + authority: string; + /** + * params defines the x/async-icq parameters to update. + * + * NOTE: All parameters must be supplied. + */ + params: Params; +} +export interface MsgUpdateParamsProtoMsg { + typeUrl: '/icq.v1.MsgUpdateParams'; + value: Uint8Array; +} +/** + * MsgUpdateParams is the Msg/UpdateParams request type. + * + * Since: cosmos-sdk 0.47 + */ +export interface MsgUpdateParamsSDKType { + authority: string; + params: ParamsSDKType; +} +/** + * MsgUpdateParamsResponse defines the response structure for executing a + * MsgUpdateParams message. + * + * Since: cosmos-sdk 0.47 + */ +export interface MsgUpdateParamsResponse {} +export interface MsgUpdateParamsResponseProtoMsg { + typeUrl: '/icq.v1.MsgUpdateParamsResponse'; + value: Uint8Array; +} +/** + * MsgUpdateParamsResponse defines the response structure for executing a + * MsgUpdateParams message. + * + * Since: cosmos-sdk 0.47 + */ +export interface MsgUpdateParamsResponseSDKType {} +function createBaseMsgUpdateParams(): MsgUpdateParams { + return { + authority: '', + params: Params.fromPartial({}), + }; +} +export const MsgUpdateParams = { + typeUrl: '/icq.v1.MsgUpdateParams', + encode( + message: MsgUpdateParams, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + if (message.authority !== '') { + writer.uint32(10).string(message.authority); + } + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + decode(input: BinaryReader | Uint8Array, length?: number): MsgUpdateParams { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpdateParams(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + case 2: + message.params = Params.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object: any): MsgUpdateParams { + return { + authority: isSet(object.authority) ? String(object.authority) : '', + params: isSet(object.params) ? Params.fromJSON(object.params) : undefined, + }; + }, + toJSON(message: MsgUpdateParams): unknown { + const obj: any = {}; + message.authority !== undefined && (obj.authority = message.authority); + message.params !== undefined && + (obj.params = message.params ? Params.toJSON(message.params) : undefined); + return obj; + }, + fromPartial(object: Partial): MsgUpdateParams { + const message = createBaseMsgUpdateParams(); + message.authority = object.authority ?? ''; + message.params = + object.params !== undefined && object.params !== null + ? Params.fromPartial(object.params) + : undefined; + return message; + }, + fromProtoMsg(message: MsgUpdateParamsProtoMsg): MsgUpdateParams { + return MsgUpdateParams.decode(message.value); + }, + toProto(message: MsgUpdateParams): Uint8Array { + return MsgUpdateParams.encode(message).finish(); + }, + toProtoMsg(message: MsgUpdateParams): MsgUpdateParamsProtoMsg { + return { + typeUrl: '/icq.v1.MsgUpdateParams', + value: MsgUpdateParams.encode(message).finish(), + }; + }, +}; +function createBaseMsgUpdateParamsResponse(): MsgUpdateParamsResponse { + return {}; +} +export const MsgUpdateParamsResponse = { + typeUrl: '/icq.v1.MsgUpdateParamsResponse', + encode( + _: MsgUpdateParamsResponse, + writer: BinaryWriter = BinaryWriter.create(), + ): BinaryWriter { + return writer; + }, + decode( + input: BinaryReader | Uint8Array, + length?: number, + ): MsgUpdateParamsResponse { + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpdateParamsResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(_: any): MsgUpdateParamsResponse { + return {}; + }, + toJSON(_: MsgUpdateParamsResponse): unknown { + const obj: any = {}; + return obj; + }, + fromPartial(_: Partial): MsgUpdateParamsResponse { + const message = createBaseMsgUpdateParamsResponse(); + return message; + }, + fromProtoMsg( + message: MsgUpdateParamsResponseProtoMsg, + ): MsgUpdateParamsResponse { + return MsgUpdateParamsResponse.decode(message.value); + }, + toProto(message: MsgUpdateParamsResponse): Uint8Array { + return MsgUpdateParamsResponse.encode(message).finish(); + }, + toProtoMsg( + message: MsgUpdateParamsResponse, + ): MsgUpdateParamsResponseProtoMsg { + return { + typeUrl: '/icq.v1.MsgUpdateParamsResponse', + value: MsgUpdateParamsResponse.encode(message).finish(), + }; + }, +}; diff --git a/packages/cosmic-proto/src/codegen/ics23/bundle.ts b/packages/cosmic-proto/src/codegen/ics23/bundle.ts index a9aec6d102c..48c7c303a2f 100644 --- a/packages/cosmic-proto/src/codegen/ics23/bundle.ts +++ b/packages/cosmic-proto/src/codegen/ics23/bundle.ts @@ -1,5 +1,5 @@ //@ts-nocheck -import * as _118 from '../proofs.js'; +import * as _123 from '../proofs.js'; export const ics23 = { - ..._118, + ..._123, }; diff --git a/packages/cosmic-proto/src/codegen/index.ts b/packages/cosmic-proto/src/codegen/index.ts index 0efa1d1d8d9..70310462595 100644 --- a/packages/cosmic-proto/src/codegen/index.ts +++ b/packages/cosmic-proto/src/codegen/index.ts @@ -12,6 +12,7 @@ export * from './cosmos/bundle.js'; export * from './gogoproto/bundle.js'; export * from './google/bundle.js'; export * from './ibc/bundle.js'; +export * from './icq/bundle.js'; export * from './ics23/bundle.js'; export * from './tendermint/bundle.js'; export * from './varint.js'; diff --git a/packages/cosmic-proto/src/codegen/tendermint/bundle.ts b/packages/cosmic-proto/src/codegen/tendermint/bundle.ts index e5033961e08..9f3f4108611 100644 --- a/packages/cosmic-proto/src/codegen/tendermint/bundle.ts +++ b/packages/cosmic-proto/src/codegen/tendermint/bundle.ts @@ -1,39 +1,39 @@ //@ts-nocheck -import * as _119 from './abci/types.js'; -import * as _120 from './crypto/keys.js'; -import * as _121 from './crypto/proof.js'; -import * as _122 from './libs/bits/types.js'; -import * as _123 from './p2p/types.js'; -import * as _124 from './types/block.js'; -import * as _125 from './types/evidence.js'; -import * as _126 from './types/params.js'; -import * as _127 from './types/types.js'; -import * as _128 from './types/validator.js'; -import * as _129 from './version/types.js'; +import * as _124 from './abci/types.js'; +import * as _125 from './crypto/keys.js'; +import * as _126 from './crypto/proof.js'; +import * as _127 from './libs/bits/types.js'; +import * as _128 from './p2p/types.js'; +import * as _129 from './types/block.js'; +import * as _130 from './types/evidence.js'; +import * as _131 from './types/params.js'; +import * as _132 from './types/types.js'; +import * as _133 from './types/validator.js'; +import * as _134 from './version/types.js'; export namespace tendermint { export const abci = { - ..._119, + ..._124, }; export const crypto = { - ..._120, - ..._121, + ..._125, + ..._126, }; export namespace libs { export const bits = { - ..._122, + ..._127, }; } export const p2p = { - ..._123, + ..._128, }; export const types = { - ..._124, - ..._125, - ..._126, - ..._127, - ..._128, + ..._129, + ..._130, + ..._131, + ..._132, + ..._133, }; export const version = { - ..._129, + ..._134, }; } diff --git a/packages/cosmic-proto/test/snapshots/test-exports.js.md b/packages/cosmic-proto/test/snapshots/test-exports.js.md index 33dcdb4d395..1c607d72e86 100644 --- a/packages/cosmic-proto/test/snapshots/test-exports.js.md +++ b/packages/cosmic-proto/test/snapshots/test-exports.js.md @@ -19,6 +19,7 @@ Generated by [AVA](https://avajs.dev). 'gogoproto', 'google', 'ibc', + 'icq', 'ics23', 'int64FromString', 'int64Length', diff --git a/packages/cosmic-proto/test/snapshots/test-exports.js.snap b/packages/cosmic-proto/test/snapshots/test-exports.js.snap index 36cef32851d4b0607a502cf578bc231d0cf3d911..9f20ab80b822920f65484fca2f2248176b041322 100644 GIT binary patch literal 1201 zcmV;i1Wx-wRzVFwf@g9_pwsgg_I z0C)u834lKVR0wd90E+~8hX4Tq?i|IwBEa_q_=Nz^2tW$Jg#z$e0k~BFbOE?q0KP5& zj|;#b1%NofoC92SfZGnR?*R84;9CcH;sAd+z(f(aTm)uFd5V;lq@*1}%)ffQ4l)Mv zGDry(K*ykUzP8wA}w z6CAgda#vlMH{qHI*SXM(3#&40Ue}Tf-w-AYVZ2Ltn+ad<81S?KC%WK`Fuk^J4A5N}y@!~>i@e8)7 zZg{yffaYx8F0{%Im^YC2MVq&a-t1Rq;0wdx#RUUjv??7Qt}r+B?5-~*kZU4F`VP}n z`&{^n=~tS{S9zx)zb2e!d;`Eu00#gb0{9ug-vA~EFi(IR1o(&m56LlZFqU8uKX<;1WYfvI+4aY%GnGdo2i=IuSdKdG2x}tS+|)R#xgnk zofo$Ado3nALt1|+(fZ0809FBT0AB$34!~0Y|KgZdm$R*&emCClnlG7Bn{2Pel+N{f zCdC=iKMwXEkc44*>jlVhM`@XCkLr)qEW}ol5Nn6}vfMitWS;Z*>@N2cb&?cZc%4 zcT~--F(>a09Yb!<fZvXc`5yq&1h`Cq*ZYPXkAI&M;7bBL zI?kBuIeBPn@C;jdN^$fsr%iFVGz~iBij-a1iFAG>t9e{$$s4VtvsovtZZqafIv6;t zg*txN&e>(jmtiZ)`C~CTveBJrpppXNe%>l3Efa^J6KL>oC z1McR42RYzT4saacf&*N1fVUmsrUQKG0QVf=HwXCJ0Z!(DIZ~VHX-I;wOR$4 z0C^guf;p1IJaIq@Go-S@Ln_-_jCxECKtfXJj^uiX<_uci;gW5)BW4iCAVum6$z2oA zn|OgXcqmM`VCk+<4WUfDXt(dj--)Eqc2&usCw$?H1XQ*OeIfk7)^)ebc+!NcJk-leYf?0>YRN<25GsZ+*(1EhLSNSnIAy@89=I)%YfswRC8OSYs23Lu zR5j2<7kVRsOq?CGUQLj4eEEwK3>^eDRso;^n3I;lO2HG@ZjbM|DH_J2VKGzU1my8fzY>Q-pM;a(ur&@oL0EFPY97%lT0(lXKAd(02Yt zlgajo);|+#ed!{Awaz=j0elMJ2LKNN{EH)6T}-!n_Vw<5mwm~U+G01FOzBLo=MtPT zUG9@MC=Y7lAeG5BdOk@S)8u|ln^e+9x~4*MJHtt8*-~0a>a*i%`7VIZ0Nexc2Y~;M zt!2NaS823LH617Be3CRq%>k><15dJWoSdgptTB2{_h=)EvTDx67zr(R`;T=-!#5v} zlJGi!CV)=>+y?ODu_eq0oP`<9%I0=tbSkuJG~CMwlW)ZybF0H-D-bRX+&bl1?SV>!jsf#(YVK1BbOx z#}}=PT^4*Pno-6d^YM|zPF}^RcegX6S4nBQH)`a{>4VIqRcc1Qq{Elj&RC@uQ<{hV zpxG|3jX2{U#~Phq0`L-m9RQ7^&b5;nrD+re+@)O^Ri=S?SIDdfR08hTwcEWBTKx}F KJr|{P3;+OlJUKQ1 From b819aa912890a93be1775beb7cd540fe5d91b8aa Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 17:01:13 -0400 Subject: [PATCH 05/10] feat(network): add `allocateICQControllerPort` to PortAllocator - refs: #9072 --- packages/network/src/network.js | 12 +++++++++++- packages/network/test/test-network-misc.js | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/network/src/network.js b/packages/network/src/network.js index a5d4136ae10..5d5d6ac46e0 100644 --- a/packages/network/src/network.js +++ b/packages/network/src/network.js @@ -1463,11 +1463,12 @@ export const preparePortAllocator = (zone, { watch }) => .optional(M.string()) .returns(Shape.Vow$(Shape.Port)), allocateICAControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)), + allocateICQControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)), allocateCustomLocalPort: M.callWhen() .optional(M.string()) .returns(Shape.Vow$(Shape.Port)), }), - ({ protocol }) => ({ protocol, lastICAPortNum: 0n }), + ({ protocol }) => ({ protocol, lastICAPortNum: 0n, lastICQPortNum: 0n }), { allocateCustomIBCPort(specifiedName = '') { const { state } = this; @@ -1491,6 +1492,15 @@ export const preparePortAllocator = (zone, { watch }) => ), ); }, + allocateICQControllerPort() { + const { state } = this; + state.lastICQPortNum += 1n; + return watch( + E(state.protocol).bindPort( + `/ibc-port/icqcontroller-${state.lastICQPortNum}`, + ), + ); + }, allocateCustomLocalPort(specifiedName = '') { const { state } = this; diff --git a/packages/network/test/test-network-misc.js b/packages/network/test/test-network-misc.js index e406e720f16..b7ce4f5fffe 100644 --- a/packages/network/test/test-network-misc.js +++ b/packages/network/test/test-network-misc.js @@ -217,6 +217,16 @@ test('verify port allocation', async t => { await t.throwsAsync(when(portAllocator.allocateCustomIBCPort('/test-1')), { message: 'Invalid IBC port name: /test-1', }); + + const icqControllerPort1 = await when( + portAllocator.allocateICQControllerPort(), + ); + t.is(icqControllerPort1.getLocalAddress(), '/ibc-port/icqcontroller-1'); + + const icqControllerPort2 = await when( + portAllocator.allocateICQControllerPort(), + ); + t.is(icqControllerPort2.getLocalAddress(), '/ibc-port/icqcontroller-2'); }); test('protocol connection listen', async t => { From 2c29c088a788785c95d651777f6431bcc47d501c Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 17:01:55 -0400 Subject: [PATCH 06/10] feat(ibc): export validateRemoteIbcAddress address - throws TypeError if string does not match REMOTE_ADDR_RE - not currently used as a runtime check in the ibc ProtocolImpl, but could be if [test-net-ibc-upgrade.ts](packages/boot/test/bootstrapTests/test-net-ibc-upgrade.ts) is amenable --- packages/vats/tools/ibc-utils.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/vats/tools/ibc-utils.js b/packages/vats/tools/ibc-utils.js index e925087490c..fd3d7cbca4d 100644 --- a/packages/vats/tools/ibc-utils.js +++ b/packages/vats/tools/ibc-utils.js @@ -6,8 +6,26 @@ harden(REMOTE_ADDR_RE); export const LOCAL_ADDR_RE = /^\/ibc-port\/(?[-a-zA-Z0-9._+#[\]<>]+)$/; /** @typedef {`/ibc-port/${string}`} LocalIbcAddress */ -/** @param {string} remoteAddr */ -export const decodeRemoteIbcAddress = remoteAddr => { +/** + * @overload + * @param {string} remoteAddr + * @param {undefined | false} [returnMatch] + * @returns {boolean} + */ +/** + * @overload + * @param {string} remoteAddr + * @param {true} returnMatch + * @returns {RegExpMatchArray} + */ +/** + * Validates a remote IBC address format and returns true if the address is + * valid. + * + * @param {string} remoteAddr + * @param {boolean} [returnMatch] + */ +export const validateRemoteIbcAddress = (remoteAddr, returnMatch = false) => { const match = remoteAddr.match(REMOTE_ADDR_RE); // .groups is to inform TS https://github.com/microsoft/TypeScript/issues/32098 if (!(match && match.groups)) { @@ -15,6 +33,14 @@ export const decodeRemoteIbcAddress = remoteAddr => { `Remote address ${remoteAddr} must be '(/ibc-hop/CONNECTION)*/ibc-port/PORT/(ordered|unordered)/VERSION'`, ); } + return returnMatch ? match : true; +}; + +/** @param {string} remoteAddr */ +export const decodeRemoteIbcAddress = remoteAddr => { + const match = validateRemoteIbcAddress(remoteAddr, true); + if (!match.groups) + throw Error('Unexpected error, validateRemoteIbcAddress should throw.'); /** @type {import('../src/types.js').IBCConnectionID[]} */ const hops = []; From fe9dab4dffd87c8026eea1fea9115a2cb925d344 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 17:02:27 -0400 Subject: [PATCH 07/10] feat(cosmic-proto): add JsonSafe and RequestQueryJson types - refs: #9072 --- packages/cosmic-proto/src/helpers.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/cosmic-proto/src/helpers.ts b/packages/cosmic-proto/src/helpers.ts index 237d985d316..feea08b879a 100644 --- a/packages/cosmic-proto/src/helpers.ts +++ b/packages/cosmic-proto/src/helpers.ts @@ -1,6 +1,7 @@ import type { QueryAllBalancesRequest } from './codegen/cosmos/bank/v1beta1/query.js'; import type { MsgSend } from './codegen/cosmos/bank/v1beta1/tx.js'; import type { MsgDelegate } from './codegen/cosmos/staking/v1beta1/tx.js'; +import type { RequestQuery } from './codegen/tendermint/abci/types.js'; /** * The result of Any.toJSON(). The type in cosms-types says it returns @@ -49,3 +50,24 @@ export const typedJson = ( export type Base64Any = { [Prop in keyof T]: T[Prop] extends Uint8Array ? string : T[Prop]; }; + +// TODO make codegen toJSON() return these instead of unknown +// https://github.com/cosmology-tech/telescope/issues/605 +/** + * Mimics behavor of .toJSON(), converting Uint8Array to base64 strings + * and bigints to strings + */ +export type JsonSafe = { + [Prop in keyof T]: T[Prop] extends Uint8Array + ? string + : T[Prop] extends bigint + ? string + : T[Prop]; +}; + +/** + * The result of RequestQuery.toJSON(). The type in cosms-types says it returns + * `unknown` but it's actually this. The Uint8Array fields are base64 encoded, while + * bigint fields are strings. + */ +export type RequestQueryJson = JsonSafe; From ea8e6d3b6cd8a01776cc42ddfca4dc65b0c0eec3 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 20:46:50 -0400 Subject: [PATCH 08/10] feat(cosmic-proto): add toRequestQueryJson and typeUrlToGrpcPath --- packages/cosmic-proto/src/helpers.ts | 30 ++++++++++- packages/cosmic-proto/test/test-helpers.js | 60 ++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/cosmic-proto/src/helpers.ts b/packages/cosmic-proto/src/helpers.ts index feea08b879a..6aafb38320d 100644 --- a/packages/cosmic-proto/src/helpers.ts +++ b/packages/cosmic-proto/src/helpers.ts @@ -1,7 +1,8 @@ import type { QueryAllBalancesRequest } from './codegen/cosmos/bank/v1beta1/query.js'; import type { MsgSend } from './codegen/cosmos/bank/v1beta1/tx.js'; import type { MsgDelegate } from './codegen/cosmos/staking/v1beta1/tx.js'; -import type { RequestQuery } from './codegen/tendermint/abci/types.js'; +import { RequestQuery } from './codegen/tendermint/abci/types.js'; +import type { Any } from './codegen/google/protobuf/any.js'; /** * The result of Any.toJSON(). The type in cosms-types says it returns @@ -71,3 +72,30 @@ export type JsonSafe = { * bigint fields are strings. */ export type RequestQueryJson = JsonSafe; + +const QUERY_REQ_TYPEURL_RE = /^\/(\w+(?:\.\w+)*)\.Query(\w+)Request$/; + +export const typeUrlToGrpcPath = (typeUrl: Any['typeUrl']) => { + const match = typeUrl.match(QUERY_REQ_TYPEURL_RE); + if (!match) { + throw new TypeError( + `Invalid typeUrl: ${typeUrl}. Must be a Query Request.`, + ); + } + const [, serviceName, methodName] = match; + return `/${serviceName}.Query/${methodName}`; +}; + +type RequestQueryOpts = Partial>; + +export const toRequestQueryJson = ( + any: Any, + opts: RequestQueryOpts = {}, +): RequestQueryJson => + RequestQuery.toJSON( + RequestQuery.fromPartial({ + path: typeUrlToGrpcPath(any.typeUrl), + data: any.value, + ...opts, + }), + ) as RequestQueryJson; diff --git a/packages/cosmic-proto/test/test-helpers.js b/packages/cosmic-proto/test/test-helpers.js index b91c299d421..c4d47979478 100644 --- a/packages/cosmic-proto/test/test-helpers.js +++ b/packages/cosmic-proto/test/test-helpers.js @@ -2,7 +2,10 @@ import test from 'ava'; import { cosmos } from '../dist/codegen/cosmos/bundle.js'; +import { ibc } from '../dist/codegen/ibc/bundle.js'; +import { icq } from '../dist/codegen/icq/bundle.js'; import { typedJson } from '../dist/index.js'; +import { typeUrlToGrpcPath, toRequestQueryJson } from '../dist/helpers.js'; const mockMsgSend = { fromAddress: 'agoric1from', @@ -44,3 +47,60 @@ test('typedJson', t => { other: 3, // retained because there's no runtime validation }); }); + +test('typeUrlToGrpcPath', t => { + t.is( + typeUrlToGrpcPath(cosmos.bank.v1beta1.QueryBalanceRequest.typeUrl), + '/cosmos.bank.v1beta1.Query/Balance', + ); + t.is( + typeUrlToGrpcPath( + cosmos.staking.v1beta1.QueryDelegatorDelegationsRequest.typeUrl, + ), + '/cosmos.staking.v1beta1.Query/DelegatorDelegations', + ); + t.is( + typeUrlToGrpcPath( + ibc.applications.transfer.v1.QueryDenomTraceRequest.typeUrl, + ), + '/ibc.applications.transfer.v1.Query/DenomTrace', + ); + t.is( + typeUrlToGrpcPath(icq.v1.QueryParamsRequest.typeUrl), + '/icq.v1.Query/Params', + ); + t.throws( + () => typeUrlToGrpcPath(cosmos.bank.v1beta1.QueryBalanceResponse.typeUrl), + { message: /Invalid typeUrl(.*?)Must be a Query Request/ }, + ); + t.throws(() => typeUrlToGrpcPath(cosmos.bank.v1beta1.MsgSend.typeUrl), { + message: /Invalid typeUrl(.*?)Must be a Query Request/, + }); +}); + +test('toRequestQueryJson', t => { + t.like( + toRequestQueryJson( + cosmos.bank.v1beta1.QueryBalanceRequest.toProtoMsg({ + address: mockMsgSend.fromAddress, + denom: mockMsgSend.amount[0].denom, + }), + ), + { + path: '/cosmos.bank.v1beta1.Query/Balance', + }, + ); + t.like( + toRequestQueryJson( + cosmos.bank.v1beta1.QueryBalanceRequest.toProtoMsg({ + address: mockMsgSend.fromAddress, + denom: mockMsgSend.amount[0].denom, + }), + { height: 0n }, + ), + { + path: '/cosmos.bank.v1beta1.Query/Balance', + height: '0', + }, + ); +}); From 79b5d0f61f0c11b00e51832b7edf3922df8f51c6 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 3 May 2024 10:49:18 -0400 Subject: [PATCH 09/10] feat(orchestration): add support for queries (icq/v1) --- .../bootstrapTests/test-vat-orchestration.ts | 130 +++++++++++++++- packages/boot/tools/ibc/mocks.js | 51 +++++-- packages/boot/tools/supports.ts | 37 +++-- packages/cosmic-proto/package.json | 36 ++++- packages/orchestration/index.js | 3 +- .../orchestration/src/exos/chainAccountKit.js | 55 +++---- .../src/exos/icqConnectionKit.js | 112 ++++++++++++++ .../src/proposals/orchestration-proposal.js | 2 +- packages/orchestration/src/service.js | 86 +++++++++-- packages/orchestration/src/typeGuards.js | 20 +++ packages/orchestration/src/types.d.ts | 11 ++ packages/orchestration/src/utils/address.js | 18 ++- packages/orchestration/src/utils/packet.js | 84 +++++++++-- .../orchestration/src/vat-orchestration.js | 6 +- .../test/test-withdraw-reward.js | 45 ++++-- .../orchestration/test/utils/address.test.js | 39 +++++ .../orchestration/test/utils/packet.test.js | 141 +++++++++++++++++- 17 files changed, 758 insertions(+), 118 deletions(-) create mode 100644 packages/orchestration/src/exos/icqConnectionKit.js create mode 100644 packages/orchestration/src/typeGuards.js diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index f4151cd6e10..d21493baf39 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -2,12 +2,20 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { ExecutionContext, TestFn } from 'ava'; import type { AnyJson } from '@agoric/cosmic-proto'; +import { toRequestQueryJson } from '@agoric/cosmic-proto'; +import { + QueryBalanceRequest, + QueryBalanceResponse, +} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; +import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { MsgDelegate, MsgDelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; -import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import type { OrchestrationService } from '@agoric/orchestration'; +import type { + OrchestrationService, + ICQConnection, +} from '@agoric/orchestration'; import { decodeBase64 } from '@endo/base64'; import { M, matches } from '@endo/patterns'; import { makeWalletFactoryContext } from './walletFactory.ts'; @@ -19,9 +27,9 @@ type DefaultTestContext = Awaited>; const test: TestFn = anyTest; /** - * To update, pass the message into `makeTxPacket` from `@agoric/orchestration`, - * and paste the resulting `data` key into `protoMsgMocks` in - * [mocks.js](../../tools/ibc/mocks.js). + * To update, pass the message into `makeTxPacket` or `makeQueryPacket` from + * `@agoric/orchestration`, and paste the resulting `data` key into `protoMsgMocks` + * in [mocks.js](../../tools/ibc/mocks.js). * If adding a new msg, reference the mock in the `sendPacket` switch statement * in [supports.ts](../../tools/supports.ts). */ @@ -32,6 +40,12 @@ const delegateMsgSuccess = Any.toJSON( amount: { denom: 'uatom', amount: '10' }, }), ) as AnyJson; +const balanceQuery = toRequestQueryJson( + QueryBalanceRequest.toProtoMsg({ + address: 'cosmos1test', + denom: 'uatom', + }), +); test.before(async t => { t.context = await makeTestContext(t); @@ -133,7 +147,7 @@ test('ICA connection can send msg with proto3', async t => { // @ts-expect-error intentional await t.throwsAsync(EV(account).executeEncodedTx('malformed'), { message: - 'In "executeEncodedTx" method of (ChainAccount account): arg 0: string "malformed" - Must be a copyArray', + 'In "executeEncodedTx" method of (ChainAccountKit account): arg 0: string "malformed" - Must be a copyArray', }); const txSuccess = await EV(account).executeEncodedTx([delegateMsgSuccess]); @@ -173,3 +187,107 @@ test('ICA connection can send msg with proto3', async t => { message: 'ABCI code: 5: error handling packet: see events for details', }); }); + +test('Query connection can be created', async t => { + const { + runUtils: { EV }, + } = t.context; + + type Powers = { orchestration: OrchestrationService }; + const contract = async ({ orchestration }: Powers) => { + const connection = + await EV(orchestration).provideICQConnection('connection-0'); + t.log('Query Connection', connection); + t.truthy(connection, 'provideICQConnection returns a connection'); + t.truthy( + matches(connection, M.remotable('ICQConnection')), + 'ICQConnection is a remotable', + ); + }; + + // core eval context + { + const orchestration: OrchestrationService = + await EV.vat('bootstrap').consumeItem('orchestration'); + await contract({ orchestration }); + } +}); + +test('Query connection can send a query', async t => { + const { + runUtils: { EV }, + } = t.context; + + type Powers = { orchestration: OrchestrationService }; + const contract = async ({ orchestration }: Powers) => { + const queryConnection: ICQConnection = + await EV(orchestration).provideICQConnection('connection-0'); + + const [result] = await EV(queryConnection).query([balanceQuery]); + t.is(result.code, 0); + t.is(typeof result.height, 'bigint'); + t.deepEqual(QueryBalanceResponse.decode(decodeBase64(result.key)), { + balance: { + amount: '0', + denom: 'uatom', + }, + }); + + const results = await EV(queryConnection).query([ + balanceQuery, + balanceQuery, + ]); + t.is(results.length, 2); + for (const { key } of results) { + t.deepEqual(QueryBalanceResponse.decode(decodeBase64(key)), { + balance: { + amount: '0', + denom: 'uatom', + }, + }); + } + + await t.throwsAsync( + EV(queryConnection).query([ + { ...balanceQuery, path: '/cosmos.bank.v1beta1.QueryBalanceRequest' }, + ]), + { + message: 'ABCI code: 4: error handling packet: see events for details', + }, + 'Use gRPC method to query, not protobuf typeUrl', + ); + }; + + // core eval context + { + const orchestration: OrchestrationService = + await EV.vat('bootstrap').consumeItem('orchestration'); + await contract({ orchestration }); + } +}); + +test('provideICQConnection is idempotent', async t => { + const { + runUtils: { EV }, + } = t.context; + const orchestration: OrchestrationService = + await EV.vat('bootstrap').consumeItem('orchestration'); + + const queryConn0 = + await EV(orchestration).provideICQConnection('connection-0'); + const queryConn1 = + await EV(orchestration).provideICQConnection('connection-1'); + const queryConn02 = + await EV(orchestration).provideICQConnection('connection-0'); + + const [addr0, addr1, addr02] = await Promise.all([ + EV(queryConn0).getRemoteAddress(), + EV(queryConn1).getRemoteAddress(), + EV(queryConn02).getRemoteAddress(), + ]); + t.is(addr0, addr02); + t.not(addr0, addr1); + + const [result] = await EV(queryConn02).query([balanceQuery]); + t.is(result.code, 0, 'ICQConnectionKit from MapStore state can send queries'); +}); diff --git a/packages/boot/tools/ibc/mocks.js b/packages/boot/tools/ibc/mocks.js index 87bfdbc1c09..603299e6d0c 100644 --- a/packages/boot/tools/ibc/mocks.js +++ b/packages/boot/tools/ibc/mocks.js @@ -6,9 +6,22 @@ const responses = { // {"result":"+/cosmos.staking.v1beta1.MsgDelegateResponse"} delegate: 'eyJyZXN1bHQiOiJFaTBLS3k5amIzTnRiM011YzNSaGEybHVaeTUyTVdKbGRHRXhMazF6WjBSbGJHVm5ZWFJsVW1WemNHOXVjMlU9In0=', - // XXX what does code 5 mean? are there other codes? + // '{"result":{"data":{"balance":{"amount":"0","denom":"uatom"}}}}' + queryBalance: + 'eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmMwZVVSQmIwdERaMVl4V1ZoU2RtSlNTVUpOUVQwOUluMD0ifQ==', + // {"result":{"data":[{"balance":{"amount":"0","denom":"uatom"}},{"balance":{"amount":"0","denom":"uatom"}}]}} + queryBalanceMulti: + 'eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmMwZVVSQmIwdERaMVl4V1ZoU2RtSlNTVUpOUVc5UFRXZDNTME5uYjBaa1YwWXdZakl3VTBGVVFUMGlmUT09In0=', + // '{"result":{"data":{"balance":{"amount":"0","denom":"some-invalid-denom"}}}}' (does not result in an error) + // eyJkYXRhIjoiQ2hzeUdRb1hDaEp6YjIxbExXbHVkbUZzYVdRdFpHVnViMjBTQVRBPSJ9 + queryBalanceUnknownDenom: + 'eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmh6ZVVkUmIxaERhRXA2WWpJeGJFeFhiSFZrYlVaellWZFJkRnBIVm5WaU1qQlRRVlJCUFNKOSJ9', + // {"error":"ABCI code: 4: error handling packet: see events for details"} + error4: + 'eyJlcnJvciI6IkFCQ0kgY29kZTogNDogZXJyb3IgaGFuZGxpbmcgcGFja2V0OiBzZWUgZXZlbnRzIGZvciBkZXRhaWxzIn0=', + // XXX what does code 5 mean? are there other codes? I have observed 1, 4, 5, 7 // {"error":"ABCI code: 5: error handling packet: see events for details"} - error: + error5: 'eyJlcnJvciI6IkFCQ0kgY29kZTogNTogZXJyb3IgaGFuZGxpbmcgcGFja2V0OiBzZWUgZXZlbnRzIGZvciBkZXRhaWxzIn0=', }; @@ -18,13 +31,33 @@ export const protoMsgMocks = { msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xVS0l5OWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxelowUmxiR1ZuWVhSbEVpNEtDMk52YzIxdmN6RjBaWE4wRWhKamIzTnRiM04yWVd4dmNHVnlNWFJsYzNRYUN3b0ZkV0YwYjIwU0FqRXciLCJtZW1vIjoiIn0=', ack: responses.delegate, }, + // QueryBalanceRequest (/cosmos.bank.v1beta1.Query/Balance) of uatom for cosmos1test + queryBalance: { + msg: 'eyJkYXRhIjoiQ2pvS0ZBb0xZMjl6Ylc5ek1YUmxjM1FTQlhWaGRHOXRFaUl2WTI5emJXOXpMbUpoYm1zdWRqRmlaWFJoTVM1UmRXVnllUzlDWVd4aGJtTmwiLCJtZW1vIjoiIn0=', + ack: responses.queryBalance, + }, + // QueryBalanceRequest of uatom for cosmos1test, repeated twice + queryBalanceMulti: { + msg: 'eyJkYXRhIjoiQ2pvS0ZBb0xZMjl6Ylc5ek1YUmxjM1FTQlhWaGRHOXRFaUl2WTI5emJXOXpMbUpoYm1zdWRqRmlaWFJoTVM1UmRXVnllUzlDWVd4aGJtTmxDam9LRkFvTFkyOXpiVzl6TVhSbGMzUVNCWFZoZEc5dEVpSXZZMjl6Ylc5ekxtSmhibXN1ZGpGaVpYUmhNUzVSZFdWeWVTOUNZV3hoYm1ObCIsIm1lbW8iOiIifQ==', + ack: responses.queryBalanceMulti, + }, + // QueryBalanceRequest of 'some-invalid-denom' for cosmos1test + queryBalanceUnknownDenom: { + msg: 'eyJkYXRhIjoiQ2tjS0lRb0xZMjl6Ylc5ek1YUmxjM1FTRW5OdmJXVXRhVzUyWVd4cFpDMWtaVzV2YlJJaUwyTnZjMjF2Y3k1aVlXNXJMbll4WW1WMFlURXVVWFZsY25rdlFtRnNZVzVqWlE9PSIsIm1lbW8iOiIifQ==', + ack: responses.queryBalanceUnknownDenom, + }, + // Query for /cosmos.bank.v1beta1.QueryBalanceRequest + queryUnknownPath: { + msg: 'eyJkYXRhIjoiQ2tBS0ZBb0xZMjl6Ylc5ek1YUmxjM1FTQlhWaGRHOXRFaWd2WTI5emJXOXpMbUpoYm1zdWRqRmlaWFJoTVM1UmRXVnllVUpoYkdGdVkyVlNaWEYxWlhOMCIsIm1lbW8iOiIifQ==', + ack: responses.error4, + }, // MsgDelegate 10uatom from cosmos1test to cosmosvaloper1test with memo: 'TESTING' and timeoutHeight: 1_000_000_000n delegateWithOpts: { msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xVS0l5OWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxelowUmxiR1ZuWVhSbEVpNEtDMk52YzIxdmN6RjBaWE4wRWhKamIzTnRiM04yWVd4dmNHVnlNWFJsYzNRYUN3b0ZkV0YwYjIwU0FqRXdFZ2RVUlZOVVNVNUhHSUNVNjl3RCIsIm1lbW8iOiIifQ==', ack: responses.delegate, }, error: { - ack: responses.error, + ack: responses.error5, }, }; @@ -60,15 +93,13 @@ export const icaMocks = { * @returns {IBCEvent<'channelOpenAck'>} */ channelOpenAck: obj => { - // Fake a channel IDs from port suffixes. _Ports have no relation to channels._ + // Fake a channel IDs from port suffixes. _Ports have no relation to channels, and hosts + // and controllers will likely have different channel IDs for the same channel._ + const mocklID = Number(obj.packet.source_port.split('-').at(-1)); /** @type {IBCChannelID} */ - const mockLocalChannelID = `channel-${Number( - obj?.packet?.source_port?.split('-')?.at(-1), - )}`; + const mockLocalChannelID = `channel-${mocklID}`; /** @type {IBCChannelID} */ - const mockRemoteChannelID = `channel-${Number( - obj?.packet?.destination_port?.split('-')?.at(-1), - )}`; + const mockRemoteChannelID = `channel-${mocklID}`; return { type: 'IBC_EVENT', diff --git a/packages/boot/tools/supports.ts b/packages/boot/tools/supports.ts index 0939fad575e..45ca480db63 100644 --- a/packages/boot/tools/supports.ts +++ b/packages/boot/tools/supports.ts @@ -298,7 +298,9 @@ export const makeSwingsetTestKit = async ( const makeAckEvent = (obj: IBCMethod<'sendPacket'>, ack: string) => { ibcSequenceNonce += 1; - return icaMocks.ackPacket(obj, ibcSequenceNonce, ack); + const msg = icaMocks.ackPacket(obj, ibcSequenceNonce, ack); + inbound(BridgeId.DIBC, msg); + return msg.packet; }; /** * Mock the bridge outbound handler. The real one is implemented in Golang so @@ -376,22 +378,37 @@ export const makeSwingsetTestKit = async ( case 'sendPacket': switch (obj.packet.data) { case protoMsgMocks.delegate.msg: { - const msg = makeAckEvent(obj, protoMsgMocks.delegate.ack); - inbound(BridgeId.DIBC, msg); - return msg.packet; + return makeAckEvent(obj, protoMsgMocks.delegate.ack); } case protoMsgMocks.delegateWithOpts.msg: { - const msg = makeAckEvent( + return makeAckEvent( obj, protoMsgMocks.delegateWithOpts.ack, ); - inbound(BridgeId.DIBC, msg); - return msg.packet; + } + case protoMsgMocks.queryBalance.msg: { + return makeAckEvent(obj, protoMsgMocks.queryBalance.ack); + } + case protoMsgMocks.queryUnknownPath.msg: { + return makeAckEvent( + obj, + protoMsgMocks.queryUnknownPath.ack, + ); + } + case protoMsgMocks.queryBalanceMulti.msg: { + return makeAckEvent( + obj, + protoMsgMocks.queryBalanceMulti.ack, + ); + } + case protoMsgMocks.queryBalanceUnknownDenom.msg: { + return makeAckEvent( + obj, + protoMsgMocks.queryBalanceUnknownDenom.ack, + ); } default: { - const msg = makeAckEvent(obj, protoMsgMocks.error.ack); - inbound(BridgeId.DIBC, msg); - return msg.packet; + return makeAckEvent(obj, protoMsgMocks.error.ack); } } default: diff --git a/packages/cosmic-proto/package.json b/packages/cosmic-proto/package.json index 7e239f9fa79..18d22bc93c3 100644 --- a/packages/cosmic-proto/package.json +++ b/packages/cosmic-proto/package.json @@ -32,17 +32,21 @@ "types": "./dist/codegen/cosmos/*.d.ts", "default": "./dist/codegen/cosmos/*.js" }, - "./cosmos/tx/v1beta1/tx.js": { - "types": "./dist/codegen/cosmos/tx/v1beta1/tx.d.ts", - "default": "./dist/codegen/cosmos/tx/v1beta1/tx.js" + "./cosmos/bank/v1beta1/query.js": { + "types": "./dist/codegen/cosmos/bank/v1beta1/query.d.ts", + "default": "./dist/codegen/cosmos/bank/v1beta1/query.js" + }, + "./cosmos/distribution/v1beta1/tx.js": { + "types": "./dist/codegen/cosmos/distribution/v1beta1/tx.d.ts", + "default": "./dist/codegen/cosmos/distribution/v1beta1/tx.js" }, "./cosmos/staking/v1beta1/tx.js": { "types": "./dist/codegen/cosmos/staking/v1beta1/tx.d.ts", "default": "./dist/codegen/cosmos/staking/v1beta1/tx.js" }, - "./cosmos/distribution/v1beta1/tx.js": { - "types": "./dist/codegen/cosmos/distribution/v1beta1/tx.d.ts", - "default": "./dist/codegen/cosmos/distribution/v1beta1/tx.js" + "./cosmos/tx/v1beta1/tx.js": { + "types": "./dist/codegen/cosmos/tx/v1beta1/tx.d.ts", + "default": "./dist/codegen/cosmos/tx/v1beta1/tx.js" }, "./google/*.js": { "types": "./dist/codegen/google/*.d.ts", @@ -56,6 +60,18 @@ "types": "./dist/codegen/ibc/*.d.ts", "default": "./dist/codegen/ibc/*.js" }, + "./ibc/applications/interchain_accounts/v1/packet.js": { + "types": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.d.ts", + "default": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.js" + }, + "./icq/*.js": { + "types": "./dist/codegen/icq/*.d.ts", + "default": "./dist/codegen/icq/v1/*.js" + }, + "./icq/v1/packet.js": { + "types": "./dist/codegen/icq/v1/packet.d.ts", + "default": "./dist/codegen/icq/v1/packet.js" + }, "./swingset/msgs.js": { "types": "./dist/codegen/agoric/swingset/msgs.d.ts", "default": "./dist/codegen/agoric/swingset/msgs.js" @@ -68,6 +84,14 @@ "types": "./dist/codegen/agoric/swingset/swingset.d.ts", "default": "./dist/codegen/agoric/swingset/swingset.js" }, + "./tendermint/*.js": { + "types": "./dist/codegen/tendermint/*.d.ts", + "default": "./dist/codegen/tendermint/*.js" + }, + "./tendermint/abci/types.js": { + "types": "./dist/codegen/tendermint/abci/types.d.ts", + "default": "./dist/codegen/tendermint/abci/types.js" + }, "./vstorage/query.js": { "types": "./dist/codegen/agoric/vstorage/query.d.ts", "default": "./dist/codegen/agoric/vstorage/query.js" diff --git a/packages/orchestration/index.js b/packages/orchestration/index.js index 29a434ab341..4db591ddc0b 100644 --- a/packages/orchestration/index.js +++ b/packages/orchestration/index.js @@ -1,5 +1,4 @@ // eslint-disable-next-line import/export export * from './src/types.js'; -export * from './src/utils/address.js'; -export * from './src/utils/packet.js'; export * from './src/service.js'; +export * from './src/typeGuards.js'; diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js index 2ec5178aea8..54549f174b9 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -1,22 +1,27 @@ // @ts-check -/** @file Orchestration service */ -import { NonNullish } from '@agoric/assert'; -import { makeTracer } from '@agoric/internal'; +/** @file ChainAccount exo */ // XXX ambient types runtime imports until https://github.com/Agoric/agoric-sdk/issues/6512 import '@agoric/network/exported.js'; +import { NonNullish } from '@agoric/assert'; +import { makeTracer } from '@agoric/internal'; import { V as E } from '@agoric/vat-data/vow.js'; import { M } from '@endo/patterns'; import { PaymentShape, PurseShape } from '@agoric/ertp'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { findAddressField } from '../utils/address.js'; +import { + ConnectionHandlerI, + ChainAddressShape, + Proto3Shape, +} from '../typeGuards.js'; import { makeTxPacket, parseTxPacket } from '../utils/packet.js'; /** + * @import { Zone } from '@agoric/base-zone'; * @import { Connection, Port } from '@agoric/network'; * @import { Remote } from '@agoric/vow'; - * @import { Zone } from '@agoric/base-zone'; * @import { AnyJson } from '@agoric/cosmic-proto'; * @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import { LocalIbcAddress, RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; @@ -24,18 +29,7 @@ import { makeTxPacket, parseTxPacket } from '../utils/packet.js'; */ const { Fail } = assert; -const trace = makeTracer('ChainAccount'); - -export const Proto3Shape = { - typeUrl: M.string(), - value: M.string(), -}; - -export const ChainAddressShape = { - address: M.string(), - chainId: M.string(), - addressEncoding: M.string(), -}; +const trace = makeTracer('ChainAccountKit'); /** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */ const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS'; @@ -55,32 +49,28 @@ export const ChainAccountI = M.interface('ChainAccount', { prepareTransfer: M.callWhen().returns(InvitationShape), }); -export const ConnectionHandlerI = M.interface('ConnectionHandler', { - onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()), - onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()), - onReceive: M.callWhen(M.any(), M.string()).returns(M.any()), -}); +/** + * @typedef {{ + * port: Port; + * connection: Remote | undefined; + * localAddress: LocalIbcAddress | undefined; + * requestedRemoteAddress: string; + * remoteAddress: RemoteIbcAddress | undefined; + * chainAddress: ChainAddress | undefined; + * }} State + */ /** @param {Zone} zone */ export const prepareChainAccountKit = zone => zone.exoClassKit( - 'ChainAccount', + 'ChainAccountKit', { account: ChainAccountI, connectionHandler: ConnectionHandlerI }, /** * @param {Port} port * @param {string} requestedRemoteAddress */ (port, requestedRemoteAddress) => - /** - * @type {{ - * port: Port; - * connection: Remote | undefined; - * localAddress: LocalIbcAddress | undefined; - * requestedRemoteAddress: string; - * remoteAddress: RemoteIbcAddress | undefined; - * chainAddress: ChainAddress | undefined; - * }} - */ ( + /** @type {State} */ ( harden({ port, connection: undefined, @@ -175,7 +165,6 @@ export const prepareChainAccountKit = zone => this.state.connection = connection; this.state.remoteAddress = remoteAddr; this.state.localAddress = localAddr; - // XXX findAddressField currently throws, should it return '' instead? this.state.chainAddress = harden({ address: findAddressField(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, // TODO get this from `Chain` object #9063 diff --git a/packages/orchestration/src/exos/icqConnectionKit.js b/packages/orchestration/src/exos/icqConnectionKit.js new file mode 100644 index 00000000000..58eea647967 --- /dev/null +++ b/packages/orchestration/src/exos/icqConnectionKit.js @@ -0,0 +1,112 @@ +// @ts-check +/** @file ICQConnection Exo */ +import { NonNullish } from '@agoric/assert'; +import { makeTracer } from '@agoric/internal'; +import { V as E } from '@agoric/vat-data/vow.js'; +import { M } from '@endo/patterns'; +import { makeQueryPacket, parseQueryPacket } from '../utils/packet.js'; +import { ConnectionHandlerI } from '../typeGuards.js'; + +/** + * @import {Zone} from '@agoric/base-zone'; + * @import {Connection, Port} from '@agoric/network'; + * @import {Remote} from '@agoric/vow'; + * @import {Base64Any, RequestQueryJson} from '@agoric/cosmic-proto'; + * @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js'; + * @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js'; + */ + +const { Fail } = assert; +const trace = makeTracer('Orchestration:ICQConnection'); + +export const ICQMsgShape = M.splitRecord( + { path: M.string(), data: M.string() }, + { height: M.string(), prove: M.boolean() }, +); + +export const ICQConnectionI = M.interface('ICQConnection', { + getLocalAddress: M.call().returns(M.string()), + getRemoteAddress: M.call().returns(M.string()), + query: M.call(M.arrayOf(ICQMsgShape)).returns(M.promise()), +}); + +/** + * @typedef {{ + * port: Port; + * connection: Remote | undefined; + * localAddress: LocalIbcAddress | undefined; + * remoteAddress: RemoteIbcAddress | undefined; + * }} ICQConnectionKitState + */ + +/** @param {Zone} zone */ +export const prepareICQConnectionKit = zone => + zone.exoClassKit( + 'ICQConnectionKit', + { connection: ICQConnectionI, connectionHandler: ConnectionHandlerI }, + /** + * @param {Port} port + */ + port => + /** @type {ICQConnectionKitState} */ ( + harden({ + port, + connection: undefined, + remoteAddress: undefined, + localAddress: undefined, + }) + ), + { + connection: { + getLocalAddress() { + return NonNullish( + this.state.localAddress, + 'local address not available', + ); + }, + getRemoteAddress() { + return NonNullish( + this.state.remoteAddress, + 'remote address not available', + ); + }, + /** + * @param {RequestQueryJson[]} msgs + * @returns {Promise[]>} + * @throws {Error} if packet fails to send or an error is returned + */ + query(msgs) { + const { connection } = this.state; + if (!connection) throw Fail`connection not available`; + return E.when( + E(connection).send(makeQueryPacket(msgs)), + // if parseTxPacket cannot find a `result` key, it throws + ack => parseQueryPacket(ack), + ); + }, + }, + connectionHandler: { + /** + * @param {Remote} connection + * @param {LocalIbcAddress} localAddr + * @param {RemoteIbcAddress} remoteAddr + */ + async onOpen(connection, localAddr, remoteAddr) { + trace(`ICQ Channel Opened for ${localAddr} at ${remoteAddr}`); + this.state.connection = connection; + this.state.remoteAddress = remoteAddr; + this.state.localAddress = localAddr; + }, + async onClose(_connection, reason) { + trace(`ICQ Channel closed. Reason: ${reason}`); + }, + async onReceive(connection, bytes) { + trace(`ICQ Channel onReceive`, connection, bytes); + return ''; + }, + }, + }, + ); + +/** @typedef {ReturnType>} ICQConnectionKit */ +/** @typedef {ICQConnectionKit['connection']} ICQConnection */ diff --git a/packages/orchestration/src/proposals/orchestration-proposal.js b/packages/orchestration/src/proposals/orchestration-proposal.js index c48eb0a4ef6..b064a167a19 100644 --- a/packages/orchestration/src/proposals/orchestration-proposal.js +++ b/packages/orchestration/src/proposals/orchestration-proposal.js @@ -50,7 +50,7 @@ export const setupOrchestrationVat = async ( const portAllocator = await portAllocatorP; - const newOrchestrationKit = await E(vats.orchestration).makeOrchestration({ + const newOrchestrationKit = await E(vats.orchestration).makeOrchestrationKit({ portAllocator, }); diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index 7ac940699b7..5503b7dd6f2 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -6,14 +6,19 @@ import '@agoric/network/exported.js'; import { V as E } from '@agoric/vat-data/vow.js'; import { M } from '@endo/patterns'; +import { Shape as NetworkShape } from '@agoric/network'; import { prepareChainAccountKit } from './exos/chainAccountKit.js'; -import { makeICAChannelAddress } from './utils/address.js'; +import { prepareICQConnectionKit } from './exos/icqConnectionKit.js'; +import { + makeICAChannelAddress, + makeICQChannelAddress, +} from './utils/address.js'; /** - * @import { PortAllocator} from '@agoric/network'; - * @import { IBCConnectionID } from '@agoric/vats'; * @import { Zone } from '@agoric/base-zone'; - * @import { ChainAccount } from './types.js'; + * @import { Port, PortAllocator } from '@agoric/network'; + * @import { IBCConnectionID } from '@agoric/vats'; + * @import { ICQConnection, ChainAccount, ICQConnectionKit } from './types.js'; */ const { Fail, bare } = assert; @@ -34,6 +39,10 @@ const { Fail, bare } = assert; * >} PowerStore */ +/** + * @typedef {MapStore} ICQConnectionStore + */ + /** * @template {keyof OrchestrationPowers} K * @param {PowerStore} powers @@ -48,18 +57,33 @@ export const OrchestrationI = M.interface('Orchestration', { makeAccount: M.callWhen(M.string(), M.string()).returns( M.remotable('ChainAccount'), ), + provideICQConnection: M.callWhen(M.string()).returns( + M.remotable('Connection'), + ), }); +/** @typedef {{ powers: PowerStore; icqConnections: ICQConnectionStore } } OrchestrationState */ + /** * @param {Zone} zone * @param {ReturnType} makeChainAccountKit + * @param {ReturnType} makeICQConnectionKit */ -const prepareOrchestration = (zone, makeChainAccountKit) => +const prepareOrchestrationKit = ( + zone, + makeChainAccountKit, + makeICQConnectionKit, +) => zone.exoClassKit( 'Orchestration', { self: M.interface('OrchestrationSelf', { - bindPort: M.callWhen().returns(M.remotable()), + allocateICAControllerPort: M.callWhen().returns( + NetworkShape.Vow$(NetworkShape.Port), + ), + allocateICQControllerPort: M.callWhen().returns( + NetworkShape.Vow$(NetworkShape.Port), + ), }), public: OrchestrationI, }, @@ -72,11 +96,16 @@ const prepareOrchestration = (zone, makeChainAccountKit) => powers.init(/** @type {keyof OrchestrationPowers} */ (name), power); } } - return { powers }; + const icqConnections = zone.detached().mapStore('ICQConnections'); + return /** @type {OrchestrationState} */ ({ powers, icqConnections }); }, { self: { - async bindPort() { + async allocateICAControllerPort() { + const portAllocator = getPower(this.state.powers, 'portAllocator'); + return E(portAllocator).allocateICAControllerPort(); + }, + async allocateICQControllerPort() { const portAllocator = getPower(this.state.powers, 'portAllocator'); return E(portAllocator).allocateICAControllerPort(); }, @@ -90,7 +119,7 @@ const prepareOrchestration = (zone, makeChainAccountKit) => * @returns {Promise} */ async makeAccount(hostConnectionId, controllerConnectionId) { - const port = await this.facets.self.bindPort(); + const port = await this.facets.self.allocateICAControllerPort(); const remoteConnAddr = makeICAChannelAddress( hostConnectionId, @@ -104,9 +133,35 @@ const prepareOrchestration = (zone, makeChainAccountKit) => chainAccountKit.connectionHandler, ); // XXX if we fail, should we close the port (if it was created in this flow)? - return chainAccountKit.account; }, + /** + * @param {IBCConnectionID} controllerConnectionId + * @returns {Promise} + */ + async provideICQConnection(controllerConnectionId) { + if (this.state.icqConnections.has(controllerConnectionId)) { + return this.state.icqConnections.get(controllerConnectionId) + .connection; + } + // allocate a new Port for every Connection + const port = await this.facets.self.allocateICQControllerPort(); + const remoteConnAddr = makeICQChannelAddress(controllerConnectionId); + const icqConnectionKit = makeICQConnectionKit(port); + + // await so we do not return/save a ICQConnection before it successfully instantiates + await E(port).connect( + remoteConnAddr, + icqConnectionKit.connectionHandler, + ); + + this.state.icqConnections.init( + controllerConnectionId, + icqConnectionKit, + ); + + return icqConnectionKit.connection; + }, }, }, ); @@ -114,12 +169,17 @@ const prepareOrchestration = (zone, makeChainAccountKit) => /** @param {Zone} zone */ export const prepareOrchestrationTools = zone => { const makeChainAccountKit = prepareChainAccountKit(zone); - const makeOrchestration = prepareOrchestration(zone, makeChainAccountKit); + const makeICQConnectionKit = prepareICQConnectionKit(zone); + const makeOrchestrationKit = prepareOrchestrationKit( + zone, + makeChainAccountKit, + makeICQConnectionKit, + ); - return harden({ makeOrchestration }); + return harden({ makeOrchestrationKit }); }; harden(prepareOrchestrationTools); /** @typedef {ReturnType} OrchestrationTools */ -/** @typedef {ReturnType} OrchestrationKit */ +/** @typedef {ReturnType} OrchestrationKit */ /** @typedef {OrchestrationKit['public']} OrchestrationService */ diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js new file mode 100644 index 00000000000..bab1629a032 --- /dev/null +++ b/packages/orchestration/src/typeGuards.js @@ -0,0 +1,20 @@ +import { M } from '@endo/patterns'; + +export const ConnectionHandlerI = M.interface('ConnectionHandler', { + onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()), + onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()), + onReceive: M.callWhen(M.any(), M.string()).returns(M.any()), +}); + +export const ChainAddressShape = { + address: M.string(), + chainId: M.string(), + addressEncoding: M.string(), +}; + +export const Proto3Shape = { + typeUrl: M.string(), + value: M.string(), +}; + +export const CoinShape = { value: M.bigint(), denom: M.string() }; diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index bb37e16ff72..13e457f29d3 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -19,6 +19,13 @@ import type { } from '@agoric/vats/tools/ibc-utils.js'; import type { Port } from '@agoric/network'; import { MsgTransferResponse } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; +import type { IBCConnectionID } from '@agoric/vats'; +import type { ICQConnection } from './exos/icqConnectionKit.js'; + +export type * from './service.js'; +export type * from './vat-orchestration.js'; +export type * from './exos/chainAccountKit.js'; +export type * from './exos/icqConnectionKit.js'; /** * static declaration of known chain types will allow type support for @@ -114,6 +121,10 @@ export interface Orchestrator { getChain: (chainName: C) => Promise>; makeLocalAccount: () => Promise; + /** Send queries to ibc chains unknown to KnownChains */ + provideICQConnection: ( + controllerConnectionId: IBCConnectionID, + ) => ICQConnection; /** * For a denom, return information about a denom including the equivalent diff --git a/packages/orchestration/src/utils/address.js b/packages/orchestration/src/utils/address.js index a7abd969524..aecb650d85d 100644 --- a/packages/orchestration/src/utils/address.js +++ b/packages/orchestration/src/utils/address.js @@ -3,8 +3,8 @@ import { Fail } from '@agoric/assert'; /** * @import { IBCConnectionID } from '@agoric/vats'; + * @import { ChainAddress } from '../types.js'; * @import { RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; - * @import { ChainAddress, CosmosValidatorAddress } from '../types.js'; */ /** @@ -15,6 +15,7 @@ import { Fail } from '@agoric/assert'; * @param {'ordered' | 'unordered'} [opts.ordering] - channel ordering. currently only `ordered` is supported for ics27-1 * @param {string} [opts.txType] - default is `sdk_multi_msg` * @param {string} [opts.version] - default is `ics27-1` + * @returns {RemoteIbcAddress} */ export const makeICAChannelAddress = ( hostConnectionId, @@ -38,6 +39,21 @@ export const makeICAChannelAddress = ( }); return `/ibc-hop/${controllerConnectionId}/ibc-port/icahost/${ordering}/${connString}`; }; +harden(makeICAChannelAddress); + +/** + * @param {IBCConnectionID} controllerConnectionId + * @param {{ version?: string }} [opts] + * @returns {RemoteIbcAddress} + */ +export const makeICQChannelAddress = ( + controllerConnectionId, + { version = 'icq-1' } = {}, +) => { + controllerConnectionId || Fail`controllerConnectionId is required`; + return `/ibc-hop/${controllerConnectionId}/ibc-port/icqhost/unordered/${version}`; +}; +harden(makeICQChannelAddress); /** * Parse a chain address from a remote address string. diff --git a/packages/orchestration/src/utils/packet.js b/packages/orchestration/src/utils/packet.js index 9d75f186b10..3b6bde1d016 100644 --- a/packages/orchestration/src/utils/packet.js +++ b/packages/orchestration/src/utils/packet.js @@ -1,16 +1,28 @@ // @ts-check import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; -import { encodeBase64 } from '@endo/base64'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; +import { RequestQuery } from '@agoric/cosmic-proto/tendermint/abci/types.js'; +import { atob, decodeBase64, encodeBase64 } from '@endo/base64'; +import { + CosmosQuery, + CosmosResponse, +} from '@agoric/cosmic-proto/icq/v1/packet.js'; +import { Type as PacketType } from '@agoric/cosmic-proto/ibc/applications/interchain_accounts/v1/packet.js'; /** - * Makes an IBC packet from an array of messages. Expects the `value` of each message + * @import {AnyJson, RequestQueryJson, Base64Any} from '@agoric/cosmic-proto'; + * @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js'; + * @import {InterchainAccountPacketData} from '@agoric/cosmic-proto/ibc/applications/interchain_accounts/v1/packet.js'; + * @import {InterchainQueryPacketData} from '@agoric/cosmic-proto/icq/v1/packet.js'; + */ + +/** + * Makes an IBC transaction packet from an array of messages. Expects the `value` of each message * to be base64 encoded bytes. * Skips checks for malformed messages in favor of interface guards. - * @param {import('@agoric/cosmic-proto').AnyJson[]} msgs - * // XXX intellisense does not seem to infer well here - * @param {Omit} [opts] - * @returns {string} - IBC TX packet + * @param {AnyJson[]} msgs + * @param {Partial>} [opts] + * @returns {string} stringified InterchainAccountPacketData * @throws {Error} if malformed messages are provided */ export function makeTxPacket(msgs, opts) { @@ -22,14 +34,40 @@ export function makeTxPacket(msgs, opts) { }), ).finish(); - return JSON.stringify({ - type: 1, - data: encodeBase64(bytes), - memo: '', - }); + return JSON.stringify( + /** @type {Base64Any} */ ({ + type: PacketType.TYPE_EXECUTE_TX, + data: encodeBase64(bytes), + memo: '', + }), + ); } harden(makeTxPacket); +/** + * Makes an IBC query packet from an array of query messages. Expects the `data` of each message + * to be base64 encoded bytes. + * Skips checks for malformed messages in favor of interface guards. + * @param {RequestQueryJson[]} msgs + * @returns {string} stringified InterchainQueryPacketData + * @throws {Error} if malformed messages are provided + */ +export function makeQueryPacket(msgs) { + const bytes = CosmosQuery.encode( + CosmosQuery.fromPartial({ + requests: msgs.map(RequestQuery.fromJSON), + }), + ).finish(); + + return JSON.stringify( + /** @type {Base64Any} */ ({ + data: encodeBase64(bytes), + memo: '', + }), + ); +} +harden(makeQueryPacket); + /** * Looks for a result or error key in the response string, and returns * a Base64Bytes string. This string can be decoded using the corresponding @@ -46,3 +84,27 @@ export function parseTxPacket(response) { else throw Error(response); } harden(parseTxPacket); + +/** + * Looks for a result or error key in the response string. If a result is found, + * `responses` is decoded via `CosmosResponse`. The `key` and `value` fields on the + * resulting entries are base64 encoded for inter-vat communication. These can be + * decoded using the corresponding Query*Response objects. + * Error strings seem to be plain text and do not need decoding. + * @param {string} response + * @returns {Base64Any[]} + * @throws {Error} if error key is detected in response string, or result key is not found + */ +export function parseQueryPacket(response) { + const result = parseTxPacket(response); + const { data } = JSON.parse(atob(result)); + const { responses = [] } = CosmosResponse.decode(decodeBase64(data)); + return harden( + responses.map(resp => ({ + ...resp, + key: encodeBase64(resp.key), + value: encodeBase64(resp.value), + })), + ); +} +harden(parseQueryPacket); diff --git a/packages/orchestration/src/vat-orchestration.js b/packages/orchestration/src/vat-orchestration.js index bd6a6219685..1f50b7d23a9 100644 --- a/packages/orchestration/src/vat-orchestration.js +++ b/packages/orchestration/src/vat-orchestration.js @@ -7,14 +7,14 @@ import { prepareOrchestrationTools } from './service.js'; export const buildRootObject = (_vatPowers, _args, baggage) => { const zone = makeDurableZone(baggage); - const { makeOrchestration } = prepareOrchestrationTools( + const { makeOrchestrationKit } = prepareOrchestrationTools( zone.subZone('orchestration'), ); return Far('OrchestrationVat', { /** @param {Partial} [initialPowers] */ - makeOrchestration(initialPowers = {}) { - return makeOrchestration(initialPowers); + makeOrchestrationKit(initialPowers = {}) { + return makeOrchestrationKit(initialPowers); }, }); }; diff --git a/packages/orchestration/test/test-withdraw-reward.js b/packages/orchestration/test/test-withdraw-reward.js index 5c77a686829..656f624f76f 100644 --- a/packages/orchestration/test/test-withdraw-reward.js +++ b/packages/orchestration/test/test-withdraw-reward.js @@ -10,7 +10,7 @@ import { E, Far } from '@endo/far'; import { prepareStakingAccountKit } from '../src/exos/stakingAccountKit.js'; /** - * @import {ChainAccount, ChainAddress} from '../src/types.js'; + * @import {ChainAccount, ChainAddress, CosmosValidatorAddress, ICQConnection} from '../src/types.js'; * @import { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; */ @@ -22,7 +22,12 @@ const scenario1 = { acct1: { address: 'agoric1spy36ltduehs5dmszfrp792f0k2emcntrql3nx', }, - validator: { address: 'agoric1valoper234', addressEncoding: 'bech32' }, + /** @type {CosmosValidatorAddress} */ + validator: { + address: 'agoric1valoper234', + addressEncoding: 'bech32', + chainId: 'agoriclocal', + }, delegations: { agoric1valoper234: { denom: 'uatom', amount: '200' }, }, @@ -142,11 +147,16 @@ const makeScenario = () => { // @ts-expect-error mock const storageNode = Far('StorageNode', {}); + /** @type {ICQConnection} */ + // @ts-expect-error mock + const icqConnection = Far('ICQConnection', {}); + return { baggage, makeRecorderKit, ...mockAccount(undefined, delegations), storageNode, + icqConnection, ...mockZCF(), }; }; @@ -154,13 +164,19 @@ const makeScenario = () => { test('withdraw rewards from staking account holder', async t => { const s = makeScenario(); const { account, calls } = s; - const { baggage, makeRecorderKit, storageNode, zcf } = s; + const { baggage, makeRecorderKit, storageNode, zcf, icqConnection } = s; const make = prepareStakingAccountKit(baggage, makeRecorderKit, zcf); // Higher fidelity tests below use invitationMakers. - const { holder } = make(account, storageNode, account.getAddress()); + const { holder } = make( + account, + storageNode, + account.getAddress(), + icqConnection, + 'uatom', + ); const { validator } = scenario1; - const actual = await E(holder).withdrawReward(validator.address); + const actual = await E(holder).withdrawReward(validator); t.deepEqual(actual, [{ denom: 'uatom', value: 2n }]); const msg = { typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward', @@ -172,20 +188,23 @@ test('withdraw rewards from staking account holder', async t => { test(`delegate; withdraw rewards`, async t => { const s = makeScenario(); const { account, calls } = s; - const { baggage, makeRecorderKit, storageNode, zcf, zoe } = s; + const { baggage, makeRecorderKit, storageNode, zcf, zoe, icqConnection } = s; const make = prepareStakingAccountKit(baggage, makeRecorderKit, zcf); - const { invitationMakers } = make(account, storageNode, account.getAddress()); + const { invitationMakers } = make( + account, + storageNode, + account.getAddress(), + icqConnection, + 'uatom', + ); const { validator, delegations } = scenario1; { const value = BigInt(Object.values(delegations)[0].amount); /** @type {Amount<'nat'>} */ const anAmount = { brand: Far('Token'), value }; - const toDelegate = await E(invitationMakers).Delegate( - validator.address, - anAmount, - ); + const toDelegate = await E(invitationMakers).Delegate(validator, anAmount); const seat = E(zoe).offer(toDelegate); const result = await E(seat).getOfferResult(); @@ -199,9 +218,7 @@ test(`delegate; withdraw rewards`, async t => { } { - const toWithdraw = await E(invitationMakers).WithdrawReward( - validator.address, - ); + const toWithdraw = await E(invitationMakers).WithdrawReward(validator); const seat = E(zoe).offer(toWithdraw); const result = await E(seat).getOfferResult(); diff --git a/packages/orchestration/test/utils/address.test.js b/packages/orchestration/test/utils/address.test.js index 618ee16e407..22b186dc632 100644 --- a/packages/orchestration/test/utils/address.test.js +++ b/packages/orchestration/test/utils/address.test.js @@ -1,13 +1,19 @@ +// @ts-check + import test from '@endo/ses-ava/prepare-endo.js'; +import { validateRemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; import { makeICAChannelAddress, + makeICQChannelAddress, findAddressField, } from '../../src/utils/address.js'; test('makeICAChannelAddress', t => { + // @ts-expect-error expected two arguments t.throws(() => makeICAChannelAddress(), { message: 'hostConnectionId is required', }); + // @ts-expect-error expected two arguments t.throws(() => makeICAChannelAddress('connection-0'), { message: 'controllerConnectionId is required', }); @@ -41,6 +47,7 @@ test('makeICAChannelAddress', t => { test('findAddressField', t => { t.is( + // @ts-expect-error intentional findAddressField('/ibc-hop/'), undefined, 'returns undefined when version json is missing', @@ -67,3 +74,35 @@ test('findAddressField', t => { 'returns address when localAddrr is appended to version string', ); }); + +test('makeICQChannelAddress', t => { + // @ts-expect-error expected 1 argument + t.throws(() => makeICQChannelAddress(), { + message: 'controllerConnectionId is required', + }); + t.is( + makeICQChannelAddress('connection-0'), + '/ibc-hop/connection-0/ibc-port/icqhost/unordered/icq-1', + 'returns connection string when controllerConnectionId is provided', + ); + t.is( + makeICQChannelAddress('connection-0', { + version: 'icq-2', + }), + '/ibc-hop/connection-0/ibc-port/icqhost/unordered/icq-2', + 'accepts custom version', + ); + t.throws( + () => + validateRemoteIbcAddress( + makeICQChannelAddress('connection-0', { + version: 'ic/q-/2', + }), + ), + { + message: + /must be '\(\/ibc-hop\/CONNECTION\)\*\/ibc-port\/PORT\/\(ordered\|unordered\)\/VERSION'/, + }, + 'makeICQChannelAddress not hardened against malformed version. use `validateRemoteIbcAddress` to detect this, or expect IBC ProtocolImpl to throw', + ); +}); diff --git a/packages/orchestration/test/utils/packet.test.js b/packages/orchestration/test/utils/packet.test.js index 76dae957585..e58626308f4 100644 --- a/packages/orchestration/test/utils/packet.test.js +++ b/packages/orchestration/test/utils/packet.test.js @@ -1,6 +1,21 @@ +// @ts-check + import test from '@endo/ses-ava/prepare-endo.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import { makeTxPacket, parseTxPacket } from '../../src/utils/packet.js'; +import { + QueryBalanceRequest, + QueryBalanceResponse, +} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; +import { RequestQuery } from '@agoric/cosmic-proto/tendermint/abci/types.js'; +import { decodeBase64 } from '@endo/base64'; +import { + makeTxPacket, + parseTxPacket, + parseQueryPacket, + makeQueryPacket, +} from '../../src/utils/packet.js'; + +/** @import { RequestQueryJson } from '@agoric/cosmic-proto'; */ test('makeTxPacket', t => { const mockMsg = { @@ -20,19 +35,25 @@ test('makeTxPacket', t => { ); t.deepEqual( makeTxPacket([ + // @ts-expect-error missing value field { typeUrl: mockMsg.typeUrl, }, ]), '{"type":1,"data":"Cg0KCy9mb28uYmFyLnYx","memo":""}', + 'Any.fromJSON() silently ignores missing `value` field', ); - t.throws(() => - makeTxPacket([ - { - typeUrl: mockMsg.typeUrl, - value: new Uint8Array([1, 2, 3]), - }, - ]), + t.throws( + () => + makeTxPacket([ + { + typeUrl: mockMsg.typeUrl, + // @ts-expect-error testing bad input + value: new Uint8Array([1, 2, 3]), + }, + ]), + undefined, + 'value cannot be Uint8Array', ); }); @@ -73,3 +94,107 @@ test('parseTxPacket', t => { 'returns original string as error if `result` is not found', ); }); + +test('makeQueryPacket', t => { + const mockQuery = /** @type {RequestQueryJson} */ ( + RequestQuery.toJSON( + RequestQuery.fromPartial({ + path: '/cosmos.bank.v1beta1.Query/Balance', + data: QueryBalanceRequest.encode( + QueryBalanceRequest.fromPartial({ + address: 'cosmos1test', + denom: 'uatom', + }), + ).finish(), + }), + ) + ); + t.is( + makeQueryPacket([mockQuery]), + '{"data":"CjoKFAoLY29zbW9zMXRlc3QSBXVhdG9tEiIvY29zbW9zLmJhbmsudjFiZXRhMS5RdWVyeS9CYWxhbmNl","memo":""}', + 'makes a query packet from messages', + ); + const requiredFields = { + path: '/foo.bar.v1', + data: new Uint8Array([1, 2, 3]), + }; + t.deepEqual( + RequestQuery.fromPartial(requiredFields), + { + ...requiredFields, + height: 0n, + prove: false, + }, + 'RequestQuery defaults to `height: 0n` and `prove: false`', + ); +}); + +test('queryToBase64', t => { + t.deepEqual( + RequestQuery.toJSON( + RequestQuery.fromPartial({ + path: '/cosmos.bank.v1beta1.Query/Balance', + data: new Uint8Array([1, 2, 3]), + }), + ), + { + path: '/cosmos.bank.v1beta1.Query/Balance', + data: 'AQID', + height: '0', + prove: false, + }, + ); +}); + +test('parseQueryPacket', t => { + const response = `{"result":"eyJkYXRhIjoiQ2c0eURBb0tDZ1YxWVhSdmJSSUJNQT09In0="}`; + const expectedOutput = [ + { + code: 0, + log: '', + info: '', + index: 0n, + key: 'CgoKBXVhdG9tEgEw', // base64 encoded Uint8Array + value: '', + proofOps: undefined, + height: 0n, + codespace: '', + }, + ]; + + t.deepEqual( + parseQueryPacket(response), + expectedOutput, + 'parses a query response packet', + ); + + const multiResponse = `{"result":"eyJkYXRhIjoiQ2c0eURBb0tDZ1YxWVhSdmJSSUJNQW9PTWd3S0Nnb0ZkV0YwYjIwU0FUQT0ifQ=="}`; + const multiParsed = parseQueryPacket(multiResponse); + t.is(multiParsed.length, 2); + for (const { key } of multiParsed) { + t.deepEqual(QueryBalanceResponse.decode(decodeBase64(key)), { + balance: { + amount: '0', + denom: 'uatom', + }, + }); + } + + t.throws( + () => + parseQueryPacket( + `{"error":"ABCI code: 4: error handling packet: see events for details"}`, + ), + { + message: 'ABCI code: 4: error handling packet: see events for details', + }, + ); + + t.throws( + () => parseQueryPacket('{"foo":"bar"}'), + { + message: '{"foo":"bar"}', + }, + 'throws an error if `result` is not found', + ); +}); From 9f0ae09e389f1750c9e550d5e6893460d1e21d07 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 3 May 2024 10:49:56 -0400 Subject: [PATCH 10/10] feat(orchestration): stakeAtom query balance --- .../test/bootstrapTests/test-orchestration.ts | 33 +++++- .../scripts/orchestration/init-stakeAtom.js | 2 + packages/cosmic-proto/src/helpers.ts | 7 +- .../test/snapshots/test-exports.js.md | 2 + .../test/snapshots/test-exports.js.snap | Bin 1201 -> 1243 bytes .../src/examples/stakeAtom.contract.js | 20 +++- .../src/exos/icqConnectionKit.js | 15 ++- .../src/exos/stakingAccountKit.js | 101 ++++++++++++------ .../src/proposals/orchestration-proposal.js | 13 ++- .../src/proposals/start-stakeAtom.js | 9 +- packages/orchestration/src/service.js | 1 + .../vm-config/decentral-devnet-config.json | 3 +- 12 files changed, 153 insertions(+), 53 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-orchestration.ts b/packages/boot/test/bootstrapTests/test-orchestration.ts index c8e0d2e8325..fa1721afd41 100644 --- a/packages/boot/test/bootstrapTests/test-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-orchestration.ts @@ -7,6 +7,7 @@ import { AmountMath } from '@agoric/ertp'; import type { start as stakeBldStart } from '@agoric/orchestration/src/examples/stakeBld.contract.js'; import type { Instance } from '@agoric/zoe/src/zoeService/utils.js'; import { M, matches } from '@endo/patterns'; +import type { CosmosValidatorAddress } from '@agoric/orchestration'; import { makeWalletFactoryContext } from './walletFactory.ts'; type DefaultTestContext = Awaited>; @@ -124,8 +125,21 @@ test.serial('stakeAtom - repl-style', async t => { const atomBrand = await EV(agoricNames).lookup('brand', 'ATOM'); const atomAmount = AmountMath.make(atomBrand, 10n); - await t.notThrowsAsync( - EV(account).delegate('cosmosvaloper1test', atomAmount), + const validatorAddress: CosmosValidatorAddress = { + address: 'cosmosvaloper1test', + chainId: 'gaiatest', + addressEncoding: 'bech32', + }; + await t.notThrowsAsync(EV(account).delegate(validatorAddress, atomAmount)); + + const queryRes = await EV(account).getBalance(); + t.deepEqual(queryRes, { value: 0n, denom: 'uatom' }); + + const queryUnknownDenom = await EV(account).getBalance('some-invalid-denom'); + t.deepEqual( + queryUnknownDenom, + { value: 0n, denom: 'some-invalid-denom' }, + 'getBalance for unknown denom returns value: 0n', ); }); @@ -156,6 +170,11 @@ test.serial('stakeAtom - smart wallet', async t => { const { ATOM } = agoricNamesRemotes.brand; ATOM || Fail`ATOM missing from agoricNames`; + const validatorAddress: CosmosValidatorAddress = { + address: 'cosmosvaloper1test', + chainId: 'gaiatest', + addressEncoding: 'bech32', + }; await t.notThrowsAsync( wd.executeOffer({ @@ -164,7 +183,7 @@ test.serial('stakeAtom - smart wallet', async t => { source: 'continuing', previousOffer: 'request-account', invitationMakerName: 'Delegate', - invitationArgs: ['cosmosvaloper1test', { brand: ATOM, value: 10n }], + invitationArgs: [validatorAddress, { brand: ATOM, value: 10n }], }, proposal: {}, }), @@ -173,6 +192,12 @@ test.serial('stakeAtom - smart wallet', async t => { status: { id: 'request-delegate-success', numWantsSatisfied: 1 }, }); + const validatorAddressFail: CosmosValidatorAddress = { + address: 'cosmosvaloper1fail', + chainId: 'gaiatest', + addressEncoding: 'bech32', + }; + await t.throwsAsync( wd.executeOffer({ id: 'request-delegate-fail', @@ -180,7 +205,7 @@ test.serial('stakeAtom - smart wallet', async t => { source: 'continuing', previousOffer: 'request-account', invitationMakerName: 'Delegate', - invitationArgs: ['cosmosvaloper1fail', { brand: ATOM, value: 10n }], + invitationArgs: [validatorAddressFail, { brand: ATOM, value: 10n }], }, proposal: {}, }), diff --git a/packages/builders/scripts/orchestration/init-stakeAtom.js b/packages/builders/scripts/orchestration/init-stakeAtom.js index 9d96b937d01..3ab10b627e2 100644 --- a/packages/builders/scripts/orchestration/init-stakeAtom.js +++ b/packages/builders/scripts/orchestration/init-stakeAtom.js @@ -8,6 +8,7 @@ export const defaultProposalBuilder = async ( const { hostConnectionId = 'connection-1', controllerConnectionId = 'connection-0', + bondDenom = 'uatom', } = options; return harden({ sourceSpec: '@agoric/orchestration/src/proposals/start-stakeAtom.js', @@ -21,6 +22,7 @@ export const defaultProposalBuilder = async ( }, hostConnectionId, controllerConnectionId, + bondDenom, }, ], }); diff --git a/packages/cosmic-proto/src/helpers.ts b/packages/cosmic-proto/src/helpers.ts index 6aafb38320d..070081bed8e 100644 --- a/packages/cosmic-proto/src/helpers.ts +++ b/packages/cosmic-proto/src/helpers.ts @@ -73,16 +73,17 @@ export type JsonSafe = { */ export type RequestQueryJson = JsonSafe; -const QUERY_REQ_TYPEURL_RE = /^\/(\w+(?:\.\w+)*)\.Query(\w+)Request$/; +const QUERY_REQ_TYPEURL_RE = + /^\/(?\w+(?:\.\w+)*)\.Query(?\w+)Request$/; export const typeUrlToGrpcPath = (typeUrl: Any['typeUrl']) => { const match = typeUrl.match(QUERY_REQ_TYPEURL_RE); - if (!match) { + if (!(match && match.groups)) { throw new TypeError( `Invalid typeUrl: ${typeUrl}. Must be a Query Request.`, ); } - const [, serviceName, methodName] = match; + const { serviceName, methodName } = match.groups; return `/${serviceName}.Query/${methodName}`; }; diff --git a/packages/cosmic-proto/test/snapshots/test-exports.js.md b/packages/cosmic-proto/test/snapshots/test-exports.js.md index 1c607d72e86..1c3710bfc2b 100644 --- a/packages/cosmic-proto/test/snapshots/test-exports.js.md +++ b/packages/cosmic-proto/test/snapshots/test-exports.js.md @@ -27,6 +27,8 @@ Generated by [AVA](https://avajs.dev). 'readInt32', 'readUInt32', 'tendermint', + 'toRequestQueryJson', + 'typeUrlToGrpcPath', 'typedJson', 'uInt64ToString', 'utf8Length', diff --git a/packages/cosmic-proto/test/snapshots/test-exports.js.snap b/packages/cosmic-proto/test/snapshots/test-exports.js.snap index 9f20ab80b822920f65484fca2f2248176b041322..64464ddbafec56f54eab8864fb2c1459708c99e1 100644 GIT binary patch literal 1243 zcmV<11SI=GRzV;AZ|x{4l!O$Z_=ZcHK(193N- zC<FQL~%w~cj9z;aZJ&7K7_n-$)DjsA(5cJ|{PqKn|61;g5L=a8SOx5&s zPY9XQ@V>8Jz5d?oZ{FP4s)xecQSaVln(L4;9{FsKtBA>WIz64d17lzT{qaP(tz{ zW%BUX0KNtAGl0JUR0;4X0iGhj1_3kyK01hfMu6J{xI=(H2ryaz9w`7z1>m&;@J0dn zqyXG50KXN0e+s}^2bgz&H3!&nfcG5Wa|ig*0e*J?xWIWAc+v%4ae4X4Gf02NIG&Hzn77v|!NkCYNlz9W#SC z1{tA&klZtI*Tjpo!6RY9MN9XDY6xZGBlh}k{MA?rZBLa9IvR*T3_xX@Fc2aLZC#@# z?(n)TYOEjhc1>{HR?0nfan^(@CS2u_o}XKmqH#q_9tDOlVF=?L!t*Q&^tJ&{7;vHk zu8YC5Rr^4aQGX@Uvlk6CX`rzLx;lW>ET&oHGuhygHrSNGrnI=scAHG;7n)4ApHm_- zoN2?U>2}Pn%5YsgD`Rhs_6wh}l_eBEefp$5c7PHr89jrX zwihNyXY34g#tub`N}dQIQwPsli4A%!efZwY;rY4r;d7b8^K%1-@3Td9!VB#_bl&Fe zK+AlO`TY*vZ}WE0jb3F2eqb0pKWE?vtxEg*i_8C8~>N zWTm#wawbQ)%v3BQWuMa8%;c&HRwAWo7%ns;Kg`?B=`16g%NkH+RRwEOw7BB^S++hj zlSZ>StSePlRj^6Jkm*aFPNlKNbSgv0W-6!e>k_ZTEb<4_S<^ZzxwH7aQ%TT^lHI>Q;%vZb_;G+@Wm@^t{$0el1CR{;MVTgz@u&(L_0 zYI>NMGlQf<)am;_2`p+Z*txO@G*cd0DKSNmjh$|8^Ba*n@kKjB)~fa_>=%&9%ancoIJGI{~5OO z6qD$FP8)Z)G<7;;L`FQ>j&**stN2`L$?MIuvsouCZ!s1~+TUBOg*t&~VGv^SI*oF F001!aRR910 literal 1201 zcmV;i1Wx-wRzVFwf@g9_pwsgg_I z0C)u834lKVR0wd90E+~8hX4Tq?i|IwBEa_q_=Nz^2tW$Jg#z$e0k~BFbOE?q0KP5& zj|;#b1%NofoC92SfZGnR?*R84;9CcH;sAd+z(f(aTm)uFd5V;lq@*1}%)ffQ4l)Mv zGDry(K*ykUzP8wA}w z6CAgda#vlMH{qHI*SXM(3#&40Ue}Tf-w-AYVZ2Ltn+ad<81S?KC%WK`Fuk^J4A5N}y@!~>i@e8)7 zZg{yffaYx8F0{%Im^YC2MVq&a-t1Rq;0wdx#RUUjv??7Qt}r+B?5-~*kZU4F`VP}n z`&{^n=~tS{S9zx)zb2e!d;`Eu00#gb0{9ug-vA~EFi(IR1o(&m56LlZFqU8uKX<;1WYfvI+4aY%GnGdo2i=IuSdKdG2x}tS+|)R#xgnk zofo$Ado3nALt1|+(fZ0809FBT0AB$34!~0Y|KgZdm$R*&emCClnlG7Bn{2Pel+N{f zCdC=iKMwXEkc44*>jlVhM`@XCkLr)qEW}ol5Nn6}vfMitWS;Z*>@N2cb&?cZc%4 zcT~--F(>a09Yb!<fZvXc`5yq&1h`Cq*ZYPXkAI&M;7bBL zI?kBuIeBPn@C;jdN^$fsr%iFVGz~iBij-a1iFAG>t9e{$$s4VtvsovtZZqafIv6;t zg*txN&e>(jmtiZ)`C~CTve { - const { hostConnectionId, controllerConnectionId } = zcf.getTerms(); + // TODO #9063 this roughly matches what we'll get from Chain.getChainInfo() + const { hostConnectionId, controllerConnectionId, bondDenom } = + zcf.getTerms(); const { orchestration, marshaller, storageNode } = privateArgs; const zone = makeDurableZone(baggage); @@ -52,12 +55,19 @@ export const start = async (zcf, privateArgs, baggage) => { hostConnectionId, controllerConnectionId, ); - const address = await E(account).getAddress(); - trace('chain address', address); + // #9212 TODO do not fail if host does not have `async-icq` module; + // communicate to OrchestrationAccount that it can't send queries + const icqConnection = await E(orchestration).provideICQConnection( + controllerConnectionId, + ); + const accountAddress = await E(account).getAddress(); + trace('account address', accountAddress); const { holder, invitationMakers } = makeStakingAccountKit( account, storageNode, - address, + accountAddress, + icqConnection, + bondDenom, ); return { publicSubscribers: holder.getPublicTopics(), diff --git a/packages/orchestration/src/exos/icqConnectionKit.js b/packages/orchestration/src/exos/icqConnectionKit.js index 58eea647967..79025061988 100644 --- a/packages/orchestration/src/exos/icqConnectionKit.js +++ b/packages/orchestration/src/exos/icqConnectionKit.js @@ -39,7 +39,20 @@ export const ICQConnectionI = M.interface('ICQConnection', { * }} ICQConnectionKitState */ -/** @param {Zone} zone */ +/** + * Prepares an ICQ Connection Kit based on the {@link https://github.com/cosmos/ibc-apps/blob/e9b46e4bf0ad0a66cf6bc53b5e5496f6e2b4b02b/modules/async-icq/README.md | `icq/v1` IBC application protocol}. + * + * `icq/v1`, also referred to as `async-icq`, is a protocol for asynchronous queries + * between IBC-enabled chains. It allows a chain to send queries to another chain + * and receive responses asynchronously. + * + * The ICQ connection kit provides the necessary functionality to establish and manage + * an ICQ connection between two chains. It includes methods for retrieving the local + * and remote addresses of the connection, as well as sending queries and handling + * connection events. + * + * @param {Zone} zone + */ export const prepareICQConnectionKit = zone => zone.exoClassKit( 'ICQConnectionKit', diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 0083a6bf717..ba0096e496a 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -8,6 +8,10 @@ import { MsgDelegate, MsgDelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; +import { + QueryBalanceRequest, + QueryBalanceResponse, +} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { AmountShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; @@ -16,11 +20,13 @@ import { M, prepareExoClassKit } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { decodeBase64 } from '@endo/base64'; import { E } from '@endo/far'; +import { toRequestQueryJson } from '@agoric/cosmic-proto'; +import { ChainAddressShape, CoinShape } from '../typeGuards.js'; /** - * @import { ChainAccount, ChainAddress, ChainAmount, CosmosValidatorAddress } from '../types.js'; - * @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js'; - * @import { Baggage } from '@agoric/swingset-liveslots'; + * @import {ChainAccount, ChainAddress, ChainAmount, CosmosValidatorAddress, ICQConnection} from '../types.js'; + * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'; + * @import {Baggage} from '@agoric/swingset-liveslots'; * @import {AnyJson} from '@agoric/cosmic-proto'; */ @@ -37,13 +43,17 @@ const { Fail } = assert; * topicKit: RecorderKit; * account: ChainAccount; * chainAddress: ChainAddress; + * icqConnection: ICQConnection; + * bondDenom: string; * }} State */ -const HolderI = M.interface('holder', { +export const ChainAccountHolderI = M.interface('ChainAccountHolder', { getPublicTopics: M.call().returns(TopicsRecordShape), - delegate: M.callWhen(M.string(), AmountShape).returns(M.record()), - withdrawReward: M.callWhen(M.string()).returns(M.array()), + getAddress: M.call().returns(ChainAddressShape), + getBalance: M.callWhen().optional(M.string()).returns(CoinShape), + delegate: M.callWhen(ChainAddressShape, AmountShape).returns(M.record()), + withdrawReward: M.callWhen(ChainAddressShape).returns(M.arrayOf(CoinShape)), }); /** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */ @@ -89,10 +99,10 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { 'Staking Account Holder', { helper: UnguardedHelperI, - holder: HolderI, + holder: ChainAccountHolderI, invitationMakers: M.interface('invitationMakers', { - Delegate: M.call(M.string(), AmountShape).returns(M.promise()), - WithdrawReward: M.call(M.string()).returns(M.promise()), + Delegate: M.call(ChainAddressShape, AmountShape).returns(M.promise()), + WithdrawReward: M.call(ChainAddressShape).returns(M.promise()), CloseAccount: M.call().returns(M.promise()), TransferAccount: M.call().returns(M.promise()), }), @@ -101,13 +111,15 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { * @param {ChainAccount} account * @param {StorageNode} storageNode * @param {ChainAddress} chainAddress + * @param {ICQConnection} icqConnection + * @param {string} bondDenom e.g. 'uatom' * @returns {State} */ - (account, storageNode, chainAddress) => { + (account, storageNode, chainAddress, icqConnection, bondDenom) => { // must be the fully synchronous maker because the kit is held in durable state const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); - return { account, chainAddress, topicKit }; + return { account, chainAddress, topicKit, icqConnection, bondDenom }; }, { helper: { @@ -126,24 +138,24 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { invitationMakers: { /** * - * @param {string} validatorAddress + * @param {CosmosValidatorAddress} validator * @param {Amount<'nat'>} amount */ - Delegate(validatorAddress, amount) { - trace('Delegate', validatorAddress, amount); + Delegate(validator, amount) { + trace('Delegate', validator, amount); return zcf.makeInvitation(async seat => { seat.exit(); - return this.facets.holder.delegate(validatorAddress, amount); + return this.facets.holder.delegate(validator, amount); }, 'Delegate'); }, - /** @param {string} validatorAddress */ - WithdrawReward(validatorAddress) { - trace('WithdrawReward', validatorAddress); + /** @param {CosmosValidatorAddress} validator */ + WithdrawReward(validator) { + trace('WithdrawReward', validator); return zcf.makeInvitation(async seat => { seat.exit(); - return this.facets.holder.withdrawReward(validatorAddress); + return this.facets.holder.withdrawReward(validator); }, 'WithdrawReward'); }, CloseAccount() { @@ -170,20 +182,23 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }, // TODO move this beneath the Orchestration abstraction, // to the OrchestrationAccount provided by makeAccount() + /** @returns {ChainAddress} */ + getAddress() { + return this.state.chainAddress; + }, /** * _Assumes users has already sent funds to their ICA, until #9193 - * @param {string} validatorAddress + * @param {CosmosValidatorAddress} validator * @param {Amount<'nat'>} ertpAmount */ - async delegate(validatorAddress, ertpAmount) { - trace('delegate', validatorAddress, ertpAmount); + async delegate(validator, ertpAmount) { + trace('delegate', validator, ertpAmount); - // FIXME get values from proposal or args - // FIXME brand handling and amount scaling + // FIXME brand handling and amount scaling #9211 trace('TODO: handle brand', ertpAmount); const amount = { amount: String(ertpAmount.value), - denom: 'uatom', + denom: this.state.bondDenom, }; const account = this.facets.helper.owned(); @@ -193,7 +208,7 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { toAnyJSON( MsgDelegate.toProtoMsg({ delegatorAddress, - validatorAddress, + validatorAddress: validator.address, amount, }), ), @@ -204,15 +219,15 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }, /** - * @param {string} validatorAddress + * @param {CosmosValidatorAddress} validator * @returns {Promise} */ - async withdrawReward(validatorAddress) { + async withdrawReward(validator) { const { chainAddress } = this.state; - assert.typeof(validatorAddress, 'string'); + assert.typeof(validator.address, 'string'); const msg = MsgWithdrawDelegatorReward.toProtoMsg({ delegatorAddress: chainAddress.address, - validatorAddress, + validatorAddress: validator.address, }); const account = this.facets.helper.owned(); const result = await E(account).executeEncodedTx([toAnyJSON(msg)]); @@ -222,9 +237,35 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { ); return harden(coins.map(toChainAmount)); }, + /** + * @param {ChainAmount['denom']} [denom] - defaults to bondDenom + * @returns {Promise} + */ + async getBalance(denom) { + const { chainAddress, icqConnection, bondDenom } = this.state; + denom ||= bondDenom; + assert.typeof(denom, 'string'); + + const [result] = await E(icqConnection).query([ + toRequestQueryJson( + QueryBalanceRequest.toProtoMsg({ + address: chainAddress.address, + denom, + }), + ), + ]); + if (!result?.key) throw Fail`Error parsing result ${result}`; + const { balance } = QueryBalanceResponse.decode( + decodeBase64(result.key), + ); + if (!balance) throw Fail`Result lacked balance key: ${result}`; + return harden(toChainAmount(balance)); + }, }, }, ); return makeStakingAccountKit; }; + /** @typedef {ReturnType>} StakingAccountKit */ +/** @typedef {StakingAccountKit['holder']} StakingAccounHolder */ diff --git a/packages/orchestration/src/proposals/orchestration-proposal.js b/packages/orchestration/src/proposals/orchestration-proposal.js index b064a167a19..1887e2c26e1 100644 --- a/packages/orchestration/src/proposals/orchestration-proposal.js +++ b/packages/orchestration/src/proposals/orchestration-proposal.js @@ -2,9 +2,9 @@ import { V as E } from '@agoric/vat-data/vow.js'; /** - * @import {Connection, Port, PortAllocator} from '@agoric/network'; - * @import { OrchestrationService } from '../service.js' - * @import { OrchestrationVat } from '../vat-orchestration.js' + * @import {PortAllocator} from '@agoric/network'; + * @import {OrchestrationService} from '../service.js' + * @import {OrchestrationVat} from '../vat-orchestration.js' */ /** @@ -19,8 +19,7 @@ import { V as E } from '@agoric/vat-data/vow.js'; * orchestrationVat: Producer; * }; * }} powers - * @param {object} options - * @param {{ orchestrationRef: VatSourceRef }} options.options + * @param {{ options: { orchestrationRef: VatSourceRef }}} options * * @typedef {{ * orchestration: ERef; @@ -84,8 +83,8 @@ export const getManifestForOrchestration = (_powers, { orchestrationRef }) => ({ }, produce: { orchestration: 'orchestration', - orchestrationKit: 'orchestration', - orchestrationVat: 'orchestration', + orchestrationKit: 'orchestrationKit', + orchestrationVat: 'orchestrationVat', }, }, }, diff --git a/packages/orchestration/src/proposals/start-stakeAtom.js b/packages/orchestration/src/proposals/start-stakeAtom.js index 13aa78b1580..888765fd18e 100644 --- a/packages/orchestration/src/proposals/start-stakeAtom.js +++ b/packages/orchestration/src/proposals/start-stakeAtom.js @@ -27,10 +27,14 @@ export const startStakeAtom = async ( produce: { stakeAtom: produceInstance }, }, }, - { options: { hostConnectionId, controllerConnectionId } }, + { options: { hostConnectionId, controllerConnectionId, bondDenom } }, ) => { const VSTORAGE_PATH = 'stakeAtom'; - trace('startStakeAtom', { hostConnectionId, controllerConnectionId }); + trace('startStakeAtom', { + hostConnectionId, + controllerConnectionId, + bondDenom, + }); await null; const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH); @@ -46,6 +50,7 @@ export const startStakeAtom = async ( terms: { hostConnectionId, controllerConnectionId, + bondDenom, }, privateArgs: { orchestration: await orchestration, diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index 5503b7dd6f2..ef832a43b63 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -145,6 +145,7 @@ const prepareOrchestrationKit = ( .connection; } // allocate a new Port for every Connection + // TODO #9317 optimize ICQ port allocation const port = await this.facets.self.allocateICQControllerPort(); const remoteConnAddr = makeICQChannelAddress(controllerConnectionId); const icqConnectionKit = makeICQConnectionKit(port); diff --git a/packages/vm-config/decentral-devnet-config.json b/packages/vm-config/decentral-devnet-config.json index ad99fc4b421..2bf94fdd6f4 100644 --- a/packages/vm-config/decentral-devnet-config.json +++ b/packages/vm-config/decentral-devnet-config.json @@ -172,7 +172,8 @@ "args": [ { "hostConnectionId": "connection-1", - "controllerConnectionId": "connection-0" + "controllerConnectionId": "connection-0", + "bondDenom": "uatom" } ] }