From 9a7229eb1eda8e5b1f3f5dea82ae741e4bf64771 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 13 Oct 2023 15:06:06 -0400 Subject: [PATCH] shuffle channel config and provide a method to accurately determine if a chain is gateway enabled --- connect/src/index.ts | 2 - connect/src/protocols/gatewayTransfer.ts | 53 +++++++++++++--------- core/definitions/src/protocols/ibc.ts | 10 +++-- platforms/cosmwasm/src/constants.ts | 40 +++++------------ platforms/cosmwasm/src/gateway.ts | 19 +++++--- platforms/cosmwasm/src/platform.ts | 12 ++--- platforms/cosmwasm/src/protocols/ibc.ts | 56 ++++++++++-------------- 7 files changed, 92 insertions(+), 100 deletions(-) diff --git a/connect/src/index.ts b/connect/src/index.ts index a1b6cff4c3..0334960906 100644 --- a/connect/src/index.ts +++ b/connect/src/index.ts @@ -11,8 +11,6 @@ export * as circle from "./circle-api"; export * as api from "./api"; // Re-export from core packages - -/** @namespace */ export { contracts, Chain, diff --git a/connect/src/protocols/gatewayTransfer.ts b/connect/src/protocols/gatewayTransfer.ts index f22ccdaf64..b54110e361 100644 --- a/connect/src/protocols/gatewayTransfer.ts +++ b/connect/src/protocols/gatewayTransfer.ts @@ -43,7 +43,11 @@ export class GatewayTransfer implements WormholeTransfer { private readonly wh: Wormhole; - private readonly wc: ChainContext; + // Wormchain context + private readonly gateway: ChainContext; + // Wormchain IBC Bridge + private readonly gatewayIbcBridge: IbcBridge; + // Contract address private readonly gatewayAddress: ChainAddress; // state machine tracker @@ -70,7 +74,12 @@ export class GatewayTransfer implements WormholeTransfer { // Any transfers we do over ibc ibcTransfers?: IbcTransferInfo[]; - private constructor(wh: Wormhole, transfer: GatewayTransferDetails) { + private constructor( + wh: Wormhole, + transfer: GatewayTransferDetails, + gateway: ChainContext, + gatewayIbc: IbcBridge, + ) { this.state = TransferState.Created; this.wh = wh; this.transfer = transfer; @@ -86,7 +95,10 @@ export class GatewayTransfer implements WormholeTransfer { }; // cache the wormchain chain context since we need it for checks - this.wc = this.wh.getChain(GatewayTransfer.chain); + this.gateway = gateway; + this.gatewayIbcBridge = gatewayIbc; + + // cache the message since we don't want to regenerate it any time we need it this.msg = gatewayTransferMsg(this.transfer); } @@ -112,8 +124,10 @@ export class GatewayTransfer implements WormholeTransfer { from: GatewayTransferDetails | WormholeMessageId | TransactionId, ): Promise { // Fresh new transfer + const wc = wh.getChain(GatewayTransfer.chain); + const wcibc = await wc.getIbcBridge(); if (isGatewayTransferDetails(from)) { - return new GatewayTransfer(wh, from); + return new GatewayTransfer(wh, from, wc, wcibc); } // Picking up where we left off @@ -129,7 +143,7 @@ export class GatewayTransfer implements WormholeTransfer { throw new Error("Invalid `from` parameter for GatewayTransfer"); } - const gt = new GatewayTransfer(wh, gtd); + const gt = new GatewayTransfer(wh, gtd, wc, wcibc); gt.transactions = txns; // Since we're picking up from somewhere we can move the @@ -401,8 +415,6 @@ export class GatewayTransfer implements WormholeTransfer { const attestations: AttestationId[] = []; this.ibcTransfers = []; - const wcIbc = await this.wc.getIbcBridge(); - // collect ibc transfers and additional transaction ids if (this.fromGateway()) { // assume all the txs are from the same chain @@ -433,7 +445,8 @@ export class GatewayTransfer implements WormholeTransfer { // now find the corresponding wormchain transaction given the ibcTransfer info const retryInterval = 5000; - const task = () => wcIbc.lookupMessageFromIbcMsgId(xfer.id); + const task = () => + this.gatewayIbcBridge.lookupMessageFromIbcMsgId(xfer.id); const whm = await retry(task, retryInterval); if (!whm) throw new Error( @@ -472,7 +485,7 @@ export class GatewayTransfer implements WormholeTransfer { // Wait until the vaa is redeemed before trying to look up the // transfer message - const wcTb = await this.wc.getTokenBridge(); + const wcTb = await this.gateway.getTokenBridge(); // Since we want to retry until its redeemed, return null // in the case that its not redeemed const isRedeemedTask = async () => { @@ -487,7 +500,8 @@ export class GatewayTransfer implements WormholeTransfer { // Note: Because we search by GatewayTransferMsg payload // there is a possibility of dupe messages being returned // using a nonce should help - const wcTransferTask = () => fetchIbcXfer(wcIbc, this.msg); + const wcTransferTask = () => + fetchIbcXfer(this.gatewayIbcBridge, this.msg); const wcTransfer = await retry( wcTransferTask, retryInterval, @@ -588,18 +602,17 @@ export class GatewayTransfer implements WormholeTransfer { throw new Error(`No serde defined for type: ${partial.payload[0]}`); } - // TODO: Is this a good enough check for what we want to do? + // Implicitly determine if the chain is Gateway enabled by + // checking to see if the Gateway IBC bridge has a transfer channel setup private fromGateway(): boolean { - //IbcBridge.getChannels(); - - return chainToPlatform(this.transfer.from.chain) === "Cosmwasm"; - // return networkChainToChannelId.has( - // this.wh.network, - // this.transfer.from.chain, - // ); + return ( + this.gatewayIbcBridge.getTransferChannel(this.transfer.from.chain) !== + null + ); } private toGateway(): boolean { - return chainToPlatform(this.transfer.to.chain) === "Cosmwasm"; - // return networkChainToChannelId.has(this.wh.network, this.transfer.to.chain); + return ( + this.gatewayIbcBridge.getTransferChannel(this.transfer.to.chain) !== null + ); } } diff --git a/core/definitions/src/protocols/ibc.ts b/core/definitions/src/protocols/ibc.ts index 47e258204b..40e377d5a9 100644 --- a/core/definitions/src/protocols/ibc.ts +++ b/core/definitions/src/protocols/ibc.ts @@ -151,7 +151,7 @@ export function gatewayTransferMsg( export function makeGatewayTransferMsg( chain: ChainName | ChainId, - recipient: NativeAddress<"Cosmwasm"> | string, + recipient: NativeAddress | string, fee: bigint = 0n, nonce: number, payload?: string, @@ -161,7 +161,7 @@ export function makeGatewayTransferMsg( const address = typeof recipient === "string" ? recipient - : // @ts-ignore + : //@ts-ignore Buffer.from(recipient.toString()).toString("base64"); const common = { @@ -229,8 +229,10 @@ export interface IbcBridge

{ ): AsyncGenerator; // cached from config - //getChannels(): IbcChannel | null; - //fetchChannels(): Promise; + getTransferChannel(chain: ChainName): string | null; + + // fetched from contract + fetchTransferChannel(chain: ChainName): Promise; lookupMessageFromIbcMsgId(msg: IbcMessageId): Promise; diff --git a/platforms/cosmwasm/src/constants.ts b/platforms/cosmwasm/src/constants.ts index a2b7d21599..7b521168cb 100644 --- a/platforms/cosmwasm/src/constants.ts +++ b/platforms/cosmwasm/src/constants.ts @@ -148,50 +148,32 @@ export const cosmwasmNetworkChainToRestUrls = constMap( cosmwasmNetworkChainRestUrl, ); -export type IbcChannel = { - srcChannel: string; - dstChannel: string; -}; +export type IbcChannels = Partial>; -// IBC Channels from the perspective of Wormchain +// For each chain, add the channel id for each other chain const gatewayConnections = [ [ "Mainnet", - [ - ["Cosmoshub", { srcChannel: "channel-5", dstChannel: "" }], - ["Osmosis", { srcChannel: "channel-4", dstChannel: "" }], - ], + [["Wormchain", { Cosmoshub: "channel-5", Osmosis: "channel-4" }]], ], [ "Testnet", [ - [ - "Cosmoshub", - { - srcChannel: "channel-5", - dstChannel: "channel-3086", - }, - ], - [ - "Osmosis", - { - srcChannel: "channel-9", - dstChannel: "channel-3906", - }, - ], + ["Wormchain", { Cosmoshub: "channel-5", Osmosis: "channel-9" }], + ["Cosmoshub", { Wormchain: "channel-3086" }], + ["Osmosis", { Wormchain: "channel-3906" }], ], ], [ "Devnet", [ - ["Cosmoshub", { srcChannel: "", dstChannel: "" }], - ["Osmosis", { srcChannel: "", dstChannel: "" }], + ["Wormchain", { Cosmoshub: "channel-1", Osmosis: "channel-2" }], + ["Cosmoshub", { Wormchain: "channel-1" }], + ["Osmosis", { Wormchain: "channel-1" }], ], ], ] as const satisfies RoArray< - readonly [Network, RoArray] + readonly [Network, RoArray] >; -export const networkChainToChannelId = constMap(gatewayConnections); -export const networkChannelToChain = constMap(gatewayConnections, [0, [2, 1]]); -export const networkToChannelMap = constMap(gatewayConnections, [0, [1, 2]]); +export const networkChainToChannels = constMap(gatewayConnections); diff --git a/platforms/cosmwasm/src/gateway.ts b/platforms/cosmwasm/src/gateway.ts index 2db1492939..912c5b8b0d 100644 --- a/platforms/cosmwasm/src/gateway.ts +++ b/platforms/cosmwasm/src/gateway.ts @@ -74,22 +74,27 @@ export module Gateway { return new CosmwasmAddress(factoryAddress); } - // Returns the destination channel on wormchain for given source chain + // Returns the destination channel from the perspective of wormchain for given source chain export function getDestinationChannel(chain: CosmwasmChainName): string { - const channels = CosmwasmPlatform.getIbcChannel(chain); + const channels = CosmwasmPlatform.getIbcChannels(Gateway.name); if (!channels) throw new Error("No channels configured for chain " + chain); - return channels.dstChannel; + if (!(chain in channels)) + throw new Error("No channel configured for chain " + chain); + return channels[chain]!; } - // Gets the source channel on wormchain for a given chain + + // Gets the source channel from the perspective of wormchain for a given chain export function getSourceChannel(chain: CosmwasmChainName): string { - const channels = CosmwasmPlatform.getIbcChannel(chain); + const channels = CosmwasmPlatform.getIbcChannels(chain); if (!channels) throw new Error("No channels configured for chain " + chain); - return channels.srcChannel; + if (!(Gateway.name in channels)) + throw new Error("No channel configured for chain " + chain); + return channels[Gateway.name]!; } // Returns whether or not a given chain is gateway supported export function isSupported(chain: CosmwasmChainName): boolean { - return CosmwasmPlatform.getIbcChannel(chain) !== null; + return CosmwasmPlatform.getIbcChannels(chain) !== null; } // Derive the Token Address with context for whether or not its managed diff --git a/platforms/cosmwasm/src/platform.ts b/platforms/cosmwasm/src/platform.ts index abf938f8bf..f40c3fd268 100644 --- a/platforms/cosmwasm/src/platform.ts +++ b/platforms/cosmwasm/src/platform.ts @@ -16,9 +16,9 @@ import { import { CosmwasmChain } from "./chain"; import { - IbcChannel, + IbcChannels, chainToNativeDenoms, - networkChainToChannelId, + networkChainToChannels, } from "./constants"; import { CosmwasmContracts } from "./contracts"; import { Gateway } from "./gateway"; @@ -118,11 +118,11 @@ export module CosmwasmPlatform { }; // cached channels from config if available - export const getIbcChannel = ( + export const getIbcChannels = ( chain: CosmwasmChainName, - ): IbcChannel | null => { - return networkChainToChannelId.has(network, chain) - ? networkChainToChannelId.get(network, chain)! + ): IbcChannels | null => { + return networkChainToChannels.has(network, chain) + ? networkChainToChannels.get(network, chain)! : null; }; } diff --git a/platforms/cosmwasm/src/protocols/ibc.ts b/platforms/cosmwasm/src/protocols/ibc.ts index f032a83472..8976d42f39 100644 --- a/platforms/cosmwasm/src/protocols/ibc.ts +++ b/platforms/cosmwasm/src/protocols/ibc.ts @@ -2,6 +2,7 @@ import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"; import { IndexedTx, MsgTransferEncodeObject, coin } from "@cosmjs/stargate"; import { ChainAddress, + ChainName, GatewayIbcTransferMsg, GatewayTransferMsg, GatewayTransferWithPayloadMsg, @@ -30,8 +31,8 @@ import { IBC_PACKET_SRC_PORT, IBC_TIMEOUT_MILLIS, IBC_TRANSFER_PORT, - IbcChannel, - networkToChannelMap, + IbcChannels, + networkChainToChannels, } from "../constants"; import { CosmwasmContracts } from "../contracts"; import { Gateway } from "../gateway"; @@ -51,7 +52,8 @@ export class CosmwasmIbcBridge implements IbcBridge<"Cosmwasm"> { private gatewayChannel?: string; // map the local channel ids to the remote chain - private channelMap: Map = new Map(); + private channelToChain: Map = new Map(); + private chainToChannel: Map = new Map(); private constructor( readonly network: Network, @@ -61,15 +63,15 @@ export class CosmwasmIbcBridge implements IbcBridge<"Cosmwasm"> { ) { this.gatewayAddress = this.contracts.getContracts(Gateway.name).gateway!; - const isGateway = this.chain === Gateway.name; - for (const [chain, channel] of networkToChannelMap(network)) { - if (!isGateway && this.chain === chain) - this.gatewayChannel = channel.dstChannel; + if (!networkChainToChannels.has(network, chain)) + throw new Error("Unsupported IBC Chain, no channels available: " + chain); - this.channelMap.set( - channel[isGateway ? "dstChannel" : "srcChannel"], - chain, - ); + // @ts-ignore + const channels: IbcChannels = networkChainToChannels(network, chain); + + for (const [chain, channel] of Object.entries(channels)) { + this.channelToChain.set(channel, chain as CosmwasmChainName); + this.chainToChannel.set(chain as CosmwasmChainName, channel); } } @@ -81,6 +83,10 @@ export class CosmwasmIbcBridge implements IbcBridge<"Cosmwasm"> { return new CosmwasmIbcBridge(network, chain, rpc, contracts); } + getTransferChannel(chain: ChainName): string | null { + return this.chainToChannel.get(chain as CosmwasmChainName) ?? null; + } + async *transfer( sender: UniversalOrCosmwasm, recipient: ChainAddress, @@ -299,7 +305,7 @@ export class CosmwasmIbcBridge implements IbcBridge<"Cosmwasm"> { msgId.chain = packet.type === IBC_PACKET_SEND ? this.chain - : this.channelMap.get(msgId.srcChannel!)!; + : this.channelToChain.get(msgId.srcChannel!)!; // Note: using the type guard to tell us if we have all the fields we expect if (isIbcMessageId(msgId)) xfer.id = msgId; @@ -341,26 +347,12 @@ export class CosmwasmIbcBridge implements IbcBridge<"Cosmwasm"> { return transfers; } - // Fetches the channel information between wormchain and a given chain - async fetchChannel(chain: CosmwasmChainName): Promise { - const queryClient = CosmwasmUtils.asQueryClient(this.rpc); - - const { channel: srcChannel } = await this.rpc.queryContractSmart( - this.gatewayAddress, - { ibc_channel: { chain_id: toChainId(chain) } }, - ); - const conn = await queryClient.ibc.channel.channel( - IBC_TRANSFER_PORT, - srcChannel, - ); - - const dstChannel = conn.channel?.counterparty?.channelId; - if (!dstChannel) - throw new Error( - `No destination channel found for chain ${chain} on ${this.chain}`, - ); - - return { srcChannel, dstChannel }; + // Fetches the local channel for the given chain + async fetchTransferChannel(chain: CosmwasmChainName): Promise { + const { channel } = await this.rpc.queryContractSmart(this.gatewayAddress, { + ibc_channel: { chain_id: toChainId(chain) }, + }); + return channel; } private createUnsignedTx(