Skip to content

Commit

Permalink
feat: abstract crud module (#3621)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalaji authored Apr 29, 2024
1 parent c4b7ad3 commit 2db77f1
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 43 deletions.
7 changes: 7 additions & 0 deletions .changeset/cool-readers-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@hyperlane-xyz/sdk': minor
---

Added RPC `concurrency` property to `ChainMetadata`.
Added `CrudModule` abstraction and related types.
Removed `Fuel` ProtocolType.
8 changes: 5 additions & 3 deletions typescript/sdk/src/core/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/crud.js';
import { EvmHookReader } from '../hook/read.js';
import { EvmIsmReader } from '../ism/read.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { ChainName } from '../types.js';
import { ChainNameOrId } from '../types.js';

import { CoreConfig } from './types.js';

Expand All @@ -21,8 +21,10 @@ export class EvmCoreReader implements CoreReader {
evmIsmReader: EvmIsmReader;
constructor(
protected readonly multiProvider: MultiProvider,
protected readonly chain: ChainName,
protected readonly concurrency: number = DEFAULT_CONTRACT_READ_CONCURRENCY,
protected readonly chain: ChainNameOrId,
protected readonly concurrency: number = multiProvider.tryGetRpcConcurrency(
chain,
) ?? DEFAULT_CONTRACT_READ_CONCURRENCY,
) {
this.provider = this.multiProvider.getProvider(chain);
this.evmHookReader = new EvmHookReader(multiProvider, chain, concurrency);
Expand Down
57 changes: 57 additions & 0 deletions typescript/sdk/src/crud/AbstractCrudModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Logger } from 'pino';

import { Address, Annotated, ProtocolType } from '@hyperlane-xyz/utils';

import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js';
import {
ProtocolTypedProvider,
ProtocolTypedTransaction,
} from '../providers/ProviderType.js';
import { ChainNameOrId } from '../types.js';

export type CrudModuleArgs<
TProtocol extends ProtocolType,
TConfig,
TAddressMap extends Record<string, Address>,
> = {
addresses: TAddressMap;
chain: ChainNameOrId;
chainMetadataManager: ChainMetadataManager;
config: TConfig;
provider: ProtocolTypedProvider<TProtocol>['provider'];
};

export abstract class CrudModule<
TProtocol extends ProtocolType,
TConfig,
TAddressMap extends Record<string, Address>,
> {
protected abstract readonly logger: Logger;

protected constructor(
protected readonly args: CrudModuleArgs<TProtocol, TConfig, TAddressMap>,
) {}

public serialize(): TAddressMap {
return this.args.addresses;
}

public abstract read(address: Address): Promise<TConfig>;
public abstract update(
config: TConfig,
): Promise<Annotated<ProtocolTypedTransaction<TProtocol>[]>>;

// /*
// Types and static methods can be challenging. Ensure each implementation includes a static create function.
// Currently, include TConfig to maintain the structure for ISM/Hook configurations.
// If found to be unnecessary, we may consider revisiting and potentially removing these config requirements later.
// */
// public static create<
// TConfig extends CrudConfig,
// TProtocol extends ProtocolType,
// TAddress extends Record<string, any>,
// TModule extends CrudModule<TProtocol, TConfig, TAddress>,
// >(_config: TConfig): Promise<TModule> {
// throw new Error('not implemented');
// }
}
52 changes: 52 additions & 0 deletions typescript/sdk/src/crud/EvmHookModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils';

import { HyperlaneAddresses } from '../contracts/types.js';
import { HookFactories } from '../hook/contracts.js';
import { EvmHookReader } from '../hook/read.js';
import { HookConfig } from '../hook/types.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { EthersV5Transaction } from '../providers/ProviderType.js';

import { CrudModule, CrudModuleArgs } from './AbstractCrudModule.js';

// WIP example implementation of EvmHookModule
export class EvmHookModule extends CrudModule<
ProtocolType.Ethereum,
HookConfig,
HyperlaneAddresses<HookFactories>
> {
protected logger = rootLogger.child({ module: 'EvmHookModule' });
protected reader: EvmHookReader;

protected constructor(
protected readonly multiProvider: MultiProvider,
args: Omit<
CrudModuleArgs<
ProtocolType.Ethereum,
HookConfig,
HyperlaneAddresses<HookFactories>
>,
'provider'
>,
) {
super({
...args,
provider: multiProvider.getProvider(args.chain),
});

this.reader = new EvmHookReader(multiProvider, args.chain);
}

public async read(address: Address): Promise<HookConfig> {
return await this.reader.deriveHookConfig(address);
}

public async update(_config: HookConfig): Promise<EthersV5Transaction[]> {
throw new Error('Method not implemented.');
}

// manually write static create function
public static create(_config: HookConfig): Promise<EvmHookModule> {
throw new Error('not implemented');
}
}
52 changes: 52 additions & 0 deletions typescript/sdk/src/crud/EvmIsmModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils';

import { HyperlaneAddresses } from '../contracts/types.js';
import { ProxyFactoryFactories } from '../deploy/contracts.js';
import { EvmIsmReader } from '../ism/read.js';
import { IsmConfig } from '../ism/types.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { EthersV5Transaction } from '../providers/ProviderType.js';

import { CrudModule, CrudModuleArgs } from './AbstractCrudModule.js';

// WIP example implementation of EvmIsmModule
export class EvmIsmModule extends CrudModule<
ProtocolType.Ethereum,
IsmConfig,
HyperlaneAddresses<ProxyFactoryFactories>
> {
protected logger = rootLogger.child({ module: 'EvmIsmModule' });
protected reader: EvmIsmReader;

protected constructor(
protected readonly multiProvider: MultiProvider,
args: Omit<
CrudModuleArgs<
ProtocolType.Ethereum,
IsmConfig,
HyperlaneAddresses<ProxyFactoryFactories>
>,
'provider'
>,
) {
super({
...args,
provider: multiProvider.getProvider(args.chain),
});

this.reader = new EvmIsmReader(multiProvider, args.chain);
}

public async read(address: Address): Promise<IsmConfig> {
return await this.reader.deriveIsmConfig(address);
}

public async update(_config: IsmConfig): Promise<EthersV5Transaction[]> {
throw new Error('Method not implemented.');
}

// manually write static create function
public static create(_config: IsmConfig): Promise<EvmIsmModule> {
throw new Error('not implemented');
}
}
12 changes: 7 additions & 5 deletions typescript/sdk/src/hook/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {

import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/crud.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { ChainName } from '../types.js';
import { ChainNameOrId } from '../types.js';

import {
AggregationHookConfig,
Expand All @@ -42,7 +42,7 @@ import {
RoutingHookConfig,
} from './types.js';

interface HookReader {
export interface HookReader {
deriveHookConfig(address: Address): Promise<WithAddress<HookConfig>>;
deriveMerkleTreeConfig(
address: Address,
Expand Down Expand Up @@ -74,10 +74,12 @@ export class EvmHookReader implements HookReader {

constructor(
protected readonly multiProvider: MultiProvider,
protected readonly chain: ChainName,
protected readonly concurrency: number = DEFAULT_CONTRACT_READ_CONCURRENCY,
protected readonly chain: ChainNameOrId,
protected readonly concurrency: number = multiProvider.tryGetRpcConcurrency(
chain,
) ?? DEFAULT_CONTRACT_READ_CONCURRENCY,
) {
this.provider = this.multiProvider.getProvider(chain);
this.provider = multiProvider.getProvider(chain);
}

async deriveHookConfig(address: Address): Promise<WithAddress<HookConfig>> {
Expand Down
12 changes: 7 additions & 5 deletions typescript/sdk/src/ism/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {

import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/crud.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { ChainName } from '../types.js';
import { ChainNameOrId } from '../types.js';

import {
AggregationIsmConfig,
Expand All @@ -45,7 +45,7 @@ export type DerivedIsmConfigWithAddress = WithAddress<
Exclude<IsmConfig, Address>
>;

interface IsmReader {
export interface IsmReader {
deriveIsmConfig(address: Address): Promise<DerivedIsmConfigWithAddress>;
deriveRoutingConfig(address: Address): Promise<WithAddress<RoutingIsmConfig>>;
deriveAggregationConfig(
Expand All @@ -63,10 +63,12 @@ export class EvmIsmReader implements IsmReader {

constructor(
protected readonly multiProvider: MultiProvider,
protected readonly chain: ChainName,
protected readonly concurrency: number = DEFAULT_CONTRACT_READ_CONCURRENCY,
protected readonly chain: ChainNameOrId,
protected readonly concurrency: number = multiProvider.tryGetRpcConcurrency(
chain,
) ?? DEFAULT_CONTRACT_READ_CONCURRENCY,
) {
this.provider = this.multiProvider.getProvider(chain);
this.provider = multiProvider.getProvider(chain);
}

async deriveIsmConfig(
Expand Down
34 changes: 29 additions & 5 deletions typescript/sdk/src/metadata/ChainMetadataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,38 @@ export class ChainMetadataManager<MetaExt = {}> {
}

/**
* Get an RPC URL for a given chain name, chain id, or domain id
* Get the RPC details for a given chain name, chain id, or domain id.
* Optional index for metadata containing more than one RPC.
* @throws if chain's metadata has not been set
*/
getRpcUrl(chainNameOrId: ChainNameOrId): string {
getRpc(
chainNameOrId: ChainNameOrId,
index = 0,
): ChainMetadata['rpcUrls'][number] {
const { rpcUrls } = this.getChainMetadata(chainNameOrId);
if (!rpcUrls?.length || !rpcUrls[0].http)
throw new Error(`No RPC URl configured for ${chainNameOrId}`);
return rpcUrls[0].http;
if (!rpcUrls?.length || !rpcUrls[index])
throw new Error(
`No RPC configured at index ${index} for ${chainNameOrId}`,
);
return rpcUrls[index];
}

/**
* Get an RPC URL for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getRpcUrl(chainNameOrId: ChainNameOrId, index = 0): string {
const { http } = this.getRpc(chainNameOrId, index);
if (!http) throw new Error(`No RPC URL configured for ${chainNameOrId}`);
return http;
}

/**
* Get an RPC concurrency level for a given chain name, chain id, or domain id
*/
tryGetRpcConcurrency(chainNameOrId: ChainNameOrId, index = 0): number | null {
const { concurrency } = this.getRpc(chainNameOrId, index);
return concurrency ?? null;
}

/**
Expand Down
6 changes: 0 additions & 6 deletions typescript/sdk/src/metadata/agentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,6 @@ export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge(
}
break;

case ProtocolType.Fuel:
if (![AgentSignerKeyType.Hex].includes(signerType)) {
return false;
}
break;

default:
// Just accept it if we don't know the protocol
}
Expand Down
6 changes: 6 additions & 0 deletions typescript/sdk/src/metadata/chainMetadataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export const RpcUrlSchema = z.object({
.string()
.url()
.describe('The HTTP URL of the RPC endpoint (preferably HTTPS).'),
concurrency: z
.number()
.int()
.positive()
.optional()
.describe('Maximum number of concurrent RPC requests.'),
webSocket: z
.string()
.optional()
Expand Down
46 changes: 43 additions & 3 deletions typescript/sdk/src/providers/ProviderType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ export enum ProviderType {
SolanaWeb3 = 'solana-web3',
CosmJs = 'cosmjs',
CosmJsWasm = 'cosmjs-wasm',
// TODO fuel provider types not yet defined below
Fuel = 'fuel',
}

export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record<
Expand All @@ -43,11 +41,53 @@ export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record<
[ProtocolType.Ethereum]: ProviderType.EthersV5,
[ProtocolType.Sealevel]: ProviderType.SolanaWeb3,
[ProtocolType.Cosmos]: ProviderType.CosmJsWasm,
[ProtocolType.Fuel]: ProviderType.Fuel,
};

export type ProviderMap<Value> = Partial<Record<ProviderType, Value>>;

type ProtocolTypesMapping = {
[ProtocolType.Ethereum]: {
transaction: EthersV5Transaction;
provider: EthersV5Provider;
contract: EthersV5Contract;
receipt: EthersV5TransactionReceipt;
};
[ProtocolType.Sealevel]: {
transaction: SolanaWeb3Transaction;
provider: SolanaWeb3Provider;
contract: SolanaWeb3Contract;
receipt: SolanaWeb3TransactionReceipt;
};
[ProtocolType.Cosmos]: {
transaction: CosmJsWasmTransaction;
provider: CosmJsWasmProvider;
contract: CosmJsWasmContract;
receipt: CosmJsWasmTransactionReceipt;
};
};

type ProtocolTyped<
T extends ProtocolType,
K extends keyof ProtocolTypesMapping[T],
> = ProtocolTypesMapping[T][K];

export type ProtocolTypedTransaction<T extends ProtocolType> = ProtocolTyped<
T,
'transaction'
>;
export type ProtocolTypedProvider<T extends ProtocolType> = ProtocolTyped<
T,
'provider'
>;
export type ProtocolTypedContract<T extends ProtocolType> = ProtocolTyped<
T,
'contract'
>;
export type ProtocolTypedReceipt<T extends ProtocolType> = ProtocolTyped<
T,
'receipt'
>;

/**
* Providers with discriminated union of type
*/
Expand Down
Loading

0 comments on commit 2db77f1

Please sign in to comment.