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 15 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
6 changes: 3 additions & 3 deletions typescript/cli/src/config/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export async function createChainConfig({
await new ethers.providers.JsonRpcProvider().getNetwork();
return ethers.providers.JsonRpcProvider.defaultUrl();
},
'rpc url',
'Enter http or https',
'rpc url',
);
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

Expand All @@ -58,8 +58,8 @@ export async function createChainConfig({
const client = clientName.split('/')[0];
return `${client}${port}`;
},
'chain name',
'Enter (one word, lower case)',
'chain name',
);

const chainId = parseInt(
Expand All @@ -68,8 +68,8 @@ export async function createChainConfig({
const network = await provider.getNetwork();
return network.chainId.toString();
},
'chain id',
'Enter a (number)',
'chain id',
),
10,
);
Expand Down
143 changes: 81 additions & 62 deletions typescript/cli/src/config/warp.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,47 @@
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 {
detectAndConfirmOrPrompt,
runMultiChainSelectionStep,
runSingleChainSelectionStep,
} 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 +63,71 @@ export async function createWarpRouteDeployConfig({
outPath: string;
}) {
logBlue('Creating a new warp route deployment config');
const baseChain = await runSingleChainSelectionStep(
context.chainMetadata,
'Select base chain with the original token to warp',
);

const isNative = await confirm({
message:
'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?',
});

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)?',
});

const addressMessage = `Enter the ${
isYieldBearing ? 'ERC-4626 vault' : 'collateral token'
} address`;
const baseAddress = isNative
? ethers.constants.AddressZero
: await input({ message: addressMessage });
const owner = await detectAndConfirmOrPrompt(
async () => context.signer?.getAddress(),
'Enter the desired',
'owner address',
);

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

// 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,
const result: WarpRouteDeployConfig = {};
WarpRouteDeployConfigSchema;
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,
});

// TODO: restore NFT prompting
const isNft =
type === TokenType.syntheticUri || type === TokenType.collateralUri;

const mailbox = await detectAndConfirmOrPrompt(
async () => {
const addresses = await context.registry.getChainAddresses(chain);
return addresses?.mailbox;
},
};
}
`For ${chain}, enter the`,
'mailbox address',
);

syntheticChains.map((chain) => {
result[chain] = {
type: TokenType.synthetic,
};
});
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 on chain ${chain}`,
}),
};
break;
default:
result[chain] = { mailbox, type, owner, isNft };
}
}

if (isValidWarpRouteDeployConfig(result)) {
try {
const parsed = WarpRouteDeployConfigSchema.parse(result);
logGreen(`Warp Route config is valid, writing to file ${outPath}`);
writeYamlOrJson(outPath, result);
} else {
writeYamlOrJson(outPath, parsed);
} catch (e) {
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');
throw e;
}
}

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