diff --git a/CHEATSHEET.md b/CHEATSHEET.md new file mode 100644 index 000000000..14db9a2bc --- /dev/null +++ b/CHEATSHEET.md @@ -0,0 +1,108 @@ +

+ + LayerZero + +

+ +

Developer Cheatsheet

+ +## 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-][-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' }) +``` diff --git a/README.md b/README.md index bafbc4905..aa53c2deb 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@

LayerZero Contract Utilities

+## Code layout + +The code is arranged into: + +- **Reusable packages** under `./packages` directory +- **Example projects** under `./examples` directory + ## Development ```bash diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts index 3397e0693..460e3778b 100644 --- a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts @@ -1,6 +1,6 @@ import { createConnectedContractFactory, - createNetworkEnvironmentFactory, + createGetHreByEid, createSignerFactory, OmniGraphBuilderHardhat, type OmniGraphHardhat, @@ -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) @@ -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) diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts index e2bc1714f..d47ae5464 100644 --- a/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts @@ -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) @@ -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) diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/omnicounter.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/omnicounter.ts index 30389ed7f..044b4f226 100644 --- a/packages/ua-utils-evm-hardhat-test/test/__utils__/omnicounter.ts +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/omnicounter.ts @@ -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) @@ -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) diff --git a/packages/utils-evm-hardhat/src/omnigraph/coordinates.ts b/packages/utils-evm-hardhat/src/omnigraph/coordinates.ts index 69e050ecc..10cd222d3 100644 --- a/packages/utils-evm-hardhat/src/omnigraph/coordinates.ts +++ b/packages/utils-evm-hardhat/src/omnigraph/coordinates.ts @@ -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 => ({ @@ -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) diff --git a/packages/utils-evm-hardhat/src/provider.ts b/packages/utils-evm-hardhat/src/provider.ts index 4d75c0b14..4b2c52c5d 100644 --- a/packages/utils-evm-hardhat/src/provider.ts +++ b/packages/utils-evm-hardhat/src/provider.ts @@ -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 => { return pMemoize(async (eid) => { const env = await networkEnvironmentFactory(eid) diff --git a/packages/utils-evm-hardhat/src/runtime.ts b/packages/utils-evm-hardhat/src/runtime.ts index 41fb500ed..edc2d488e 100644 --- a/packages/utils-evm-hardhat/src/runtime.ts +++ b/packages/utils-evm-hardhat/src/runtime.ts @@ -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 = (networkName: string) => Promise +export type GetByNetwork = Factory<[networkName: string], TValue> + +/** + * Helper type for when we need to grab something asynchronously by the network name + */ +export type GetByEid = Factory<[eid: EndpointId], TValue> /** * Returns the default hardhat context for the project, i.e. @@ -62,7 +67,7 @@ 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") @@ -70,7 +75,7 @@ export const getDefaultRuntimeEnvironment = (): HardhatRuntimeEnvironment => { * * @returns {Promise} */ -export const getNetworkRuntimeEnvironment: GetByNetwork = pMemoize(async (networkName) => { +export const getHreByNetworkName: GetByNetwork = pMemoize(async (networkName) => { const context = getDefaultContext() const environment = getDefaultRuntimeEnvironment() @@ -98,6 +103,25 @@ export const getNetworkRuntimeEnvironment: GetByNetwork} + */ +export const createGetHreByEid = ( + hre = getDefaultRuntimeEnvironment() +): EndpointBasedFactory => + 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 @@ -107,26 +131,6 @@ export const getNetworkRuntimeEnvironment: GetByNetwork 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} - */ -export const createNetworkEnvironmentFactory = ( - hre: HardhatRuntimeEnvironment = getDefaultRuntimeEnvironment() -): EndpointBasedFactory => { - return async (eid) => getNetworkRuntimeEnvironment(getNetworkNameForEid(eid, hre)) -} - /** * Gets an EndpointId defined in the hardhat config * for a particular network name (as an `eid` property). diff --git a/packages/utils-evm-hardhat/src/signer/factory.ts b/packages/utils-evm-hardhat/src/signer/factory.ts index 010a0a159..704738472 100644 --- a/packages/utils-evm-hardhat/src/signer/factory.ts +++ b/packages/utils-evm-hardhat/src/signer/factory.ts @@ -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 => { return pMemoize(async (eid) => { const provider = await providerFactory(eid) diff --git a/packages/utils-evm-hardhat/test/omnigraph/coordinates.test.ts b/packages/utils-evm-hardhat/test/omnigraph/coordinates.test.ts index 82f5ea9ac..4c444d1f7 100644 --- a/packages/utils-evm-hardhat/test/omnigraph/coordinates.test.ts +++ b/packages/utils-evm-hardhat/test/omnigraph/coordinates.test.ts @@ -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') @@ -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) @@ -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) diff --git a/packages/utils-evm-hardhat/test/provider.test.ts b/packages/utils-evm-hardhat/test/provider.test.ts index 760babe48..a2a1b0f1e 100644 --- a/packages/utils-evm-hardhat/test/provider.test.ts +++ b/packages/utils-evm-hardhat/test/provider.test.ts @@ -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' @@ -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) diff --git a/packages/utils-evm-hardhat/test/runtime.test.ts b/packages/utils-evm-hardhat/test/runtime.test.ts index f01d3dadf..86e1f0a91 100644 --- a/packages/utils-evm-hardhat/test/runtime.test.ts +++ b/packages/utils-evm-hardhat/test/runtime.test.ts @@ -1,11 +1,6 @@ import hre from 'hardhat' import { DeploymentsManager } from 'hardhat-deploy/dist/src/DeploymentsManager' -import { - createNetworkEnvironmentFactory, - getEidForNetworkName, - getNetworkNameForEid, - getNetworkRuntimeEnvironment, -} from '@/runtime' +import { createGetHreByEid, getEidForNetworkName, getNetworkNameForEid, getHreByNetworkName } from '@/runtime' import type { DeploymentSubmission } from 'hardhat-deploy/dist/types' import { EndpointId } from '@layerzerolabs/lz-definitions' import { HardhatRuntimeEnvironment } from 'hardhat/types' @@ -13,13 +8,13 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types' jest.spyOn(DeploymentsManager.prototype, 'getChainId').mockResolvedValue('1') describe('runtime', () => { - describe('getNetworkRuntimeEnvironment', () => { + describe('getHreByNetworkName', () => { it('should reject with an invalid network', async () => { - await expect(getNetworkRuntimeEnvironment('not-in-hardhat-config')).rejects.toBeTruthy() + await expect(getHreByNetworkName('not-in-hardhat-config')).rejects.toBeTruthy() }) it('should return a HardhatRuntimeEnvironment with correct network', async () => { - const runtime = await getNetworkRuntimeEnvironment('ethereum-mainnet') + const runtime = await getHreByNetworkName('ethereum-mainnet') expect(runtime.network.name).toEqual('ethereum-mainnet') expect(runtime.deployments).toMatchObject({ @@ -28,8 +23,8 @@ describe('runtime', () => { }) it('should have the config setup correctly', async () => { - const ethRuntime = await getNetworkRuntimeEnvironment('ethereum-mainnet') - const bscRuntime = await getNetworkRuntimeEnvironment('bsc-testnet') + const ethRuntime = await getHreByNetworkName('ethereum-mainnet') + const bscRuntime = await getHreByNetworkName('bsc-testnet') expect(ethRuntime.network.name).toBe('ethereum-mainnet') expect(ethRuntime.network.config).toBe(hre.config.networks['ethereum-mainnet']) @@ -38,8 +33,8 @@ describe('runtime', () => { }) it('should save the deployment to correct network', async () => { - const bscRuntime = await getNetworkRuntimeEnvironment('bsc-testnet') - const ethRuntime = await getNetworkRuntimeEnvironment('ethereum-mainnet') + const bscRuntime = await getHreByNetworkName('bsc-testnet') + const ethRuntime = await getHreByNetworkName('ethereum-mainnet') const now = Date.now() const deploymentSubmission = { args: ['bsc-testnet', now], @@ -58,15 +53,15 @@ describe('runtime', () => { }) }) - describe('createNetworkEnvironmentFactory', () => { + describe('createGetHreByEid()', () => { it('should reject with an endpoint that is not in the hardhat config', async () => { - await expect(createNetworkEnvironmentFactory(hre)(EndpointId.CATHAY_TESTNET)).rejects.toThrow( + await expect(createGetHreByEid(hre)(EndpointId.CATHAY_TESTNET)).rejects.toThrow( 'Could not find a network for eid 10171 (CATHAY_TESTNET)' ) }) it('should return a HardhatRuntimeEnvironment with correct network', async () => { - const runtime = await createNetworkEnvironmentFactory(hre)(EndpointId.ETHEREUM_MAINNET) + const runtime = await createGetHreByEid(hre)(EndpointId.ETHEREUM_MAINNET) expect(runtime.network.name).toEqual('ethereum-mainnet') expect(runtime.deployments).toMatchObject({ diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index d2ecad81e..aae0f6e55 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -21,7 +21,7 @@ export type Bytes32 = string */ export type Factory = (...input: TInput) => TOutput | Promise -export type EndpointBasedFactory = (eid: EndpointId) => TValue | Promise +export type EndpointBasedFactory = Factory<[eid: EndpointId], TValue> /** * Helper type that grabs all the keys of a type / an interface