Skip to content

Commit

Permalink
Warp route config read support (#43)
Browse files Browse the repository at this point in the history
### Description

Add support for reading warp route configs from registries

### Backward compatibility

Technically yes but marking changeset as major to compensate for
incorrect version bump in last version. See
hyperlane-xyz/hyperlane-monorepo#3821

### Testing

New unit tests
  • Loading branch information
jmrossy authored Jun 3, 2024
1 parent 296ef58 commit 05815a7
Show file tree
Hide file tree
Showing 17 changed files with 548 additions and 211 deletions.
6 changes: 6 additions & 0 deletions .changeset/beige-cats-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperlane-xyz/registry': major
---

Add support for reading warp route configs from registries
Add getURI method to registry classes
2 changes: 1 addition & 1 deletion .changeset/yellow-keys-peel.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@hyperlane-xyz/registry": patch
'@hyperlane-xyz/registry': patch
---

Add black border to Blast logo.svg
239 changes: 157 additions & 82 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import fs from 'fs';
import { parse } from 'yaml';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { CoreChains } from '../src/core/chains';
import { warpRouteConfigToId } from '../src/registry/warp-utils';

const chainMetadata = {};
const chainAddresses = {};
const warpRouteConfigs = {};

function genJsExport(data, exportName) {
return `export const ${exportName} = ${JSON.stringify(data, null, 2)}`;
Expand All @@ -20,102 +25,172 @@ function genChainMetadataMapExport(data, exportName) {
${genJsExport(data, exportName)} as ChainMap<ChainMetadata>`;
}

console.log('Preparing tmp directory');
if (fs.existsSync('./tmp')) fs.rmSync(`./tmp`, { recursive: true });
// Start with the contents of src, which we will add to in this script
fs.cpSync(`./src`, `./tmp`, { recursive: true });
function genWarpRouteConfigExport(data, exportName) {
return `import type { WarpCoreConfig } from '@hyperlane-xyz/sdk';
${genJsExport(data, exportName)} as WarpCoreConfig`;
}

const chainMetadata = {};
const chainAddresses = {};
function genWarpRouteConfigMapExport(data, exportName) {
return `import type { WarpCoreConfig } from '@hyperlane-xyz/sdk';
${genJsExport(data, exportName)} as Record<string, WarpCoreConfig>`;
}

console.log('Parsing and copying chain data');
for (const file of fs.readdirSync('./chains')) {
const inDirPath = `./chains/${file}`;
const assetOutPath = `./dist/chains/${file}`;
// ts files go to the tmp dir so they can be compiled along with other generated code
const tsOutPath = `./tmp/chains/${file}`;
const stat = fs.statSync(`${inDirPath}`);
if (!stat.isDirectory()) continue;

// Convert and copy metadata
const metadata = parse(fs.readFileSync(`${inDirPath}/metadata.yaml`, 'utf8'));
chainMetadata[metadata.name] = metadata;
fs.mkdirSync(`${assetOutPath}`, { recursive: true });
fs.mkdirSync(`${tsOutPath}`, { recursive: true });
fs.copyFileSync(`${inDirPath}/metadata.yaml`, `${assetOutPath}/metadata.yaml`);
fs.writeFileSync(`${assetOutPath}/metadata.json`, JSON.stringify(metadata, null, 2));
fs.writeFileSync(`${tsOutPath}/metadata.ts`, genChainMetadataExport(metadata, 'metadata'));

// Convert and copy addresses if there are any
if (fs.existsSync(`${inDirPath}/addresses.yaml`)) {
const addresses = parse(fs.readFileSync(`${inDirPath}/addresses.yaml`, 'utf8'));
chainAddresses[metadata.name] = addresses;
fs.copyFileSync(`${inDirPath}/addresses.yaml`, `${assetOutPath}/addresses.yaml`);
fs.writeFileSync(`${assetOutPath}/addresses.json`, JSON.stringify(addresses, null, 2));
fs.writeFileSync(`${tsOutPath}/addresses.ts`, genJsExport(addresses, 'addresses'));
function createTmpDir() {
console.log('Preparing tmp directory');
if (fs.existsSync('./tmp')) fs.rmSync(`./tmp`, { recursive: true });
// Start with the contents of src, which we will add to in this script
fs.cpSync(`./src`, `./tmp`, { recursive: true });
}

function createChainFiles() {
console.log('Parsing and copying chain data');
for (const file of fs.readdirSync('./chains')) {
const inDirPath = `./chains/${file}`;
const assetOutPath = `./dist/chains/${file}`;
// ts files go to the tmp dir so they can be compiled along with other generated code
const tsOutPath = `./tmp/chains/${file}`;
const stat = fs.statSync(`${inDirPath}`);
if (!stat.isDirectory()) continue;

// Convert and copy metadata
const metadata = parse(fs.readFileSync(`${inDirPath}/metadata.yaml`, 'utf8'));
chainMetadata[metadata.name] = metadata;
fs.mkdirSync(`${assetOutPath}`, { recursive: true });
fs.mkdirSync(`${tsOutPath}`, { recursive: true });
fs.copyFileSync(`${inDirPath}/metadata.yaml`, `${assetOutPath}/metadata.yaml`);
fs.writeFileSync(`${assetOutPath}/metadata.json`, JSON.stringify(metadata, null, 2));
fs.writeFileSync(`${tsOutPath}/metadata.ts`, genChainMetadataExport(metadata, 'metadata'));

// Convert and copy addresses if there are any
if (fs.existsSync(`${inDirPath}/addresses.yaml`)) {
const addresses = parse(fs.readFileSync(`${inDirPath}/addresses.yaml`, 'utf8'));
chainAddresses[metadata.name] = addresses;
fs.copyFileSync(`${inDirPath}/addresses.yaml`, `${assetOutPath}/addresses.yaml`);
fs.writeFileSync(`${assetOutPath}/addresses.json`, JSON.stringify(addresses, null, 2));
fs.writeFileSync(`${tsOutPath}/addresses.ts`, genJsExport(addresses, 'addresses'));
}

// Copy the logo file
fs.copyFileSync(`${inDirPath}/logo.svg`, `${assetOutPath}/logo.svg`);
}
}

function createWarpConfigFiles() {
console.log('Parsing and copying warp config data');
const warpPathBase = 'deployments/warp_routes';
// Outer loop for token symbol directories
for (const warpDir of fs.readdirSync(`./${warpPathBase}`)) {
const inDirPath = `./${warpPathBase}/${warpDir}`;
const assetOutPath = `./dist/${warpPathBase}/${warpDir}`;
// ts files go to the tmp dir so they can be compiled along with other generated code
const tsOutPath = `./tmp/${warpPathBase}/${warpDir}`;
const stat = fs.statSync(`${inDirPath}`);
if (!stat.isDirectory()) continue;

// Copy the logo file
fs.copyFileSync(`${inDirPath}/logo.svg`, `${assetOutPath}/logo.svg`);
// Inner loop for individual warp route configs
for (const warpFile of fs.readdirSync(inDirPath)) {
if (!warpFile.endsWith('config.yaml')) continue;
const [warpFileName] = warpFile.split('.');
const config = parse(fs.readFileSync(`${inDirPath}/${warpFile}`, 'utf8'));
const id = warpRouteConfigToId(config);
warpRouteConfigs[id] = config;
fs.mkdirSync(`${assetOutPath}`, { recursive: true });
fs.mkdirSync(`${tsOutPath}`, { recursive: true });
fs.copyFileSync(`${inDirPath}/${warpFileName}.yaml`, `${assetOutPath}/${warpFile}.yaml`);
fs.writeFileSync(`${assetOutPath}/${warpFileName}.json`, JSON.stringify(config, null, 2));
fs.writeFileSync(
`${tsOutPath}/${warpFileName}.ts`,
genWarpRouteConfigExport(config, 'warpRouteConfig'),
);
}
}
}

console.log('Assembling typescript code');
// Create files for the chain metadata and addresses maps
fs.writeFileSync(
`./tmp/chainMetadata.ts`,
genChainMetadataMapExport(chainMetadata, 'chainMetadata'),
);
fs.writeFileSync(`./tmp/chainAddresses.ts`, genJsExport(chainAddresses, 'chainAddresses'));
// And also alternate versions with just the core chains
const coreChainMetadata = pick<any>(chainMetadata, CoreChains);
const coreChainAddresses = pick<any>(chainAddresses, CoreChains);
fs.writeFileSync(
`./tmp/coreChainMetadata.ts`,
genChainMetadataMapExport(coreChainMetadata, 'coreChainMetadata'),
);
fs.writeFileSync(
`./tmp/coreChainAddresses.ts`,
genJsExport(coreChainAddresses, 'coreChainAddresses'),
);
// Add the exports for new files to the index file
fs.appendFileSync(
`./tmp/index.ts`,
`
export { chainMetadata } from './chainMetadata.js';
export { coreChainMetadata } from './coreChainMetadata.js';
export { chainAddresses } from './chainAddresses.js';
export { coreChainAddresses } from './coreChainAddresses.js';
`,
);
// Also create individual js files for each chain
for (const name of Object.keys(chainMetadata)) {
// Create an index file for each chain folder to allow for direct, single-chain imports
fs.writeFileSync(`./tmp/chains/${name}/index.ts`, `export { metadata } from './metadata.js';\n`);
// Also add a metadata export to the root index for convenience
function generateChainTsCode() {
console.log('Assembling chain typescript code');
// Create files for the chain metadata and addresses maps
fs.writeFileSync(
`./tmp/chainMetadata.ts`,
genChainMetadataMapExport(chainMetadata, 'chainMetadata'),
);
fs.writeFileSync(`./tmp/chainAddresses.ts`, genJsExport(chainAddresses, 'chainAddresses'));
// And also alternate versions with just the core chains
const coreChainMetadata = pick<any>(chainMetadata, CoreChains);
const coreChainAddresses = pick<any>(chainAddresses, CoreChains);
fs.writeFileSync(
`./tmp/coreChainMetadata.ts`,
genChainMetadataMapExport(coreChainMetadata, 'coreChainMetadata'),
);
fs.writeFileSync(
`./tmp/coreChainAddresses.ts`,
genJsExport(coreChainAddresses, 'coreChainAddresses'),
);
// Add the exports for new files to the index file
fs.appendFileSync(
`./tmp/index.ts`,
`export { metadata as ${name} } from './chains/${name}/metadata.js';\n`,
`
export { chainMetadata } from './chainMetadata.js';
export { coreChainMetadata } from './coreChainMetadata.js';
export { chainAddresses } from './chainAddresses.js';
export { coreChainAddresses } from './coreChainAddresses.js';
`,
);
// Ditto as above for addresses if they exist
if (chainAddresses[name]) {
fs.appendFileSync(
// Also create individual js files for each chain
for (const name of Object.keys(chainMetadata)) {
// Create an index file for each chain folder to allow for direct, single-chain imports
fs.writeFileSync(
`./tmp/chains/${name}/index.ts`,
`export { addresses } from './addresses.js';\n`,
`export { metadata } from './metadata.js';\n`,
);
// Also add a metadata export to the root index for convenience
fs.appendFileSync(
`./tmp/index.ts`,
`export { addresses as ${name}Addresses } from './chains/${name}/addresses.js';\n`,
`export { metadata as ${name} } from './chains/${name}/metadata.js';\n`,
);
// Ditto as above for addresses if they exist
if (chainAddresses[name]) {
fs.appendFileSync(
`./tmp/chains/${name}/index.ts`,
`export { addresses } from './addresses.js';\n`,
);
fs.appendFileSync(
`./tmp/index.ts`,
`export { addresses as ${name}Addresses } from './chains/${name}/addresses.js';\n`,
);
}
}
}

console.log('Updating & copying chain JSON schemas');
const chainSchema = zodToJsonSchema(ChainMetadataSchemaObject, 'hyperlaneChainMetadata');
fs.writeFileSync(`./chains/schema.json`, JSON.stringify(chainSchema, null, 2), 'utf8');
fs.copyFileSync(`./chains/schema.json`, `./dist/chains/schema.json`);
const warpSchema = zodToJsonSchema(WarpCoreConfigSchema, 'hyperlaneWarpCoreConfig');
fs.writeFileSync(
`./deployments/warp_routes/schema.json`,
JSON.stringify(warpSchema, null, 2),
'utf8',
);
function generateWarpConfigTsCode() {
console.log('Assembling warp config typescript code');
// Generate a combined config map
fs.writeFileSync(
`./tmp/warpRouteConfigs.ts`,
genWarpRouteConfigMapExport(warpRouteConfigs, 'warpRouteConfigs'),
);
// Add the export to the index file
fs.appendFileSync(
`./tmp/index.ts`,
`\nexport { warpRouteConfigs } from './warpRouteConfigs.js';`,
);
}

function updateJsonSchemas() {
console.log('Updating & copying chain JSON schemas');
const chainSchema = zodToJsonSchema(ChainMetadataSchemaObject, 'hyperlaneChainMetadata');
fs.writeFileSync(`./chains/schema.json`, JSON.stringify(chainSchema, null, 2), 'utf8');
fs.copyFileSync(`./chains/schema.json`, `./dist/chains/schema.json`);
const warpSchema = zodToJsonSchema(WarpCoreConfigSchema, 'hyperlaneWarpCoreConfig');
fs.writeFileSync(
`./deployments/warp_routes/schema.json`,
JSON.stringify(warpSchema, null, 2),
'utf8',
);
}

createTmpDir();
createChainFiles();
createWarpConfigFiles();
generateChainTsCode();
generateWarpConfigTsCode();
updateJsonSchemas();
4 changes: 4 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const SCHEMA_REF = '# yaml-language-server: $schema=../schema.json';

export const DEFAULT_GITHUB_REGISTRY = 'https://github.com/hyperlane-xyz/hyperlane-registry';
export const GITHUB_FETCH_CONCURRENCY_LIMIT = 5;

export const CHAIN_FILE_REGEX = /chains\/([a-z0-9]+)\/([a-z]+)\.(yaml|svg)/;
export const WARP_ROUTE_CONFIG_FILE_REGEX = /warp_routes\/([a-zA-Z0-9]+)\/([a-z0-9-]+)-config.yaml/;
14 changes: 12 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
export { CoreChain, CoreChainName, CoreChains, CoreMainnets, CoreTestnets } from './core/chains.js';

export { DEFAULT_GITHUB_REGISTRY } from './consts.js';
export { BaseRegistry, CHAIN_FILE_REGEX } from './registry/BaseRegistry.js';
export {
CHAIN_FILE_REGEX,
DEFAULT_GITHUB_REGISTRY,
WARP_ROUTE_CONFIG_FILE_REGEX,
} from './consts.js';
export { BaseRegistry } from './registry/BaseRegistry.js';
export { GithubRegistry, GithubRegistryOptions } from './registry/GithubRegistry.js';
export { ChainFiles, IRegistry, RegistryContent, RegistryType } from './registry/IRegistry.js';
export { MergedRegistry, MergedRegistryOptions } from './registry/MergedRegistry.js';
export { PartialRegistry, PartialRegistryOptions } from './registry/PartialRegistry.js';
export {
filterWarpRoutesIds,
warpConfigToWarpAddresses,
warpRouteConfigPathToId,
warpRouteConfigToId,
} from './registry/warp-utils.js';
export { ChainAddresses, ChainAddressesSchema } from './types.js';
40 changes: 25 additions & 15 deletions src/registry/BaseRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import type { Logger } from 'pino';

import type { ChainMap, ChainMetadata, ChainName, WarpCoreConfig } from '@hyperlane-xyz/sdk';
import type { ChainAddresses, MaybePromise } from '../types.js';
import type { IRegistry, RegistryContent, RegistryType } from './IRegistry.js';
import { WarpRouteConfigMap } from '../types.js';
import { stripLeadingSlash } from '../utils.js';
import type {
IRegistry,
RegistryContent,
RegistryType,
UpdateChainParams,
WarpRouteFilterParams,
} from './IRegistry.js';
import { MergedRegistry } from './MergedRegistry.js';

export const CHAIN_FILE_REGEX = /chains\/([a-z0-9]+)\/([a-z]+)\.(yaml|svg)/;

export abstract class BaseRegistry implements IRegistry {
public abstract type: RegistryType;
public readonly uri: string;
Expand All @@ -27,11 +33,20 @@ export abstract class BaseRegistry implements IRegistry {
this.logger = logger || console;
}

getUri(itemPath?: string): string {
if (itemPath) itemPath = stripLeadingSlash(itemPath);
return itemPath ? `${this.uri}/${itemPath}` : this.uri;
}

protected getChainsPath(): string {
return 'chains';
}

protected getWarpArtifactsPaths({ tokens }: WarpCoreConfig) {
protected getWarpRoutesPath(): string {
return 'deployments/warp_routes';
}

protected getWarpRoutesArtifactPaths({ tokens }: WarpCoreConfig) {
if (!tokens.length) throw new Error('No tokens provided in config');
const symbols = new Set<string>(tokens.map((token) => token.symbol.toUpperCase()));
if (symbols.size !== 1)
Expand All @@ -41,7 +56,7 @@ export abstract class BaseRegistry implements IRegistry {
.map((token) => token.chainName)
.sort()
.join('-');
const basePath = `deployments/warp_routes/${symbol}/${chains}`;
const basePath = `${this.getWarpRoutesPath()}/${symbol}/${chains}`;
return { configPath: `${basePath}-config.yaml`, addressesPath: `${basePath}-addresses.yaml` };
}

Expand All @@ -61,17 +76,12 @@ export abstract class BaseRegistry implements IRegistry {
return chain?.logo ?? null;
}

abstract addChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
abstract updateChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
abstract addChain(chain: UpdateChainParams): MaybePromise<void>;
abstract updateChain(chain: UpdateChainParams): MaybePromise<void>;
abstract removeChain(chain: ChainName): MaybePromise<void>;

abstract getWarpRoute(routeId: string): MaybePromise<WarpCoreConfig | null>;
abstract getWarpRoutes(filter?: WarpRouteFilterParams): MaybePromise<WarpRouteConfigMap>;
abstract addWarpRoute(config: WarpCoreConfig): MaybePromise<void>;

merge(otherRegistry: IRegistry): IRegistry {
Expand Down
Loading

0 comments on commit 05815a7

Please sign in to comment.