Token deployer yb route (#3435)

### Description
Adds yield-bearing warp routes to CLI `create` and `deploy`

### Drive-by changes
- hyperlane config create warp
- Add logic to prompt user `Do you want this warp route to be
yield-bearing (i.e. deposits into ERC-4626 vault)?`
  - If yes
    -  Prompt `Enter the ERC-4626 vault address`
      -  Otherwise, `Enter the collateral token address`
- warp-token will be created with `base.address` set to the vault
address
    - Updated prompt flow
    ```
    Select base chain with the original token to warp sepolia? 
Are you creating a route for the native token of the base chain (e.g.
Ether on Ethereum)? no
    Is this an NFT (i.e. ERC-721)? no
Do you want this warp route to be yield-bearing (i.e. deposits into
ERC-4626 vault)?
    // If yes
    Enter the ERC-4626 vault address
    // If not a warp route
    Enter the collateral token address
    ``` 
- hyperlane deploy warp
- Adds logic to check if `base.type == collateralVault`, make sure that
`base.address` exists (and not address(0)).
  - Deploys `HypERC20CollateralVaultDeposit.sol` using `base.address`
- Update example/warp-route-deployment.yaml
- Made some quality of life fixes
  - Add more details to logs
- Add `yarn dev` in sdk to use `tsc --watch`. Similar to what's in the
cli

### Related issues
#3416 

### Backward compatibility
Yes

### Testing
Use this mock vault: 0xa2ecc6478d0aa9f4423a118c9a2d2b4bcf178a5b

Manual/Unit Tests
pull/3447/head
Lee 8 months ago committed by GitHub
parent 0338f81116
commit dcb67e97da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      typescript/cli/examples/warp-route-deployment.yaml
  2. 67
      typescript/cli/src/config/warp.ts
  3. 6
      typescript/cli/src/deploy/utils.ts
  4. 15
      typescript/cli/src/deploy/warp.ts
  5. 1
      typescript/sdk/package.json
  6. 6
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  7. 9
      typescript/sdk/src/token/Token.test.ts
  8. 3
      typescript/sdk/src/token/TokenStandard.ts
  9. 8
      typescript/sdk/src/token/config.ts
  10. 2
      typescript/sdk/src/token/contracts.ts
  11. 26
      typescript/sdk/src/token/deploy.ts

@ -4,6 +4,7 @@
# Token Types:
# native
# collateral
# collateralVault
# synthetic
# collateralUri
# syntheticUri
@ -13,7 +14,7 @@
base:
chainName: anvil1
type: native
# address: 0x123... # Required for collateral types
# address: 0x123... # Required for collateral types. For collateralVault types, specifies the ERC4626 vault to deposit collateral into
# isNft: true # If the token is an NFT (ERC721), set to true
# owner: 0x123 # Optional owner address for synthetic token
# mailbox: 0x123 # Optional mailbox address route

@ -20,16 +20,36 @@ const ConnectionConfigSchema = {
};
export const WarpRouteDeployConfigSchema = z.object({
base: z.object({
type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)),
chainName: z.string(),
address: ZHash.optional(),
isNft: z.boolean().optional(),
name: z.string().optional(),
symbol: z.string().optional(),
decimals: z.number().optional(),
...ConnectionConfigSchema,
}),
base: z
.object({
type: z
.literal(TokenType.native)
.or(z.literal(TokenType.collateral))
.or(z.literal(TokenType.collateralVault)),
chainName: z.string(),
address: ZHash.optional(),
isNft: z.boolean().optional(),
name: z.string().optional(),
symbol: z.string().optional(),
decimals: z.number().optional(),
...ConnectionConfigSchema,
})
.refine(
(data) => {
// For collateralVault Warp Routes, address will specify the vault
if (
data.type === TokenType.collateralVault &&
data.address === ethers.constants.AddressZero
)
return false;
return true;
},
{
message: 'Vault address is required when type is collateralVault',
path: ['address'],
},
),
synthetics: z
.array(
z.object({
@ -89,13 +109,23 @@ export async function createWarpRouteDeployConfig({
'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?',
});
const baseType = isNative ? TokenType.native : TokenType.collateral;
const baseAddress = isNative
? ethers.constants.AddressZero
: await input({ message: 'Enter the token address' });
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 syntheticChains = await runMultiChainSelectionStep(
customChains,
@ -103,7 +133,14 @@ export async function createWarpRouteDeployConfig({
);
// TODO add more prompts here to support customizing the token metadata
let baseType: TokenType;
if (isNative) {
baseType = TokenType.native;
} else {
baseType = isYieldBearing
? TokenType.collateralVault
: TokenType.collateral;
}
const result: WarpRouteDeployConfig = {
base: {
chainName: baseChain,

@ -32,8 +32,12 @@ export async function runPreflightChecks({
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({
chains: [origin, ...remotes],
signer,
@ -58,7 +62,7 @@ export async function runPreflightChecksForChains({
// Defaults to all chains if not specified
chainsToGasCheck?: ChainName[];
}) {
log('Running pre-flight checks...');
log('Running pre-flight checks for chains...');
if (!chains?.length) throw new Error('Empty chain selection');
for (const chain of chains) {

@ -124,8 +124,8 @@ async function runBuildConfigStep({
const { type: baseType, chainName: baseChainName, isNft } = base;
const owner = await signer.getAddress();
const baseMetadata = await fetchBaseTokenMetadata(base, multiProvider);
log(
`Using base token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`,
);
@ -141,7 +141,8 @@ async function runBuildConfigStep({
[baseChainName]: {
type: baseType,
token:
baseType === TokenType.collateral
baseType === TokenType.collateral ||
baseType === TokenType.collateralVault
? base.address!
: ethers.constants.AddressZero,
owner,
@ -233,6 +234,7 @@ async function runDeployPlanStep({
const baseName = getTokenName(baseToken);
logBlue('\nDeployment plan');
logGray('===============');
log(`Collateral type will be ${baseToken.type}`);
log(`Transaction signer and owner of new contracts will be ${address}`);
log(`Deploying a warp route with a base of ${baseName} token on ${origin}`);
log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`);
@ -289,18 +291,21 @@ async function fetchBaseTokenMetadata(
multiProvider.getChainMetadata(chainName).nativeToken;
if (chainNativeToken) return chainNativeToken;
else throw new Error(`No native token metadata for ${chainName}`);
} else if (base.type === TokenType.collateral && address) {
} else if (
base.type === TokenType.collateralVault ||
(base.type === TokenType.collateral && address)
) {
// If it's a collateral type, use a TokenAdapter to query for its metadata
log(`Fetching token metadata for ${address} on ${chainName}}`);
const adapter = new EvmTokenAdapter(
chainName,
MultiProtocolProvider.fromMultiProvider(multiProvider),
{ token: address },
{ token: address as string },
);
return adapter.getMetadata();
} else {
throw new Error(
`Unsupported token: ${base}. Consider setting token metadata in your deployment config.`,
`Unsupported token: ${base.type}. Consider setting token metadata in your deployment config.`,
);
}
}

@ -56,6 +56,7 @@
"repository": "https://github.com/hyperlane-xyz/hyperlane-monorepo",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"check": "tsc --noEmit",
"clean": "rm -rf ./dist ./cache",
"lint": "eslint src --ext .ts",

@ -340,7 +340,11 @@ export abstract class HyperlaneDeployer<
}
}
this.logger(`Deploy ${contractName} on ${chain}`);
this.logger(
`Deploy ${contractName} on ${chain} with constructor args (${constructorArgs.join(
', ',
)})`,
);
const contract = await this.multiProvider.handleDeploy(
chain,
factory,

@ -44,6 +44,15 @@ const STANDARD_TO_TOKEN: Record<TokenStandard, TokenArgs | null> = {
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypcollateralVault]: {
chainName: Chains.bsctestnet,
standard: TokenStandard.EvmHypCollateral,
addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131',
collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930',
decimals: 18,
symbol: 'USDC',
name: 'USDC',
},
[TokenStandard.EvmHypSynthetic]: {
chainName: Chains.inevm,
standard: TokenStandard.EvmHypSynthetic,

@ -14,6 +14,7 @@ export enum TokenStandard {
EvmNative = 'EvmNative',
EvmHypNative = 'EvmHypNative',
EvmHypCollateral = 'EvmHypCollateral',
EvmHypcollateralVault = 'EvmHypcollateralVault',
EvmHypSynthetic = 'EvmHypSynthetic',
// Sealevel (Solana)
@ -50,6 +51,7 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record<TokenStandard, ProtocolType> = {
EvmNative: ProtocolType.Ethereum,
EvmHypNative: ProtocolType.Ethereum,
EvmHypCollateral: ProtocolType.Ethereum,
EvmHypcollateralVault: ProtocolType.Ethereum,
EvmHypSynthetic: ProtocolType.Ethereum,
// Sealevel (Solana)
@ -133,6 +135,7 @@ export const TOKEN_COSMWASM_STANDARDS = [
export const TOKEN_TYPE_TO_STANDARD: Record<TokenType, TokenStandard> = {
[TokenType.native]: TokenStandard.EvmHypNative,
[TokenType.collateral]: TokenStandard.EvmHypCollateral,
[TokenType.collateralVault]: TokenStandard.EvmHypcollateralVault,
[TokenType.collateralUri]: TokenStandard.EvmHypCollateral,
[TokenType.fastCollateral]: TokenStandard.EvmHypCollateral,
[TokenType.synthetic]: TokenStandard.EvmHypSynthetic,

@ -7,6 +7,7 @@ export enum TokenType {
fastSynthetic = 'fastSynthetic',
syntheticUri = 'syntheticUri',
collateral = 'collateral',
collateralVault = 'collateralVault',
fastCollateral = 'fastCollateral',
collateralUri = 'collateralUri',
native = 'native',
@ -40,7 +41,9 @@ export type CollateralConfig = {
type:
| TokenType.collateral
| TokenType.collateralUri
| TokenType.fastCollateral;
| TokenType.fastCollateral
| TokenType.fastSynthetic
| TokenType.collateralVault;
token: string;
} & Partial<ERC20Metadata>;
export type NativeConfig = {
@ -54,7 +57,8 @@ export const isCollateralConfig = (
): config is CollateralConfig =>
config.type === TokenType.collateral ||
config.type === TokenType.collateralUri ||
config.type === TokenType.fastCollateral;
config.type === TokenType.fastCollateral ||
config.type == TokenType.collateralVault;
export const isSyntheticConfig = (
config: TokenConfig,

@ -1,6 +1,7 @@
import {
FastHypERC20Collateral__factory,
FastHypERC20__factory,
HypERC20CollateralVaultDeposit__factory,
HypERC20Collateral__factory,
HypERC20__factory,
HypERC721Collateral__factory,
@ -18,6 +19,7 @@ export const hypERC20factories = {
[TokenType.fastSynthetic]: new FastHypERC20__factory(),
[TokenType.synthetic]: new HypERC20__factory(),
[TokenType.collateral]: new HypERC20Collateral__factory(),
[TokenType.collateralVault]: new HypERC20CollateralVaultDeposit__factory(),
[TokenType.native]: new HypNative__factory(),
[TokenType.nativeScaled]: new HypNativeScaled__factory(),
};

@ -135,11 +135,27 @@ export class HypERC20Deployer extends GasRouterDeployer<
chain: ChainName,
config: HypERC20CollateralConfig,
): Promise<HypERC20Collateral> {
return this.deployContract(
chain,
isFastConfig(config) ? TokenType.fastCollateral : TokenType.collateral,
[config.token, config.mailbox],
);
let contractName:
| TokenType.fastCollateral
| TokenType.collateral
| TokenType.collateralVault;
switch (config.type) {
case TokenType.fastSynthetic || TokenType.fastCollateral:
contractName = TokenType.fastCollateral;
break;
case TokenType.collateral:
contractName = TokenType.collateral;
break;
case TokenType.collateralVault:
contractName = TokenType.collateralVault;
break;
default:
throw new Error(`Unknown collateral type ${config.type}`);
}
return this.deployContract(chain, contractName, [
config.token,
config.mailbox,
]);
}
protected async deployNative(

Loading…
Cancel
Save