Skip to content

Commit

Permalink
🪚 OmniGraph™ Bit more docs (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Dec 14, 2023
1 parent 451cc7a commit 3901613
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 71 deletions.
108 changes: 108 additions & 0 deletions CHEATSHEET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<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">Developer Cheatsheet</h1>

## Glossary

| Name | Package | Meaning |
| ------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OmniPoint` | `utils` | Location of a contract/program in omnichain environment. Consists of an `address` and `EndpointId` |
| `OmniVector` | `utils` | Directional connection between two `OmniPoint`s. Consists of `from` and `to` `OmniPoint`s |
| `OmniNode` | `utils` | Combination of an `OmniPoint` and an arbitrary configuration attached to it. Consists of a `point` and `config` |
| `OmniEdge` | `utils` | Combination of an `OmniVector` and an arbitrary configuration attached to it. Consists of a `vector` and `config` |
| `OmniGraph` | `utils` | Collection of `OmniNode`s and `OmniEdge`s that together represent a state of an omnichain application. Consists of `contracts` and `connections` |
| `OmniError` | `utils` | Wraps an arbitrary `error` object to add information about where that error happened. Consists of `error` and `point` |
| `OmniContract` | `utils-evm` | Wraps an `ethers` `Contract` instance to add information about the endpoint. Consists of `eid` and `contract` |
| `OmniPointHardhat` | `utils-evm-hardhat` | Hardhat-specific variation of `OmniPoint`. Since in hardhat we can get a contract address by its name (from `deployments`), this version of `OmniPoint` allows us to use `contractName` instead of `address` |

## Conventions

The packages are laid out according to the [principle of least knowledge](https://en.wikipedia.org/wiki/Law_of_Demeter). Their domain of action is also reflected in their name that follows the convention `[DOMAIN-]<ELEMENT>[-MODIFIER]`, for example:

- `utils` package is the most generic package and it itself does not know and cannot use any implementation details of any more specific packages, nor is it specific to any domain
- `utils-evm` package is specific to the `EVM` implementaion but it is not specific to any domain
- `ua-utils-evm` package is specific to the `EVM` implementation and specific to the `ua` (user application) domain
- `ua-utils-evm-hardhat` package is specific to the `EVM` implementation using `hardhat` and specific to the `ua` (user application) domain

The only exceptions to this rule are packages that need to follow an existing naming convention (`create-lz-oapp`) or packages for which the name needs to appeal or be intuitive/familiar to the user (`toolbox-hardhat`)

## Recipes

### `*-hardhat` packages

These packages augment the `hardhat` types and introduce a new property on the `network` configuration: `eid`. This property links the user-defined network names to LayerZero endpoint IDs:

```typescript
// hardhat.config.ts

const config: HardhatUserConfig = {
networks: {
"ethereum-mainnet": {
eid: EndpointId.ETHEREUM_MAINNET,
// ...
},
},
};
```

This property is required for a lot of the tooling to work - the link between network names and endpoints needs to be specified in order to wire OApps successfully.

#### Getting `hre` (`HardhatRuntimeEnvironment`) for a network

```typescript
// By network name (as specified in hardhat config)
import { getHreByNetworkName } from "@layerzerolabs/utils-evm-hardhat";

const environment = await getHreByNetworkName("avalanche-testnet");

// By endpoint ID (as specified in hardhat config, using the eid property of a network)
import { createGetHreByEid } from "@layerzerolabs/utils-evm-hardhat";

// In this case we need to instantiate an environemnt factory
const getEnvironment = createGetHreByEid();

const eid = EndpointId.AVALANCHE_TESTNET;
const environment = await getNetworkRuntimeEnvironmentByEid(eid);
```

#### Getting a contract instance

##### Disconnected, without a provider

```typescript
// By OmniPointHardhat
import { createContractFactory } from "@layerzerolabs/utils-evm-hardhat";

// In this case we need to instantiate a contract factory
const createContract = createContractFactory();

const eid = EndpointId.BST_MAINNET;

// We can ask for the contract by its name and eid
const contract = await createContract({ eid: address: '0x' })

// Or its name
const contract = await createContract({ eid: contractName: 'MyOApp' })
```

##### Connected, with a provider

```typescript
// By OmniPointHardhat
import { createConnectedContractFactory } from "@layerzerolabs/utils-evm-hardhat";

// In this case we need to instantiate a contract factory
const createContract = createConnectedContractFactory();

const eid = EndpointId.BST_MAINNET;

// We can ask for the contract by its name and eid
const contract = await createContract({ eid: address: '0x' })

// Or its name
const contract = await createContract({ eid: contractName: 'MyOApp' })
```
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

<h1 align="center">LayerZero Contract Utilities</h1>

## Code layout

The code is arranged into:

- **Reusable packages** under `./packages` directory
- **Example projects** under `./examples` directory

## Development

```bash
Expand Down
6 changes: 3 additions & 3 deletions packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
createConnectedContractFactory,
createNetworkEnvironmentFactory,
createGetHreByEid,
createSignerFactory,
OmniGraphBuilderHardhat,
type OmniGraphHardhat,
Expand Down Expand Up @@ -65,7 +65,7 @@ export const getDefaultUlnConfig = (dvnAddress: string): Uln302UlnConfig => {
* Deploys an enpoint fixture. Useful for tests
*/
export const deployEndpointFixture = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand All @@ -76,7 +76,7 @@ export const deployEndpointFixture = async () => {
* Deploys an enpoint fixture. Useful for when deployment files need to be persisted
*/
export const deployEndpoint = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand Down
6 changes: 3 additions & 3 deletions packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { createNetworkEnvironmentFactory } from '@layerzerolabs/utils-evm-hardhat'
import { createGetHreByEid } from '@layerzerolabs/utils-evm-hardhat'

export const deployOApp = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand All @@ -13,7 +13,7 @@ export const deployOApp = async () => {
}

export const deployOAppFixture = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { createNetworkEnvironmentFactory } from '@layerzerolabs/utils-evm-hardhat'
import { createGetHreByEid } from '@layerzerolabs/utils-evm-hardhat'

export const deployOmniCounter = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand All @@ -13,7 +13,7 @@ export const deployOmniCounter = async () => {
}

export const deployOmniCounterFixture = async () => {
const environmentFactory = createNetworkEnvironmentFactory()
const environmentFactory = createGetHreByEid()
const eth = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const avax = await environmentFactory(EndpointId.AVALANCHE_MAINNET)

Expand Down
6 changes: 2 additions & 4 deletions packages/utils-evm-hardhat/src/omnigraph/coordinates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OmniContract } from '@layerzerolabs/utils-evm'
import { Contract } from '@ethersproject/contracts'
import assert from 'assert'
import { OmniContractFactoryHardhat, OmniDeployment } from './types'
import { createNetworkEnvironmentFactory } from '@/runtime'
import { createGetHreByEid } from '@/runtime'
import { assertHardhatDeploy } from '@/internal/assertions'

export const omniDeploymentToPoint = ({ eid, deployment }: OmniDeployment): OmniPoint => ({
Expand All @@ -17,9 +17,7 @@ export const omniDeploymentToContract = ({ eid, deployment }: OmniDeployment): O
contract: new Contract(deployment.address, deployment.abi),
})

export const createContractFactory = (
environmentFactory = createNetworkEnvironmentFactory()
): OmniContractFactoryHardhat => {
export const createContractFactory = (environmentFactory = createGetHreByEid()): OmniContractFactoryHardhat => {
return pMemoize(async ({ eid, address, contractName }) => {
const env = await environmentFactory(eid)
assertHardhatDeploy(env)
Expand Down
6 changes: 2 additions & 4 deletions packages/utils-evm-hardhat/src/provider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { JsonRpcProvider } from '@ethersproject/providers'
import type { ProviderFactory } from '@layerzerolabs/utils-evm'
import type { HardhatRuntimeEnvironment } from 'hardhat/types'
import pMemoize from 'p-memoize'
import { createNetworkEnvironmentFactory, getDefaultRuntimeEnvironment, wrapEIP1193Provider } from './runtime'
import { createGetHreByEid, wrapEIP1193Provider } from './runtime'

export const createProviderFactory = (
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment(),
networkEnvironmentFactory = createNetworkEnvironmentFactory(hre)
networkEnvironmentFactory = createGetHreByEid()
): ProviderFactory<JsonRpcProvider> => {
return pMemoize(async (eid) => {
const env = await networkEnvironmentFactory(eid)
Expand Down
52 changes: 28 additions & 24 deletions packages/utils-evm-hardhat/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ 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, formatEid } from '@layerzerolabs/utils'
import { EndpointBasedFactory, Factory, formatEid } from '@layerzerolabs/utils'
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'
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>
export type GetByNetwork<TValue> = Factory<[networkName: string], TValue>

/**
* Helper type for when we need to grab something asynchronously by the network name
*/
export type GetByEid<TValue> = Factory<[eid: EndpointId], TValue>

/**
* Returns the default hardhat context for the project, i.e.
Expand Down Expand Up @@ -62,15 +67,15 @@ export const getDefaultRuntimeEnvironment = (): HardhatRuntimeEnvironment => {
* Creates a clone of the HardhatRuntimeEnvironment for a particular network
*
* ```typescript
* const env = getEnvironment("bsc-testnet");
* const env = getHreByNetworkName("bsc-testnet");
*
* // All the ususal properties are present
* env.deployments.get("MyContract")
* ```
*
* @returns {Promise<HardhatRuntimeEnvironment>}
*/
export const getNetworkRuntimeEnvironment: GetByNetwork<HardhatRuntimeEnvironment> = pMemoize(async (networkName) => {
export const getHreByNetworkName: GetByNetwork<HardhatRuntimeEnvironment> = pMemoize(async (networkName) => {
const context = getDefaultContext()
const environment = getDefaultRuntimeEnvironment()

Expand Down Expand Up @@ -98,6 +103,25 @@ export const getNetworkRuntimeEnvironment: GetByNetwork<HardhatRuntimeEnvironmen
}
})

/**
* Creates a clone of the HardhatRuntimeEnvironment for a particular network
* identified by endpoint ID
*
* ```typescript
* const getHreByEid = createGetHreByEid()
* const env = await getHreByEid(EndpointId.AVALANCHE_V2_TESTNET);
*
* // All the ususal properties are present
* env.deployments.get("MyContract")
* ```
*
* @returns {Promise<HardhatRuntimeEnvironment>}
*/
export const createGetHreByEid = (
hre = getDefaultRuntimeEnvironment()
): EndpointBasedFactory<HardhatRuntimeEnvironment> =>
pMemoize(async (eid: EndpointId) => getHreByNetworkName(getNetworkNameForEid(eid, hre)))

/**
* Helper function that wraps an EthereumProvider with EthersProviderWrapper
* so that we can use it further with ethers as a regular JsonRpcProvider
Expand All @@ -107,26 +131,6 @@ export const getNetworkRuntimeEnvironment: GetByNetwork<HardhatRuntimeEnvironmen
*/
export const wrapEIP1193Provider = (provider: EthereumProvider): JsonRpcProvider => new EthersProviderWrapper(provider)

/**
* Creates a factory function for creating HardhatRuntimeEnvironment
* based on a hardhat config and an EndpointId
*
* ```typescript
* import hre from "hardhat";
*
* const factory = createNetworkEnvironmentFactory(hre);
* const env = factory(EndpointId.FANTOM_MAINNET)
* ```
*
* @param {HardhatRuntimeEnvironment | undefined} [hre]
* @returns {(eid: EndpointId) => Promise<HardhatRuntimeEnvironment>}
*/
export const createNetworkEnvironmentFactory = (
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment()
): EndpointBasedFactory<HardhatRuntimeEnvironment> => {
return async (eid) => getNetworkRuntimeEnvironment(getNetworkNameForEid(eid, hre))
}

/**
* Gets an EndpointId defined in the hardhat config
* for a particular network name (as an `eid` property).
Expand Down
5 changes: 1 addition & 4 deletions packages/utils-evm-hardhat/src/signer/factory.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types'
import type { OmniSignerFactory } from '@layerzerolabs/utils'
import { OmniSignerEVM } from '@layerzerolabs/utils-evm'
import pMemoize from 'p-memoize'
import { getDefaultRuntimeEnvironment } from '../runtime'
import { createProviderFactory } from '../provider'

export const createSignerFactory = (
addressOrIndex?: string | number,
hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment(),
providerFactory = createProviderFactory(hre)
providerFactory = createProviderFactory()
): OmniSignerFactory<OmniSignerEVM> => {
return pMemoize(async (eid) => {
const provider = await providerFactory(eid)
Expand Down
6 changes: 3 additions & 3 deletions packages/utils-evm-hardhat/test/omnigraph/coordinates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { OmniDeployment, createContractFactory, omniDeploymentToContract, omniDe
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { Contract } from '@ethersproject/contracts'
import { makeZeroAddress } from '@layerzerolabs/utils-evm'
import { createNetworkEnvironmentFactory } from '@/runtime'
import { createGetHreByEid } from '@/runtime'

jest.spyOn(DeploymentsManager.prototype, 'getChainId').mockResolvedValue('1')

Expand Down Expand Up @@ -59,7 +59,7 @@ describe('omnigraph/coordinates', () => {
})

it('should resolve when contract has been deployed', async () => {
const environmentFactory = createNetworkEnvironmentFactory(hre)
const environmentFactory = createGetHreByEid(hre)
const contractFactory = createContractFactory(environmentFactory)

const env = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('omnigraph/coordinates', () => {
it('should resolve when contract has been deployed', async () => {
await fc.assert(
fc.asyncProperty(evmAddressArbitrary, async (address) => {
const environmentFactory = createNetworkEnvironmentFactory(hre)
const environmentFactory = createGetHreByEid(hre)
const contractFactory = createContractFactory(environmentFactory)

const env = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
Expand Down
17 changes: 11 additions & 6 deletions packages/utils-evm-hardhat/test/provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getNetworkRuntimeEnvironment } from '@/runtime'
import hre from 'hardhat'
import 'hardhat'
import { createGetHreByEid } from '@/runtime'
import { EndpointId } from '@layerzerolabs/lz-definitions'
import { createProviderFactory } from '@/provider'
import { JsonRpcProvider } from '@ethersproject/providers'
Expand All @@ -10,12 +10,17 @@ jest.spyOn(JsonRpcProvider.prototype, 'detectNetwork').mockResolvedValue({ chain
describe('provider', () => {
describe('createProviderFactory', () => {
it('should reject with an endpoint that is not in the hardhat config', async () => {
await expect(createProviderFactory(hre)(EndpointId.CATHAY_TESTNET)).rejects.toBeTruthy()
const environmentFactory = createGetHreByEid()
const providerFactory = createProviderFactory(environmentFactory)

await expect(providerFactory(EndpointId.CATHAY_TESTNET)).rejects.toBeTruthy()
})

it('should return a Web3Provider wrapping the network provider', async () => {
const env = await getNetworkRuntimeEnvironment('ethereum-mainnet')
const provider = await createProviderFactory(hre)(EndpointId.ETHEREUM_MAINNET)
it('should return a JsonRpcProvider wrapping the network provider', async () => {
const environmentFactory = createGetHreByEid()
const providerFactory = createProviderFactory(environmentFactory)
const env = await environmentFactory(EndpointId.ETHEREUM_MAINNET)
const provider = await providerFactory(EndpointId.ETHEREUM_MAINNET)

expect(provider).toBeInstanceOf(JsonRpcProvider)

Expand Down
Loading

0 comments on commit 3901613

Please sign in to comment.