Monitor warp balances script (#2682)

### Description

- Script to monitor token balances for Nautilus-Zebec-Solana warp route
(hardcoded)
- Helm command to deploy warp route monitoring script to Kubernetes

### Drive-by changes

- Updated default version to testnet3 for kathy and liquidity-layer

### Related issues

- issue https://github.com/hyperlane-xyz/issues/issues/549

### Backward compatibility

Yes

### Testing

Manual
token-testing-forge
Kunal Arora 1 year ago committed by GitHub
parent a9b522c5bd
commit 2b5d4c7b24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      typescript/infra/helm/helloworld-kathy/values.yaml
  2. 2
      typescript/infra/helm/liquidity-layer-relayers/values.yaml
  3. 24
      typescript/infra/helm/warp-routes/Chart.yaml
  4. 66
      typescript/infra/helm/warp-routes/templates/_helpers.tpl
  5. 24
      typescript/infra/helm/warp-routes/templates/stateful-set.yaml
  6. 8
      typescript/infra/helm/warp-routes/values.yaml
  7. 1
      typescript/infra/package.json
  8. 11
      typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts
  9. 35
      typescript/infra/scripts/warp-routes/helm.ts
  10. 147
      typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts
  11. 68
      typescript/infra/src/config/nautilus_token_config.ts
  12. 3
      yarn.lock

@ -2,7 +2,7 @@ image:
repository: gcr.io/hyperlane-labs-dev/hyperlane-monorepo
tag:
hyperlane:
runEnv: testnet2
runEnv: testnet3
context: hyperlane
# Only used for fetching secrets, so all chains should be included regardless of being skipped
chains: []

@ -2,7 +2,7 @@ image:
repository: gcr.io/abacus-labs-dev/hyperlane-monorepo
tag:
abacus:
runEnv: testnet2
runEnv: testnet3
# Used for fetching secrets
chains: []
externalSecrets:

@ -0,0 +1,24 @@
apiVersion: v2
name: warp-routes
description: Warp Routes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: '1.16.0'

@ -0,0 +1,66 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "hyperlane.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "hyperlane.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "hyperlane.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "hyperlane.labels" -}}
helm.sh/chart: {{ include "hyperlane.chart" . }}
hyperlane/deployment: {{ .Values.hyperlane.runEnv | quote }}
{{ include "hyperlane.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "hyperlane.selectorLabels" -}}
app.kubernetes.io/name: {{ include "hyperlane.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
The warp-routes container
*/}}
{{- define "hyperlane.warp-routes.container" }}
- name: warp-routes
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: IfNotPresent
command:
- ./node_modules/.bin/ts-node
- ./typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts
- -c
- "10000"
{{- end }}

@ -0,0 +1,24 @@
{{- if not .Values.hyperlane.cycleOnce }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "hyperlane.fullname" . }}
labels: &metadata_labels
hyperlane/deployment: {{ .Values.hyperlane.runEnv | quote }}
hyperlane/context: {{ .Values.hyperlane.context | quote }}
app.kubernetes.io/component: warp-routes
spec:
selector:
matchLabels: *metadata_labels
replicas: 1
serviceName: {{ include "hyperlane.fullname" . }}
template:
metadata:
labels: *metadata_labels
annotations:
prometheus.io/port: "9090"
prometheus.io/scrape: "true"
spec:
containers:
{{- include "hyperlane.warp-routes.container" . | indent 6 }}
{{- end }}

@ -0,0 +1,8 @@
image:
repository: gcr.io/hyperlane-labs-dev/hyperlane-monorepo
tag:
hyperlane:
runEnv: mainnet2
context: hyperlane
nameOverride: ""
fullnameOverride: ""

@ -12,6 +12,7 @@
"@ethersproject/hardware-wallets": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@hyperlane-xyz/helloworld": "1.5.0",
"@hyperlane-xyz/hyperlane-token": "1.5.0",
"@hyperlane-xyz/sdk": "1.5.0",
"@hyperlane-xyz/utils": "1.5.0",
"@nomiclabs/hardhat-etherscan": "^3.0.3",

@ -0,0 +1,11 @@
import { HelmCommand } from '../../src/utils/helm';
import { runWarpRouteHelmCommand } from './helm';
async function main() {
await runWarpRouteHelmCommand(HelmCommand.InstallOrUpgrade, 'mainnet2');
}
main()
.then(() => console.log('Deploy successful!'))
.catch(console.error);

@ -0,0 +1,35 @@
import { DeployEnvironment } from '../../src/config';
import { HelmCommand, helmifyValues } from '../../src/utils/helm';
import { execCmd } from '../../src/utils/utils';
import { assertCorrectKubeContext, getEnvironmentConfig } from '../utils';
export async function runWarpRouteHelmCommand(
helmCommand: HelmCommand,
runEnv: DeployEnvironment,
) {
const envConfig = getEnvironmentConfig(runEnv);
await assertCorrectKubeContext(envConfig);
const values = getWarpRoutesHelmValues();
return execCmd(
`helm ${helmCommand} ${getHelmReleaseName(
'zebec',
)} ./helm/warp-routes --namespace ${runEnv} ${values.join(
' ',
)} --set fullnameOverride="${getHelmReleaseName('zebec')}"`,
);
}
function getHelmReleaseName(route: string): string {
return `hyperlane-warp-route-${route}`;
}
function getWarpRoutesHelmValues() {
const values = {
image: {
repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo',
tag: '962d34b-20230905-194531',
},
};
return helmifyValues(values);
}

@ -0,0 +1,147 @@
import { Connection } from '@solana/web3.js';
import { ethers } from 'ethers';
import { Gauge, Registry } from 'prom-client';
import yargs from 'yargs';
import {
ERC20__factory,
SealevelHypCollateralAdapter,
TokenType,
} from '@hyperlane-xyz/hyperlane-token';
import { ChainMap, ChainName, MultiProvider } from '@hyperlane-xyz/sdk';
import {
ProtocolType,
debug,
objMap,
promiseObjAll,
} from '@hyperlane-xyz/utils';
import {
WarpTokenConfig,
tokenList,
} from '../../src/config/nautilus_token_config';
import { startMetricsServer } from '../../src/utils/metrics';
const metricsRegister = new Registry();
const warpRouteTokenBalance = new Gauge({
name: 'hyperlane_warp_route_token_balance',
help: 'HypERC20 token balance of a Warp Route',
registers: [metricsRegister],
labelNames: [
'chain_name',
'token_address',
'token_name',
'wallet_address',
'token_type',
],
});
async function main(): Promise<boolean> {
const { checkFrequency } = await yargs(process.argv.slice(2))
.describe('checkFrequency', 'frequency to check balances in ms')
.demandOption('checkFrequency')
.alias('c', 'checkFrequency')
.number('checkFrequency')
.parse();
startMetricsServer(metricsRegister);
const multiProvider = new MultiProvider();
setInterval(async () => {
try {
console.log('Checking balances');
const balances = await checkBalance(tokenList, multiProvider);
updateTokenBalanceMetrics(tokenList, balances);
} catch (e) {
console.error('Error checking balances', e);
}
}, checkFrequency);
return true;
}
// TODO: see issue https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2708
async function checkBalance(
tokenConfig: WarpTokenConfig,
multiprovider: MultiProvider,
): Promise<ChainMap<number>> {
const output: ChainMap<Promise<number>> = objMap(
tokenConfig,
async (chain: ChainName, token: WarpTokenConfig[ChainName]) => {
const provider = multiprovider.getProvider(chain);
if (token.type === TokenType.native) {
if (token.protocolType === ProtocolType.Ethereum) {
const nativeBalance = await provider.getBalance(
token.hypNativeAddress,
);
return parseFloat(
ethers.utils.formatUnits(nativeBalance, token.decimals),
);
} else {
// TODO - solana native
return 0;
}
} else {
if (token.protocolType === ProtocolType.Ethereum) {
const tokenContract = ERC20__factory.connect(token.address, provider);
const collateralBalance = await tokenContract.balanceOf(
token.hypCollateralAddress,
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
);
} else {
const connection = new Connection(multiprovider.getRpcUrl(chain));
const adapter = new SealevelHypCollateralAdapter(
connection,
token.hypCollateralAddress,
token.address,
token.isSpl2022,
);
const collateralBalance = ethers.BigNumber.from(
await adapter.getBalance(token.hypCollateralAddress),
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
);
}
}
},
);
return await promiseObjAll(output);
}
function updateTokenBalanceMetrics(
tokenConfig: WarpTokenConfig,
balances: ChainMap<number>,
) {
objMap(tokenConfig, (chain: ChainName, token: WarpTokenConfig[ChainName]) => {
const tokenAddress =
token.type === TokenType.native
? ethers.constants.AddressZero
: token.address;
const walletAddress =
token.type === TokenType.native
? token.hypNativeAddress
: token.hypCollateralAddress;
warpRouteTokenBalance
.labels({
chain_name: chain,
token_address: tokenAddress,
token_name: token.name,
wallet_address: walletAddress,
token_type: token.type,
})
.set(balances[chain]);
debug('Wallet balance updated for chain', {
chain,
token: token.name,
balance: balances[chain],
});
});
}
main().then(console.log).catch(console.error);

@ -0,0 +1,68 @@
import { TokenType } from '@hyperlane-xyz/hyperlane-token';
import { ChainMap } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
interface NativeTokenConfig {
chainId: number;
symbol: string;
name: string;
type: TokenType.native;
decimals: number;
hypNativeAddress: string;
protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel;
}
interface CollateralTokenConfig {
type: TokenType.collateral;
address: string;
chainId: number;
decimals: number;
symbol: string;
name: string;
hypCollateralAddress: string;
isSpl2022?: boolean;
protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel;
}
// TODO: migrate and dedupe to SDK from infra and Warp UI
export type WarpTokenConfig = ChainMap<
CollateralTokenConfig | NativeTokenConfig
>;
export const tokenList: WarpTokenConfig = {
// bsc
bsc: {
type: TokenType.collateral,
chainId: 56,
address: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303',
hypCollateralAddress: '0xC27980812E2E66491FD457D488509b7E04144b98',
symbol: 'ZBC',
name: 'Zebec',
decimals: 9,
protocolType: ProtocolType.Ethereum,
},
// nautilus
nautilus: {
type: TokenType.native,
chainId: 22222,
hypNativeAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7',
symbol: 'ZBC',
name: 'Zebec',
decimals: 18,
protocolType: ProtocolType.Ethereum,
},
// solana
solana: {
type: TokenType.collateral,
chainId: 1399811149,
address: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59',
hypCollateralAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa',
name: 'Zebec',
symbol: 'ZBC',
decimals: 9,
isSpl2022: false,
protocolType: ProtocolType.Sealevel,
},
};

@ -3990,7 +3990,7 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/hyperlane-token@workspace:typescript/token":
"@hyperlane-xyz/hyperlane-token@1.5.0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token":
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token"
dependencies:
@ -4038,6 +4038,7 @@ __metadata:
"@ethersproject/hardware-wallets": ^5.7.0
"@ethersproject/providers": ^5.7.2
"@hyperlane-xyz/helloworld": 1.5.0
"@hyperlane-xyz/hyperlane-token": 1.5.0
"@hyperlane-xyz/sdk": 1.5.0
"@hyperlane-xyz/utils": 1.5.0
"@nomiclabs/hardhat-ethers": ^2.2.1

Loading…
Cancel
Save