Skip to content

Commit

Permalink
🪚 OmniGraph™ Polish eid ↔︎ network conversions; Add executor config f…
Browse files Browse the repository at this point in the history
…unctionality (#69)
  • Loading branch information
janjakubnanista authored Dec 6, 2023
1 parent 9a3e724 commit 8c5c641
Show file tree
Hide file tree
Showing 16 changed files with 306 additions and 51 deletions.
2 changes: 1 addition & 1 deletion examples/oft/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@rushstack/eslint-patch": "^1.5.1",
"ethers": "^5.7.0",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.11.43",
"hardhat-deploy-ethers": "^0.4.1",
Expand Down
9 changes: 9 additions & 0 deletions packages/protocol-utils-evm/src/uln302/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AddressSchema } from '@layerzerolabs/utils'
import { BigNumberishNumberSchema } from '@layerzerolabs/utils-evm'
import { BigNumberishBigintSchema } from '@layerzerolabs/utils-evm'
import { z } from 'zod'

Expand All @@ -12,6 +13,14 @@ export const Uln302UlnConfigSchema = z.object({
optionalDVNThreshold: z.coerce.number().int().nonnegative(),
})

/**
* Schema for parsing an ethers-specific ExecutorConfig into a common format
*/
export const Uln302ExecutorConfigSchema = z.object({
maxMessageSize: BigNumberishNumberSchema,
executor: AddressSchema,
})

/**
* Schema for parsing a common UlnConfig into a ethers-specific format
*/
Expand Down
20 changes: 15 additions & 5 deletions packages/protocol-utils-evm/src/uln302/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { EndpointId } from '@layerzerolabs/lz-definitions'
import type { IUln302, Uln302ExecutorConfig, Uln302UlnConfig } from '@layerzerolabs/protocol-utils'
import { formatEid, type OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint, type OmniContract } from '@layerzerolabs/utils-evm'
import { Uln302UlnConfigInputSchema, Uln302UlnConfigSchema } from './schema'
import { Address, formatEid, type OmniTransaction } from '@layerzerolabs/utils'
import { omniContractToPoint, type OmniContract, makeZero } from '@layerzerolabs/utils-evm'
import { Uln302ExecutorConfigSchema, Uln302UlnConfigInputSchema, Uln302UlnConfigSchema } from './schema'

export class Uln302 implements IUln302 {
constructor(public readonly contract: OmniContract) {}

async getUlnConfig(eid: EndpointId, address: string): Promise<Uln302UlnConfig> {
const config = await this.contract.contract.getUlnConfig(address, eid)
async getUlnConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302UlnConfig> {
const config = await this.contract.contract.getUlnConfig(makeZero(address), eid)

// Now we convert the ethers-specific object into the common structure
//
Expand All @@ -17,6 +17,16 @@ export class Uln302 implements IUln302 {
return Uln302UlnConfigSchema.parse({ ...config })
}

async getExecutorConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302ExecutorConfig> {
const config = await this.contract.contract.getExecutorConfig(makeZero(address), eid)

// Now we convert the ethers-specific object into the common structure
//
// Here we need to spread the config into an object because what ethers gives us
// is actually an array with extra properties
return Uln302ExecutorConfigSchema.parse({ ...config })
}

async setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setDefaultExecutorConfigs', [
[{ eid, config }],
Expand Down
3 changes: 2 additions & 1 deletion packages/protocol-utils/src/uln302/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { Address, OmniGraph, OmniPointBasedFactory, OmniTransaction } from
import type { EndpointId } from '@layerzerolabs/lz-definitions'

export interface IUln302 {
getUlnConfig(eid: EndpointId, address: Address): Promise<Uln302UlnConfig>
getUlnConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302UlnConfig>
getExecutorConfig(eid: EndpointId, address?: Address | null | undefined): Promise<Uln302ExecutorConfig>
setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise<OmniTransaction>
setDefaultUlnConfig(eid: EndpointId, config: Uln302UlnConfig): Promise<OmniTransaction>
}
Expand Down
2 changes: 1 addition & 1 deletion packages/test-evm-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"start": "npx hardhat node --hostname 0.0.0.0"
},
"devDependencies": {
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ua-utils-evm-hardhat-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"chai": "^4.3.10",
"chai-as-promised": "^7.1.1",
"ethers": "^5.7.0",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.11.22",
"hardhat-deploy-ethers": "^0.3.0-beta.12",
"sinon": "^17.0.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/ua-utils-evm-hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"dotenv": "^16.0.3",
"ethers": "^5.7.0",
"fast-check": "^3.14.0",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.11.22",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
Expand All @@ -72,7 +72,7 @@
"@layerzerolabs/utils-evm-hardhat": "~0.0.2",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"ethers": "^5.5.2",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.11.22"
}
}
65 changes: 65 additions & 0 deletions packages/utils-evm-hardhat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,68 @@ pnpm add @layerzerolabs/utils-evm-hardhat

npm install @layerzerolabs/utils-evm-hardhat
```

## API Documentation

### Omnigraph types

#### OmniContract

Interface that represents an ethers.js contract connected to a particular endpoint

```typescript
import { EndpointId } from "@layerzerolabs/lz-definitions";
import { OmniContract } from "@layerzerolabs/utils-evm";

const omniContract: OmniContract = {
eid: EndpointId.ETHEREUM_MAINNET,
contract: new Contract(address, abi),
};
```

#### OmniContractFactory

Type that represents a function that can return an `OmniContract` based on an `OmniPoint`

```typescript
import { EndpointId } from "@layerzerolabs/lz-definitions";
import { OmniPoint } from "@layerzerolabs/utils";
import { OmniContractFactory } from "@layerzerolabs/utils-evm";

declare const omniContractFactory: OmniContractFactory;

const omniPoint: OmniPoint = {
eid: EndpointId.ETHEREUM_MAINNET,
address: "0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8",
};

const omniContract = await omniContractFactory(omniPoint);
```

### Address utilities

#### ignoreZero(address: Address | null | undefined)

Turns EVM zero addresses to `undefined`

```typescript
import { ignoreZero } from "@layerzerolabs/utils-evm";

ignoreZero("0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8"); // Returns '0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8'
ignoreZero("0x0000000000000000000000000000000000000000"); // Returns undefined
ignoreZero(undefined); // Returns undefined
ignoreZero(null); // Returns undefined
```

#### makeZero(address)

Turns `null` and `undefined` into EVM zero address

```typescript
import { makeZero } from "@layerzerolabs/utils-evm";

makeZero("0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8"); // Returns '0xEe6cF2E1Bc7645F8439d241ce37820305F2BB3F8'
makeZero("0x0000000000000000000000000000000000000000"); // Returns '0x0000000000000000000000000000000000000000'
makeZero(undefined); // Returns '0x0000000000000000000000000000000000000000'
makeZero(null); // Returns '0x0000000000000000000000000000000000000000'
```
4 changes: 2 additions & 2 deletions packages/utils-evm-hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@layerzerolabs/utils-evm": "~0.0.1",
"@types/jest": "^29.5.10",
"fast-check": "^3.14.0",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.11.22",
"jest": "^29.7.0",
"p-memoize": "~4.0.1",
Expand All @@ -69,7 +69,7 @@
"@ethersproject/providers": "^5.7.0",
"@layerzerolabs/lz-definitions": "~1.5.68",
"@layerzerolabs/utils-evm": "~0.0.1",
"hardhat": "^2.19.0",
"hardhat": "^2.19.2",
"hardhat-deploy": "^0.9.19"
}
}
96 changes: 71 additions & 25 deletions packages/utils-evm-hardhat/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ import { ConfigurationError } from './errors'
import { HardhatContext } from 'hardhat/internal/context'
import { Environment as HardhatRuntimeEnvironmentImplementation } from 'hardhat/internal/core/runtime-environment'
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { EndpointBasedFactory } from '@layerzerolabs/utils'
import { EndpointBasedFactory, formatEid } from '@layerzerolabs/utils'
import assert from 'assert'

/**
* Helper type for when we need to grab something asynchronously by the network name
*/
export type GetByNetwork<TValue> = (networkName: string) => Promise<TValue>

/**
* Returns the default hardhat context for the project, i.e.
* the context that the project has been setup with.
*
* Throws if there is no context.
*
* @returns {HardhatContext}
*/
export const getDefaultContext = (): HardhatContext => {
// Context is registered globally as a singleton and can be accessed
// using the static methods of the HardhatContext class
Expand All @@ -26,6 +35,13 @@ export const getDefaultContext = (): HardhatContext => {
}
}

/**
* Returns the default `HardhatRuntimeEnvironment` (`hre`) for the project.
*
* Throws if there is no `HardhatRuntimeEnvironment`.
*
* @returns {HardhatRuntimeEnvironment}
*/
export const getDefaultRuntimeEnvironment = (): HardhatRuntimeEnvironment => {
// The first step is to get the hardhat context
const context = getDefaultContext()
Expand All @@ -50,6 +66,8 @@ export const getDefaultRuntimeEnvironment = (): HardhatRuntimeEnvironment => {
* // All the ususal properties are present
* env.deployments.get("MyContract")
* ```
*
* @returns {Promise<HardhatRuntimeEnvironment>}
*/
export const getNetworkRuntimeEnvironment: GetByNetwork<HardhatRuntimeEnvironment> = pMemoize(async (networkName) => {
const context = getDefaultContext()
Expand Down Expand Up @@ -83,8 +101,8 @@ export const getNetworkRuntimeEnvironment: GetByNetwork<HardhatRuntimeEnvironmen
* Helper function that wraps an EIP1193Provider with Web3Provider
* so that we can use it further with ethers
*
* @param provider `EIP1193Provider`
* @returns `Web3Provider`
* @param {EIP1193Provider} provider
* @returns {Web3Provider}
*/
export const wrapEIP1193Provider = (provider: EIP1193Provider): Web3Provider => new Web3Provider(provider)

Expand All @@ -99,42 +117,70 @@ export const wrapEIP1193Provider = (provider: EIP1193Provider): Web3Provider =>
* const env = factory(EndpointId.FANTOM_MAINNET)
* ```
*
* @param hre `HardhatRuntimeEnvironment`
* @returns `(eid: EndpointId) => Promise<HardhatRuntimeEnvironment>`
* @param {HardhatRuntimeEnvironment | undefined} [hre]
* @returns {(eid: EndpointId) => Promise<HardhatRuntimeEnvironment>}
*/
export const createNetworkEnvironmentFactory = (
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment()
): EndpointBasedFactory<HardhatRuntimeEnvironment> => {
const networkNamesByEndpointId = getNetworkNamesByEid(hre)
return async (eid) => getNetworkRuntimeEnvironment(getNetworkNameForEid(eid, hre))
}

return async (eid) => {
const networkName = networkNamesByEndpointId.get(eid)
if (networkName == null) throw new Error(`No network defined for eid ${eid}`)
/**
* Gets an EndpointId defined in the hardhat config
* for a particular network name (as an `eid` property).
*
* Throws if the network or the eid are not defined
*
* @param {string} networkName
* @param {HardhatRuntimeEnvironment | undefined} [hre]
* @returns {EndpointId}
*/
export const getEidForNetworkName = (
networkName: string,
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment()
): EndpointId => {
const networkConfig = hre.config.networks[networkName]
assert(networkConfig, `Network '${networkName}' is not defined in hardhat config`)
assert(networkConfig.eid != null, `Network '${networkName}' does not have 'eid' property defined in its config`)

return getNetworkRuntimeEnvironment(networkName)
}
return networkConfig.eid
}

/**
* Creates a mapping between EndpointId and network name
* based on the hardhat project configuration.
* Gets a network name with its `eid` property matching
* a particular `eid`
*
* It will silently ignore networks that don't have `eid`
* specified in their network configuration.
* Throws if there is no such network or if there are multiple
* networks defined with the same `eid`
*
* @param hre `HardhatRuntimeEnvironment`
* @returns `Map<EndpointId, string>`
* @param {EndpointId} eid
* @param {HardhatRuntimeEnvironment | undefined} [hre]
* @returns {string}
*/
export const getNetworkNamesByEid = (
export const getNetworkNameForEid = (
eid: EndpointId,
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment()
): Map<EndpointId, string> => {
const networks = Object.entries(hre.config.networks)
): string => {
const networkNames: string[] = []

for (const [networkName, networkConfig] of Object.entries(hre.config.networks)) {
// This is basically just an extra condition that ensures that even if the user
// passes undefined / null despite the TypeScript telling them not to, they won't get a messed up return value
if (networkConfig.eid == null) continue

return new Map(
networks.flatMap(([networkName, networkConfig]) => {
if (networkConfig.eid == null) return []
if (eid === networkConfig.eid) networkNames.push(networkName)
}

return [[networkConfig.eid, networkName]]
})
// Here we error out of the user by accident specified the same eid for multiple networks
assert(
networkNames.length < 2,
`Multiple networks found with 'eid' set to ${eid} (${formatEid(eid)}): ${networkNames.join(', ')}`
)

// Here we error out if there are no networks with this eid
const networkName = networkNames.at(0)
assert(networkName, `Could not find a network for eid ${eid} (${formatEid(eid)})`)

return networkName
}
Loading

0 comments on commit 8c5c641

Please sign in to comment.