Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi collateral warp routes #3820

Merged
merged 20 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/nice-rivers-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---

Implement multi collateral warp routes
137 changes: 75 additions & 62 deletions typescript/cli/src/config/warp.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import { confirm, input } from '@inquirer/prompts';
import { ethers } from 'ethers';
import { input, select } from '@inquirer/prompts';

import {
ChainMetadata,
TokenType,
WarpCoreConfig,
WarpCoreConfigSchema,
WarpRouteDeployConfig,
WarpRouteDeployConfigSchema,
} from '@hyperlane-xyz/sdk';
import { objFilter } from '@hyperlane-xyz/utils';

import { CommandContext } from '../context/types.js';
import { errorRed, logBlue, logGreen } from '../logger.js';
import {
runMultiChainSelectionStep,
runSingleChainSelectionStep,
} from '../utils/chains.js';
import { runMultiChainSelectionStep } from '../utils/chains.js';
import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js';

const TYPE_DESCRIPTIONS: Record<TokenType, string> = {
[TokenType.synthetic]: 'A new ERC20 with remote transfer functionality',
[TokenType.collateral]:
'Extends an existing ERC20 with remote transfer functionality',
[TokenType.native]:
'Extends the native token with remote transfer functionality',
[TokenType.collateralVault]:
'Extends an existing ERC4626 with remote transfer functionality',
[TokenType.collateralFiat]:
'Extends an existing FiatToken with remote transfer functionality',
[TokenType.collateralXERC20]:
'Extends an existing xERC20 with Warp Route functionality',
// TODO: describe
[TokenType.fastSynthetic]: '',
[TokenType.syntheticUri]: '',
[TokenType.fastCollateral]: '',
[TokenType.collateralUri]: '',
[TokenType.nativeScaled]: '',
};

const TYPE_CHOICES = Object.values(TokenType).map((type) => ({
name: type,
value: type,
description: TYPE_DESCRIPTIONS[type],
}));

export function readWarpRouteDeployConfig(
filePath: string,
): WarpRouteDeployConfig {
Expand All @@ -40,75 +60,68 @@ export async function createWarpRouteDeployConfig({
outPath: string;
}) {
logBlue('Creating a new warp route deployment config');
const baseChain = await runSingleChainSelectionStep(

const owner =
(await context.signer?.getAddress()) ??
(await input({
message: 'Enter owner address',
}));

const warpChains = await runMultiChainSelectionStep(
context.chainMetadata,
'Select base chain with the original token to warp',
'Select chains to connect',
);

const isNative = await confirm({
message:
'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?',
});
const result: WarpRouteDeployConfig = {};
for (const chain of warpChains) {
logBlue(`Configuring warp route for chain ${chain}`);
const type = await select({
message: `Select ${chain}'s token type`,
choices: TYPE_CHOICES,
});

const isNft = isNative
? false
: await confirm({ message: 'Is this an NFT (i.e. ERC-721)?' });
const isYieldBearing =
isNative || isNft
? false
: await confirm({
message:
'Do you want this warp route to be yield-bearing (i.e. deposits into ERC-4626 vault)?',
});
// TODO: restore NFT prompting
const isNft =
type === TokenType.syntheticUri || type === TokenType.collateralUri;

const addressMessage = `Enter the ${
isYieldBearing ? 'ERC-4626 vault' : 'collateral token'
} address`;
const baseAddress = isNative
? ethers.constants.AddressZero
: await input({ message: addressMessage });
// TODO: migrate to detectAndConfirmOrPrompt
const addresses = await context.registry.getChainAddresses(chain);
yorhodes marked this conversation as resolved.
Show resolved Hide resolved
const mailbox =
addresses?.mailbox ??
(await input({
message: `Enter the mailbox address for chain ${chain}`,
}));

const metadataWithoutBase = objFilter(
context.chainMetadata,
(chain, _): _ is ChainMetadata => chain !== baseChain,
);
const syntheticChains = await runMultiChainSelectionStep(
metadataWithoutBase,
'Select chains to which the base token will be connected',
);

// TODO add more prompts here to support customizing the token metadata
let result: WarpRouteDeployConfig;
if (isNative) {
result = {
[baseChain]: {
type: TokenType.native,
},
};
} else {
result = {
[baseChain]: {
type: isYieldBearing ? TokenType.collateralVault : TokenType.collateral,
token: baseAddress,
isNft,
},
};
switch (type) {
case TokenType.collateral:
case TokenType.collateralXERC20:
case TokenType.collateralFiat:
case TokenType.collateralUri:
case TokenType.fastCollateral:
case TokenType.collateralVault:
result[chain] = {
mailbox,
type,
owner,
isNft,
token: await input({
message: `Enter the existing token address for chain ${chain}`,
}),
};
break;
default:
result[chain] = { mailbox, type, owner, isNft };
}
}

syntheticChains.map((chain) => {
result[chain] = {
type: TokenType.synthetic,
};
});

if (isValidWarpRouteDeployConfig(result)) {
logGreen(`Warp Route config is valid, writing to file ${outPath}`);
writeYamlOrJson(outPath, result);
} else {
errorRed(
`Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example`,
yorhodes marked this conversation as resolved.
Show resolved Hide resolved
);
throw new Error('Invalid multisig config');
WarpRouteDeployConfigSchema.parse(result); // throws error
}
}

Expand Down
30 changes: 0 additions & 30 deletions typescript/cli/src/deploy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,6 @@ import { assertSigner } from '../utils/keys.js';

import { completeDryRun } from './dry-run.js';

export async function runPreflightChecks({
context,
origin,
remotes,
minGas,
chainsToGasCheck,
}: {
context: WriteCommandContext;
origin: ChainName;
remotes: ChainName[];
minGas: string;
chainsToGasCheck?: ChainName[];
}) {
log('Running pre-flight checks...');

if (!origin || !remotes?.length) throw new Error('Invalid chain selection');
logGreen('✅ Chain selections are valid');

if (remotes.includes(origin))
throw new Error('Origin and remotes must be distinct');
logGreen('✅ Origin and remote are distinct');

return runPreflightChecksForChains({
context,
chains: [origin, ...remotes],
minGas,
chainsToGasCheck,
});
}

export async function runPreflightChecksForChains({
context,
chains,
Expand Down
Loading
Loading