Skip to content

Commit

Permalink
🗞️ Solana: Setting ULN & Executor configs [18/N] (LayerZero-Labs#778)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Jul 25, 2024
1 parent f8efbda commit ae7f1b3
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-seas-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@layerzerolabs/protocol-devtools-solana": patch
---

Add setConfig related functionality
27 changes: 27 additions & 0 deletions packages/protocol-devtools-solana/src/endpointv2/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SetConfigType } from '@layerzerolabs/lz-solana-sdk-v2'
import { Uln302ExecutorConfigSchema, Uln302UlnConfigSchema } from '@layerzerolabs/protocol-devtools'
import { PublicKey } from '@solana/web3.js'
import { z } from 'zod'

export const SetConfigSchema = z.union([
z.object({
configType: z.literal(SetConfigType.EXECUTOR),
config: Uln302ExecutorConfigSchema.transform(({ executor, maxMessageSize }) => ({
executor: new PublicKey(executor),
maxMessageSize,
})),
}),
z.object({
configType: z.union([z.literal(SetConfigType.RECEIVE_ULN), z.literal(SetConfigType.SEND_ULN)]),
config: Uln302UlnConfigSchema.transform(
({ confirmations, requiredDVNs, optionalDVNs, optionalDVNThreshold }) => ({
confirmations: Number(confirmations),
optionalDvnCount: optionalDVNs.length,
requiredDvnCount: requiredDVNs.length,
requiredDvns: requiredDVNs.map((dvn) => new PublicKey(dvn)),
optionalDvns: optionalDVNs.map((dvn) => new PublicKey(dvn)),
optionalDvnThreshold: optionalDVNThreshold,
})
),
}),
])
100 changes: 80 additions & 20 deletions packages/protocol-devtools-solana/src/endpointv2/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import { OmniSDK } from '@layerzerolabs/devtools-solana'
import { Timeout } from '@layerzerolabs/protocol-devtools'
import { Uln302SetExecutorConfig } from '@layerzerolabs/protocol-devtools'
import { Logger, printJson } from '@layerzerolabs/io-devtools'
import { EndpointProgram } from '@layerzerolabs/lz-solana-sdk-v2'
import { EndpointProgram, SetConfigType } from '@layerzerolabs/lz-solana-sdk-v2'
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
import assert from 'assert'
import { Uln302 } from '@/uln302'
import { SetConfigSchema } from './schema'

/**
* Solana-specific SDK for EndpointV2 contracts
Expand Down Expand Up @@ -61,12 +62,14 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {

@AsyncRetriable()
async getDefaultReceiveLibrary(eid: EndpointId): Promise<OmniAddress | undefined> {
this.logger.debug(`Getting default receive library for eid ${eid} (${formatEid(eid)})`)
const eidLabel = formatEid(eid)

this.logger.debug(`Getting default receive library for eid ${eid} (${eidLabel})`)

const config = await mapError(
() => this.program.getDefaultReceiveLibrary(this.connection, eid),
(error) =>
new Error(`Failed to get the default receive library for ${this.label} for ${formatEid(eid)}: ${error}`)
new Error(`Failed to get the default receive library for ${this.label} for ${eidLabel}: ${error}`)
)

return config?.msgLib.toBase58() ?? undefined
Expand All @@ -86,7 +89,7 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
)
)

return config?.msgLib.toBase58() ?? undefined
return config?.programId?.toBase58() ?? undefined
}

@AsyncRetriable()
Expand All @@ -96,7 +99,7 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
): Promise<[address: OmniAddress | undefined, isDefault: boolean]> {
const eidLabel = formatEid(srcEid)

this.logger.debug(`Getting receive library for eid ${srcEid} (${formatEid(srcEid)}) and address ${receiver}`)
this.logger.debug(`Getting receive library for eid ${srcEid} (${eidLabel}) and address ${receiver}`)

const config = await mapError(
() => this.program.getReceiveLibrary(this.connection, new PublicKey(receiver), srcEid),
Expand All @@ -106,7 +109,7 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
)
)

return [config?.msgLib.toBase58() ?? undefined, config?.isDefault ?? true]
return [config?.programId?.toBase58() ?? undefined, config?.isDefault ?? true]
}

async setDefaultReceiveLibrary(
Expand All @@ -123,12 +126,13 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {

@AsyncRetriable()
async getDefaultSendLibrary(eid: EndpointId): Promise<OmniAddress | undefined> {
this.logger.debug(`Getting default send library for eid ${eid} (${formatEid(eid)})`)
const eidLabel = formatEid(eid)

this.logger.debug(`Getting default send library for eid ${eid} (${eidLabel})`)

const config = await mapError(
() => this.program.getDefaultSendLibrary(this.connection, eid),
(error) =>
new Error(`Failed to get the default send library for ${this.label} for ${formatEid(eid)}: ${error}`)
(error) => new Error(`Failed to get the default send library for ${this.label} for ${eidLabel}: ${error}`)
)

return config?.msgLib.toBase58() ?? undefined
Expand Down Expand Up @@ -255,10 +259,39 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
throw new TypeError(`setReceiveLibraryTimeout() not implemented on Solana Endpoint SDK`)
}

async setConfig(oapp: OmniAddress, uln: OmniAddress, setConfigParam: SetConfigParam[]): Promise<OmniTransaction> {
this.logger.debug(`Setting config for OApp ${oapp} to ULN ${uln} with config ${printJson(setConfigParam)}`)
async setConfig(oapp: OmniAddress, uln: OmniAddress, setConfigParams: SetConfigParam[]): Promise<OmniTransaction> {
this.logger.debug(`Setting config for OApp ${oapp} to ULN ${uln} with config ${printJson(setConfigParams)}`)

throw new TypeError(`setConfig() not implemented on Solana Endpoint SDK`)
const transaction = new Transaction()

for (const setConfigParam of setConfigParams) {
try {
// We run the config through a schema to ensure the formatting is good
// and so that we convert the string DVN/executor addresses to public keys
const parsedConfig = SetConfigSchema.parse(setConfigParam)

const instruction = await this.program.setOappConfig(
this.connection,
this.userAccount,
new PublicKey(oapp),
new PublicKey(uln),
setConfigParam.eid,
{
configType: parsedConfig.configType,
value: parsedConfig.config,
}
)

transaction.add(instruction)
} catch (error) {
throw new Error(`Failed to setConfig for ${this.label} and OApp ${oapp} and ULN ${uln}: ${error}`)
}
}

return {
...(await this.createTransaction(transaction)),
description: `Setting config for ULN ${uln} to ${printJson(setConfigParams)}`,
}
}

async setUlnConfig(
Expand Down Expand Up @@ -294,14 +327,25 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
`Getting executor app config for eid ${eid} (${formatEid(eid)}) and OApp ${oapp} and ULN ${uln}`
)

throw new TypeError(`getAppExecutorConfig() not implemented on Solana Endpoint SDK`)
const ulnSdk = await this.getUln302SDK(uln)
return await ulnSdk.getAppExecutorConfig(eid, oapp)
}

/**
* @see {@link IEndpointV2.hasAppExecutorConfig}
*/
async hasAppExecutorConfig(): Promise<boolean> {
throw new TypeError(`hasAppExecutorConfig() not implemented on Solana Endpoint SDK`)
async hasAppExecutorConfig(
oapp: OmniAddress,
uln: OmniAddress,
eid: EndpointId,
config: Uln302ExecutorConfig
): Promise<boolean> {
this.logger.debug(
`Checking executor app config for eid ${eid} (${formatEid(eid)}) and OApp ${oapp} and ULN ${uln}`
)

const ulnSdk = await this.getUln302SDK(uln)
return await ulnSdk.hasAppExecutorConfig(eid, oapp, config)
}

/**
Expand Down Expand Up @@ -332,8 +376,9 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
eid: EndpointId,
config: Uln302UlnUserConfig
): Promise<boolean> {
const ulnSdk = await this.getUln302SDK(uln)
this.logger.debug(`Checking ULN app config for eid ${eid} (${formatEid(eid)}) and OApp ${oapp} and ULN ${uln}`)

const ulnSdk = await this.getUln302SDK(uln)
return ulnSdk.hasAppUlnConfig(eid, oapp, config)
}

Expand All @@ -351,11 +396,26 @@ export class EndpointV2 extends OmniSDK implements IEndpointV2 {
throw new TypeError(`quote() not implemented on Solana Endpoint SDK`)
}

async getUlnConfigParams(): Promise<SetConfigParam[]> {
throw new TypeError(`getUlnConfigParams() not implemented on Solana Endpoint SDK`)
async getUlnConfigParams(uln: OmniAddress, setUlnConfig: Uln302SetUlnConfig[]): Promise<SetConfigParam[]> {
const ulnSdk = (await this.getUln302SDK(uln)) as Uln302

return setUlnConfig.map(({ eid, ulnConfig, type }) => ({
eid,
configType: type === 'send' ? SetConfigType.SEND_ULN : SetConfigType.RECEIVE_ULN,
config: ulnSdk.encodeUlnConfig(ulnConfig),
}))
}

async getExecutorConfigParams(): Promise<SetConfigParam[]> {
throw new TypeError(`getExecutorConfigParams() not implemented on Solana Endpoint SDK`)
async getExecutorConfigParams(
uln: OmniAddress,
setExecutorConfig: Uln302SetExecutorConfig[]
): Promise<SetConfigParam[]> {
const ulnSdk = (await this.getUln302SDK(uln)) as Uln302

return setExecutorConfig.map(({ eid, executorConfig }) => ({
eid,
configType: SetConfigType.EXECUTOR,
config: ulnSdk.encodeExecutorConfig(executorConfig),
}))
}
}
16 changes: 4 additions & 12 deletions packages/protocol-devtools-solana/src/uln302/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,20 +168,12 @@ export class Uln302 extends OmniSDK implements IUln302 {
throw new TypeError(`getExecutorConfig() not implemented on Solana Endpoint SDK`)
}

decodeExecutorConfig(_executorConfigBytes: string): Uln302ExecutorConfig {
throw new TypeError(`getExecutorConfig() not implemented on Solana Endpoint SDK`)
}

encodeExecutorConfig(_config: Uln302ExecutorConfig): string {
throw new TypeError(`getExecutorConfig() not implemented on Solana Endpoint SDK`)
encodeExecutorConfig(config: Uln302ExecutorConfig): SerializedUln302ExecutorConfig {
return this.serializeExecutorConfig(config)
}

decodeUlnConfig(_ulnConfigBytes: string): Uln302UlnConfig {
throw new TypeError(`getExecutorConfig() not implemented on Solana Endpoint SDK`)
}

encodeUlnConfig(_config: Uln302UlnUserConfig): string {
throw new TypeError(`getExecutorConfig() not implemented on Solana Endpoint SDK`)
encodeUlnConfig(config: Uln302UlnUserConfig): SerializedUln302UlnConfig {
return this.serializeUlnConfig(config)
}

async setDefaultUlnConfig(_eid: EndpointId, _config: Uln302UlnUserConfig): Promise<OmniTransaction> {
Expand Down
54 changes: 53 additions & 1 deletion packages/protocol-devtools-solana/test/endpointv2/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@ describe('endpointv2/sdk', () => {
const blockhash = '3dY6Hp5N6MiztdurusKF59i2fE9tJbo9nPf5JsFarJFg'

let connectionFactory: ConnectionFactory
let getLatestBlockhashMock: jest.SpyInstance

beforeAll(() => {
connectionFactory = createConnectionFactory(defaultRpcUrlFactory)
})

beforeEach(() => {
// We mock the getLatestBlockhash to reduce the RPC load
jest.spyOn(Connection.prototype, 'getLatestBlockhash').mockResolvedValue({
getLatestBlockhashMock = jest.spyOn(Connection.prototype, 'getLatestBlockhash').mockResolvedValue({
blockhash,
lastValidBlockHeight: NaN,
})
})

afterEach(() => {
getLatestBlockhashMock.mockRestore()
})

describe('getDefaultReceiveLibrary', () => {
it('should return undefined if we are asking for a default library that has not been set', async () => {
const connection = await connectionFactory(EndpointId.SOLANA_V2_MAINNET)
Expand Down Expand Up @@ -301,4 +308,49 @@ describe('endpointv2/sdk', () => {
).toBeFalsy()
})
})

describe('setConfig', () => {
it('should create an OmniTransaction when called with uln configs', async () => {
getLatestBlockhashMock.mockRestore()

const connection = await connectionFactory(EndpointId.SOLANA_V2_MAINNET)
const sdk = new EndpointV2(connection, point, account)

const eid = EndpointId.ETHEREUM_V2_MAINNET

const sendUln = await sdk.getSendLibrary(oftConfig.toBase58(), eid)
expect(sendUln).not.toBeUndefined()

const params = await sdk.getUlnConfigParams(sendUln!, [
{
type: 'send',
eid: EndpointId.ETHEREUM_V2_MAINNET,
ulnConfig: {
confirmations: BigInt(32),
optionalDVNThreshold: 0,
requiredDVNs: [
'4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb',
'GPjyWr8vCotGuFubDpTxDxy9Vj1ZeEN4F2dwRmFiaGab',
],
optionalDVNs: [],
},
},
{
type: 'receive',
eid: EndpointId.ETHEREUM_V2_MAINNET,
ulnConfig: {
confirmations: BigInt(32),
optionalDVNThreshold: 0,
requiredDVNs: [
'4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb',
'GPjyWr8vCotGuFubDpTxDxy9Vj1ZeEN4F2dwRmFiaGab',
],
optionalDVNs: [],
},
},
])

await sdk.setConfig(oftConfig.toBase58(), sendUln!, params)
})
})
})

0 comments on commit ae7f1b3

Please sign in to comment.