Skip to content

Commit

Permalink
Finish migrating warp UI utils + adapters to SDK (#2711)
Browse files Browse the repository at this point in the history
### Description

- Build on #2684, migrates the EvmAdapter and missing utils from the warp UI
- Remove the token App as it's unused and redundant with adapters
- Add IGP serialization code for #2546 
- Simplify multi-protocol adapters in SDK
- Make token adapters extend base adapters
- Move token serialization code to token package

### Drive-by changes

- Update Sepolia RPC which was causing errors

### Related issues

#2652 

### Backward compatibility

Yes

### Testing

Tested these in warp UI

---------

Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>
  • Loading branch information
jmrossy and yorhodes authored Sep 13, 2023
1 parent 35fdc74 commit 7a5dce2
Show file tree
Hide file tree
Showing 40 changed files with 1,108 additions and 674 deletions.
46 changes: 29 additions & 17 deletions typescript/helloworld/src/multiProtocolApp/evmAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
ChainName,
EthersV5Transaction,
EvmRouterAdapter,
MultiProtocolProvider,
ProviderType,
RouterAddress,
} from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';

Expand All @@ -13,19 +13,27 @@ import { HelloWorld, HelloWorld__factory } from '../types';
import { IHelloWorldAdapter } from './types';

export class EvmHelloWorldAdapter
extends EvmRouterAdapter<RouterAddress & { mailbox: Address }>
extends EvmRouterAdapter
implements IHelloWorldAdapter
{
constructor(
public readonly chainName: ChainName,
public readonly multiProvider: MultiProtocolProvider,
public readonly addresses: { router: Address; mailbox: Address },
) {
super(chainName, multiProvider, addresses);
}

async populateSendHelloTx(
origin: ChainName,
destination: ChainName,
message: string,
value: string,
): Promise<EthersV5Transaction> {
const contract = this.getConnectedContract(origin);
const contract = this.getConnectedContract();
const toDomain = this.multiProvider.getDomainId(destination);
const { transactionOverrides } =
this.multiProvider.getChainMetadata(origin);
const { transactionOverrides } = this.multiProvider.getChainMetadata(
this.chainName,
);

// apply gas buffer due to https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/634
const estimated = await contract.estimateGas.sendHelloWorld(
Expand All @@ -48,23 +56,27 @@ export class EvmHelloWorldAdapter
}

async channelStats(
origin: ChainName,
destination: ChainName,
destinationMailbox: Address,
): Promise<StatCounts> {
const originDomain = this.multiProvider.getDomainId(origin);
const originDomain = this.multiProvider.getDomainId(this.chainName);
const destinationDomain = this.multiProvider.getDomainId(destination);
const sent = await this.getConnectedContract(origin).sentTo(
destinationDomain,
);
const received = await this.getConnectedContract(destination).sentTo(
originDomain,
const originContract = this.getConnectedContract();
const sent = await originContract.sentTo(destinationDomain);
const destinationProvider =
this.multiProvider.getEthersV5Provider(destination);
const destinationContract = HelloWorld__factory.connect(
destinationMailbox,
destinationProvider,
);
const received = await destinationContract.sentTo(originDomain);
return { sent: sent.toNumber(), received: received.toNumber() };
}

override getConnectedContract(chain: ChainName): HelloWorld {
const address = this.multiProvider.getChainMetadata(chain).router;
const provider = this.multiProvider.getEthersV5Provider(chain);
return HelloWorld__factory.connect(address, provider);
override getConnectedContract(): HelloWorld {
return HelloWorld__factory.connect(
this.addresses.router,
this.getProvider(),
);
}
}
10 changes: 6 additions & 4 deletions typescript/helloworld/src/multiProtocolApp/multiProtocolApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { SealevelHelloWorldAdapter } from './sealevelAdapter';
import { IHelloWorldAdapter } from './types';

export class HelloMultiProtocolApp extends MultiProtocolRouterApp<
RouterAddress & { mailbox: Address },
IHelloWorldAdapter
IHelloWorldAdapter,
RouterAddress & { mailbox: Address }
> {
override protocolToAdapter(protocol: ProtocolType) {
if (protocol === ProtocolType.Ethereum) return EvmHelloWorldAdapter;
Expand All @@ -31,7 +31,6 @@ export class HelloMultiProtocolApp extends MultiProtocolRouterApp<
sender: Address,
): Promise<TypedTransaction> {
return this.adapter(origin).populateSendHelloTx(
origin,
destination,
message,
value,
Expand All @@ -40,7 +39,10 @@ export class HelloMultiProtocolApp extends MultiProtocolRouterApp<
}

channelStats(origin: ChainName, destination: ChainName): Promise<StatCounts> {
return this.adapter(origin).channelStats(origin, destination);
return this.adapter(origin).channelStats(
destination,
this.addresses[destination].mailbox,
);
}

async stats(): Promise<ChainMap<ChainMap<StatCounts>>> {
Expand Down
51 changes: 26 additions & 25 deletions typescript/helloworld/src/multiProtocolApp/sealevelAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
import { deserializeUnchecked, serialize } from 'borsh';

import {
BaseSealevelAdapter,
ChainName,
MultiProtocolProvider,
ProviderType,
RouterAddress,
SEALEVEL_SPL_NOOP_ADDRESS,
SealevelAccountDataWrapper,
SealevelCoreAdapter,
SealevelInstructionWrapper,
SealevelInterchainGasPaymasterConfig,
SealevelInterchainGasPaymasterConfigSchema,
SealevelRouterAdapter,
SolanaWeb3Transaction,
getSealevelAccountDataSchema,
Expand All @@ -28,26 +29,32 @@ import { StatCounts } from '../app/types';
import { IHelloWorldAdapter } from './types';

export class SealevelHelloWorldAdapter
extends SealevelRouterAdapter<RouterAddress & { mailbox: Address }>
extends SealevelRouterAdapter
implements IHelloWorldAdapter
{
constructor(
public readonly chainName: ChainName,
public readonly multiProvider: MultiProtocolProvider,
public readonly addresses: { router: Address; mailbox: Address },
) {
super(chainName, multiProvider, addresses);
}

async populateSendHelloTx(
origin: ChainName,
destination: ChainName,
message: string,
value: string,
sender: Address,
): Promise<SolanaWeb3Transaction> {
this.logger(
'Creating sendHelloWorld tx for sealevel',
origin,
this.chainName,
destination,
message,
value,
);

const { mailbox, router: programId } =
this.multiProvider.getChainMetadata(origin);
const { mailbox, router: programId } = this.addresses;
const mailboxPubKey = new PublicKey(mailbox);
const senderPubKey = new PublicKey(sender);
const programPubKey = new PublicKey(programId);
Expand Down Expand Up @@ -75,7 +82,7 @@ export class SealevelHelloWorldAdapter
data: Buffer.from(serializedData),
});

const connection = this.multiProvider.getSolanaWeb3Provider(origin);
const connection = this.getProvider();
const recentBlockhash = (await connection.getLatestBlockhash('finalized'))
.blockhash;
// @ts-ignore Workaround for bug in the web3 lib, sometimes uses recentBlockhash and sometimes uses blockhash
Expand Down Expand Up @@ -151,23 +158,20 @@ export class SealevelHelloWorldAdapter

// Should match https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/dd7ff727b0d3d393a159afa5f0a364775bde3a58/rust/sealevel/programs/helloworld/src/processor.rs#L44
deriveProgramStoragePDA(programId: string | PublicKey): PublicKey {
return BaseSealevelAdapter.derivePda(
return this.derivePda(
['hello_world', '-', 'handle', '-', 'storage'],
programId,
);
}

async channelStats(
origin: ChainName,
_destination: ChainName,
): Promise<StatCounts> {
const data = await this.getAccountInfo(origin);
async channelStats(_destination: ChainName): Promise<StatCounts> {
const data = await this.getAccountInfo();
return { sent: data.sent, received: data.received };
}

async getAccountInfo(chain: ChainName): Promise<HelloWorldData> {
const address = this.multiProvider.getChainMetadata(chain).router;
const connection = this.multiProvider.getSolanaWeb3Provider(chain);
async getAccountInfo(): Promise<HelloWorldData> {
const address = this.addresses.router;
const connection = this.getProvider();

const msgRecipientPda = this.deriveMessageRecipientPda(address);
const accountInfo = await connection.getAccountInfo(msgRecipientPda);
Expand Down Expand Up @@ -290,14 +294,7 @@ export const HelloWorldDataSchema = new Map<any, any>([
'igp',
{
kind: 'option',
type: {
kind: 'struct',
fields: [
['program_id', [32]],
['type', 'u8'],
['igp_account', [32]],
],
},
type: SealevelInterchainGasPaymasterConfig,
},
],
['owner', { kind: 'option', type: [32] }],
Expand All @@ -309,4 +306,8 @@ export const HelloWorldDataSchema = new Map<any, any>([
],
},
],
[
SealevelInterchainGasPaymasterConfig,
SealevelInterchainGasPaymasterConfigSchema,
],
]);
9 changes: 4 additions & 5 deletions typescript/helloworld/src/multiProtocolApp/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import {
ChainName,
IRouterAdapter,
RouterAddress,
TypedTransaction,
} from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';

import { StatCounts } from '../app/types';

export interface IHelloWorldAdapter
extends IRouterAdapter<RouterAddress & { mailbox: Address }> {
export interface IHelloWorldAdapter extends IRouterAdapter {
populateSendHelloTx: (
origin: ChainName,
destination: ChainName,
message: string,
value: string,
sender: Address,
) => Promise<TypedTransaction>;

// TODO break apart into separate origin + destination methods to
// handle case where origin/dest protocols differ
channelStats: (
origin: ChainName,
destination: ChainName,
destinationMailbox: Address,
) => Promise<StatCounts>;
}
18 changes: 9 additions & 9 deletions typescript/infra/scripts/helloworld/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
igpFactories,
} from '@hyperlane-xyz/sdk';
import { hyperlaneEnvironmentsWithSealevel } from '@hyperlane-xyz/sdk/src';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ProtocolType, objMerge } from '@hyperlane-xyz/utils';

import { Contexts } from '../../config/contexts';
import { EnvironmentConfig } from '../../src/config';
Expand Down Expand Up @@ -74,20 +74,20 @@ export async function getHelloWorldMultiProtocolApp(
if (!multiProtocolProvider.getKnownChainNames().includes('solanadevnet')) {
multiProtocolProvider.addChain(chainMetadata.solanadevnet);
}
// Add the helloWorld contract addresses to the metadata
const mpWithHelloWorld = multiProtocolProvider.extendChainMetadata(
helloworldConfig.addresses,
);

const core = MultiProtocolCore.fromAddressesMap(
hyperlaneEnvironmentsWithSealevel[sdkEnvName],
multiProtocolProvider,
);

// Extend the MP with mailbox addresses because the sealevel
// adapter needs that to function
const mpWithMailbox = mpWithHelloWorld.extendChainMetadata(core.chainMap);
const app = new HelloMultiProtocolApp(mpWithMailbox);
const routersAndMailboxes = objMerge(
core.chainMap,
helloworldConfig.addresses,
);
const app = new HelloMultiProtocolApp(
multiProtocolProvider,
routersAndMailboxes,
);

// TODO we need a MultiProtocolIgp
// Using an standard IGP for just evm chains for now
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Connection } from '@solana/web3.js';
import { SystemProgram } from '@solana/web3.js';
import { ethers } from 'ethers';
import { Gauge, Registry } from 'prom-client';
import yargs from 'yargs';
Expand All @@ -8,7 +8,12 @@ import {
SealevelHypCollateralAdapter,
TokenType,
} from '@hyperlane-xyz/hyperlane-token';
import { ChainMap, ChainName, MultiProvider } from '@hyperlane-xyz/sdk';
import {
ChainMap,
ChainName,
MultiProtocolProvider,
MultiProvider,
} from '@hyperlane-xyz/sdk';
import {
ProtocolType,
debug,
Expand Down Expand Up @@ -92,11 +97,15 @@ async function checkBalance(
ethers.utils.formatUnits(collateralBalance, token.decimals),
);
} else {
const connection = new Connection(multiprovider.getRpcUrl(chain));
const adapter = new SealevelHypCollateralAdapter(
connection,
token.hypCollateralAddress,
token.address,
chain,
MultiProtocolProvider.fromMultiProvider(multiprovider),
{
token: token.address,
warpRouter: token.hypCollateralAddress,
// Mailbox only required for transfers, using system as placeholder
mailbox: SystemProgram.programId.toBase58(),
},
token.isSpl2022,
);
const collateralBalance = ethers.BigNumber.from(
Expand Down
9 changes: 6 additions & 3 deletions typescript/sdk/src/app/MultiProtocolApp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { Chains } from '../consts/chains';
import { MultiProtocolProvider } from '../providers/MultiProtocolProvider';

import {
BaseAppAdapter,
BaseEvmAdapter,
BaseSealevelAdapter,
MultiProtocolApp,
} from './MultiProtocolApp';

class TestMultiProtocolApp extends MultiProtocolApp {
class TestMultiProtocolApp extends MultiProtocolApp<BaseAppAdapter> {
override protocolToAdapter(protocol: ProtocolType) {
if (protocol === ProtocolType.Ethereum) return BaseEvmAdapter;
if (protocol === ProtocolType.Sealevel) return BaseSealevelAdapter;
Expand All @@ -23,9 +24,11 @@ describe('MultiProtocolApp', () => {
describe('constructs', () => {
const multiProvider = new MultiProtocolProvider();
it('creates an app class and gleans types from generic', async () => {
const app = new TestMultiProtocolApp(multiProvider);
const app = new TestMultiProtocolApp(multiProvider, {});
expect(app).to.be.instanceOf(MultiProtocolApp);
expect(app.adapter(Chains.ethereum).protocol).to.eql(Chains.ethereum);
expect(app.adapter(Chains.ethereum).protocol).to.eql(
ProtocolType.Ethereum,
);
});
});
});
Loading

0 comments on commit 7a5dce2

Please sign in to comment.