diff --git a/solana/ts/src/protocol/tokenRouter.ts b/solana/ts/src/protocol/tokenRouter.ts index 3d0ba541..ae062b25 100644 --- a/solana/ts/src/protocol/tokenRouter.ts +++ b/solana/ts/src/protocol/tokenRouter.ts @@ -1,6 +1,8 @@ +import * as splToken from "@solana/spl-token"; import { AddressLookupTableAccount, Connection, + Keypair, PublicKey, TransactionInstruction, TransactionMessage, @@ -10,7 +12,7 @@ import { FastTransfer, TokenRouter, } from "@wormhole-foundation/example-liquidity-layer-definitions"; -import { Chain, Network, Platform } from "@wormhole-foundation/sdk-base"; +import { Chain, Network, Platform, toChainId } from "@wormhole-foundation/sdk-base"; import { AccountAddress, ChainAddress, @@ -18,7 +20,6 @@ import { CircleBridge, Contracts, UnsignedTransaction, - VAA, } from "@wormhole-foundation/sdk-definitions"; import { AnySolanaAddress, @@ -88,8 +89,47 @@ export class SolanaTokenRouter yield this.createUnsignedTx({ transaction }, "TokenRouter.Initialize"); } - getInitialAuctionFee(): Promise { - throw new Error("Method not implemented."); + async *prepareMarketOrder( + sender: AnySolanaAddress, + amount: bigint, + redeemer: ChainAddress, + minAmountOut?: bigint, + redeemerMessage?: Uint8Array, + preparedOrder?: Keypair, + ) { + const payer = new SolanaAddress(sender).unwrap(); + + // assume sender token is the usdc mint address + const senderToken = splToken.getAssociatedTokenAddressSync(this.mint, payer); + + // Where we'll write the prepared order + preparedOrder = preparedOrder ?? Keypair.generate(); + + const [approveIx, prepareIx] = await this.prepareMarketOrderIx( + { + payer, + senderToken, + preparedOrder: preparedOrder.publicKey, + }, + { + amountIn: amount, + minAmountOut: minAmountOut ? minAmountOut : null, + targetChain: toChainId(redeemer.chain), + redeemer: Array.from(redeemer.address.toUniversalAddress().toUint8Array()), + redeemerMessage: redeemerMessage ? Buffer.from(redeemerMessage) : Buffer.from(""), + }, + ); + + // TODO: fix prepareMarketOrderIx to not return null at all + const ixs = []; + if (approveIx) ixs.push(approveIx); + ixs.push(prepareIx); + + const transaction = this.createTx(payer, ixs); + yield this.createUnsignedTx( + { transaction, signers: [preparedOrder] }, + "TokenRouter.PrepareMarketOrder", + ); } placeMarketOrder( @@ -101,6 +141,7 @@ export class SolanaTokenRouter ): AsyncGenerator, any, unknown> { throw new Error("Method not implemented."); } + placeFastMarketOrder( amount: bigint, chain: RC, @@ -113,6 +154,7 @@ export class SolanaTokenRouter ): AsyncGenerator, any, unknown> { throw new Error("Method not implemented."); } + redeemFill( vaa: FastTransfer.VAA, cctp: CircleBridge.Attestation, diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index cd3071ec..fe168419 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -1,3 +1,4 @@ +// @ts-ignore import * as splToken from "@solana/spl-token"; import { AddressLookupTableProgram, @@ -8,8 +9,8 @@ import { SystemProgram, TransactionInstruction, } from "@solana/web3.js"; -import { ChainId, toChain, toChainId } from "@wormhole-foundation/sdk-base"; -import { toUniversal } from "@wormhole-foundation/sdk-definitions"; +import { ChainId, encoding, toChain, toChainId } from "@wormhole-foundation/sdk-base"; +import { UniversalAddress, toUniversal } from "@wormhole-foundation/sdk-definitions"; import { deserializePostMessage } from "@wormhole-foundation/sdk-solana-core"; import { expect } from "chai"; import { CctpTokenBurnMessage } from "../src/cctp"; @@ -26,6 +27,9 @@ import { USDC_MINT_ADDRESS, expectIxErr, expectIxOk, + expectTxsErr, + expectTxsOk, + getSdkSigner, postLiquidityLayerVaa, toUniversalAddress, } from "../src/testing"; @@ -38,6 +42,8 @@ describe("Token Router", function () { const connection = new Connection(LOCALHOST, "processed"); // payer is also the recipient in all tests const payer = PAYER_KEYPAIR; + const { signer: payerSigner } = getSdkSigner(connection, payer); + const relayer = Keypair.generate(); const owner = OWNER_KEYPAIR; const ownerAssistant = OWNER_ASSISTANT_KEYPAIR; @@ -58,36 +64,18 @@ describe("Token Router", function () { describe("Initialize", function () { it("Cannot Initialize without USDC Mint", async function () { const mint = await splToken.createMint(connection, payer, payer.publicKey, null, 6); - - const ix = await tokenRouter.initializeIx({ - owner: payer.publicKey, - ownerAssistant: ownerAssistant.publicKey, - mint, - }); - const unknownAta = splToken.getAssociatedTokenAddressSync( - mint, - tokenRouter.custodianAddress(), - true, - ); - await expectIxErr(connection, [ix], [payer], `mint. Error Code: ConstraintAddress`); + const txs = tokenRouter.initialize(payer.publicKey, ownerAssistant.publicKey, mint); + await expectTxsErr(payerSigner, txs, `mint. Error Code: ConstraintAddress`); }); it("Cannot Initialize with Default Owner Assistant", async function () { - const ix = await tokenRouter.initializeIx({ - owner: payer.publicKey, - ownerAssistant: PublicKey.default, - }); - - await expectIxErr(connection, [ix], [payer], "Error Code: AssistantZeroPubkey"); + const txs = tokenRouter.initialize(payer.publicKey, PublicKey.default); + await expectTxsErr(payerSigner, txs, "Error Code: AssistantZeroPubkey"); }); it("Initialize", async function () { - const ix = await tokenRouter.initializeIx({ - owner: payer.publicKey, - ownerAssistant: ownerAssistant.publicKey, - }); - - await expectIxOk(connection, [ix], [payer]); + const txs = tokenRouter.initialize(payer.publicKey, ownerAssistant.publicKey); + await expectTxsOk(payerSigner, txs); const custodianData = await tokenRouter.fetchCustodian(); expect(custodianData).to.eql( @@ -108,15 +96,11 @@ describe("Token Router", function () { }); it("Cannot Initialize Again", async function () { - const ix = await tokenRouter.initializeIx({ - owner: payer.publicKey, - ownerAssistant: ownerAssistant.publicKey, - }); + const txs = tokenRouter.initialize(payer.publicKey, ownerAssistant.publicKey); - await expectIxErr( - connection, - [ix], - [payer], + await expectTxsErr( + payerSigner, + txs, `Allocate: account Address { address: ${tokenRouter .custodianAddress() .toString()}, base: None } already in use`, @@ -390,127 +374,81 @@ describe("Token Router", function () { const localVariables = new Map(); it("Cannot Prepare Market Order with Large Redeemer Payload", async function () { - const preparedOrder = Keypair.generate(); - const amountIn = 5000n; const minAmountOut = 0n; - const targetChain = foreignChain; - const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); + const redeemer = { + chain: toChain(foreignChain), + address: toUniversalAddress(Buffer.alloc(32, "deadbeef", "hex")), + }; const redeemerMessage = Buffer.alloc(501, "deadbeef", "hex"); - const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( - { - payer: payer.publicKey, - preparedOrder: preparedOrder.publicKey, - senderToken: payerToken, - }, - { - amountIn, - minAmountOut, - targetChain, - redeemer, - redeemerMessage, - }, - ); - await expectIxErr( - connection, - [approveIx!, prepareIx], - [payer, preparedOrder], - "Error Code: RedeemerMessageTooLarge", + const txs = tokenRouter.prepareMarketOrder( + payer.publicKey, + amountIn, + redeemer, + minAmountOut, + redeemerMessage, ); + + await expectTxsErr(payerSigner, txs, "Error Code: RedeemerMessageTooLarge"); }); it("Cannot Prepare Market Order with Insufficient Amount", async function () { - const preparedOrder = Keypair.generate(); - const amountIn = 0n; const minAmountOut = 0n; - const targetChain = foreignChain; - const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); + const redeemer = { + chain: toChain(foreignChain), + address: toUniversalAddress(Buffer.alloc(32, "deadbeef", "hex")), + }; const redeemerMessage = Buffer.from("All your base are belong to us"); - const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( - { - payer: payer.publicKey, - preparedOrder: preparedOrder.publicKey, - senderToken: payerToken, - }, - { - amountIn, - minAmountOut, - targetChain, - redeemer, - redeemerMessage, - }, - ); - await expectIxErr( - connection, - [approveIx!, prepareIx], - [payer, preparedOrder], - "Error Code: InsufficientAmount", + const txs = tokenRouter.prepareMarketOrder( + payer.publicKey, + amountIn, + redeemer, + minAmountOut, + redeemerMessage, ); + await expectTxsErr(payerSigner, txs, "Error Code: InsufficientAmount"); }); it("Cannot Prepare Market Order with Invalid Redeemer", async function () { - const preparedOrder = Keypair.generate(); - const amountIn = 69n; const minAmountOut = 0n; - const targetChain = foreignChain; - const redeemer = Array.from(Buffer.alloc(32, 0, "hex")); + const redeemer = { + chain: toChain(foreignChain), + address: toUniversalAddress(Buffer.alloc(32, 0, "hex")), + }; const redeemerMessage = Buffer.from("All your base are belong to us"); - const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( - { - payer: payer.publicKey, - preparedOrder: preparedOrder.publicKey, - senderToken: payerToken, - }, - { - amountIn, - minAmountOut, - targetChain, - redeemer, - redeemerMessage, - }, + const txs = tokenRouter.prepareMarketOrder( + payer.publicKey, + amountIn, + redeemer, + minAmountOut, + redeemerMessage, ); - await expectIxErr( - connection, - [approveIx!, prepareIx], - [payer, preparedOrder], - "Error Code: InvalidRedeemer", - ); + await expectTxsErr(payerSigner, txs, "Error Code: InvalidRedeemer"); }); it("Cannot Prepare Market Order with Min Amount Too High", async function () { - const preparedOrder = Keypair.generate(); - const amountIn = 1n; const minAmountOut = 2n; - const targetChain = foreignChain; - const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); + const redeemer = { + chain: toChain(foreignChain), + address: toUniversalAddress(Buffer.alloc(32, "deadbeef", "hex")), + }; const redeemerMessage = Buffer.from("All your base are belong to us"); - const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( - { - payer: payer.publicKey, - preparedOrder: preparedOrder.publicKey, - senderToken: payerToken, - }, - { - amountIn, - minAmountOut, - targetChain, - redeemer, - redeemerMessage, - }, - ); - await expectIxErr( - connection, - [approveIx!, prepareIx], - [payer, preparedOrder], - "Error Code: MinAmountOutTooHigh", + const txs = tokenRouter.prepareMarketOrder( + payer.publicKey, + amountIn, + redeemer, + minAmountOut, + redeemerMessage, ); + + await expectTxsErr(payerSigner, txs, "Error Code: MinAmountOutTooHigh"); }); it("Cannot Prepare Market Order without Delegating Authority to Program Transfer Authority", async function () { @@ -831,27 +769,21 @@ describe("Token Router", function () { const preparedOrder = Keypair.generate(); const amountIn = 69n; - const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( - { - payer: payer.publicKey, - preparedOrder: preparedOrder.publicKey, - senderToken: payerToken, - sender: payer.publicKey, - }, + const txs = tokenRouter.prepareMarketOrder( + payer.publicKey, + amountIn, { - useTransferAuthority: false, - amountIn, - minAmountOut: null, - targetChain: foreignChain, - redeemer: Array.from(Buffer.alloc(32, "deadbeef", "hex")), - redeemerMessage: Buffer.from("All your base are belong to us"), + chain: toChain(foreignChain), + address: toUniversalAddress(Buffer.alloc(32, "deadbeef", "hex")), }, + undefined, + encoding.bytes.encode("All your base are belong to us"), + preparedOrder, ); - expect(approveIx).is.null; const { amount: balanceBefore } = await splToken.getAccount(connection, payerToken); - await expectIxOk(connection, [prepareIx], [payer, preparedOrder]); + await expectTxsOk(payerSigner, txs); const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore - amountIn); diff --git a/universal/ts/src/protocol.ts b/universal/ts/src/protocol.ts index b0bf5023..73ad3fec 100644 --- a/universal/ts/src/protocol.ts +++ b/universal/ts/src/protocol.ts @@ -2,9 +2,7 @@ import { Chain, Network } from "@wormhole-foundation/sdk-base"; import { AccountAddress, ChainAddress, - CircleAttestation, CircleBridge, - CircleTransferMessage, EmptyPlatformMap, ProtocolVAA, UnsignedTransaction, @@ -99,8 +97,6 @@ export interface MatchingEngine { } export interface TokenRouter { - getInitialAuctionFee(): Promise; - placeMarketOrder( amount: bigint, redeemer: ChainAddress,