Skip to content

Commit

Permalink
🪚 Price feed SDK (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Jan 10, 2024
1 parent 2dac0da commit 7bd271e
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 16 deletions.
7 changes: 7 additions & 0 deletions .changeset/perfect-apes-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@layerzerolabs/ua-devtools-evm-hardhat-test": patch
"@layerzerolabs/protocol-devtools-evm": patch
"@layerzerolabs/protocol-devtools": patch
---

Add PriceFeed SDK
1 change: 1 addition & 0 deletions packages/protocol-devtools-evm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './endpoint'
export * from './priceFeed'
export * from './uln302'
15 changes: 15 additions & 0 deletions packages/protocol-devtools-evm/src/priceFeed/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pMemoize from 'p-memoize'
import type { OmniContractFactory } from '@layerzerolabs/devtools-evm'
import type { PriceFeedFactory } from '@layerzerolabs/protocol-devtools'
import { PriceFeed } from './sdk'

/**
* Syntactic sugar that creates an instance of EVM `PriceFeed` SDK
* based on an `OmniPoint` with help of an `OmniContractFactory`
*
* @param {OmniContractFactory} contractFactory
* @returns {PriceFeedFactory<PriceFeed>}
*/
export const createPriceFeedFactory = <TOmniPoint = never>(
contractFactory: OmniContractFactory<TOmniPoint>
): PriceFeedFactory<PriceFeed, TOmniPoint> => pMemoize(async (point) => new PriceFeed(await contractFactory(point)))
3 changes: 3 additions & 0 deletions packages/protocol-devtools-evm/src/priceFeed/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './factory'
export * from './schema'
export * from './sdk'
13 changes: 13 additions & 0 deletions packages/protocol-devtools-evm/src/priceFeed/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BigNumberishBigintSchema } from '@layerzerolabs/devtools-evm'
import { PriceData } from '@layerzerolabs/protocol-devtools'
import { PriceDataSchema as PriceDataSchemaBase } from '@layerzerolabs/protocol-devtools'
import { z } from 'zod'

/**
* Schema for parsing an ethers-specific PriceData into a common format
*/
export const PriceDataSchema = PriceDataSchemaBase.extend({
priceRatio: BigNumberishBigintSchema,
gasPriceInUnit: BigNumberishBigintSchema,
gasPerByte: BigNumberishBigintSchema,
}) satisfies z.ZodSchema<PriceData, z.ZodTypeDef, unknown>
34 changes: 34 additions & 0 deletions packages/protocol-devtools-evm/src/priceFeed/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { EndpointId } from '@layerzerolabs/lz-definitions'
import type { IPriceFeed, PriceData } from '@layerzerolabs/protocol-devtools'
import { formatEid, type OmniTransaction } from '@layerzerolabs/devtools'
import { OmniSDK } from '@layerzerolabs/devtools-evm'
import { printRecord } from '@layerzerolabs/io-devtools'
import { PriceDataSchema } from './schema'

export class PriceFeed extends OmniSDK implements IPriceFeed {
async getPrice(eid: EndpointId): Promise<PriceData> {
const config = await this.contract.contract['getPrice(uint32)'](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 PriceDataSchema.parse({ ...config })
}

async setPrice(eid: EndpointId, priceData: PriceData): Promise<OmniTransaction> {
const data = this.contract.contract.interface.encodeFunctionData('setPrice', [
[
{
eid,
price: priceData,
},
],
])

return {
...this.createTransaction(data),
description: `Setting price for ${formatEid(eid)}: ${printRecord(priceData)}`,
}
}
}
4 changes: 2 additions & 2 deletions packages/protocol-devtools/src/endpoint/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export type EndpointConfigurator = (graph: EndpointOmniGraph, createSdk: Endpoin

export const configureEndpoint: EndpointConfigurator = async (graph, createSdk) =>
flattenTransactions([
...(await configureEndpointDefaultReceiveLibraries(graph, createSdk)),
...(await configureEndpointDefaultSendLibraries(graph, createSdk)),
await configureEndpointDefaultReceiveLibraries(graph, createSdk),
await configureEndpointDefaultSendLibraries(graph, createSdk),
])

export const configureEndpointDefaultReceiveLibraries: EndpointConfigurator = async (graph, createSdk) =>
Expand Down
1 change: 1 addition & 0 deletions packages/protocol-devtools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './endpoint'
export * from './priceFeed'
export * from './uln302'
25 changes: 25 additions & 0 deletions packages/protocol-devtools/src/priceFeed/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { flattenTransactions, isDeepEqual, type OmniTransaction } from '@layerzerolabs/devtools'
import type { PriceFeedFactory, PriceFeedOmniGraph } from './types'

export type PriceFeedConfigurator = (
graph: PriceFeedOmniGraph,
createSdk: PriceFeedFactory
) => Promise<OmniTransaction[]>

export const configurePriceFeed: PriceFeedConfigurator = async (graph, createSdk) =>
flattenTransactions([await configurePriceFeedPriceData(graph, createSdk)])

export const configurePriceFeedPriceData: PriceFeedConfigurator = async (graph, createSdk) =>
flattenTransactions(
await Promise.all(
graph.connections.map(async ({ vector: { from, to }, config }): Promise<OmniTransaction[]> => {
const sdk = await createSdk(from)
const priceData = await sdk.getPrice(to.eid)

// TODO Normalize the config values using a schema before comparing them
if (isDeepEqual(priceData, config.priceData)) return []

return [await sdk.setPrice(to.eid, config.priceData)]
})
)
)
3 changes: 3 additions & 0 deletions packages/protocol-devtools/src/priceFeed/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './config'
export * from './schema'
export * from './types'
9 changes: 9 additions & 0 deletions packages/protocol-devtools/src/priceFeed/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod'
import type { PriceData } from './types'
import { UIntSchema } from '@layerzerolabs/devtools'

export const PriceDataSchema = z.object({
priceRatio: UIntSchema,
gasPriceInUnit: UIntSchema,
gasPerByte: UIntSchema,
}) satisfies z.ZodSchema<PriceData, z.ZodTypeDef, unknown>
24 changes: 24 additions & 0 deletions packages/protocol-devtools/src/priceFeed/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Factory, IOmniSDK, OmniGraph, OmniPoint, OmniTransaction } from '@layerzerolabs/devtools'
import type { EndpointId } from '@layerzerolabs/lz-definitions'

export interface IPriceFeed extends IOmniSDK {
getPrice(eid: EndpointId): Promise<PriceData>
setPrice(eid: EndpointId, priceData: PriceData): Promise<OmniTransaction>
}

export interface PriceData {
priceRatio: bigint | string | number
gasPriceInUnit: bigint | string | number
gasPerByte: bigint | string | number
}

export interface PriceFeedEdgeConfig {
priceData: PriceData
}

export type PriceFeedOmniGraph = OmniGraph<unknown, PriceFeedEdgeConfig>

export type PriceFeedFactory<TPriceFeed extends IPriceFeed = IPriceFeed, TOmniPoint = OmniPoint> = Factory<
[TOmniPoint],
TPriceFeed
>
13 changes: 0 additions & 13 deletions tests/ua-devtools-evm-hardhat-test/deploy/001_bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,6 @@ const deploy: DeployFunction = async ({ getUnnamedAccounts, deployments, network
},
},
})
const priceFeedContract = new Contract(priceFeed.address, priceFeed.abi).connect(signer)
const setPriceResp: TransactionResponse = await priceFeedContract.setPrice([
{
eid: dstEid,
price: {
priceRatio: '100000000000000000000',
gasPriceInUnit: 1,
gasPerByte: 1,
},
},
])
const setPriceReceipt = await setPriceResp.wait()
assert(setPriceReceipt?.status === 1)

await deployments.delete('ExecutorFeeLib')
const executorFeeLib = await deployments.deploy('ExecutorFeeLib', {
Expand Down
50 changes: 49 additions & 1 deletion tests/ua-devtools-evm-hardhat-test/test/__utils__/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,42 @@ import {
Uln302ExecutorConfig,
configureUln302,
Uln302UlnConfig,
configurePriceFeed,
PriceFeedEdgeConfig,
PriceData,
} from '@layerzerolabs/protocol-devtools'
import { createEndpointFactory, createUln302Factory } from '@layerzerolabs/protocol-devtools-evm'
import {
createEndpointFactory,
createPriceFeedFactory,
createUln302Factory,
} from '@layerzerolabs/protocol-devtools-evm'
import { createSignAndSend } from '@layerzerolabs/devtools'

export const ethEndpoint = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'EndpointV2' }
export const ethReceiveUln = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'ReceiveUln302' }
export const ethSendUln = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'SendUln302' }
export const ethPriceFeed = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'PriceFeed' }
export const ethReceiveUln2_Opt2 = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'ReceiveUln302_Opt2' }
export const ethSendUln2_Opt2 = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'SendUln302_Opt2' }
export const ethExecutor = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'Executor' }
export const ethDvn = { eid: EndpointId.ETHEREUM_V2_MAINNET, contractName: 'DVN' }
export const avaxEndpoint = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'EndpointV2' }
export const avaxReceiveUln = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'ReceiveUln302' }
export const avaxSendUln = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'SendUln302' }
export const avaxPriceFeed = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'PriceFeed' }
export const avaxReceiveUln2_Opt2 = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'ReceiveUln302_Opt2' }
export const avaxSendUln2_Opt2 = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'SendUln302_Opt2' }
export const avaxExecutor = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'Executor' }
export const avaxDvn = { eid: EndpointId.AVALANCHE_V2_MAINNET, contractName: 'DVN' }

export const MAX_MESSAGE_SIZE = 10000 // match on-chain value

const defaultPriceData: PriceData = {
priceRatio: '100000000000000000000',
gasPriceInUnit: 1,
gasPerByte: 1,
}

/**
* Helper function to generate the default Uln302ExecutorConfig for a given chain.
*
Expand Down Expand Up @@ -97,6 +112,7 @@ export const setupDefaultEndpoint = async (): Promise<void> => {
const signAndSend = createSignAndSend(createSignerFactory())
const ulnSdkFactory = createUln302Factory(contractFactory)
const endpointSdkFactory = createEndpointFactory(contractFactory, ulnSdkFactory)
const priceFeedSdkFactory = createPriceFeedFactory(contractFactory)

// For the graphs, we'll also need the pointers to the contracts
const ethSendUlnPoint = omniContractToPoint(await contractFactory(ethSendUln))
Expand All @@ -111,6 +127,34 @@ export const setupDefaultEndpoint = async (): Promise<void> => {
const ethUlnConfig: Uln302UlnConfig = getDefaultUlnConfig(ethDvnPoint.address)
const avaxUlnConfig: Uln302UlnConfig = getDefaultUlnConfig(avaxDvnPoint.address)

// This is the graph for PriceFeed
const priceFeedConfig: OmniGraphHardhat<unknown, PriceFeedEdgeConfig> = {
contracts: [
{
contract: ethPriceFeed,
},
{
contract: avaxPriceFeed,
},
],
connections: [
{
from: ethPriceFeed,
to: avaxPriceFeed,
config: {
priceData: defaultPriceData,
},
},
{
from: avaxPriceFeed,
to: ethPriceFeed,
config: {
priceData: defaultPriceData,
},
},
],
}

// This is the graph for SendUln302
const sendUlnConfig: OmniGraphHardhat<Uln302NodeConfig, unknown> = {
contracts: [
Expand Down Expand Up @@ -236,6 +280,9 @@ export const setupDefaultEndpoint = async (): Promise<void> => {
const builderEndpoint = await OmniGraphBuilderHardhat.fromConfig(config)
const endpointTransactions = await configureEndpoint(builderEndpoint.graph, endpointSdkFactory)

const builderPriceFeed = await OmniGraphBuilderHardhat.fromConfig(priceFeedConfig)
const priceFeedTransactions = await configurePriceFeed(builderPriceFeed.graph, priceFeedSdkFactory)

const builderSendUln = await OmniGraphBuilderHardhat.fromConfig(sendUlnConfig)
const sendUlnTransactions = await configureUln302(builderSendUln.graph, ulnSdkFactory)

Expand All @@ -249,6 +296,7 @@ export const setupDefaultEndpoint = async (): Promise<void> => {
const receiveUlnTransactions_Opt2 = await configureUln302(builderReceiveUln_Opt2.graph, ulnSdkFactory)

const transactions = [
...priceFeedTransactions,
...sendUlnTransactions,
...receiveUlnTransactions,
...endpointTransactions,
Expand Down

0 comments on commit 7bd271e

Please sign in to comment.