Skip to content

Commit

Permalink
Implement deployer balance checking
Browse files Browse the repository at this point in the history
Progress on balance refunding
  • Loading branch information
jmrossy committed Dec 26, 2024
1 parent 0b258e7 commit 4f10d60
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 30 deletions.
9 changes: 6 additions & 3 deletions src/features/chains/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,19 @@ export async function assembleChainMetadata(
(_, m): m is ChainMetadata => m.protocol === ProtocolType.Ethereum,
);

const chainMetadata = mergeChainMetadataMap(evmRegistryChainMetadata, filesystemMetadata);
let chainMetadata = mergeChainMetadataMap(evmRegistryChainMetadata, filesystemMetadata);

// Filter to only chains for which there are core deployment artifacts in the registry
// May want to revisit this later but it would require a way for users to provide these addresses
const chainsWithMailboxes = objFilter(
chainMetadata = objFilter(
chainMetadata,
(c, m): m is ChainMetadata => !!chainAddresses[c]?.mailbox,
);

// Filter to only chains with native token information as this is used in a few places
chainMetadata = objFilter(chainMetadata, (_, m): m is ChainMetadata => !!m.nativeToken);

const chainMetadataWithOverrides = mergeChainMetadataMap(chainMetadata, storeMetadataOverrides);

return { chainMetadata: chainsWithMailboxes, chainMetadataWithOverrides };
return { chainMetadata, chainMetadataWithOverrides };
}
32 changes: 18 additions & 14 deletions src/features/deployerWallet/fund.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
EvmNativeTokenAdapter,
getChainIdNumber,
MultiProtocolProvider,
ProviderType,
Token,
WarpTxCategory,
WarpTypedTransaction,
} from '@hyperlane-xyz/sdk';
Expand Down Expand Up @@ -80,15 +80,17 @@ async function executeTransfer({
}) {
logger.debug('Preparing to fund deployer', deployerAddress, chainName);

const protocol = multiProvider.getProtocol(chainName);
const sendTransaction = transactionFns[protocol].sendTransaction;
const activeChainName = activeChains.chains[protocol].chainName;
const chainMetadata = multiProvider.getChainMetadata(chainName);
const sendTransaction = transactionFns[chainMetadata.protocol].sendTransaction;
const activeChainName = activeChains.chains[chainMetadata.protocol].chainName;
const sender = getAccountAddressForChain(multiProvider, chainName, activeAccounts.accounts);
if (!sender) throw new Error(`No active account found for chain ${chainName}`);

const amount = await getFundingAmount(chainName, gasUnits, multiProvider);
await assertSenderBalance(sender, chainName, amount, multiProvider);
const tx = await getFundingTx(deployerAddress, chainName, amount, multiProvider);

const token = Token.FromChainMetadataNativeToken(chainMetadata);
await assertSenderBalance(sender, amount, token, multiProvider);
const tx = await getFundingTx(deployerAddress, amount, token, multiProvider);

try {
const { hash, confirm } = await sendTransaction({
Expand All @@ -112,6 +114,7 @@ async function executeTransfer({
}
}

// TODO multi-protocol support
async function getFundingAmount(
chainName: ChainName,
gasUnits: bigint,
Expand All @@ -124,26 +127,27 @@ async function getFundingAmount(

async function assertSenderBalance(
sender: Address,
chainName: ChainName,
amount: bigint,
token: Token,
multiProvider: MultiProtocolProvider,
) {
const adapter = new EvmNativeTokenAdapter(chainName, multiProvider, {});
const balance = await adapter.getBalance(sender);
assert(balance >= amount, 'Insufficient balance for deployment');
const balance = await token.getBalance(multiProvider, sender);
assert(balance.amount >= amount, 'Insufficient balance for deployment');
}

// TODO edit Widgets lib to default to TypedTransaction instead of WarpTypedTransaction?
// TODO multi-protocol support
async function getFundingTx(
recipient: Address,
chainName: ChainName,
amount: bigint,
token: Token,
multiProvider: MultiProtocolProvider,
): Promise<WarpTypedTransaction> {
const adapter = new EvmNativeTokenAdapter(chainName, multiProvider, {});
const tx = await adapter.populateTransferTx({ recipient, weiAmountOrId: amount });
const tx = token
.getAdapter(multiProvider)
.populateTransferTx({ recipient, weiAmountOrId: amount });
// Add chainId to help reduce likely of wallet signing on wrong chain
const chainId = getChainIdNumber(multiProvider.getChainMetadata(chainName));
const chainId = getChainIdNumber(multiProvider.getChainMetadata(token.chainName));
// TODO remove data when widgets lib is updated
const txParams = { ...tx, chainId, data: '0x' };
return {
Expand Down
6 changes: 5 additions & 1 deletion src/features/deployerWallet/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ async function getOrCreateTempDeployerWallets(
return wallets;
}

// TODO multi-protocol support
async function createTempDeployerWallet(protocol: ProtocolType): Promise<[TypedWallet, string]> {
logger.info('Creating temp deployer wallet for:', protocol);
let wallet: TypedWallet;
Expand All @@ -95,6 +96,7 @@ async function createTempDeployerWallet(protocol: ProtocolType): Promise<[TypedW
return [wallet, encryptedKey];
}

// TODO multi-protocol support
async function getTempDeployerWallet(
protocol: ProtocolType,
encryptedKey: string,
Expand All @@ -113,10 +115,12 @@ async function getTempDeployerWallet(
}
}

// TODO multi-protocol support
export function getDeployerAddressForProtocol(
wallets: TempDeployerWallets,
protocol: ProtocolType,
protocol?: ProtocolType,
) {
if (!protocol) return undefined;
const typedWallet = wallets[protocol];
if (!typedWallet) return undefined;
if (typedWallet.type === ProviderType.EthersV5) {
Expand Down
83 changes: 74 additions & 9 deletions src/features/deployerWallet/refund.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,94 @@
import { sleep } from '@hyperlane-xyz/utils';
import { MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { useMutation } from '@tanstack/react-query';
import { logger } from 'ethers';
import { useToastError } from '../../components/toast/useToastError';
import { useTempDeployerWallets } from './hooks';
import { logger } from '../../utils/logger';
import { useMultiProvider } from '../chains/hooks';
import { useDeploymentChains } from '../deployment/hooks';
import { getDeployerAddressForProtocol, useTempDeployerWallets } from './hooks';
import { TempDeployerWallets } from './types';

export function useRefundDeployerAccounts({ onSuccess }: { onSuccess?: () => void }) {
const multiProvider = useMultiProvider();
const chains = useDeploymentChains();
const { wallets } = useTempDeployerWallets([]);

const { error, mutate } = useMutation({
mutationKey: ['refundDeployerAccounts', wallets],
mutationFn: () => refundDeployerAccounts(wallets),
mutationKey: ['refundDeployerAccounts', chains, wallets],
mutationFn: () => refundDeployerAccounts(chains, wallets, multiProvider),
retry: 3,
onSuccess,
});

useToastError(error, 'Error refunding deployer balances. Please try again later.');
useToastError(
error,
'Error refunding deployer balances. The key has been stored. Please try again later.',
);

return mutate;
}

async function refundDeployerAccounts(_wallets: TempDeployerWallets) {
//TODO
async function refundDeployerAccounts(
chains: ChainName[],
wallets: TempDeployerWallets,
multiProvider: MultiProtocolProvider,
) {
logger.info('Refunding deployer accounts');
await sleep(10_000);
const nonZeroBalances = await getDeployerBalances(chains, wallets, multiProvider);
const txReceipts = await transferBalances(nonZeroBalances, wallets, multiProvider);
logger.info('Done refunding deployer accounts');
return true;
}

interface Balance {
chainName: ChainName;
protocol: ProtocolType;
address: Address;
amount: bigint;
}

async function getDeployerBalances(
chains: ChainName[],
wallets: TempDeployerWallets,
multiProvider: MultiProtocolProvider,
) {
const balances: Array<PromiseSettledResult<Balance | undefined>> = await Promise.allSettled(
chains.map(async (chainName) => {
const chainMetadata = multiProvider.tryGetChainMetadata(chainName);
const address = getDeployerAddressForProtocol(wallets, chainMetadata?.protocol);
if (!chainMetadata || !address) return undefined;
const token = Token.FromChainMetadataNativeToken(chainMetadata);
logger.debug('Checking balance', chainName, address);
const balance = await token.getBalance(multiProvider, address);
logger.debug('Balance retrieved', chainName, address, balance.amount);
return { chainName, protocol: chainMetadata.protocol, address, amount: balance.amount };
}),
);
const nonZeroBalances = balances
.filter((b) => b.status === 'fulfilled')
.map((b) => b.value)
.filter((b): b is Balance => !!b && b.amount > 0n);
logger.debug(
'Non-zero balances found for chains:',
nonZeroBalances.map((b) => b.chainName),
);
return nonZeroBalances;
}

async function transferBalances(
balances: Balance[],
wallets: TempDeployerWallets,
multiProvider: MultiProtocolProvider,
) {
const txReceipts: Array<PromiseSettledResult<string>> = await Promise.allSettled(
balances.map(async (balance) => {
const { chainName, protocol, address: deployerAddress, amount } = balance;
const chainMetadata = multiProvider.getChainMetadata(chainName);
const token = Token.FromChainMetadataNativeToken(chainMetadata);
logger.debug('Preparing transfer', chainName, amount);
// TODO generalize and call getFundingTx from fund.ts
}),
);

// TODO process txReceipts
}
9 changes: 9 additions & 0 deletions src/features/deployment/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { useStore } from '../store';
import { DeploymentType } from './types';

Expand Down Expand Up @@ -31,3 +32,11 @@ export function useDeploymentHistory() {
currentIndex: state.deployments.length - 1,
};
}

export function useDeploymentChains() {
const { deployments } = useDeploymentHistory();
return useMemo<ChainName[]>(
() => Array.from(new Set(deployments.map((d) => d.config.chains).flat())),
[deployments],
);
}
4 changes: 1 addition & 3 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
declare type Address = string;
declare type Address = UtilsAddress;
declare type ChainName = string;
declare type ChainId = number | string;
declare type DomainId = number;

declare module '*.yaml' {
const data: any;
Expand Down

0 comments on commit 4f10d60

Please sign in to comment.