Skip to content

Commit

Permalink
add simple config generator based on metadata endpoint (#1115)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanL0 authored Dec 12, 2024
1 parent 1c8681e commit d255778
Show file tree
Hide file tree
Showing 21 changed files with 1,227 additions and 48 deletions.
6 changes: 6 additions & 0 deletions .changeset/sixty-radios-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@layerzerolabs/toolbox-hardhat": patch
"@layerzerolabs/metadata-tools": patch
---

add metadata-tools package
2 changes: 2 additions & 0 deletions packages/metadata-tools/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
3 changes: 3 additions & 0 deletions packages/metadata-tools/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json"
}
1 change: 1 addition & 0 deletions packages/metadata-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache
2 changes: 2 additions & 0 deletions packages/metadata-tools/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
38 changes: 38 additions & 0 deletions packages/metadata-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>

<h1 align="center">@layerzerolabs/metadata-tools</h1>

<!-- The badges section -->
<p align="center">
<!-- Shields.io NPM published package version -->
<a href="https://www.npmjs.com/package/@layerzerolabs/metadata-tools"><img alt="NPM Version" src="https://img.shields.io/npm/v/@layerzerolabs/metadata-tools"/></a>
<!-- Shields.io NPM downloads -->
<a href="https://www.npmjs.com/package/@layerzerolabs/metadata-tools"><img alt="Downloads" src="https://img.shields.io/npm/dm/@layerzerolabs/metadata-tools"/></a>
<!-- Shields.io license badge -->
<a href="https://www.npmjs.com/package/@layerzerolabs/metadata-tools"><img alt="NPM License" src="https://img.shields.io/npm/l/@layerzerolabs/metadata-tools"/></a>
</p>

## Installation

```bash
yarn add @layerzerolabs/metadata-tools

pnpm add @layerzerolabs/metadata-tools

npm install @layerzerolabs/metadata-tools
```

## Usage

```typescript
import { generateConnectionsConfig } from "@layerzerolabs/metadata-tools";

// [srcContract, dstContract, [requiredDVNs, [optionalDVNs, threshold]], [srcToDstConfirmations, dstToSrcConfirmations]], [enforcedOptionsSrcToDst, enforcedOptionsDstToSrc]
const connections = await generateConnectionsConfig([
[avalancheContract, polygonContract, [['LayerZero'], []], [1, 1], [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS]],
]);
```
12 changes: 12 additions & 0 deletions packages/metadata-tools/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
cache: false,
reporters: [['github-actions', { silent: false }], 'default'],
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
};
56 changes: 56 additions & 0 deletions packages/metadata-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@layerzerolabs/metadata-tools",
"version": "0.0.1",
"description": "LayerZero metadata API tools",
"repository": {
"type": "git",
"url": "git+https://github.com/LayerZero-Labs/devtools.git",
"directory": "packages/metadata-tools"
},
"license": "MIT",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./*": {
"types": "./dist/*.d.ts",
"require": "./dist/*.js",
"import": "./dist/*.mjs"
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prebuild": "$npm_execpath tsc --noEmit",
"build": "$npm_execpath tsup",
"clean": "rm -rf dist",
"dev": "$npm_execpath tsup --watch",
"lint": "$npm_execpath eslint '**/*.{js,ts,json}'",
"lint:fix": "eslint --fix '**/*.{js,ts,json}'",
"test": "jest"
},
"devDependencies": {
"@layerzerolabs/devtools-evm-hardhat": "~2.0.3",
"@layerzerolabs/ua-devtools": "~3.0.1",
"@swc/core": "^1.4.0",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"tslib": "~2.6.2",
"tsup": "~8.0.1",
"typescript": "^5.4.4"
},
"peerDependencies": {
"@layerzerolabs/devtools-evm-hardhat": "~2.0.3",
"@layerzerolabs/ua-devtools": "~3.0.1"
},
"publishConfig": {
"access": "public"
}
}
208 changes: 208 additions & 0 deletions packages/metadata-tools/src/config-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import type { OmniEdgeHardhat } from '@layerzerolabs/devtools-evm-hardhat'
import type { OAppEdgeConfig } from '@layerzerolabs/ua-devtools'
import { IMetadata } from './types'
import { TwoWayConfig } from './types'

const METADATA_URL = process.env.LZ_METADATA_URL || 'https://metadata.layerzero-api.com/v1/metadata'

function getEndpointIdDeployment(eid: number, metadata: IMetadata) {
const srcEidString = eid.toString()
for (const objectKey in metadata) {
const entry = metadata[objectKey]

if (typeof entry?.deployments !== 'undefined') {
for (const deployment of entry.deployments) {
if (srcEidString === deployment.eid) {
return deployment
}
}
}
}

throw new Error(`Can't find endpoint with eid: "${eid}",`)
}

export function DVNsToAddresses(dvns: string[], chainKey: string, metadata: IMetadata) {
if (dvns.length === 0) {
return []
}

const dvnAddresses: string[] = []
const seenDVNs = new Set<string>()

if (!metadata[chainKey]?.dvns) {
throw new Error(`Can't find DVNs for chainKey: "${chainKey}".`)
}

const metadataDVNs = Object.entries(metadata[chainKey].dvns)

for (const dvn of dvns) {
if (seenDVNs.has(dvn)) {
throw new Error(`Duplicate DVN name found: "${dvn}".`)
}
seenDVNs.add(dvn)

let i = 0
for (const [dvnAddress, dvnDetails] of metadataDVNs) {
if (dvnDetails.canonicalName === dvn && !dvnDetails.lzReadCompatible) {
if (dvnDetails.deprecated) {
console.log(`Warning: DVN "${dvn}" is deprecated.`)
}

dvnAddresses.push(dvnAddress)
break
}

if (i === metadataDVNs.length - 1) {
throw new Error(
`Can't find DVN: "${dvn}" on chainKey: "${chainKey}". Double check you're using valid DVN canonical name (not an address).`
)
}

i++
}
}

if (dvns.length !== dvnAddresses.length) {
throw new Error(`Can't find all DVNs: "${dvns.join(', ')}".`)
}

return dvnAddresses.sort()
}

export async function translatePathwayToConfig(
pathway: TwoWayConfig,
metadata: IMetadata
): Promise<OmniEdgeHardhat<OAppEdgeConfig | undefined>[]> {
const configs: OmniEdgeHardhat<OAppEdgeConfig | undefined>[] = []

const sourceContract = pathway[0]
const destinationContract = pathway[1]
const [requiredDVNs, optionalDVNConfig] = pathway[2]
const [sourceToDestinationConfirmations, destinationToSourceConfirmations] = pathway[3]
const [enforcedOptionsSrcToDst, enforcedOptionsDstToSrc] = pathway[4]

const optionalDVNs = optionalDVNConfig[0]
const optionalDVNThreshold = optionalDVNConfig[1] || 0

if (optionalDVNThreshold > (optionalDVNs?.length || 0)) {
throw new Error(`Optional DVN threshold is greater than the number of optional DVNs.`)
}

const sourceLZDeployment = getEndpointIdDeployment(sourceContract.eid, metadata)
const destinationLZDeployment = getEndpointIdDeployment(destinationContract.eid, metadata)

const sourceRequiredDVNs = DVNsToAddresses(requiredDVNs, sourceLZDeployment.chainKey, metadata)
const destinationRequiredDVNs = DVNsToAddresses(requiredDVNs, destinationLZDeployment.chainKey, metadata)

let sourceOptionalDVNs: string[] = []
let destinationOptionalDVNs: string[] = []

if (optionalDVNs) {
sourceOptionalDVNs = DVNsToAddresses(optionalDVNs, sourceLZDeployment.chainKey, metadata)
destinationOptionalDVNs = DVNsToAddresses(optionalDVNs, destinationLZDeployment.chainKey, metadata)
}

if (!sourceLZDeployment.sendUln302 || !sourceLZDeployment.receiveUln302 || !sourceLZDeployment.executor) {
throw new Error(
`Can't find sendUln302, receiveUln302 or executor for source endpoint with eid: "${sourceContract.eid}".`
)
}

if (
!destinationLZDeployment.sendUln302 ||
!destinationLZDeployment.receiveUln302 ||
!destinationLZDeployment.executor
) {
throw new Error(
`Can't find sendUln302, receiveUln302 or executor for destination endpoint with eid: "${destinationContract.eid}".`
)
}

const sourceToDestinationConfig: OmniEdgeHardhat<OAppEdgeConfig> = {
from: sourceContract,
to: destinationContract,
config: {
sendLibrary: sourceLZDeployment.sendUln302.address,
receiveLibraryConfig: {
receiveLibrary: sourceLZDeployment.receiveUln302.address,
gracePeriod: BigInt(0),
},
sendConfig: {
executorConfig: {
maxMessageSize: 10000,
executor: sourceLZDeployment.executor.address,
},
ulnConfig: {
confirmations: BigInt(sourceToDestinationConfirmations),
requiredDVNs: sourceRequiredDVNs,
optionalDVNs: sourceOptionalDVNs,
optionalDVNThreshold,
},
},
enforcedOptions: enforcedOptionsSrcToDst,
},
}

const destinationToSourceConfig: OmniEdgeHardhat<OAppEdgeConfig> = {
from: destinationContract,
to: sourceContract,
config: {
sendLibrary: destinationLZDeployment.sendUln302.address,
receiveLibraryConfig: {
receiveLibrary: destinationLZDeployment.receiveUln302.address,
gracePeriod: BigInt(0),
},
receiveConfig: {
ulnConfig: {
confirmations: BigInt(sourceToDestinationConfirmations),
requiredDVNs: destinationRequiredDVNs,
optionalDVNs: destinationOptionalDVNs,
optionalDVNThreshold,
},
},
},
}

if (destinationToSourceConfirmations) {
sourceToDestinationConfig.config.receiveConfig = {
ulnConfig: {
confirmations: BigInt(destinationToSourceConfirmations),
requiredDVNs: sourceRequiredDVNs,
optionalDVNs: sourceOptionalDVNs,
optionalDVNThreshold,
},
}

destinationToSourceConfig.config.enforcedOptions = enforcedOptionsDstToSrc

destinationToSourceConfig.config.sendConfig = {
executorConfig: {
maxMessageSize: 10000,
executor: destinationLZDeployment.executor.address,
},
ulnConfig: {
confirmations: BigInt(destinationToSourceConfirmations),
requiredDVNs: destinationRequiredDVNs,
optionalDVNs: destinationOptionalDVNs,
optionalDVNThreshold,
},
}
}

configs.push(sourceToDestinationConfig)
configs.push(destinationToSourceConfig)

return configs
}

export async function generateConnectionsConfig(pathways: TwoWayConfig[]) {
const metadata = (await fetch(METADATA_URL).then((res) => res.json())) as IMetadata
const connections: OmniEdgeHardhat<OAppEdgeConfig | undefined>[] = []

for (const pathway of pathways) {
connections.push(...(await translatePathwayToConfig(pathway, metadata)))
}

return connections
}
2 changes: 2 additions & 0 deletions packages/metadata-tools/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './config-metadata'
export * from './types'
Loading

0 comments on commit d255778

Please sign in to comment.