diff --git a/.pnp.cjs b/.pnp.cjs index a2614124..14211683 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2305,7 +2305,7 @@ const RAW_RUNTIME_STATE = ["@wormhole-foundation/sdk-definitions", "npm:0.11.0"],\ ["@wormhole-foundation/sdk-solana", "npm:0.11.0"],\ ["@wormhole-foundation/sdk-solana-tokenbridge", "npm:0.11.0"],\ - ["@xlabs/solana-price-oracle-sdk", "link:./lib/../../relayer-infra-contracts/src/solana/::locator=%40xlabs-xyz%2Fsolana-arbitrary-token-transfers%40workspace%3Asdk%2Fsolana"],\ + ["@xlabs/solana-price-oracle-sdk", "npm:0.0.9::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40xlabs%2Fsolana-price-oracle-sdk%2F0.0.9%2Fbaaa3306c801645eb7b0c15f199d2685731163e1"],\ ["borsh", "npm:2.0.0"],\ ["tsup", "virtual:056deaaa1268de825ebb7f2126e3a2787838e2876e69eb46bc3ce4857072f92cede74aabe3915aa2d505c2a087a7d59d9170f4e7cc6428f30bd09123b7267c8f#npm:8.3.0"],\ ["tsx", "npm:4.19.1"],\ @@ -2315,13 +2315,17 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@xlabs/solana-price-oracle-sdk", [\ - ["link:./lib/../../relayer-infra-contracts/src/solana/::locator=%40xlabs-xyz%2Fsolana-arbitrary-token-transfers%40workspace%3Asdk%2Fsolana", {\ - "packageLocation": "./sdk/relayer-infra-contracts/src/solana/",\ + ["npm:0.0.9::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40xlabs%2Fsolana-price-oracle-sdk%2F0.0.9%2Fbaaa3306c801645eb7b0c15f199d2685731163e1", {\ + "packageLocation": "./.yarn/cache/@xlabs-solana-price-oracle-sdk-npm-0.0.9-0385bf8662-35a55f5b66.zip/node_modules/@xlabs/solana-price-oracle-sdk/",\ "packageDependencies": [\ - ["@xlabs/solana-price-oracle-sdk", "link:./lib/../../relayer-infra-contracts/src/solana/::locator=%40xlabs-xyz%2Fsolana-arbitrary-token-transfers%40workspace%3Asdk%2Fsolana"]\ + ["@xlabs/solana-price-oracle-sdk", "npm:0.0.9::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40xlabs%2Fsolana-price-oracle-sdk%2F0.0.9%2Fbaaa3306c801645eb7b0c15f199d2685731163e1"],\ + ["@coral-xyz/anchor", "npm:0.30.1"],\ + ["@solana/web3.js", "npm:1.95.3"],\ + ["@wormhole-foundation/sdk-base", "npm:0.11.0"],\ + ["@wormhole-foundation/sdk-definitions", "npm:0.11.0"],\ + ["@wormhole-foundation/sdk-solana", "npm:0.11.0"]\ ],\ - "linkType": "SOFT",\ - "discardFromLookup": true\ + "linkType": "HARD"\ }]\ ]],\ ["@yarnpkg/types", [\ @@ -3092,8 +3096,8 @@ const RAW_RUNTIME_STATE = ["common-arbitrary-token-transfer", "workspace:sdk/common"],\ ["@types/bn.js", "npm:5.1.5"],\ ["@types/node", "npm:20.16.10"],\ - ["@wormhole-foundation/sdk-base", "npm:0.10.9"],\ - ["@wormhole-foundation/sdk-definitions", "npm:0.10.9"],\ + ["@wormhole-foundation/sdk-base", "npm:0.11.0"],\ + ["@wormhole-foundation/sdk-definitions", "npm:0.11.0"],\ ["bn.js", "npm:5.2.1"],\ ["tsup", "virtual:056deaaa1268de825ebb7f2126e3a2787838e2876e69eb46bc3ce4857072f92cede74aabe3915aa2d505c2a087a7d59d9170f4e7cc6428f30bd09123b7267c8f#npm:8.3.0"],\ ["tsx", "npm:4.19.1"],\ diff --git a/sdk/common/package.json b/sdk/common/package.json index d6932b82..5f2718fe 100644 --- a/sdk/common/package.json +++ b/sdk/common/package.json @@ -22,8 +22,8 @@ "prettier": "prettier --write ." }, "dependencies": { - "@wormhole-foundation/sdk-base": "^0.10.9", - "@wormhole-foundation/sdk-definitions": "^0.10.9", + "@wormhole-foundation/sdk-base": "^0.11", + "@wormhole-foundation/sdk-definitions": "^0.11", "bn.js": "^5.2.1" }, "devDependencies": { diff --git a/sdk/solana/package.json b/sdk/solana/package.json index e4ef0d1b..eb6aaefe 100644 --- a/sdk/solana/package.json +++ b/sdk/solana/package.json @@ -28,7 +28,7 @@ "@wormhole-foundation/sdk-definitions": "^0.11", "@wormhole-foundation/sdk-solana": "^0.11", "@wormhole-foundation/sdk-solana-tokenbridge": "^0.11", - "@xlabs/solana-price-oracle-sdk": "link:./lib/../../relayer-infra-contracts/src/solana/", + "@xlabs/solana-price-oracle-sdk": "0.0.9", "borsh": "^2.0.0" }, "devDependencies": { diff --git a/sdk/solana/tbrv3/idl/token_bridge_relayer.ts b/sdk/solana/tbrv3/idl/token_bridge_relayer.ts index dbbf9a40..c0d6e559 100644 --- a/sdk/solana/tbrv3/idl/token_bridge_relayer.ts +++ b/sdk/solana/tbrv3/idl/token_bridge_relayer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/token_bridge_relayer.json`. */ export type TokenBridgeRelayer = { - "address": "46kv4wCpfEtLsHPDh4zm7jJb2pVdvke8Pj2ABYYJotFD", + "address": "7TLiBkpDGshV4o3jmacTCx93CLkmo3VjZ111AsijN9f8", "metadata": { "name": "tokenBridgeRelayer", "version": "3.0.0", @@ -589,38 +589,38 @@ export type TokenBridgeRelayer = { { "kind": "const", "value": [ + 95, + 229, + 130, + 69, 46, - 12, - 158, - 160, - 36, - 68, + 141, + 178, + 231, + 56, + 213, 199, - 53, - 193, - 188, - 121, - 0, - 45, - 72, - 226, - 163, - 188, - 92, - 29, - 151, - 151, - 123, - 73, - 1, - 120, - 85, - 125, - 88, - 159, - 229, - 38, - 196 + 161, + 79, + 236, + 191, + 130, + 165, + 181, + 246, + 134, + 44, + 135, + 169, + 19, + 212, + 103, + 182, + 152, + 69, + 169, + 9, + 75 ] } ], diff --git a/sdk/solana/tbrv3/token-bridge-relayer.ts b/sdk/solana/tbrv3/token-bridge-relayer.ts index 00c41463..ed83c50b 100644 --- a/sdk/solana/tbrv3/token-bridge-relayer.ts +++ b/sdk/solana/tbrv3/token-bridge-relayer.ts @@ -31,7 +31,7 @@ import { SolanaPriceOracle } from '@xlabs/solana-price-oracle-sdk'; import { deserializeTbrV3Message, VaaMessage, throwError } from 'common-arbitrary-token-transfer'; import { BpfLoaderUpgradeableProgram } from './bpf-loader-upgradeable.js'; -import { TokenBridgeRelayer } from './idl/token_bridge_relayer.js'; +import { TokenBridgeRelayer as IdlType } from './idl/token_bridge_relayer.js'; import IDL from '../../../target/idl/token_bridge_relayer.json' with { type: 'json' }; import networkConfig from '../../../solana/programs/token-bridge-relayer/network.json'; import testProgramKeypair from '../../../solana/programs/token-bridge-relayer/test-program-keypair.json'; @@ -67,11 +67,11 @@ export interface TransferWrappedParameters { unwrapIntent: boolean; } -export type TbrConfigAccount = anchor.IdlAccounts['tbrConfigState']; -export type ChainConfigAccount = anchor.IdlAccounts['chainConfigState']; -export type PeerAccount = anchor.IdlAccounts['peerState']; -export type SignerSequenceAccount = anchor.IdlAccounts['signerSequenceState']; -export type AuthBadgeAccount = anchor.IdlAccounts['authBadgeState']; +export type TbrConfigAccount = anchor.IdlAccounts['tbrConfigState']; +export type ChainConfigAccount = anchor.IdlAccounts['chainConfigState']; +export type PeerAccount = anchor.IdlAccounts['peerState']; +export type SignerSequenceAccount = anchor.IdlAccounts['signerSequenceState']; +export type AuthBadgeAccount = anchor.IdlAccounts['authBadgeState']; export type NetworkOrLocal = Network | 'Localnet'; @@ -81,7 +81,7 @@ export type NetworkOrLocal = Network | 'Localnet'; export const uaToArray = (ua: UniversalAddress): number[] => Array.from(ua.toUint8Array()); export class SolanaTokenBridgeRelayer { - public readonly program: anchor.Program; + public readonly program: anchor.Program; private readonly priceOracleClient: SolanaPriceOracle; private readonly wormholeProgramId: PublicKey; private readonly tokenBridgeProgramId: PublicKey; @@ -89,11 +89,16 @@ export class SolanaTokenBridgeRelayer { /** * Creates a SolanaTokenBridgeRelayer instance. */ - constructor(provider: anchor.Provider, network: NetworkOrLocal, programId: PublicKey) { + constructor( + provider: anchor.Provider, + network: NetworkOrLocal, + programId: PublicKey, + priceOracle: SolanaPriceOracle, + ) { const wormholeNetwork = network === 'Localnet' ? 'Mainnet' : network; this.program = new Program(patchAddress(IDL, programId), provider); - this.priceOracleClient = new SolanaPriceOracle(provider.connection); + this.priceOracleClient = priceOracle; this.wormholeProgramId = new PublicKey(contracts.coreBridge(wormholeNetwork, 'Solana')); this.tokenBridgeProgramId = new PublicKey(contracts.tokenBridge(wormholeNetwork, 'Solana')); } @@ -102,28 +107,64 @@ export class SolanaTokenBridgeRelayer { const network = await networkFromConnection(provider.connection); const programId = await programIdFromNetwork(network); myDebug('Detected environment', { network, programId }); + const priceOracle = await SolanaPriceOracle.create(provider.connection); - return new SolanaTokenBridgeRelayer(provider, network, programId); + return new SolanaTokenBridgeRelayer(provider, network, programId, priceOracle); } get connection(): Connection { return this.program.provider.connection; } - get address() { + /** Raw Solana accounts. */ + get account() { return { - config: () => pda.tbrConfig(this.program.programId), - chainConfig: (chain: Chain) => pda.chainConfig(this.program.programId, chain), + config: () => this.accountInfo(this.program.account.tbrConfigState, [Buffer.from('config')]), + chainConfig: (chain: Chain) => + this.accountInfo(this.program.account.chainConfigState, [ + Buffer.from('chainconfig'), + chainSeed(chain), + ]), peer: (chain: Chain, peerAddress: UniversalAddress) => - pda.peer(this.program.programId, chain, peerAddress), - signerSequence: (signer: PublicKey) => pda.signerSequence(this.program.programId, signer), - authBadge: (account: PublicKey) => pda.authBadge(this.program.programId, account), + this.accountInfo(this.program.account.peerState, [ + Buffer.from('peer'), + chainSeed(chain), + peerAddress.toUint8Array(), + ]), + signerSequence: (signer: PublicKey) => + this.accountInfo(this.program.account.signerSequenceState, [ + Buffer.from('seq'), + signer.toBuffer(), + ]), + authBadge: (account: PublicKey) => + this.accountInfo(this.program.account.authBadgeState, [ + Buffer.from('authbadge'), + account.toBuffer(), + ]), + + temporary: (mint: PublicKey) => + findPda(this.program.programId, [Buffer.from('tmp'), mint.toBuffer()]), + vaa: (vaaHash: Uint8Array) => + findPda(this.wormholeProgramId, [Buffer.from('PostedVAA'), vaaHash]), + wormholeMessage: (payer: PublicKey, payerSequence: anchor.BN) => { + const buf = Buffer.alloc(8); + buf.writeBigInt64BE(BigInt(payerSequence.toString()), 0); + return findPda(this.program.programId, [Buffer.from('bridged'), payer.toBuffer(), buf]); + }, }; } get read() { return { - /* High level data */ + config: async () => + this.account + .config() + .fetch() + .then(({ evmTransactionGas, evmTransactionSize, ...rest }) => ({ + evmTransactionGas: bnToBigint(evmTransactionGas), + evmTransactionSize: bnToBigint(evmTransactionSize), + ...rest, + })), allAdminAccounts: async () => { const [accounts, owner] = await Promise.all([ this.program.account.authBadgeState.all().then((state) => state.map((pa) => pa.account)), @@ -209,32 +250,13 @@ export class SolanaTokenBridgeRelayer { }, {}), }; }, - - /* Solana accounts */ - config: async () => { - const { evmTransactionGas, evmTransactionSize, ...rest } = - await this.program.account.tbrConfigState.fetch(this.address.config()); - return { - evmTransactionGas: bnToBigint(evmTransactionGas), - evmTransactionSize: bnToBigint(evmTransactionSize), - ...rest, - }; - }, - chainConfig: (chain: Chain) => - this.program.account.chainConfigState.fetch(this.address.chainConfig(chain)), - peer: (chain: Chain, peerAddress: UniversalAddress) => - this.program.account.peerState.fetch(this.address.peer(chain, peerAddress)), - signerSequence: (signer: PublicKey) => - this.program.account.signerSequenceState.fetch(this.address.signerSequence(signer)), - authBadge: (account: PublicKey) => - this.program.account.authBadgeState.fetch(this.address.authBadge(account)), }; } private async payerSequenceNumber(payer: PublicKey): Promise { const impl = async (payer: PublicKey) => { try { - return (await this.read.signerSequence(payer)).value; + return (await this.account.signerSequence(payer).fetch()).value; } catch { return new anchor.BN(0); } @@ -260,7 +282,7 @@ export class SolanaTokenBridgeRelayer { admins: PublicKey[]; }): Promise { const authBadges = admins.map((key) => ({ - pubkey: this.address.authBadge(key), + pubkey: this.account.authBadge(key).address, isSigner: false, isWritable: true, })); @@ -307,7 +329,7 @@ export class SolanaTokenBridgeRelayer { .confirmOwnerTransferRequest() .accounts({ newOwner: config.pendingOwner ?? throwError('No pending owner in the program'), - tbrConfig: this.address.config(), + tbrConfig: this.account.config().address, }) .instruction(); } @@ -322,7 +344,7 @@ export class SolanaTokenBridgeRelayer { .cancelOwnerTransferRequest() .accountsStrict({ owner: config.owner, - tbrConfig: this.address.config(), + tbrConfig: this.account.config().address, }) .instruction(); } @@ -337,8 +359,8 @@ export class SolanaTokenBridgeRelayer { .addAdmin(newAdmin) .accountsStrict({ owner: config.owner, - tbrConfig: this.address.config(), - authBadge: this.address.authBadge(newAdmin), + tbrConfig: this.account.config().address, + authBadge: this.account.authBadge(newAdmin).address, systemProgram: SystemProgram.programId, }) .instruction(); @@ -352,9 +374,9 @@ export class SolanaTokenBridgeRelayer { .removeAdmin() .accountsStrict({ signer, - authBadge: this.address.authBadge(signer), - tbrConfig: this.address.config(), - authBadgeToBeRemoved: this.address.authBadge(adminToRemove), + authBadge: this.account.authBadge(signer).address, + tbrConfig: this.account.config().address, + authBadgeToBeRemoved: this.account.authBadge(adminToRemove).address, }) .instruction(); } @@ -373,10 +395,10 @@ export class SolanaTokenBridgeRelayer { .registerPeer(chainToChainId(chain), uaToArray(peerAddress)) .accountsStrict({ signer, - authBadge: this.address.authBadge(signer), - tbrConfig: this.address.config(), - peer: this.address.peer(chain, peerAddress), - chainConfig: this.address.chainConfig(chain), + authBadge: this.account.authBadge(signer).address, + tbrConfig: this.account.config().address, + peer: this.account.peer(chain, peerAddress).address, + chainConfig: this.account.chainConfig(chain).address, systemProgram: SystemProgram.programId, }) .instruction(); @@ -395,9 +417,9 @@ export class SolanaTokenBridgeRelayer { .updateCanonicalPeer() .accountsStrict({ owner: config.owner, - tbrConfig: this.address.config(), - peer: this.address.peer(chain, peerAddress), - chainConfig: this.address.chainConfig(chain), + tbrConfig: this.account.config().address, + peer: this.account.peer(chain, peerAddress).address, + chainConfig: this.account.chainConfig(chain).address, systemProgram: SystemProgram.programId, }) .instruction(); @@ -417,9 +439,9 @@ export class SolanaTokenBridgeRelayer { .setPauseForOutboundTransfers(paused) .accountsStrict({ signer, - authBadge: this.address.authBadge(signer), - chainConfig: this.address.chainConfig(chain), - tbrConfig: this.address.config(), + authBadge: this.account.authBadge(signer).address, + chainConfig: this.account.chainConfig(chain).address, + tbrConfig: this.account.config().address, }) .instruction(); } @@ -436,9 +458,9 @@ export class SolanaTokenBridgeRelayer { .updateMaxGasDropoff(maxGasDropoff) .accountsStrict({ signer, - authBadge: this.address.authBadge(signer), - chainConfig: this.address.chainConfig(chain), - tbrConfig: this.address.config(), + authBadge: this.account.authBadge(signer).address, + chainConfig: this.account.chainConfig(chain).address, + tbrConfig: this.account.config().address, }) .instruction(); } @@ -455,9 +477,9 @@ export class SolanaTokenBridgeRelayer { .updateRelayerFee(relayerFee) .accountsStrict({ signer, - authBadge: this.address.authBadge(signer), - chainConfig: this.address.chainConfig(chain), - tbrConfig: this.address.config(), + authBadge: this.account.authBadge(signer).address, + chainConfig: this.account.chainConfig(chain).address, + tbrConfig: this.account.config().address, }) .instruction(); } @@ -475,8 +497,8 @@ export class SolanaTokenBridgeRelayer { .updateFeeRecipient(newFeeRecipient) .accounts({ signer, - authBadge: this.address.authBadge(signer), - tbrConfig: this.address.config(), + authBadge: this.account.authBadge(signer).address, + tbrConfig: this.account.config().address, }) .instruction(); } @@ -493,8 +515,8 @@ export class SolanaTokenBridgeRelayer { .updateEvmTransactionConfig(bigintToBn(evmTransactionGas), bigintToBn(evmTransactionSize)) .accounts({ signer, - authBadge: this.address.authBadge(signer), - tbrConfig: this.address.config(), + authBadge: this.account.authBadge(signer).address, + tbrConfig: this.account.config().address, }) .instruction(); } @@ -529,17 +551,17 @@ export class SolanaTokenBridgeRelayer { }); const accounts = { payer: signer, - tbrConfig: this.address.config(), - chainConfig: this.address.chainConfig(recipient.chain), + tbrConfig: this.account.config().address, + chainConfig: this.account.chainConfig(recipient.chain).address, mint, userTokenAccount, - temporaryAccount: pda.temporary(this.program.programId, mint), + temporaryAccount: this.account.temporary(mint).address, feeRecipient, oracleConfig: this.priceOracleClient.account.config().address, oracleEvmPrices: this.priceOracleClient.account.evmPrices(recipient.chain).address, ...tokenBridgeAccounts, - wormholeMessage: pda.wormholeMessage(this.program.programId, signer, payerSequenceNumber), - payerSequence: this.address.signerSequence(signer), + wormholeMessage: this.account.wormholeMessage(signer, payerSequenceNumber).address, + payerSequence: this.account.signerSequence(signer).address, tokenBridgeProgram: this.tokenBridgeProgramId, wormholeProgram: this.wormholeProgramId, }; @@ -587,16 +609,16 @@ export class SolanaTokenBridgeRelayer { }); const accounts = { payer: signer, - tbrConfig: this.address.config(), - chainConfig: this.address.chainConfig(recipient.chain), + tbrConfig: this.account.config().address, + chainConfig: this.account.chainConfig(recipient.chain).address, userTokenAccount, - temporaryAccount: pda.temporary(this.program.programId, tokenBridgeAccounts.mint), + temporaryAccount: this.account.temporary(tokenBridgeAccounts.mint).address, feeRecipient, oracleConfig: this.priceOracleClient.account.config().address, oracleEvmPrices: this.priceOracleClient.account.evmPrices(recipient.chain).address, ...tokenBridgeAccounts, - wormholeMessage: pda.wormholeMessage(this.program.programId, signer, payerSequenceNumber), - payerSequence: this.address.signerSequence(signer), + wormholeMessage: this.account.wormholeMessage(signer, payerSequenceNumber).address, + payerSequence: this.account.signerSequence(signer).address, tokenBridgeProgram: this.tokenBridgeProgramId, wormholeProgram: this.wormholeProgramId, }; @@ -635,15 +657,15 @@ export class SolanaTokenBridgeRelayer { const { recipient } = deserializeTbrV3Message(vaa); const accounts = { payer: signer, - tbrConfig: this.address.config(), + tbrConfig: this.account.config().address, recipientTokenAccount, recipient: new PublicKey(recipient.address), - vaa: pda.vaa(this.wormholeProgramId, vaa.hash), - temporaryAccount: pda.temporary(this.program.programId, tokenBridgeAccounts.mint), + vaa: this.account.vaa(vaa.hash).address, + temporaryAccount: this.account.temporary(tokenBridgeAccounts.mint).address, ...tokenBridgeAccounts, tokenBridgeProgram: this.tokenBridgeProgramId, wormholeProgram: this.wormholeProgramId, - peer: pda.peer(this.program.programId, vaa.emitterChain, vaa.payload.from), + peer: this.account.peer(vaa.emitterChain, vaa.payload.from).address, }; myDebug('completeNativeTransfer:', accounts); @@ -671,15 +693,15 @@ export class SolanaTokenBridgeRelayer { const { recipient } = deserializeTbrV3Message(vaa); const accounts = { payer: signer, - tbrConfig: this.address.config(), + tbrConfig: this.account.config().address, recipientTokenAccount, recipient: new PublicKey(recipient.address), - vaa: pda.vaa(this.wormholeProgramId, vaa.hash), - temporaryAccount: pda.temporary(this.program.programId, tokenBridgeAccounts.mint), + vaa: this.account.vaa(vaa.hash).address, + temporaryAccount: this.account.temporary(tokenBridgeAccounts.mint).address, ...tokenBridgeAccounts, tokenBridgeProgram: this.tokenBridgeProgramId, wormholeProgram: this.wormholeProgramId, - peer: pda.peer(this.program.programId, vaa.emitterChain, vaa.payload.from), + peer: this.account.peer(vaa.emitterChain, vaa.payload.from).address, }; myDebug('completeWrappedTransfer:', accounts); @@ -701,8 +723,8 @@ export class SolanaTokenBridgeRelayer { const tx = await this.program.methods .relayingFee(dropoffAmount) .accountsStrict({ - tbrConfig: this.address.config(), - chainConfig: this.address.chainConfig(chain), + tbrConfig: this.account.config().address, + chainConfig: this.account.chainConfig(chain).address, oracleConfig: this.priceOracleClient.account.config().address, oracleEvmPrices: this.priceOracleClient.account.evmPrices(chain).address, }) @@ -715,47 +737,28 @@ export class SolanaTokenBridgeRelayer { return Number(result) / LAMPORTS_PER_SOL; } + + private accountInfo>( + account: anchor.AccountClient, + seeds: Array, + ) { + const { address, seed } = findPda(this.program.programId, seeds); + return { + address, + seed, + fetch: () => account.fetch(address), + }; + } } const chainSeed = (chain: Chain) => encoding.bignum.toBytes(chainToChainId(chain), 2); -const pda = { - tbrConfig: (programId: PublicKey) => - PublicKey.findProgramAddressSync([Buffer.from('config')], programId)[0], - - peer: (programId: PublicKey, chain: Chain, peerAddress: UniversalAddress) => - PublicKey.findProgramAddressSync( - [Buffer.from('peer'), chainSeed(chain), peerAddress.toUint8Array()], - programId, - )[0], - - chainConfig: (programId: PublicKey, chain: Chain) => - PublicKey.findProgramAddressSync([Buffer.from('chainconfig'), chainSeed(chain)], programId)[0], - - signerSequence: (programId: PublicKey, signer: PublicKey) => - PublicKey.findProgramAddressSync([Buffer.from('seq'), signer.toBuffer()], programId)[0], - - authBadge: (programId: PublicKey, account: PublicKey) => - PublicKey.findProgramAddressSync([Buffer.from('authbadge'), account.toBuffer()], programId)[0], - - // Internal: - - temporary: (programId: PublicKey, mint: PublicKey) => - PublicKey.findProgramAddressSync([Buffer.from('tmp'), mint.toBuffer()], programId)[0], - - vaa: (programId: PublicKey, vaaHash: Uint8Array) => - PublicKey.findProgramAddressSync([Buffer.from('PostedVAA'), vaaHash], programId)[0], - - wormholeMessage: (programId: PublicKey, payer: PublicKey, payerSequence: anchor.BN) => { - const buf = Buffer.alloc(8); - - buf.writeBigInt64BE(BigInt(payerSequence.toString()), 0); - - return PublicKey.findProgramAddressSync( - [Buffer.from('bridged'), payer.toBuffer(), buf], - programId, - )[0]; - }, -}; +function findPda(programId: PublicKey, seeds: Array) { + const [address, seed] = PublicKey.findProgramAddressSync(seeds, programId); + return { + address, + seed, + }; +} function assertProvider(provider: anchor.Provider) { if (provider.sendAndConfirm === undefined) { diff --git a/solana/tests/token-bridge-relayer-tests.ts b/solana/tests/token-bridge-relayer-tests.ts index 63b5aa1b..8313f0aa 100644 --- a/solana/tests/token-bridge-relayer-tests.ts +++ b/solana/tests/token-bridge-relayer-tests.ts @@ -1,4 +1,3 @@ -import anchor from '@coral-xyz/anchor'; import { chainToChainId } from '@wormhole-foundation/sdk-base'; import { UniversalAddress } from '@wormhole-foundation/sdk-definitions'; import { PublicKey, SendTransactionError, Transaction } from '@solana/web3.js'; @@ -21,8 +20,12 @@ const OASIS_ID = chainToChainId(OASIS); const authorityKeypairPath = './target/deploy/token_bridge_relayer-keypair.json'; describe('Token Bridge Relayer Program', () => { + const oracleClient = new SolanaPriceOracle( + newProvider().connection, + new PublicKey('CefQJaxQTV28gCf4MMd1PgDHgCcRmuEHZgXZwjJReUY3'), + ); const clients = (['owner', 'owner', 'admin', 'admin', 'admin', 'regular'] as const).map( - (typeAccount) => new TbrWrapper(newProvider(), typeAccount), + (typeAccount) => TbrWrapper.from(newProvider(), typeAccount, oracleClient), ); const [ ownerClient, @@ -32,7 +35,6 @@ describe('Token Bridge Relayer Program', () => { adminClient3, unauthorizedClient, ] = clients; - let upgradeAuthorityClient: TbrWrapper; const wormholeCoreOwner = newProvider(); //const wormholeCoreClient = new WormholeCoreWrapper(wormholeCoreOwner); @@ -56,10 +58,6 @@ describe('Token Bridge Relayer Program', () => { before(async () => { await Promise.all(clients.map((client) => requestAirdrop(client.provider))); - upgradeAuthorityClient = new TbrWrapper( - newProvider(await keypairFromFile(authorityKeypairPath)), - 'owner', - ); // Program Deployment // ============ @@ -83,7 +81,9 @@ describe('Token Bridge Relayer Program', () => { // ============ const oracleAuthorityProvider = newProvider(await keypairFromFile(authorityKeypairPath)); - const oracleAuthorityClient = new SolanaPriceOracle(oracleAuthorityProvider.connection); + const oracleAuthorityClient = await SolanaPriceOracle.create( + oracleAuthorityProvider.connection, + ); await oracleAuthorityProvider.sendAndConfirm( new Transaction().add( await oracleAuthorityClient.initialize(oracleAuthorityProvider.publicKey, [], []), @@ -108,11 +108,14 @@ describe('Token Bridge Relayer Program', () => { after(async () => { // Prevents the tests to be stuck, by closing the open channels. - clients.push(upgradeAuthorityClient); await Promise.all(clients.map((client) => client.close())); }); it('Is initialized!', async () => { + const upgradeAuthorityClient = await TbrWrapper.create( + newProvider(await keypairFromFile(authorityKeypairPath)), + ); + await upgradeAuthorityClient.initialize({ feeRecipient, owner: ownerClient.publicKey, @@ -123,21 +126,23 @@ describe('Token Bridge Relayer Program', () => { assert.key(config.owner).equal(ownerClient.publicKey); // The owner has an auth badge: - expect(await unauthorizedClient.read.authBadge(ownerClient.publicKey)).deep.equal({ + expect(await unauthorizedClient.account.authBadge(ownerClient.publicKey).fetch()).deep.equal({ address: ownerClient.publicKey, }); // The admins have an auth badge: - expect(await unauthorizedClient.read.authBadge(adminClient1.publicKey)).deep.equal({ + expect(await unauthorizedClient.account.authBadge(adminClient1.publicKey).fetch()).deep.equal({ address: adminClient1.publicKey, }); - expect(await unauthorizedClient.read.authBadge(adminClient2.publicKey)).deep.equal({ + expect(await unauthorizedClient.account.authBadge(adminClient2.publicKey).fetch()).deep.equal({ address: adminClient2.publicKey, }); // Verify that the accounts reader works: const adminAccounts = await unauthorizedClient.client.read.allAdminAccounts(); assert.array(adminAccounts).equal([adminClient1.publicKey, adminClient2.publicKey]); + + await upgradeAuthorityClient.close(); }); describe('Roles', () => { @@ -155,12 +160,14 @@ describe('Token Bridge Relayer Program', () => { await newOwnerClient.confirmOwnerTransferRequest(); // The owner has an admin badge: - expect(await unauthorizedClient.read.authBadge(newOwnerClient.publicKey)).deep.equal({ + expect( + await unauthorizedClient.account.authBadge(newOwnerClient.publicKey).fetch(), + ).deep.equal({ address: newOwnerClient.publicKey, }); // The previous owner doesn't have a badge anymore: - await assert.promise(newOwnerClient.read.authBadge(ownerClient.publicKey)).fails(); + await assert.promise(newOwnerClient.account.authBadge(ownerClient.publicKey).fetch()).fails(); }); it('Correctly cancels an ownership transfer', async () => { @@ -181,25 +188,33 @@ describe('Token Bridge Relayer Program', () => { await newOwnerClient.addAdmin(adminClient3.publicKey); // It now has an auth badge: - expect(await unauthorizedClient.read.authBadge(adminClient3.publicKey)).deep.equal({ - address: adminClient3.publicKey, - }); + expect(await unauthorizedClient.account.authBadge(adminClient3.publicKey).fetch()).deep.equal( + { + address: adminClient3.publicKey, + }, + ); // The owner can also remove admins: await newOwnerClient.removeAdmin(adminClient3.publicKey); // No admin auth badge anymore: - await assert.promise(newOwnerClient.read.authBadge(adminClient3.publicKey)).fails(); + await assert + .promise(newOwnerClient.account.authBadge(adminClient3.publicKey).fetch()) + .fails(); }); it('Admins can remove admin roles', async () => { // Admin2 can remove admin1: await adminClient2.removeAdmin(adminClient1.publicKey); - await assert.promise(newOwnerClient.read.authBadge(adminClient1.publicKey)).fails(); + await assert + .promise(newOwnerClient.account.authBadge(adminClient1.publicKey).fetch()) + .fails(); // Admin2 can remove itself: await adminClient2.removeAdmin(adminClient2.publicKey); - await assert.promise(newOwnerClient.read.authBadge(adminClient2.publicKey)).fails(); + await assert + .promise(newOwnerClient.account.authBadge(adminClient2.publicKey).fetch()) + .fails(); // Let's reinstate admin1 and admin2 back: await newOwnerClient.addAdmin(adminClient1.publicKey); @@ -208,7 +223,7 @@ describe('Token Bridge Relayer Program', () => { it('Admin cannot add admin', async () => { await assert.promise(adminClient1.addAdmin(adminClient3.publicKey)).fails(); - await assert.promise(adminClient1.read.authBadge(adminClient3.publicKey)).fails(); + await assert.promise(adminClient1.account.authBadge(adminClient3.publicKey).fetch()).fails(); }); it('Unauthorized cannot add or remove an admin', async () => { @@ -224,47 +239,47 @@ describe('Token Bridge Relayer Program', () => { describe('Peers', () => { it('Registers peers', async () => { await newOwnerClient.registerPeer(ETHEREUM, ethereumPeer1); - assert.chainConfig(await unauthorizedClient.read.chainConfig(ETHEREUM)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(ETHEREUM).fetch()).equal({ chainId: ETHEREUM_ID, canonicalPeer: uaToArray(ethereumPeer1), maxGasDropoffMicroToken: 0, pausedOutboundTransfers: true, relayerFeeMicroUsd: 0, }); - expect(await unauthorizedClient.read.peer(ETHEREUM, ethereumPeer1)).deep.include({ + expect(await unauthorizedClient.account.peer(ETHEREUM, ethereumPeer1).fetch()).deep.include({ chainId: ETHEREUM_ID, address: uaToArray(ethereumPeer1), }); await adminClient1.registerPeer(ETHEREUM, ethereumPeer2); - assert.chainConfig(await unauthorizedClient.read.chainConfig(ETHEREUM)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(ETHEREUM).fetch()).equal({ chainId: ETHEREUM_ID, canonicalPeer: uaToArray(ethereumPeer1), maxGasDropoffMicroToken: 0, pausedOutboundTransfers: true, relayerFeeMicroUsd: 0, }); - expect(await unauthorizedClient.read.peer(ETHEREUM, ethereumPeer2)).deep.include({ + expect(await unauthorizedClient.account.peer(ETHEREUM, ethereumPeer2).fetch()).deep.include({ chainId: ETHEREUM_ID, address: uaToArray(ethereumPeer2), }); await adminClient1.registerPeer(OASIS, oasisPeer); - assert.chainConfig(await unauthorizedClient.read.chainConfig(OASIS)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(OASIS).fetch()).equal({ chainId: OASIS_ID, canonicalPeer: uaToArray(oasisPeer), maxGasDropoffMicroToken: 0, pausedOutboundTransfers: true, relayerFeeMicroUsd: 0, }); - assert.chainConfig(await unauthorizedClient.read.chainConfig(ETHEREUM)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(ETHEREUM).fetch()).equal({ chainId: ETHEREUM_ID, canonicalPeer: uaToArray(ethereumPeer1), maxGasDropoffMicroToken: 0, pausedOutboundTransfers: true, relayerFeeMicroUsd: 0, }); - expect(await unauthorizedClient.read.peer(OASIS, oasisPeer)).deep.include({ + expect(await unauthorizedClient.account.peer(OASIS, oasisPeer).fetch()).deep.include({ chainId: OASIS_ID, address: uaToArray(oasisPeer), }); @@ -273,7 +288,7 @@ describe('Token Bridge Relayer Program', () => { it('Updates the canonical peer to another one', async () => { await newOwnerClient.updateCanonicalPeer(ETHEREUM, ethereumPeer2); - assert.chainConfig(await unauthorizedClient.read.chainConfig(ETHEREUM)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(ETHEREUM).fetch()).equal({ chainId: ETHEREUM_ID, canonicalPeer: uaToArray(ethereumPeer2), maxGasDropoffMicroToken: 0, @@ -322,7 +337,7 @@ describe('Token Bridge Relayer Program', () => { adminClient1.updateRelayerFee(ETHEREUM, relayerFeeMicroUsd), ]); - assert.chainConfig(await unauthorizedClient.read.chainConfig(ETHEREUM)).equal({ + assert.chainConfig(await unauthorizedClient.account.chainConfig(ETHEREUM).fetch()).equal({ chainId: ETHEREUM_ID, canonicalPeer: uaToArray(ethereumPeer2), maxGasDropoffMicroToken, diff --git a/solana/tests/utils/client-wrapper.ts b/solana/tests/utils/client-wrapper.ts index ad5a39e1..3b5834d1 100644 --- a/solana/tests/utils/client-wrapper.ts +++ b/solana/tests/utils/client-wrapper.ts @@ -1,8 +1,9 @@ -import { AnchorProvider, BN } from '@coral-xyz/anchor'; +import { AnchorProvider } from '@coral-xyz/anchor'; import { PublicKey, TransactionSignature } from '@solana/web3.js'; import { Chain } from '@wormhole-foundation/sdk-base'; import { UniversalAddress } from '@wormhole-foundation/sdk-definitions'; import { + SolanaPriceOracle, SolanaTokenBridgeRelayer, TransferNativeParameters, TransferWrappedParameters, @@ -25,33 +26,54 @@ export class TbrWrapper { readonly logs: { [key: string]: string[] }; readonly logsSubscriptionId: number; - constructor(provider: AnchorProvider, accountType: 'owner' | 'admin' | 'regular') { + constructor(provider: AnchorProvider, tbrClient: SolanaTokenBridgeRelayer) { this.provider = provider; + this.client = tbrClient; + this.logs = {}; + + this.logsSubscriptionId = provider.connection.onLogs( + 'all', + (l) => (this.logs[l.signature] = l.logs), + ); + } + + static from( + provider: AnchorProvider, + accountType: 'owner' | 'admin' | 'regular', + oracleClient: SolanaPriceOracle, + ) { const clientProvider = accountType === 'regular' ? provider : { connection: provider.connection }; - this.client = new SolanaTokenBridgeRelayer( + const client = new SolanaTokenBridgeRelayer( clientProvider, 'Localnet', keypairFromArray(testProgramKeypair).publicKey, + oracleClient, ); - this.logs = {}; + return new TbrWrapper(provider, client); + } - this.logsSubscriptionId = provider.connection.onLogs( - 'all', - (l) => (this.logs[l.signature] = l.logs), - ); + static async create(provider: AnchorProvider) { + const client = await SolanaTokenBridgeRelayer.create({ connection: provider.connection }); + + return new TbrWrapper(provider, client); } get publicKey(): PublicKey { return this.provider.publicKey; } + get account() { + return this.client.account; + } + get read() { return this.client.read; } + /** Unregister the logs event so that the test does not hang. */ async close() { await this.provider.connection.removeOnLogsListener(this.logsSubscriptionId); } diff --git a/target/idl/token_bridge_relayer.json b/target/idl/token_bridge_relayer.json index ee344daa..fb1685da 100644 --- a/target/idl/token_bridge_relayer.json +++ b/target/idl/token_bridge_relayer.json @@ -1,5 +1,5 @@ { - "address": "46kv4wCpfEtLsHPDh4zm7jJb2pVdvke8Pj2ABYYJotFD", + "address": "HrZSAEW9QVbC4kQu51C7oFMHaam18AvAhpbBYeY9mKht", "metadata": { "name": "token_bridge_relayer", "version": "3.0.0", @@ -583,38 +583,38 @@ { "kind": "const", "value": [ + 95, + 229, + 130, + 69, 46, - 12, - 158, - 160, - 36, - 68, + 141, + 178, + 231, + 56, + 213, 199, - 53, - 193, - 188, - 121, - 0, - 45, - 72, - 226, - 163, - 188, - 92, - 29, - 151, - 151, - 123, - 73, - 1, - 120, - 85, - 125, - 88, - 159, - 229, - 38, - 196 + 161, + 79, + 236, + 191, + 130, + 165, + 181, + 246, + 134, + 44, + 135, + 169, + 19, + 212, + 103, + 182, + 152, + 69, + 169, + 9, + 75 ] } ], diff --git a/target/types/token_bridge_relayer.ts b/target/types/token_bridge_relayer.ts index dbbf9a40..c0d6e559 100644 --- a/target/types/token_bridge_relayer.ts +++ b/target/types/token_bridge_relayer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/token_bridge_relayer.json`. */ export type TokenBridgeRelayer = { - "address": "46kv4wCpfEtLsHPDh4zm7jJb2pVdvke8Pj2ABYYJotFD", + "address": "7TLiBkpDGshV4o3jmacTCx93CLkmo3VjZ111AsijN9f8", "metadata": { "name": "tokenBridgeRelayer", "version": "3.0.0", @@ -589,38 +589,38 @@ export type TokenBridgeRelayer = { { "kind": "const", "value": [ + 95, + 229, + 130, + 69, 46, - 12, - 158, - 160, - 36, - 68, + 141, + 178, + 231, + 56, + 213, 199, - 53, - 193, - 188, - 121, - 0, - 45, - 72, - 226, - 163, - 188, - 92, - 29, - 151, - 151, - 123, - 73, - 1, - 120, - 85, - 125, - 88, - 159, - 229, - 38, - 196 + 161, + 79, + 236, + 191, + 130, + 165, + 181, + 246, + 134, + 44, + 135, + 169, + 19, + 212, + 103, + 182, + 152, + 69, + 169, + 9, + 75 ] } ], diff --git a/yarn.lock b/yarn.lock index 8c6a9c74..95f7ab9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1787,7 +1787,7 @@ __metadata: "@wormhole-foundation/sdk-definitions": "npm:^0.11" "@wormhole-foundation/sdk-solana": "npm:^0.11" "@wormhole-foundation/sdk-solana-tokenbridge": "npm:^0.11" - "@xlabs/solana-price-oracle-sdk": "link:./lib/../../relayer-infra-contracts/src/solana/" + "@xlabs/solana-price-oracle-sdk": "npm:0.0.9" borsh: "npm:^2.0.0" tsup: "npm:^8.3.0" tsx: "npm:4.19.1" @@ -1795,11 +1795,18 @@ __metadata: languageName: unknown linkType: soft -"@xlabs/solana-price-oracle-sdk@link:./lib/../../relayer-infra-contracts/src/solana/::locator=%40xlabs-xyz%2Fsolana-arbitrary-token-transfers%40workspace%3Asdk%2Fsolana": - version: 0.0.0-use.local - resolution: "@xlabs/solana-price-oracle-sdk@link:./lib/../../relayer-infra-contracts/src/solana/::locator=%40xlabs-xyz%2Fsolana-arbitrary-token-transfers%40workspace%3Asdk%2Fsolana" +"@xlabs/solana-price-oracle-sdk@npm:0.0.9": + version: 0.0.9 + resolution: "@xlabs/solana-price-oracle-sdk@npm:0.0.9::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40xlabs%2Fsolana-price-oracle-sdk%2F0.0.9%2Fbaaa3306c801645eb7b0c15f199d2685731163e1" + dependencies: + "@coral-xyz/anchor": "npm:^0.30.1" + "@solana/web3.js": "npm:^1.95.2" + "@wormhole-foundation/sdk-base": "npm:^0.11" + "@wormhole-foundation/sdk-definitions": "npm:^0.11" + "@wormhole-foundation/sdk-solana": "npm:^0.11" + checksum: 10c0/35a55f5b66e9fa324aafda3ca448b1666ed92a5c9c0c72a95dd91137698fb32c3c01f9076da454db88c4e5231937c799817841131b47c06e5cb21f0400d84d0c languageName: node - linkType: soft + linkType: hard "@yarnpkg/types@npm:^4.0.0": version: 4.0.0 @@ -2485,8 +2492,8 @@ __metadata: dependencies: "@types/bn.js": "npm:^5.1.5" "@types/node": "npm:20.16.10" - "@wormhole-foundation/sdk-base": "npm:^0.10.9" - "@wormhole-foundation/sdk-definitions": "npm:^0.10.9" + "@wormhole-foundation/sdk-base": "npm:^0.11" + "@wormhole-foundation/sdk-definitions": "npm:^0.11" bn.js: "npm:^5.2.1" tsup: "npm:^8.3.0" tsx: "npm:4.19.1"