Skip to content

Commit

Permalink
Move getForeignAsset gateway logic from the CosmosGateway route to th…
Browse files Browse the repository at this point in the history
…e cosmos context. Remove some sdk unneeded dependencies (#941)
  • Loading branch information
M-Picco authored Sep 11, 2023
1 parent 0a22028 commit ab3950a
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 328 deletions.
310 changes: 99 additions & 211 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@
"@cosmjs/cosmwasm-stargate": "^0.31.0",
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@injectivelabs/sdk-ts": "1.0.211",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@mysten/sui.js": "^0.32.2",
"@nomad-xyz/multi-provider": "^1.1.0",
"@solana/spl-token": "^0.3.7",
"@solana/web3.js": "^1.73.0",
"@terra-money/terra.js": "^3.1.7",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@xpla/xpla.js": "^0.2.3",
"algosdk": "^1.15.0",
"aptos": "1.5.0",
"axios": "^0.27.2",
Expand Down
157 changes: 139 additions & 18 deletions sdk/src/contexts/cosmos/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {
CHAIN_ID_OSMOSIS,
CHAIN_ID_SEI,
CHAIN_ID_WORMCHAIN,
cosmos,
parseTokenTransferPayload,
parseVaa,
Expand All @@ -7,15 +10,19 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { EncodeObject, decodeTxRaw } from '@cosmjs/proto-signing';
import {
Coin,
IbcExtension,
QueryClient,
StargateClient,
StdFee,
calculateFee,
logs as cosmosLogs,
setupIbcExtension,
} from '@cosmjs/stargate';
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx';
import { BigNumber } from 'ethers';
import { BigNumber, utils } from 'ethers';
import {
arrayify,
base58,
base64,
hexStripZeros,
hexlify,
Expand All @@ -35,8 +42,13 @@ import {
import { WormholeContext } from '../../wormhole';
import { TokenBridgeAbstract } from '../abstracts/tokenBridge';
import { CosmosContracts } from './contracts';
import { searchCosmosLogs } from './utils';
import { isCosmWasmChain, searchCosmosLogs } from './utils';
import { ForeignAssetCache } from '../../utils';
import {
Tendermint34Client,
Tendermint37Client,
TendermintClient,
} from '@cosmjs/tendermint-rpc';

export interface CosmosTransaction {
fee: StdFee | 'auto' | number;
Expand Down Expand Up @@ -76,6 +88,8 @@ const buildExecuteMsg = (
}),
});

const IBC_PORT = 'transfer';

export class CosmosContext<
T extends WormholeContext,
> extends TokenBridgeAbstract<CosmosTransaction> {
Expand All @@ -84,7 +98,7 @@ export class CosmosContext<
readonly context: T;
readonly chain: ChainName;

private static CLIENT_MAP: Record<string, CosmWasmClient> = {};
private static CLIENT_MAP: Record<string, TendermintClient> = {};
private foreignAssetCache: ForeignAssetCache;

constructor(
Expand Down Expand Up @@ -192,6 +206,96 @@ export class CosmosContext<
}

async getForeignAsset(
tokenId: TokenId,
chain: ChainId | ChainName,
): Promise<string | null> {
return this.context.toChainId(chain) === CHAIN_ID_WORMCHAIN
? this.getWhForeignAsset(tokenId, chain)
: this.getGatewayForeignAsset(tokenId, chain);
}

async getGatewayForeignAsset(
tokenId: TokenId,
chain: ChainId | ChainName,
): Promise<string | null> {
// add check here in case the token is a native cosmos denom
// in such cases there's no need to look for in the wormchain network
if (tokenId.chain === chain) return tokenId.address;
const wrappedAsset = await this.getWhForeignAsset(
tokenId,
CHAIN_ID_WORMCHAIN,
);
if (!wrappedAsset) return null;
return this.isNativeDenom(wrappedAsset, chain)
? wrappedAsset
: this.deriveIBCDenom(this.CW20AddressToFactory(wrappedAsset), chain);
}

private CW20AddressToFactory(address: string): string {
const encodedAddress = base58.encode(cosmos.canonicalAddress(address));
return `factory/${this.getTranslatorAddress()}/${encodedAddress}`;
}

getTranslatorAddress(): string {
const addr =
this.context.conf.chains['wormchain']?.contracts.ibcShimContract;
if (!addr) throw new Error('IBC Shim contract not configured');
return addr;
}

async deriveIBCDenom(
denom: string,
chain: ChainId | ChainName,
): Promise<string | null> {
const channel = await this.getIbcDestinationChannel(chain);
const hashData = utils.hexlify(Buffer.from(`transfer/${channel}/${denom}`));
const hash = utils.sha256(hashData).substring(2);
return `ibc/${hash.toUpperCase()}`;
}

async getIbcDestinationChannel(chain: ChainId | ChainName): Promise<string> {
const sourceChannel = await this.getIbcSourceChannel(chain);
const queryClient = await this.getQueryClient(CHAIN_ID_WORMCHAIN);
const conn = await queryClient.ibc.channel.channel(IBC_PORT, sourceChannel);

const destChannel = conn.channel?.counterparty?.channelId;
if (!destChannel) {
throw new Error(`No destination channel found on chain ${chain}`);
}

return destChannel;
}

async getIbcSourceChannel(chain: ChainId | ChainName): Promise<string> {
const id = this.context.toChainId(chain);
if (!isCosmWasmChain(id)) throw new Error('Chain is not cosmos chain');
const client = await this.getCosmWasmClient(CHAIN_ID_WORMCHAIN);
const { channel } = await client.queryContractSmart(
this.getTranslatorAddress(),
{
ibc_channel: {
chain_id: id,
},
},
);
return channel;
}

private isNativeDenom(denom: string, chain: ChainName | ChainId): boolean {
const chainId = this.context.toChainId(chain);
switch (chainId) {
case CHAIN_ID_SEI:
return denom === 'usei';
case CHAIN_ID_WORMCHAIN:
return denom === 'uworm';
case CHAIN_ID_OSMOSIS:
return denom === 'uosmo';
default:
return false;
}
}

async getWhForeignAsset(
tokenId: TokenId,
chain: ChainName | ChainId,
): Promise<string | null> {
Expand Down Expand Up @@ -269,16 +373,7 @@ export class CosmosContext<
): Promise<BigNumber | null> {
const assetAddress = await this.getForeignAsset(tokenId, chain);
if (!assetAddress) return null;

if (assetAddress === this.getNativeDenom(chain)) {
return this.getNativeBalance(walletAddress, chain);
}

const client = await this.getCosmWasmClient(chain);
const { balance } = await client.queryContractSmart(assetAddress, {
balance: { address: walletAddress },
});
return BigNumber.from(balance);
return this.getNativeBalance(walletAddress, chain, assetAddress);
}

private getNativeDenom(chain: ChainName | ChainId): string {
Expand Down Expand Up @@ -434,19 +529,45 @@ export class CosmosContext<
};
}

private async getCosmWasmClient(
private async getQueryClient(
chain: ChainId | ChainName,
): Promise<CosmWasmClient> {
): Promise<QueryClient & IbcExtension> {
const tmClient = await this.getTmClient(chain);
return QueryClient.withExtensions(tmClient, setupIbcExtension);
}

private async getTmClient(
chain: ChainId | ChainName,
): Promise<Tendermint34Client | Tendermint37Client> {
const name = this.context.toChainName(chain);
if (CosmosContext.CLIENT_MAP[name]) {
return CosmosContext.CLIENT_MAP[name];
}

const rpc = this.context.conf.rpcs[name];
if (!rpc) throw new Error(`${chain} RPC not configured`);
const client = await CosmWasmClient.connect(rpc);
CosmosContext.CLIENT_MAP[name] = client;
return client;

// from cosmjs: https://github.com/cosmos/cosmjs/blob/358260bff71c9d3e7ad6644fcf64dc00325cdfb9/packages/stargate/src/stargateclient.ts#L218
let tmClient: TendermintClient;
const tm37Client = await Tendermint37Client.connect(rpc);
const version = (await tm37Client.status()).nodeInfo.version;
if (version.startsWith('0.37.')) {
tmClient = tm37Client;
} else {
tm37Client.disconnect();
tmClient = await Tendermint34Client.connect(rpc);
}

CosmosContext.CLIENT_MAP[name] = tmClient;

return tmClient;
}

private async getCosmWasmClient(
chain: ChainId | ChainName,
): Promise<CosmWasmClient> {
const tmClient = await this.getTmClient(chain);
return CosmWasmClient.create(tmClient);
}

getTokenBridgeAddress(chain: ChainName): string {
Expand Down
17 changes: 17 additions & 0 deletions sdk/src/contexts/cosmos/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import {
CHAIN_ID_OSMOSIS,
CHAIN_ID_TERRA2,
CHAIN_ID_WORMCHAIN,
isCosmWasmChain as isBaseCosmWasmChain,
} from '@certusone/wormhole-sdk';
import { logs as cosmosLogs } from '@cosmjs/stargate';
import { ChainId } from '../../types';

/**
* Search for a specific piece of information emitted by the contracts during the transaction
Expand All @@ -20,3 +27,13 @@ export const searchCosmosLogs = (
}
return null;
};

const COSMOS_CHAINS: ChainId[] = [
CHAIN_ID_WORMCHAIN,
CHAIN_ID_OSMOSIS,
CHAIN_ID_TERRA2,
];

export function isCosmWasmChain(chainId: ChainId): boolean {
return isBaseCosmWasmChain(chainId) || COSMOS_CHAINS.includes(chainId);
}
1 change: 1 addition & 0 deletions wormhole-connect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.20.2",
"jest": "^29.5.0",
"react": "^18.2.0",
Expand Down
39 changes: 12 additions & 27 deletions wormhole-connect/src/components/TokensModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import Scroll from './Scroll';
import TokenIcon from '../icons/TokenIcons';
import CircularProgress from '@mui/material/CircularProgress';
import Tabs from './Tabs';
import { CosmosGatewayRoute } from 'utils/routes';

const useStyles = makeStyles()((theme: any) => ({
tokensContainer: {
Expand Down Expand Up @@ -271,7 +270,6 @@ function TokensModal(props: Props) {
supportedSourceTokens,
supportedDestTokens,
allSupportedDestTokens,
route,
} = useSelector((state: RootState) => state.transferInput);

const supportedTokens = useMemo(() => {
Expand Down Expand Up @@ -346,30 +344,9 @@ function TokensModal(props: Props) {
queryTokens.map(async (t) => {
let balance: BigNumber | null = null;
try {
if (t.tokenId) {
// TODO: going to do some refactoring of gateway/cosmos contexts
if (isCosmWasmChain(wh.toChainId(chain))) {
const denom = await new CosmosGatewayRoute().getForeignAsset(
t.tokenId,
chain,
);
if (denom) {
balance = await wh.getNativeBalance(
walletAddress,
chain,
denom,
);
}
} else {
balance = await wh.getTokenBalance(
walletAddress,
t.tokenId,
chain,
);
}
} else {
balance = await wh.getNativeBalance(walletAddress, chain);
}
balance = t.tokenId
? await wh.getTokenBalance(walletAddress, t.tokenId, chain)
: await wh.getNativeBalance(walletAddress, chain);
} catch (e) {
console.warn('Failed to fetch balance', e);
}
Expand All @@ -388,7 +365,15 @@ function TokensModal(props: Props) {
balances,
}),
);
}, [walletAddress, chain, dispatch, type, supportedTokens, route, open]);
}, [
walletAddress,
chain,
dispatch,
type,
supportedTokens,
chainBalancesCache,
allSupportedDestTokens,
]);

// fetch token balances and set in store
useEffect(() => {
Expand Down
18 changes: 5 additions & 13 deletions wormhole-connect/src/utils/cosmos.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { ChainName, ChainId } from '@wormhole-foundation/wormhole-connect-sdk';
import {
CHAIN_ID_OSMOSIS,
CHAIN_ID_TERRA2,
CHAIN_ID_WORMCHAIN,
isCosmWasmChain as isBaseCosmWasmChain,
} from '@certusone/wormhole-sdk';
ChainName,
ChainId,
isCosmWasmChain as isCosmWasmChainSdk,
} from '@wormhole-foundation/wormhole-connect-sdk';
import { wh } from './sdk';

const COSMOS_CHAINS: ChainId[] = [
CHAIN_ID_WORMCHAIN,
CHAIN_ID_OSMOSIS,
CHAIN_ID_TERRA2,
];

export function isCosmWasmChain(chainId: ChainId | ChainName): boolean {
const id = wh.toChainId(chainId);
return isBaseCosmWasmChain(id) || COSMOS_CHAINS.includes(id);
return isCosmWasmChainSdk(id);
}
Loading

0 comments on commit ab3950a

Please sign in to comment.