diff --git a/evm/.gitignore b/evm/.gitignore index 57aa5691..b9e11d34 100644 --- a/evm/.gitignore +++ b/evm/.gitignore @@ -12,4 +12,5 @@ ts/lib ts/src/types dist ts-types -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo +selectors.txt \ No newline at end of file diff --git a/evm/package.json b/evm/package.json index 09e469c0..b00b639a 100644 --- a/evm/package.json +++ b/evm/package.json @@ -30,9 +30,9 @@ } }, "dependencies": { - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-evm": "^0.7.0-beta.6", + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-evm": "^0.7.1-beta.2", "@wormhole-foundation/example-liquidity-layer-definitions": "0.0.1", "ethers": "^6.5.1" }, diff --git a/evm/ts/src/MatchingEngine/evm.ts b/evm/ts/src/MatchingEngine/evm.ts index 6034c080..6a04a8ba 100644 --- a/evm/ts/src/MatchingEngine/evm.ts +++ b/evm/ts/src/MatchingEngine/evm.ts @@ -1,6 +1,6 @@ import { ChainId, asChainId } from "@wormhole-foundation/sdk-base"; import { ethers } from "ethers"; -import { RouterEndpoint, LiveAuctionData, MatchingEngine, RedeemParameters } from "."; +import { AbstractMatchingEngine, LiveAuctionData, RedeemParameters, RouterEndpoint } from "."; import { LiquidityLayerTransactionResult } from ".."; import { IMatchingEngine, @@ -11,7 +11,7 @@ import { IWormhole__factory, } from "../types"; -export class EvmMatchingEngine implements MatchingEngine { +export class MatchingEngine implements AbstractMatchingEngine { contract: IMatchingEngine; circle: ITokenMessenger; @@ -39,11 +39,11 @@ export class EvmMatchingEngine implements MatchingEngine { return this.contract.placeInitialBid.populateTransaction(fastTransferVaa, feeBid); } - async improveBid( + async improveBidTx( auctionId: Buffer | Uint8Array, feeBid: bigint | ethers.BigNumberish, ): Promise { return this.contract.improveBid.populateTransaction(auctionId, feeBid); } - async executeFastOrder( + async executeFastOrderTx( fastTransferVaa: Buffer | Uint8Array, ): Promise { return this.contract.executeFastOrder.populateTransaction(fastTransferVaa); } - async executeSlowOrderAndRedeem( + async executeSlowOrderAndRedeemTx( fastTransferVaa: Buffer | Uint8Array, params: RedeemParameters, ): Promise { diff --git a/evm/ts/src/MatchingEngine/index.ts b/evm/ts/src/MatchingEngine/index.ts index 208036c8..8c7c88c9 100644 --- a/evm/ts/src/MatchingEngine/index.ts +++ b/evm/ts/src/MatchingEngine/index.ts @@ -24,10 +24,10 @@ export type RouterEndpoint = { mintRecipient: string | Buffer | Uint8Array; }; -export abstract class MatchingEngine { +export abstract class AbstractMatchingEngine { abstract get address(): string; - abstract addRouterEndpoint( + abstract addRouterEndpointTx( chain: number, endpoint: RouterEndpoint, domain: number, @@ -37,21 +37,21 @@ export abstract class MatchingEngine; - abstract placeInitialBid( + abstract placeInitialBidTx( fastTransferVaa: Buffer | Uint8Array, feeBid: bigint, ): Promise; - abstract improveBid( + abstract improveBidTx( auctionId: Buffer | Uint8Array, feeBid: bigint, ): Promise; - abstract executeFastOrder( + abstract executeFastOrderTx( fastTransferVaa: Buffer | Uint8Array, ): Promise; - abstract executeSlowOrderAndRedeem( + abstract executeSlowOrderAndRedeemTx( fastTransferVaa: Buffer | Uint8Array, params: RedeemParameters, ): Promise; diff --git a/evm/ts/src/TokenRouter/evm.ts b/evm/ts/src/TokenRouter/evm.ts index 863ebc97..f9c421cd 100644 --- a/evm/ts/src/TokenRouter/evm.ts +++ b/evm/ts/src/TokenRouter/evm.ts @@ -1,6 +1,6 @@ import { ChainId, asChainId } from "@wormhole-foundation/sdk-base"; import { ethers } from "ethers"; -import { Endpoint, OrderResponse, TokenRouter, FastTransferParameters } from "."; +import { Endpoint, OrderResponse, AbstractTokenRouter, FastTransferParameters } from "."; import { LiquidityLayerTransactionResult } from ".."; import { ITokenRouter, @@ -11,7 +11,7 @@ import { ITokenMessenger, } from "../types"; -export class EvmTokenRouter implements TokenRouter { +export class TokenRouter implements AbstractTokenRouter { contract: ITokenRouter; circle: ITokenMessenger; diff --git a/evm/ts/src/TokenRouter/index.ts b/evm/ts/src/TokenRouter/index.ts index 0bd4acb0..76b4aba2 100644 --- a/evm/ts/src/TokenRouter/index.ts +++ b/evm/ts/src/TokenRouter/index.ts @@ -1,4 +1,10 @@ +import { encoding } from "@wormhole-foundation/sdk-base"; +import { CircleBridge, VAA, deserialize, serialize } from "@wormhole-foundation/sdk-definitions"; import { LiquidityLayerTransactionResult, PreparedInstruction } from ".."; +import { + FastTransfer, + MatchingEngine, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; export * from "./evm"; export type FastTransferParameters = { @@ -14,12 +20,35 @@ export type OrderResponse = { circleAttestation: Buffer | Uint8Array; }; +export function encodeOrderResponse(response: FastTransfer.OrderResponse): OrderResponse { + return FastTransfer.isFastFill(response) + ? { + encodedWormholeMessage: serialize(response.vaa), + circleAttestation: new Uint8Array(), + circleBridgeMessage: new Uint8Array(), + } + : { + encodedWormholeMessage: serialize(response.vaa), + circleAttestation: encoding.hex.decode(response.cctp!.attestation!), + circleBridgeMessage: CircleBridge.serialize(response.cctp!.message), + }; +} +export function decodedOrderResponse(response: OrderResponse): FastTransfer.OrderResponse { + if (response.circleAttestation.length > 0) { + const [message] = CircleBridge.deserialize(response.circleBridgeMessage); + const attestation = encoding.hex.encode(response.circleAttestation, true); + const vaa = deserialize("FastTransfer:CctpDeposit", response.encodedWormholeMessage); + return { vaa, cctp: { message, attestation } }; + } + return { vaa: deserialize("FastTransfer:FastFill", response.encodedWormholeMessage) }; +} + export type Endpoint = { router: string | Buffer | Uint8Array; mintRecipient: string | Buffer | Uint8Array; }; -export abstract class TokenRouter { +export abstract class AbstractTokenRouter { abstract get address(): string; abstract placeMarketOrderTx( diff --git a/evm/ts/src/index.ts b/evm/ts/src/index.ts index 5c5c01b9..3047626c 100644 --- a/evm/ts/src/index.ts +++ b/evm/ts/src/index.ts @@ -5,6 +5,7 @@ export * from "./TokenRouter"; export * from "./error"; export * from "./messages"; export * from "./utils"; +export * from "./protocol"; export * as ethers_types from "./types"; diff --git a/evm/ts/src/protocol/index.ts b/evm/ts/src/protocol/index.ts index d3eb3fad..0c36c240 100644 --- a/evm/ts/src/protocol/index.ts +++ b/evm/ts/src/protocol/index.ts @@ -1 +1,2 @@ export * from "./tokenRouter"; +export * from "./matchingEngine"; diff --git a/evm/ts/src/protocol/matchingEngine.ts b/evm/ts/src/protocol/matchingEngine.ts new file mode 100644 index 00000000..89cef4c9 --- /dev/null +++ b/evm/ts/src/protocol/matchingEngine.ts @@ -0,0 +1,156 @@ +import { + FastTransfer, + MatchingEngine, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { Chain, Network } from "@wormhole-foundation/sdk-base"; +import { + AccountAddress, + CircleBridge, + Contracts, + UnsignedTransaction, + serialize, +} from "@wormhole-foundation/sdk-definitions"; +import { + AnyEvmAddress, + EvmAddress, + EvmChains, + EvmUnsignedTransaction, +} from "@wormhole-foundation/sdk-evm"; +import { ethers } from "ethers"; +import { MatchingEngine as _MatchingEngine } from "../MatchingEngine"; +import { IUSDC__factory } from "../types"; + +export class EvmMatchingEngine + extends _MatchingEngine + implements MatchingEngine +{ + constructor( + readonly network: N, + readonly chain: C, + provider: ethers.Provider, + readonly contracts: Contracts & MatchingEngine.Addresses, + ) { + super(provider, contracts.matchingEngine, contracts.cctp.tokenMessenger); + } + async *registerRouter( + sender: AnyEvmAddress, + chain: RC, + cctpDomain: number, + router: AccountAddress, + tokenAccount?: AccountAddress | undefined, + ) { + throw new Error("Method not implemented."); + } + async *updateRouter( + sender: AnyEvmAddress, + chain: RC, + cctpDomain: number, + router: AccountAddress, + tokenAccount?: AccountAddress | undefined, + ) { + throw new Error("Method not implemented."); + } + async *disableRouter(sender: AnyEvmAddress, chain: RC) { + throw new Error("Method not implemented."); + } + async *setPause(sender: AnyEvmAddress, pause: boolean) { + throw new Error("Method not implemented."); + } + async *setConfiguration(config: { + enabled: boolean; + maxAmount: bigint; + baseFee: bigint; + initAuctionFee: bigint; + }) { + throw new Error("Method not implemented."); + } + + async *approveAllowance(sender: AnyEvmAddress, amount: bigint) { + const from = new EvmAddress(sender).unwrap(); + const tokenContract = IUSDC__factory.connect(this.contracts.cctp.usdcMint, this.provider); + + const allowed = await tokenContract.allowance(from, this.address); + if (amount > allowed) { + const txReq = await tokenContract.approve.populateTransaction(this.address, amount); + yield this.createUnsignedTx( + { ...txReq, from }, + "MatchingEngine.approveAllowance", + false, + ); + } + } + + async *placeInitialOffer(sender: AnyEvmAddress, order: FastTransfer.Order, offerPrice: bigint) { + const from = new EvmAddress(sender).unwrap(); + + const { amountIn, maxFee } = order.payload; + yield* this.approveAllowance(sender, amountIn + maxFee); + + const txReq = await this.connect(this.provider).placeInitialBidTx( + serialize(order), + offerPrice, + ); + + yield this.createUnsignedTx({ ...txReq, from }, "MatchingEngine.placeInitialOffer"); + } + async *improveOffer(sender: AnyEvmAddress, order: FastTransfer.Order, offer: bigint) { + const from = new EvmAddress(sender).unwrap(); + + const auctionId = FastTransfer.auctionId(order); + + // TODO: is this the correct amount to request for allowance here + const { amount, securityDeposit } = await this.liveAuctionInfo(auctionId); + yield* this.approveAllowance(sender, amount + securityDeposit); + + const txReq = await this.improveBidTx(auctionId, offer); + yield this.createUnsignedTx({ ...txReq, from }, "MatchingEngine.improveOffer"); + } + + async *executeFastOrder(sender: AnyEvmAddress, vaa: FastTransfer.Order) { + const from = new EvmAddress(sender).unwrap(); + const txReq = await this.executeFastOrderTx(serialize(vaa)); + yield this.createUnsignedTx({ ...txReq, from }, "MatchingEngine.executeFastOrder"); + } + + async *prepareOrderResponse( + sender: AnyEvmAddress, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, + ) { + throw new Error("Method not implemented."); + } + + async *settleOrder( + sender: AnyEvmAddress, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, + ) { + const from = new EvmAddress(sender).unwrap(); + + const fastVaaBytes = serialize(order); + + const txReq = await (FastTransfer.isFastFill(response) + ? this.executeFastOrderTx(fastVaaBytes) + : this.executeSlowOrderAndRedeemTx(fastVaaBytes, { + encodedWormholeMessage: serialize(response.vaa), + circleBridgeMessage: CircleBridge.serialize(response.cctp!.message), + circleAttestation: response.cctp!.attestation!, + })); + + yield this.createUnsignedTx({ ...txReq, from }, "MatchingEngine.settleOrder"); + } + + private createUnsignedTx( + txReq: ethers.TransactionRequest, + description: string, + parallelizable: boolean = false, + ): UnsignedTransaction { + return new EvmUnsignedTransaction( + txReq, + this.network, + this.chain, + description, + parallelizable, + ); + } +} diff --git a/evm/ts/src/protocol/tokenRouter.ts b/evm/ts/src/protocol/tokenRouter.ts index e49b64e3..b95ddab4 100644 --- a/evm/ts/src/protocol/tokenRouter.ts +++ b/evm/ts/src/protocol/tokenRouter.ts @@ -1,11 +1,9 @@ -import { TokenRouter } from "@wormhole-foundation/example-liquidity-layer-definitions"; -import { Network, nativeChainIds, toChainId } from "@wormhole-foundation/sdk-base"; import { - CircleBridge, - Contracts, - UnsignedTransaction, - VAA, -} from "@wormhole-foundation/sdk-definitions"; + FastTransfer, + TokenRouter, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { Network, toChainId } from "@wormhole-foundation/sdk-base"; +import { Contracts, UnsignedTransaction } from "@wormhole-foundation/sdk-definitions"; import { AnyEvmAddress, EvmAddress, @@ -13,13 +11,13 @@ import { EvmUnsignedTransaction, } from "@wormhole-foundation/sdk-evm"; import { ethers } from "ethers"; -import { EvmTokenRouter as _EvmTokenRouter } from "../TokenRouter"; +import { OrderResponse, TokenRouter as _TokenRouter, encodeOrderResponse } from "../TokenRouter"; +import { IUSDC__factory } from "../types"; export class EvmTokenRouter - extends _EvmTokenRouter + extends _TokenRouter implements TokenRouter { - private _chainId: number; constructor( readonly network: N, readonly chain: C, @@ -27,20 +25,35 @@ export class EvmTokenRouter readonly contracts: Contracts & TokenRouter.Addresses, ) { super(provider, contracts.tokenRouter, contracts.cctp.tokenMessenger); - this._chainId = 0; //nativeChainIds.networkChainToNativeChainId(network, chain); } - async *placeMarketOrder( - sender: AnyEvmAddress, - order: TokenRouter.OrderRequest, - ): AsyncGenerator, any, unknown> { + async *approveAllowance(sender: AnyEvmAddress, amount: bigint) { + const from = new EvmAddress(sender).unwrap(); + + const tokenContract = IUSDC__factory.connect(this.contracts.cctp.usdcMint, this.provider); + const allowed = await tokenContract.allowance(from, this.address); + + if (amount > allowed) { + const txReq = await tokenContract.approve.populateTransaction(this.address, amount); + yield this.createUnsignedTx( + { ...txReq, from }, + "MatchingEngine.approveAllowance", + false, + ); + } + } + + async *placeMarketOrder(sender: AnyEvmAddress, order: TokenRouter.OrderRequest) { + const from = new EvmAddress(sender).unwrap(); const msg = order.redeemerMessage ? order.redeemerMessage : new Uint8Array(); const refundAddress = order.refundAddress ? new EvmAddress(order.refundAddress).unwrap() : undefined; - const tx = await this.placeMarketOrderTx( + yield* this.approveAllowance(sender, order.amountIn + (order.maxFee || 0n)); + + const txReq = await this.placeMarketOrderTx( order.amountIn, toChainId(order.targetChain), order.redeemer.toUint8Array(), @@ -49,29 +62,49 @@ export class EvmTokenRouter refundAddress, ); - yield this.createUnsignedTx(tx, "TokenRouter.placeMarketOrder"); + yield this.createUnsignedTx({ ...txReq, from }, "TokenRouter.placeMarketOrder"); } - async *redeemFill( - sender: AnyEvmAddress, - vaa: - | VAA<"FastTransfer:CctpDeposit"> - | VAA<"FastTransfer:FastMarketOrder"> - | VAA<"FastTransfer:FastFill">, - cctp: CircleBridge.Attestation, - ): AsyncGenerator, any, unknown> { - throw new Error("Method not implemented."); + + async *placeFastMarketOrder(sender: AnyEvmAddress, order: TokenRouter.OrderRequest) { + const from = new EvmAddress(sender).unwrap(); + const msg = order.redeemerMessage ? order.redeemerMessage : new Uint8Array(); + + const refundAddress = order.refundAddress + ? new EvmAddress(order.refundAddress).unwrap() + : undefined; + + // If necessary, approve the amountIn to be spent by the TokenRouter. + yield* this.approveAllowance(sender, order.amountIn + (order.maxFee || 0n)); + + const txReq = await this.placeFastMarketOrderTx( + order.amountIn, + toChainId(order.targetChain), + order.redeemer.toUint8Array(), + msg, + order.maxFee!, + order.deadline!, + order.minAmountOut, + refundAddress, + ); + + yield this.createUnsignedTx({ ...txReq, from }, "TokenRouter.placeMarketOrder"); + } + + async *redeemFill(sender: AnyEvmAddress, orderResponse: FastTransfer.OrderResponse) { + const from = new EvmAddress(sender).unwrap(); + + const response: OrderResponse = encodeOrderResponse(orderResponse); + const txReq = await this.redeemFillTx(response); + yield this.createUnsignedTx({ ...txReq, from }, "TokenRouter.redeemFill"); } private createUnsignedTx( - txReq: ethers.ContractTransaction, + txReq: ethers.TransactionRequest, description: string, parallelizable: boolean = false, - ): EvmUnsignedTransaction { - //txReq.chainId = this._chainId; - + ): UnsignedTransaction { return new EvmUnsignedTransaction( - // txReq, - {}, + txReq, this.network, this.chain, description, diff --git a/evm/ts/src/testing/env.ts b/evm/ts/src/testing/env.ts index fe7577e5..34520d67 100644 --- a/evm/ts/src/testing/env.ts +++ b/evm/ts/src/testing/env.ts @@ -1,14 +1,12 @@ +import { TokenRouter } from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { Chain, Platform, toChain } from "@wormhole-foundation/sdk-base"; +import { toUniversal } from "@wormhole-foundation/sdk-definitions"; //@ts-ignore import { parse as envParse } from "envfile"; import * as fs from "fs"; -export enum ChainType { - Evm, - Solana, -} - export type LiquidityLayerEnv = { - chainType: ChainType; + chainType: Platform; chainId: number; domain: number; tokenAddress: string; @@ -18,7 +16,7 @@ export type LiquidityLayerEnv = { tokenRouterAddress: string; tokenRouterMintRecipient?: string; feeRecipient?: string; - matchingEngineChain: string; + matchingEngineChain: Chain; matchingEngineAddress: string; matchingEngineMintRecipient: string; matchingEngineDomain?: string; @@ -70,23 +68,38 @@ export function parseLiquidityLayerEnvFile(envPath: string): LiquidityLayerEnv { tokenRouterAddress: contents.TOKEN_ROUTER_ADDRESS, tokenRouterMintRecipient: contents.TOKEN_ROUTER_MINT_RECIPIENT, feeRecipient: contents.FEE_RECIPIENT_ADDRESS, - matchingEngineChain: contents.MATCHING_ENGINE_CHAIN, + matchingEngineChain: toChain(parseInt(contents.MATCHING_ENGINE_CHAIN)), matchingEngineAddress: contents.MATCHING_ENGINE_ADDRESS, matchingEngineMintRecipient: contents.MATCHING_ENGINE_MINT_RECIPIENT, matchingEngineDomain: contents.MATCHING_ENGINE_DOMAIN, }; } +export function toContractAddresses(env: LiquidityLayerEnv): TokenRouter.Addresses { + return { + tokenRouter: env.tokenRouterAddress, + matchingEngine: toUniversal(env.matchingEngineChain, env.matchingEngineAddress) + .toNative(env.matchingEngineChain) + .toString(), + coreBridge: env.wormholeAddress, + cctp: { + tokenMessenger: env.tokenMessengerAddress, + usdcMint: env.tokenAddress, + // TODO: needed? + messageTransmitter: "", + wormhole: "", + wormholeRelayer: "", + }, + }; +} + function parseChainType(chainType: string) { switch (chainType) { - case "evm": { - return ChainType.Evm; - } - case "solana": { - return ChainType.Solana; - } - default: { + case "evm": + return "Evm"; + case "solana": + return "Solana"; + default: throw new Error(`invalid chain type: ${chainType}`); - } } } diff --git a/evm/ts/src/testing/utils.ts b/evm/ts/src/testing/utils.ts index 01749ce5..5a8fcdbf 100644 --- a/evm/ts/src/testing/utils.ts +++ b/evm/ts/src/testing/utils.ts @@ -1,17 +1,106 @@ -import { ethers } from "ethers"; +import { Network } from "@wormhole-foundation/sdk-base"; +import { signAndSendWait } from "@wormhole-foundation/sdk-connect"; +import { + SignAndSendSigner, + UnsignedTransaction, + toUniversal, +} from "@wormhole-foundation/sdk-definitions"; +import { EvmChains, EvmNativeSigner } from "@wormhole-foundation/sdk-evm"; +import { ethers, isError } from "ethers"; +import { MatchingEngine } from ".."; import { IERC20 } from "../types"; import { IUSDC__factory } from "../types/factories/IUSDC__factory"; import { WALLET_PRIVATE_KEYS } from "./consts"; -import { EvmMatchingEngine } from ".."; -import { Chain } from "@wormhole-foundation/sdk-base"; -import { toUniversal } from "@wormhole-foundation/sdk-definitions"; export interface ScoreKeeper { - player: ethers.NonceManager; + player: ethers.Wallet; bid: bigint; balance: bigint; } +export function getSdkSigner( + fromChain: C, + wallet: ethers.Wallet | ethers.NonceManager, +): SdkSigner { + wallet = "reset" in wallet ? wallet : new ethers.NonceManager(wallet); + + const address = (wallet.provider as unknown as ethers.Wallet).address; + + return new SdkSigner( + fromChain, + address, + // @ts-ignore -- incorrect ethers version + wallet, + ); +} + +export class SdkSigner + extends EvmNativeSigner + implements SignAndSendSigner +{ + get provider() { + return this._signer.provider as unknown as ethers.JsonRpcProvider; + } + get wallet() { + return this._signer as unknown as ethers.NonceManager; + } + + // Does not wait for confirmations + async signSendOnly(txs: UnsignedTransaction[]): Promise { + this._address = await this.wallet.getAddress(); + + const txids: string[] = []; + for (let tx of txs) { + for (let retries = 0; retries < 3; retries++) { + try { + const res = await this._signer.sendTransaction(tx.transaction); + txids.push(res.hash); + break; + } catch (e) { + if ( + (isError(e, "CALL_EXCEPTION") && + "info" in e && + e.info!.error.message === "nonce too low") || + isError(e, "NONCE_EXPIRED") + ) { + // Sometimes it take a second for the mempool to update the new nonce + await sleep(1); + this.wallet.reset(); + const nonce = await this.wallet.getNonce("pending"); + if (this.opts?.debug) + console.log("Setting nonce for", this.address(), " to ", nonce); + tx.transaction.nonce = nonce; + continue; + } + throw e; + } + } + } + return txids; + } + + // Mine and wait for transaction + async signAndSend(txs: UnsignedTransaction[]): Promise { + try { + const txids = await this.signSendOnly(txs); + await mine(this.provider); + await this.provider.waitForTransaction(txids[0], 1, 5000); + return txids; + } catch (e) { + console.error(e); + throw e; + } + } +} + +export const sleep = async (seconds: number) => + await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); + +export function getSigners(key: string, provider: ethers.Provider) { + const wallet = new ethers.Wallet(key, provider); + return { wallet, signer: getSdkSigner("Ethereum", new ethers.NonceManager(wallet)) }; +} + export async function mine(provider: ethers.JsonRpcProvider) { await provider.send("evm_mine", []); } @@ -22,13 +111,13 @@ export async function mineMany(provider: ethers.JsonRpcProvider, count: number) } } -export function tryNativeToUint8Array(address: string, chain: Chain) { - return toUniversal(chain, address).toUint8Array(); +export function asUniversalBytes(address: string) { + return toUniversal("Ethereum", address).toUint8Array(); } export async function mineToGracePeriod( auctionId: Uint8Array, - engine: EvmMatchingEngine, + engine: MatchingEngine, provider: ethers.JsonRpcProvider, ) { const { startBlock } = await engine.liveAuctionInfo(auctionId); @@ -42,7 +131,7 @@ export async function mineToGracePeriod( export async function mineToPenaltyPeriod( auctionId: Uint8Array, - engine: EvmMatchingEngine, + engine: MatchingEngine, provider: ethers.JsonRpcProvider, penaltyBlocks: number, ) { @@ -54,6 +143,23 @@ export async function mineToPenaltyPeriod( await mineMany(provider, Number(blocksToMine)); } +export async function signSendOnly( + txs: AsyncGenerator, void, unknown>, + signer: SdkSigner, +) { + const txns = []; + for await (const tx of txs) txns.push(tx); + await signer.signSendOnly(txns); +} + +export async function signSendMineWait( + txs: AsyncGenerator, void, unknown>, + signer: SdkSigner, +) { + const txids = await signAndSendWait(txs, signer); + return await signer.provider.waitForTransaction(txids[txids.length - 1].txid); +} + export async function mineWait(provider: ethers.JsonRpcProvider, tx: ethers.TransactionResponse) { await mine(provider); // 1 is default confirms, 5000ms timeout to prevent hanging forever. diff --git a/evm/ts/tests/01__registration.ts b/evm/ts/tests/01__registration.ts index 17ea3549..a1280993 100644 --- a/evm/ts/tests/01__registration.ts +++ b/evm/ts/tests/01__registration.ts @@ -7,7 +7,6 @@ import { ValidNetwork, MATCHING_ENGINE_NAME, parseLiquidityLayerEnvFile, - ChainType, LiquidityLayerEnv, } from "../src/testing"; import { expect } from "chai"; @@ -103,7 +102,7 @@ function fetchTokenRouterEndpoint( ): [Uint8Array, Uint8Array] { const formattedAddress = toUniversal(chainName, targetEnv.tokenRouterAddress).toUint8Array(); let formattedMintRecipient; - if (targetEnv.chainType === ChainType.Evm) { + if (targetEnv.chainType === "Evm") { formattedMintRecipient = formattedAddress; } else { if (targetEnv.tokenRouterMintRecipient === undefined) { diff --git a/evm/ts/tests/03__marketOrder.ts b/evm/ts/tests/03__marketOrder.ts index cccfdb45..e0e62090 100644 --- a/evm/ts/tests/03__marketOrder.ts +++ b/evm/ts/tests/03__marketOrder.ts @@ -1,21 +1,26 @@ +import { + FastTransfer, + TokenRouter, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { toNative } from "@wormhole-foundation/sdk-definitions"; import { expect } from "chai"; import { ethers } from "ethers"; -import { EvmTokenRouter, errorDecoder, OrderResponse } from "../src"; -import { IERC20__factory } from "../src/types"; +import { EvmTokenRouter, decodedOrderResponse } from "../src"; import { CircleAttester, GuardianNetwork, LOCALHOSTS, ValidNetwork, WALLET_PRIVATE_KEYS, + asUniversalBytes, burnAllUsdc, - mineWait, + getSdkSigner, mintNativeUsdc, - ChainType, parseLiquidityLayerEnvFile, - tryNativeToUint8Array, + signSendMineWait, + toContractAddresses, } from "../src/testing"; -import { toChainId } from "@wormhole-foundation/sdk-base"; +import { IERC20__factory } from "../src/types"; const CHAIN_PATHWAYS: ValidNetwork[][] = [ ["Ethereum", "Avalanche"], @@ -41,14 +46,16 @@ describe("Market Order Business Logic -- CCTP to CCTP", () => { // From setup. const fromProvider = new ethers.JsonRpcProvider(LOCALHOSTS[fromChainName]); const fromWallet = new ethers.Wallet(WALLET_PRIVATE_KEYS[0], fromProvider); + const fromSigner = getSdkSigner(fromChainName, new ethers.NonceManager(fromWallet)); const fromEnv = parseLiquidityLayerEnvFile(`${envPath}/${fromChainName}.env`); const fromTokenRouter = (() => { - if (fromEnv.chainType === ChainType.Evm) { + if (fromEnv.chainType === "Evm") { return new EvmTokenRouter( - fromWallet, - fromEnv.tokenRouterAddress, - fromEnv.tokenMessengerAddress, + "Devnet", + fromChainName, + fromProvider, + toContractAddresses(fromEnv), ); } else { throw new Error("Unsupported chain"); @@ -58,14 +65,16 @@ describe("Market Order Business Logic -- CCTP to CCTP", () => { // To setup. const toProvider = new ethers.JsonRpcProvider(LOCALHOSTS[toChainName]); const toWallet = new ethers.Wallet(WALLET_PRIVATE_KEYS[1], toProvider); + const toSigner = getSdkSigner(toChainName, toWallet); const toEnv = parseLiquidityLayerEnvFile(`${envPath}/${toChainName}.env`); const toTokenRouter = (() => { - if (toEnv.chainType === ChainType.Evm) { + if (toEnv.chainType === "Evm") { return new EvmTokenRouter( - toWallet, - toEnv.tokenRouterAddress, - toEnv.tokenMessengerAddress, + "Devnet", + toChainName, + toProvider, + toContractAddresses(toEnv), ); } else { throw new Error("Unsupported chain"); @@ -91,48 +100,29 @@ describe("Market Order Business Logic -- CCTP to CCTP", () => { it(`From Network -- Place Market Order`, async () => { const amountIn = await (async () => { - if (fromEnv.chainType == ChainType.Evm) { - const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - const amount = await usdc.balanceOf(fromWallet.address); - await usdc - .approve(fromTokenRouter.address, amount) - .then((tx) => mineWait(fromProvider, tx)); - - return BigInt(amount.toString()); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return await IERC20__factory.connect( + fromEnv.tokenAddress, + fromWallet, + ).balanceOf(fromWallet.address); })(); localVariables.set("amountIn", amountIn); - const targetChain = toChainId(toChainName); - const minAmountOut = BigInt(0); - const receipt = await fromTokenRouter - .placeMarketOrderTx( - amountIn, - targetChain, - Buffer.from(tryNativeToUint8Array(toWallet.address, toChainName)), - Buffer.from("All your base are belong to us."), - minAmountOut, - fromWallet.address, - ) - .then(async (txReq) => { - txReq.nonce = await fromWallet.getNonce(); - return await fromWallet.sendTransaction(txReq); - }) - .then((tx) => mineWait(fromProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const order: TokenRouter.OrderRequest = { + amountIn, + minAmountOut: BigInt(0), + redeemer: toNative("Ethereum", toWallet.address).toUniversalAddress(), + targetChain: toChainName, + }; + const txs = fromTokenRouter.placeMarketOrder(fromWallet.address, order); + const receipt = await signSendMineWait(txs, fromSigner); const transactionResult = await fromTokenRouter.getTransactionResults( receipt!.hash, ); expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(fromEnv.tokenRouterAddress, fromChainName), + asUniversalBytes(fromEnv.tokenRouterAddress), ); expect(transactionResult.wormhole.message.body).has.property("fill"); expect(transactionResult.circleMessage).is.not.undefined; @@ -146,30 +136,23 @@ describe("Market Order Business Logic -- CCTP to CCTP", () => { const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - const orderResponse: OrderResponse = { + const orderResponse: FastTransfer.OrderResponse = decodedOrderResponse({ encodedWormholeMessage: fillVaa, circleBridgeMessage, circleAttestation, - }; + }); localVariables.set("orderResponse", orderResponse); }); it(`To Network -- Redeem Fill`, async () => { - const orderResponse = localVariables.get("orderResponse") as OrderResponse; + const response = localVariables.get("orderResponse") as FastTransfer.OrderResponse; expect(localVariables.delete("orderResponse")).is.true; const usdc = IERC20__factory.connect(toEnv.tokenAddress, toProvider); const balanceBefore = await usdc.balanceOf(toWallet.address); - const receipt = await toTokenRouter - .redeemFillTx(orderResponse) - .then((txReq) => toWallet.sendTransaction(txReq)) - .then((tx) => mineWait(toProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = toTokenRouter.redeemFill(toWallet.address, response); + await signSendMineWait(txs, toSigner); const balanceAfter = await usdc.balanceOf(toWallet.address); diff --git a/evm/ts/tests/04__fastMarketOrder.ts b/evm/ts/tests/04__fastMarketOrder.ts index cb489214..a25be70a 100644 --- a/evm/ts/tests/04__fastMarketOrder.ts +++ b/evm/ts/tests/04__fastMarketOrder.ts @@ -1,14 +1,26 @@ +import { + FastTransfer, + MatchingEngine, + TokenRouter, + payloadIds, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { encoding } from "@wormhole-foundation/sdk-base"; +import { + deserialize, + keccak256, + serialize, + toUniversal, +} from "@wormhole-foundation/sdk-definitions"; import { expect } from "chai"; import { ethers } from "ethers"; import { EvmMatchingEngine, EvmTokenRouter, - MessageDecoder, OrderResponse, + decodedOrderResponse, errorDecoder, } from "../src"; import { - ChainType, CircleAttester, GuardianNetwork, LOCALHOSTS, @@ -17,21 +29,22 @@ import { ScoreKeeper, ValidNetwork, WALLET_PRIVATE_KEYS, + asUniversalBytes, burnAllUsdc, + getSdkSigner, + getSigners, mine, mineToGracePeriod, mineToPenaltyPeriod, mineWait, mintNativeUsdc, parseLiquidityLayerEnvFile, - tryNativeToUint8Array, + signSendMineWait, + signSendOnly, + toContractAddresses, } from "../src/testing"; import { IERC20__factory } from "../src/types"; -import { toChainId } from "@wormhole-foundation/sdk-base"; -import { deserialize, keccak256, toUniversal } from "@wormhole-foundation/sdk-definitions"; -import "@wormhole-foundation/sdk-evm"; - // Cannot send a fast market order from the matching engine chain. const CHAIN_PATHWAYS: ValidNetwork[][] = [ ["Base", "Ethereum"], @@ -43,9 +56,6 @@ const CHAIN_PATHWAYS: ValidNetwork[][] = [ const TEST_AMOUNT = ethers.parseUnits("1000", 6); const FEE_AMOUNT = BigInt(ethers.parseUnits("10", 6).toString()); -const sleep = async (seconds: number) => - await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); - describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Mocha.Suite) { const envPath = `${__dirname}/../../env/localnet`; @@ -54,38 +64,29 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc // Matching Engine configuration. const engineProvider = new ethers.JsonRpcProvider(LOCALHOSTS[MATCHING_ENGINE_NAME]); - const engineWallet = new ethers.Wallet(WALLET_PRIVATE_KEYS[2], engineProvider); const engineEnv = parseLiquidityLayerEnvFile(`${envPath}/${MATCHING_ENGINE_NAME}.env`); - const engine = (() => { - if (engineEnv.chainType === ChainType.Evm) { - return new EvmMatchingEngine( - engineProvider, - toUniversal("Avalanche", engineEnv.matchingEngineAddress) - .toNative("Avalanche") - .toString(), - engineEnv.tokenMessengerAddress, - ); - } else { - throw new Error("Unsupported chain"); - } - })(); - // Auction participants. - const initialBidder = new ethers.NonceManager( - new ethers.Wallet(WALLET_PRIVATE_KEYS[3], engineProvider), - ); - const bidderTwo = new ethers.NonceManager( - new ethers.Wallet(WALLET_PRIVATE_KEYS[4], engineProvider), - ); - const bidderThree = new ethers.NonceManager( - new ethers.Wallet(WALLET_PRIVATE_KEYS[5], engineProvider), + if (engineEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + + const engine = new EvmMatchingEngine( + "Devnet", + MATCHING_ENGINE_NAME, + engineProvider, + toContractAddresses(engineEnv), ); - const highestBidder = new ethers.NonceManager( - new ethers.Wallet(WALLET_PRIVATE_KEYS[6], engineProvider), + + // Auction participants. + const { wallet: initialBidder, signer: initialBidderSigner } = getSigners( + WALLET_PRIVATE_KEYS[3], + engineProvider, ); - const liquidator = new ethers.NonceManager( - new ethers.Wallet(WALLET_PRIVATE_KEYS[7], engineProvider), + const { wallet: bidderTwo } = getSigners(WALLET_PRIVATE_KEYS[4], engineProvider); + const { wallet: bidderThree } = getSigners(WALLET_PRIVATE_KEYS[5], engineProvider); + const { wallet: highestBidder, signer: highestBidderSigner } = getSigners( + WALLET_PRIVATE_KEYS[6], + engineProvider, ); + const { wallet: liquidator } = getSigners(WALLET_PRIVATE_KEYS[7], engineProvider); for (const [fromChainName, toChainName] of CHAIN_PATHWAYS) { const localVariables = new Map(); @@ -93,36 +94,38 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc describe(`${fromChainName} <> ${toChainName}`, () => { // From setup. const fromProvider = new ethers.JsonRpcProvider(LOCALHOSTS[fromChainName]); - const fromWallet = new ethers.Wallet(WALLET_PRIVATE_KEYS[0], fromProvider); + const { wallet: fromWallet, signer: fromSigner } = getSigners( + WALLET_PRIVATE_KEYS[0], + fromProvider, + ); const fromEnv = parseLiquidityLayerEnvFile(`${envPath}/${fromChainName}.env`); const fromTokenRouter = (() => { - if (fromEnv.chainType === ChainType.Evm) { - return new EvmTokenRouter( - fromWallet, - fromEnv.tokenRouterAddress, - fromEnv.tokenMessengerAddress, - ); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return new EvmTokenRouter( + "Devnet", + fromChainName, + fromProvider, + toContractAddresses(fromEnv), + ); })(); // To setup. const toProvider = new ethers.JsonRpcProvider(LOCALHOSTS[toChainName]); - const toWallet = new ethers.Wallet(WALLET_PRIVATE_KEYS[1], toProvider); + const { wallet: toWallet, signer: toSigner } = getSigners( + WALLET_PRIVATE_KEYS[1], + toProvider, + ); const toEnv = parseLiquidityLayerEnvFile(`${envPath}/${toChainName}.env`); const toTokenRouter = (() => { - if (toEnv.chainType === ChainType.Evm) { - return new EvmTokenRouter( - toWallet, - toEnv.tokenRouterAddress, - fromEnv.tokenMessengerAddress, - ); - } else { - throw new Error("Unsupported chain"); - } + if (toEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return new EvmTokenRouter( + "Devnet", + toChainName, + toProvider, + toContractAddresses(toEnv), + ); })(); describe(`Successful Auction`, () => { @@ -133,14 +136,8 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc } const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - await burnAllUsdc(usdc); - - await mintNativeUsdc( - IERC20__factory.connect(fromEnv.tokenAddress, fromProvider), - fromWallet.address, - TEST_AMOUNT, - ); + await mintNativeUsdc(usdc, fromWallet.address, TEST_AMOUNT); }); after(`Burn USDC`, async () => { @@ -150,47 +147,33 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc it(`From Network -- Place Fast Market Order`, async () => { const amountIn = await (async () => { - if (fromEnv.chainType == ChainType.Evm) { - const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - const amount = await usdc.balanceOf(fromWallet.address); - await usdc - .approve(fromTokenRouter.address, amount) - .then((tx) => mineWait(fromProvider, tx)); - - return BigInt(amount.toString()); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return await IERC20__factory.connect( + fromEnv.tokenAddress, + fromWallet, + ).balanceOf(fromWallet.address); })(); localVariables.set("amountIn", amountIn); - const targetChain = toChainId(toChainName); - const minAmountOut = BigInt(0); - const deadline = 0; - const receipt = await fromTokenRouter - .placeFastMarketOrderTx( - amountIn, - targetChain, - Buffer.from(tryNativeToUint8Array(toWallet.address, toChainName)), - Buffer.from("All your base are belong to us."), - FEE_AMOUNT, - deadline, - minAmountOut, - fromWallet.address, - ) - .then((txReq) => fromWallet.sendTransaction(txReq)) - .then((tx) => mineWait(fromProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const order: TokenRouter.OrderRequest = { + amountIn, + minAmountOut: BigInt(0), + deadline: 0, + maxFee: FEE_AMOUNT, + targetChain: toChainName, + redeemer: toUniversal(toChainName, toWallet.address), + redeemerMessage: encoding.bytes.encode("All your base are belong to us."), + refundAddress: toUniversal(fromChainName, fromWallet.address), + }; + const txs = fromTokenRouter.placeFastMarketOrder(fromWallet.address, order); + const receipt = await signSendMineWait(txs, fromSigner); const transactionResult = await fromTokenRouter.getTransactionResults( receipt!.hash, ); + expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(fromEnv.tokenRouterAddress, fromChainName), + asUniversalBytes(fromEnv.tokenRouterAddress), ); expect(transactionResult.wormhole.message.body).has.property( "slowOrderResponse", @@ -211,27 +194,26 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - const redeemParameters: OrderResponse = { - encodedWormholeMessage: slowOrderResponse, - circleBridgeMessage, - circleAttestation, - }; - localVariables.set("redeemParameters", redeemParameters); - localVariables.set("fastVaa", fastVaa); + localVariables.set( + "redeemParameters", + decodedOrderResponse({ + encodedWormholeMessage: slowOrderResponse, + circleBridgeMessage, + circleAttestation, + }), + ); + localVariables.set( + "fastVaa", + deserialize("FastTransfer:FastMarketOrder", fastVaa), + ); }); it(`Matching Engine -- Start Fast Order Auction`, async () => { - const fastVaa = localVariables.get("fastVaa") as Uint8Array; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; - // Parse the vaa, we will need the hash for later. - const parsedFastVaa = deserialize("Uint8Array", fastVaa); - localVariables.set("auctionId", keccak256(parsedFastVaa.hash)); - const fastOrder = MessageDecoder.decode(parsedFastVaa.payload).body - .fastMarketOrder; + const auctionId = FastTransfer.auctionId(fastVaa); - if (fastOrder === undefined) { - throw new Error("Fast order undefined"); - } + const fastOrder = fastVaa.payload; // Security deposit amount of the initial bid. const initialDeposit = fastOrder.amountIn + fastOrder.maxFee; @@ -241,44 +223,32 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc engineEnv.tokenAddress, initialBidder.provider!, ); - const initialBidderAddress = await initialBidder.getAddress(); - await mintNativeUsdc(usdc, initialBidderAddress, initialDeposit); - await usdc.approve - .populateTransaction(engine.address, initialDeposit) - .then((txReq) => initialBidder.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)); - const balanceBefore = await usdc.balanceOf(initialBidderAddress); + await mintNativeUsdc(usdc, initialBidder.address, initialDeposit); - const receipt = await engine - .connect(initialBidder.provider!) - .placeInitialBid(fastVaa, fastOrder.maxFee) - .then(async (txReq) => { - return await initialBidder.sendTransaction(txReq); - }) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const balanceBefore = await usdc.balanceOf(initialBidder.address); + + const txs = engine.placeInitialOffer( + initialBidder.address, + fastVaa, + fastOrder.maxFee, + ); + const receipt = await signSendMineWait(txs, initialBidderSigner); - const balanceAfter = await usdc.balanceOf(initialBidderAddress); + const balanceAfter = await usdc.balanceOf(initialBidder.address); expect((balanceBefore - balanceAfter).toString()).to.eql( initialDeposit.toString(), ); // Validate state changes. - const auctionData = await engine.liveAuctionInfo( - localVariables.get("auctionId"), - ); + const auctionData = await engine.liveAuctionInfo(auctionId); expect(auctionData.status).to.eql(1n); expect(auctionData.startBlock.toString()).to.eql( receipt!.blockNumber.toString(), ); - expect(auctionData.highestBidder).to.eql(initialBidderAddress); - expect(auctionData.initialBidder).to.eql(initialBidderAddress); + expect(auctionData.highestBidder).to.eql(initialBidder.address); + expect(auctionData.initialBidder).to.eql(initialBidder.address); expect(auctionData.amount.toString()).to.eql(fastOrder.amountIn.toString()); expect(auctionData.securityDeposit.toString()).to.eql( fastOrder.maxFee.toString(), @@ -287,8 +257,9 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }); it(`Matching Engine -- Fast Order Auction Period`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; + const vaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(vaa); const auctionInfoBefore = await engine.liveAuctionInfo(auctionId); const startingBid = auctionInfoBefore.bidPrice; const initialDeposit = @@ -319,25 +290,15 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc // Loop through and make multiple bids in the same block. for (let i = 0; i < bids.length; i++) { const player = bids[i].player; - const playerAddress = await player.getAddress(); + const playerSigner = getSdkSigner(fromChainName, player); const usdc = IERC20__factory.connect(engineEnv.tokenAddress, player); - await mintNativeUsdc(usdc, playerAddress, initialDeposit, false); - await usdc.approve(engine.address, initialDeposit); - - // give it time to hit the mempool - await sleep(1); - - bids[i].balance = await usdc.balanceOf(playerAddress); - - // Improve the bid. - await engine - .connect(player.provider!) - .improveBid(auctionId, bids[i].bid) - .then(async (txReq) => { - txReq.nonce = await player.getNonce("pending"); - return await player.sendTransaction(txReq); - }); + await mintNativeUsdc(usdc, player.address, initialDeposit, false); + + bids[i].balance = await usdc.balanceOf(player.address); + + const txs = engine.improveOffer(player.address, vaa, bids[i].bid); + await signSendOnly(txs, playerSigner); } // Mine the block. @@ -347,9 +308,8 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc // should've been refunded. for (let i = 0; i < bids.length; i++) { const player = bids[i].player; - const playerAddress = await player.getAddress(); const usdc = IERC20__factory.connect(engineEnv.tokenAddress, player); - const balanceAfter = await usdc.balanceOf(playerAddress); + const balanceAfter = await usdc.balanceOf(player.address); if (i == 2) { expect((balanceAfter - bids[i].balance).toString()).to.eql("0"); @@ -367,7 +327,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc expect(auctionInfoAfter.startBlock.toString()).to.eql( auctionInfoBefore.startBlock.toString(), ); - expect(auctionInfoAfter.highestBidder).to.eql(await highestBidder.getAddress()); + expect(auctionInfoAfter.highestBidder).to.eql(highestBidder.address); expect(auctionInfoAfter.initialBidder).to.eql(auctionInfoBefore.initialBidder); expect(auctionInfoAfter.amount.toString()).to.eql( auctionInfoBefore.amount.toString(), @@ -379,7 +339,8 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }); it(`Matching Engine -- Execute Fast Order Within Grace Period`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(fastVaa); await mineToGracePeriod(auctionId, engine, engineProvider); @@ -387,32 +348,21 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const auctionInfo = await engine.liveAuctionInfo(auctionId); const usdc = IERC20__factory.connect(engineEnv.tokenAddress, engineProvider); - const balanceBefore = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceBefore = await usdc.balanceOf(highestBidder.address); const initialBidderBefore = await usdc.balanceOf(auctionInfo.initialBidder); - const receipt = await engine - .connect(engineProvider) - .executeFastOrder(localVariables.get("fastVaa")) - .then((txReq) => highestBidder.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.executeFastOrder(highestBidder.address, fastVaa); + const receipt = await signSendMineWait(txs, highestBidderSigner); const transactionResult = await engine.getTransactionResults(receipt!.hash); + expect(transactionResult.wormhole.emitterAddress).to.eql( + asUniversalBytes(engine.address), + ); if (toChainName == MATCHING_ENGINE_NAME) { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fastFill"); expect(transactionResult.circleMessage).is.undefined; } else { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fill"); expect(transactionResult.circleMessage).is.not.undefined; } @@ -420,7 +370,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc expect(transactionResult.fastMessage).is.undefined; // Validate state and balance changes. - const balanceAfter = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceAfter = await usdc.balanceOf(highestBidder.address); const initialBidderAfter = await usdc.balanceOf(auctionInfo.initialBidder); const initAuctionFee = await fromTokenRouter.getInitialAuctionFee(); @@ -461,26 +411,23 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }; } - localVariables.set("fastOrderResponse", orderResponse); + localVariables.set("fastOrderResponse", decodedOrderResponse(orderResponse)); }); it(`To Network -- Redeem Fill`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; - const orderResponse = localVariables.get("fastOrderResponse") as OrderResponse; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(fastVaa); + + const orderResponse = localVariables.get( + "fastOrderResponse", + ) as FastTransfer.OrderResponse; expect(localVariables.delete("fastOrderResponse")).is.true; const usdc = IERC20__factory.connect(toEnv.tokenAddress, toProvider); const balanceBefore = await usdc.balanceOf(toWallet.address); - const receipt = await toTokenRouter - .redeemFillTx(orderResponse) - .then((txReq) => toWallet.sendTransaction(txReq)) - .then((tx) => mineWait(toProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = toTokenRouter.redeemFill(toWallet.address, orderResponse); + await signSendMineWait(txs, toSigner); // Validate balance changes. const { bidPrice, amount } = await engine.liveAuctionInfo(auctionId); @@ -493,37 +440,32 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }); it(`Matching Engine -- Execute Slow Vaa And Redeem`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; - const fastVaa = localVariables.get("fastVaa") as Uint8Array; - const params = localVariables.get("redeemParameters") as OrderResponse; - expect(localVariables.delete("redeemParameters")).is.true; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; expect(localVariables.delete("fastVaa")).is.true; - expect(localVariables.delete("auctionId")).is.true; + + const orderResponse = localVariables.get( + "redeemParameters", + ) as FastTransfer.Fill; + expect(localVariables.delete("redeemParameters")).is.true; + + const auctionId = FastTransfer.auctionId(fastVaa); // Fetch balance of player four since they were the highest bidder. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, engineProvider); - const balanceBefore = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceBefore = await usdc.balanceOf(highestBidder.address); const expectedAmount = await engine .liveAuctionInfo(auctionId) .then((info) => info.amount); - const receipt = await engine - .executeSlowOrderAndRedeem(fastVaa, params) - .then((txReq) => engineWallet.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.settleOrder(highestBidder.address, fastVaa, orderResponse); + await signSendMineWait(txs, highestBidderSigner); - const balanceAfter = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceAfter = await usdc.balanceOf(highestBidder.address); expect((balanceAfter - balanceBefore).toString()).to.eql( expectedAmount.toString(), ); }); }); - describe(`Penalized Auction`, () => { before(`From Network -- Mint USDC`, async () => { if (fromEnv.chainId == MATCHING_ENGINE_CHAIN) { @@ -532,9 +474,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc } const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - await burnAllUsdc(usdc); - await mintNativeUsdc( IERC20__factory.connect(fromEnv.tokenAddress, fromProvider), fromWallet.address, @@ -549,50 +489,32 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc it(`From Network -- Place Fast Market Order`, async () => { const amountIn = await (async () => { - if (fromEnv.chainType == ChainType.Evm) { - const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - const amount = await usdc.balanceOf(fromWallet.address); - await usdc.approve - .populateTransaction(fromTokenRouter.address, amount) - .then(async (txReq) => { - txReq.nonce = await fromWallet.getNonce("pending"); - return fromWallet.sendTransaction(txReq); - }) - .then((tx) => mineWait(fromProvider, tx)); - - return BigInt(amount.toString()); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return await IERC20__factory.connect( + fromEnv.tokenAddress, + fromWallet, + ).balanceOf(fromWallet.address); })(); localVariables.set("amountIn", amountIn); - const targetChain = toChainId(toChainName); - const minAmountOut = BigInt(0); - const deadline = 0; - const receipt = await fromTokenRouter - .placeFastMarketOrderTx( - amountIn, - targetChain, - Buffer.from(tryNativeToUint8Array(toWallet.address, toChainName)), - Buffer.from("All your base are belong to us."), - FEE_AMOUNT, - deadline, - minAmountOut, - fromWallet.address, - ) - .then((txReq) => fromWallet.sendTransaction(txReq)) - .then((tx) => mineWait(fromProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const order: TokenRouter.OrderRequest = { + amountIn, + minAmountOut: BigInt(0), + deadline: 0, + maxFee: FEE_AMOUNT, + targetChain: toChainName, + redeemer: toUniversal(toChainName, toWallet.address), + redeemerMessage: encoding.bytes.encode("All your base are belong to us."), + refundAddress: toUniversal(fromChainName, fromWallet.address), + }; + + const txs = fromTokenRouter.placeFastMarketOrder(fromWallet.address, order); + const receipt = await signSendMineWait(txs, fromSigner); const transactionResult = await fromTokenRouter.getTransactionResults( receipt!.hash, ); expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(fromEnv.tokenRouterAddress, fromChainName), + asUniversalBytes(fromEnv.tokenRouterAddress), ); expect(transactionResult.wormhole.message.body).has.property( "slowOrderResponse", @@ -613,78 +535,61 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - const redeemParameters: OrderResponse = { - encodedWormholeMessage: slowOrderResponse, - circleBridgeMessage, - circleAttestation, - }; - localVariables.set("redeemParameters", redeemParameters); - localVariables.set("fastVaa", fastVaa); + localVariables.set( + "redeemParameters", + decodedOrderResponse({ + encodedWormholeMessage: slowOrderResponse, + circleBridgeMessage, + circleAttestation, + }), + ); + localVariables.set( + "fastVaa", + deserialize("FastTransfer:FastMarketOrder", fastVaa), + ); }); it(`Matching Engine -- Start Fast Order Auction`, async () => { - const fastVaa = localVariables.get("fastVaa") as Uint8Array; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; // Parse the vaa, we will need the hash for later. - const parsedFastVaa = deserialize("Uint8Array", fastVaa); - localVariables.set("auctionId", keccak256(parsedFastVaa.hash)); - const fastOrder = MessageDecoder.decode(parsedFastVaa.payload).body - .fastMarketOrder; - - if (fastOrder === undefined) { - throw new Error("Fast order undefined"); - } + const auctionId = FastTransfer.auctionId(fastVaa); // Security deposit amount of the initial bid. - const initialDeposit = fastOrder.amountIn + fastOrder.maxFee; + const { amountIn, maxFee } = fastVaa.payload; + const initialDeposit = amountIn + maxFee; - const initialBidderAddress = await initialBidder.getAddress(); // Prepare usdc for the auction. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, initialBidder); - await mintNativeUsdc(usdc, initialBidderAddress, initialDeposit); - await usdc.approve(engine.address, initialDeposit); + await mintNativeUsdc(usdc, initialBidder.address, initialDeposit); - const balanceBefore = await usdc.balanceOf(initialBidderAddress); + const balanceBefore = await usdc.balanceOf(initialBidder.address); - const receipt = await engine - .connect(initialBidder.provider!) - .placeInitialBid(fastVaa, fastOrder.maxFee) - .then(async (txReq) => { - txReq.nonce = await initialBidder.getNonce("pending"); - return initialBidder.sendTransaction(txReq); - }) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.placeInitialOffer(initialBidder.address, fastVaa, maxFee); + const receipt = await signSendMineWait(txs, initialBidderSigner); - const balanceAfter = await usdc.balanceOf(initialBidderAddress); + const balanceAfter = await usdc.balanceOf(initialBidder.address); expect((balanceBefore - balanceAfter).toString()).to.eql( initialDeposit.toString(), ); // Validate state changes. - const auctionData = await engine.liveAuctionInfo( - localVariables.get("auctionId"), - ); + const auctionData = await engine.liveAuctionInfo(auctionId); expect(auctionData.status).to.eql(1n); expect(auctionData.startBlock.toString()).to.eql( receipt!.blockNumber.toString(), ); - expect(auctionData.highestBidder).to.eql(initialBidderAddress); - expect(auctionData.initialBidder).to.eql(initialBidderAddress); - expect(auctionData.amount.toString()).to.eql(fastOrder.amountIn.toString()); - expect(auctionData.securityDeposit.toString()).to.eql( - fastOrder.maxFee.toString(), - ); - expect(auctionData.bidPrice.toString()).to.eql(fastOrder.maxFee.toString()); + expect(auctionData.highestBidder).to.eql(initialBidder.address); + expect(auctionData.initialBidder).to.eql(initialBidder.address); + expect(auctionData.amount.toString()).to.eql(amountIn.toString()); + expect(auctionData.securityDeposit.toString()).to.eql(maxFee.toString()); + expect(auctionData.bidPrice.toString()).to.eql(maxFee.toString()); }); it(`Matching Engine -- Fast Order Auction Period`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(fastVaa); const auctionInfoBefore = await engine.liveAuctionInfo(auctionId); const startingBid = auctionInfoBefore.bidPrice; @@ -715,24 +620,13 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc // Loop through and make multiple bids in the same block. for (let i = 0; i < bids.length; i++) { const player = bids[i].player; - const playerAddress = await player.getAddress(); const usdc = IERC20__factory.connect(engineEnv.tokenAddress, player); - await mintNativeUsdc(usdc, playerAddress, initialDeposit, false); - await usdc.approve(engine.address, initialDeposit); - - // give it time to hit the mempool - await sleep(1); - - bids[i].balance = await usdc.balanceOf(playerAddress); - - // Improve the bid. - await engine - .connect(player.provider!) - .improveBid(auctionId, bids[i].bid) - .then(async (txReq) => { - txReq.nonce = await player.getNonce("pending"); - return await player.sendTransaction(txReq); - }); + await mintNativeUsdc(usdc, player.address, initialDeposit, false); + + bids[i].balance = await usdc.balanceOf(player.address); + + const txs = engine.improveOffer(player.address, fastVaa, bids[i].bid); + await signSendOnly(txs, getSdkSigner(MATCHING_ENGINE_NAME, player)); } // Mine the block. await mine(engineProvider); @@ -742,7 +636,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc for (let i = 0; i < bids.length; i++) { const player = bids[i].player; const usdc = IERC20__factory.connect(engineEnv.tokenAddress, player); - const balanceAfter = await usdc.balanceOf(await player.getAddress()); + const balanceAfter = await usdc.balanceOf(player.address); if (i == 2) { expect((balanceAfter - bids[i].balance).toString()).to.eql("0"); @@ -760,7 +654,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc expect(auctionInfoAfter.startBlock.toString()).to.eql( auctionInfoBefore.startBlock.toString(), ); - expect(auctionInfoAfter.highestBidder).to.eql(await highestBidder.getAddress()); + expect(auctionInfoAfter.highestBidder).to.eql(highestBidder.address); expect(auctionInfoAfter.initialBidder).to.eql(auctionInfoBefore.initialBidder); expect(auctionInfoAfter.amount.toString()).to.eql( auctionInfoBefore.amount.toString(), @@ -772,7 +666,8 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }); it(`Matching Engine -- Execute Fast Order As Liquidator (After Grace Period Ends)`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(fastVaa); // Mine 50% of the way through the penalty period. await engine @@ -790,15 +685,13 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const auctionInfo = await engine.liveAuctionInfo(auctionId); const usdc = IERC20__factory.connect(engineEnv.tokenAddress, highestBidder); - const balanceBefore = await usdc.balanceOf(await highestBidder.getAddress()); - const balanceLiquidatorBefore = await usdc.balanceOf( - await liquidator.getAddress(), - ); + const balanceBefore = await usdc.balanceOf(highestBidder.address); + const balanceLiquidatorBefore = await usdc.balanceOf(liquidator.address); const initialBidderBefore = await usdc.balanceOf(auctionInfo.initialBidder); const receipt = await engine .connect(liquidator.provider!) - .executeFastOrder(localVariables.get("fastVaa")) + .executeFastOrderTx(serialize(fastVaa)) .then((txReq) => liquidator.sendTransaction(txReq)) .then((tx) => mineWait(engineProvider, tx)) .catch((err) => { @@ -811,16 +704,13 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const transactionResult = await engine.getTransactionResults(receipt!.hash); + expect(transactionResult.wormhole.emitterAddress).to.eql( + asUniversalBytes(engine.address), + ); if (toChainName == MATCHING_ENGINE_NAME) { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fastFill"); expect(transactionResult.circleMessage).is.undefined; } else { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fill"); expect(transactionResult.circleMessage).is.not.undefined; } @@ -828,11 +718,9 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc expect(transactionResult.fastMessage).is.undefined; // Validate state and balance changes. - const balanceAfter = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceAfter = await usdc.balanceOf(highestBidder.address); const initialBidderAfter = await usdc.balanceOf(auctionInfo.initialBidder); - const balanceLiquidatorAfter = await usdc.balanceOf( - await liquidator.getAddress(), - ); + const balanceLiquidatorAfter = await usdc.balanceOf(liquidator.address); const initAuctionFee = await fromTokenRouter.getInitialAuctionFee(); expect((balanceAfter - balanceBefore).toString()).to.eql( @@ -860,48 +748,41 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc receipt!, ); - let orderResponse: OrderResponse; - if (toChainName == MATCHING_ENGINE_NAME) { - orderResponse = { - encodedWormholeMessage: signedVaa, - circleBridgeMessage: Buffer.from(""), - circleAttestation: Buffer.from(""), - }; - } else { - const circleBridgeMessage = transactionResult.circleMessage!; - const circleAttestation = - circleAttester.createAttestation(circleBridgeMessage); - - orderResponse = { - encodedWormholeMessage: signedVaa, - circleBridgeMessage, - circleAttestation, - }; - } - - localVariables.set("fastOrderResponse", orderResponse); + const orderResponse = + toChainName === MATCHING_ENGINE_NAME + ? { + encodedWormholeMessage: signedVaa, + circleBridgeMessage: Buffer.from(""), + circleAttestation: Buffer.from(""), + } + : { + encodedWormholeMessage: signedVaa, + circleBridgeMessage: transactionResult.circleMessage!, + circleAttestation: circleAttester.createAttestation( + transactionResult.circleMessage!, + ), + }; + + localVariables.set("fastOrderResponse", decodedOrderResponse(orderResponse)); localVariables.set("reward", reward); }); it(`To Network -- Redeem Fill`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; - const orderResponse = localVariables.get("fastOrderResponse") as OrderResponse; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; + const auctionId = FastTransfer.auctionId(fastVaa); + const orderResponse = localVariables.get( + "fastOrderResponse", + ) as FastTransfer.OrderResponse; + expect(localVariables.delete("fastOrderResponse")).is.true; + const reward = localVariables.get("reward") as string; expect(localVariables.delete("reward")).is.true; - expect(localVariables.delete("fastOrderResponse")).is.true; const usdc = IERC20__factory.connect(toEnv.tokenAddress, toProvider); const balanceBefore = await usdc.balanceOf(toWallet.address); - const receipt = await toTokenRouter - .redeemFillTx(orderResponse) - .then((txReq) => toWallet.sendTransaction(txReq)) - .then((tx) => mineWait(toProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = toTokenRouter.redeemFill(toWallet.address, orderResponse); + await signSendMineWait(txs, toSigner); // Validate balance changes. const [bidPrice, amount] = await engine @@ -918,37 +799,30 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc }); it(`Matching Engine -- Execute Slow Vaa And Redeem`, async () => { - const auctionId = localVariables.get("auctionId") as Uint8Array; - const fastVaa = localVariables.get("fastVaa") as Uint8Array; - const params = localVariables.get("redeemParameters") as OrderResponse; - expect(localVariables.delete("redeemParameters")).is.true; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; expect(localVariables.delete("fastVaa")).is.true; - expect(localVariables.delete("auctionId")).is.true; + + const orderResponse = localVariables.get( + "redeemParameters", + ) as FastTransfer.Fill; + expect(localVariables.delete("redeemParameters")).is.true; // Fetch balance of player four since they were the highest bidder. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, engineProvider); - const balanceBefore = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceBefore = await usdc.balanceOf(highestBidder.address); const expectedAmount = await engine - .liveAuctionInfo(auctionId) + .liveAuctionInfo(FastTransfer.auctionId(fastVaa)) .then((info) => info.amount); - const receipt = await engine - .executeSlowOrderAndRedeem(fastVaa, params) - .then((txReq) => engineWallet.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.settleOrder(highestBidder.address, fastVaa, orderResponse); + await signSendMineWait(txs, highestBidderSigner); - const balanceAfter = await usdc.balanceOf(await highestBidder.getAddress()); + const balanceAfter = await usdc.balanceOf(highestBidder.address); expect((balanceAfter - balanceBefore).toString()).to.eql( expectedAmount.toString(), ); }); }); - describe(`No Auction`, () => { before(`From Network -- Mint USDC`, async () => { if (fromEnv.chainId == MATCHING_ENGINE_CHAIN) { @@ -974,46 +848,31 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc it(`From Network -- Place Fast Market Order`, async () => { const amountIn = await (async () => { - if (fromEnv.chainType == ChainType.Evm) { - const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - const amount = await usdc.balanceOf(fromWallet.address); - await usdc - .approve(fromTokenRouter.address, amount) - .then((tx) => mineWait(fromProvider, tx)); - - return BigInt(amount.toString()); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return await IERC20__factory.connect( + fromEnv.tokenAddress, + fromWallet, + ).balanceOf(fromWallet.address); })(); localVariables.set("amountIn", amountIn); - const targetChain = toChainId(toChainName); - const minAmountOut = BigInt(0); - const deadline = 0; - const receipt = await fromTokenRouter - .placeFastMarketOrderTx( - amountIn, - targetChain, - Buffer.from(tryNativeToUint8Array(toWallet.address, toChainName)), - Buffer.from("All your base are belong to us."), - FEE_AMOUNT, - deadline, - minAmountOut, - fromWallet.address, - ) - .then((txReq) => fromWallet.sendTransaction(txReq)) - .then((tx) => mineWait(fromProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const order: TokenRouter.OrderRequest = { + amountIn, + minAmountOut: BigInt(0), + deadline: 0, + maxFee: FEE_AMOUNT, + targetChain: toChainName, + redeemer: toUniversal(toChainName, toWallet.address), + redeemerMessage: encoding.bytes.encode("All your base are belong to us."), + refundAddress: toUniversal(fromChainName, fromWallet.address), + }; + const txs = fromTokenRouter.placeFastMarketOrder(fromWallet.address, order); + const receipt = await signSendMineWait(txs, fromSigner); const transactionResult = await fromTokenRouter.getTransactionResults( receipt!.hash, ); expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(fromEnv.tokenRouterAddress, fromChainName), + asUniversalBytes(fromEnv.tokenRouterAddress), ); expect(transactionResult.wormhole.message.body).has.property( "slowOrderResponse", @@ -1034,42 +893,42 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - const redeemParameters: OrderResponse = { - encodedWormholeMessage: slowOrderResponse, - circleBridgeMessage, - circleAttestation, - }; - localVariables.set("redeemParameters", redeemParameters); - localVariables.set("fastVaa", fastVaa); + localVariables.set( + "redeemParameters", + decodedOrderResponse({ + encodedWormholeMessage: slowOrderResponse, + circleBridgeMessage, + circleAttestation, + }), + ); + localVariables.set( + "fastVaa", + deserialize("FastTransfer:FastMarketOrder", fastVaa), + ); }); it(`Matching Engine -- Execute Slow Vaa And Redeem`, async () => { - const fastVaa = localVariables.get("fastVaa") as Uint8Array; - const params = localVariables.get("redeemParameters") as OrderResponse; - expect(localVariables.delete("redeemParameters")).is.true; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; expect(localVariables.delete("fastVaa")).is.true; // NOTE: Imagine that several minutes have passed, and no auction has been started :). + const orderResponse = localVariables.get( + "redeemParameters", + ) as FastTransfer.Fill; + expect(localVariables.delete("redeemParameters")).is.true; - // Parse the slow VAA for the baseFee and amount - const baseFee = MessageDecoder.decode( - deserialize("Uint8Array", params.encodedWormholeMessage).payload, - ).body.slowOrderResponse!.baseFee; + const deposit = orderResponse.vaa.payload; + if (deposit.payload.id !== payloadIds("SlowOrderResponse")) + throw new Error("Invalid message type"); + + const { baseFee } = deposit.payload; // Use player one as the relayer. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, engineProvider); const feeRecipientBefore = await usdc.balanceOf(engineEnv.feeRecipient!); - const receipt = await engine - .connect(initialBidder.provider!) - .executeSlowOrderAndRedeem(fastVaa, params) - .then((txReq) => initialBidder.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.settleOrder(initialBidder.address, fastVaa, orderResponse); + const receipt = await signSendMineWait(txs, initialBidderSigner); // Balance check. const feeRecipientAfter = await usdc.balanceOf(engineEnv.feeRecipient!); @@ -1079,16 +938,13 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const transactionResult = await engine.getTransactionResults(receipt!.hash); + expect(transactionResult.wormhole.emitterAddress).to.eql( + asUniversalBytes(engine.address), + ); if (toChainName == MATCHING_ENGINE_NAME) { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fastFill"); expect(transactionResult.circleMessage).is.undefined; } else { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fill"); expect(transactionResult.circleMessage).is.not.undefined; } @@ -1102,9 +958,9 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc receipt!, ); - let orderResponse: OrderResponse; + let fastOrderResponse: OrderResponse; if (toChainName == MATCHING_ENGINE_NAME) { - orderResponse = { + fastOrderResponse = { encodedWormholeMessage: signedVaa, circleBridgeMessage: Buffer.from(""), circleAttestation: Buffer.from(""), @@ -1113,8 +969,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - - orderResponse = { + fastOrderResponse = { encodedWormholeMessage: signedVaa, circleBridgeMessage, circleAttestation, @@ -1122,34 +977,32 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc } // Confirm that the auction was market as complete. - const auctionId = keccak256(deserialize("Uint8Array", fastVaa).hash); const auctionStatus = await engine - .liveAuctionInfo(auctionId) + .liveAuctionInfo(FastTransfer.auctionId(fastVaa)) .then((info) => info.status); expect(auctionStatus).to.eql(2n); - localVariables.set("fastOrderResponse", orderResponse); + localVariables.set( + "fastOrderResponse", + decodedOrderResponse(fastOrderResponse), + ); localVariables.set("baseFee", baseFee); }); it(`To Network -- Redeem Fill`, async () => { - const orderResponse = localVariables.get("fastOrderResponse") as OrderResponse; - const baseFee = localVariables.get("baseFee") as bigint; + const orderResponse = localVariables.get( + "fastOrderResponse", + ) as FastTransfer.OrderResponse; expect(localVariables.delete("fastOrderResponse")).is.true; + + const baseFee = localVariables.get("baseFee") as bigint; expect(localVariables.delete("baseFee")).is.true; const usdc = IERC20__factory.connect(toEnv.tokenAddress, toProvider); const balanceBefore = await usdc.balanceOf(toWallet.address); - const receipt = await toTokenRouter - .redeemFillTx(orderResponse) - .then((txReq) => toWallet.sendTransaction(txReq)) - .then((tx) => mineWait(toProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = toTokenRouter.redeemFill(toWallet.address, orderResponse); + await signSendMineWait(txs, toSigner); // Validate balance changes. const balanceAfter = await usdc.balanceOf(toWallet.address); @@ -1184,50 +1037,38 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc it(`From Network -- Place Fast Market Order`, async () => { const amountIn = await (async () => { - if (fromEnv.chainType == ChainType.Evm) { - const usdc = IERC20__factory.connect(fromEnv.tokenAddress, fromWallet); - const amount = await usdc.balanceOf(fromWallet.address); - await usdc - .approve(fromTokenRouter.address, amount) - .then((tx) => mineWait(fromProvider, tx)); - - return BigInt(amount.toString()); - } else { - throw new Error("Unsupported chain"); - } + if (fromEnv.chainType !== "Evm") throw new Error("Unsupported chain"); + return await IERC20__factory.connect( + fromEnv.tokenAddress, + fromWallet, + ).balanceOf(fromWallet.address); })(); - localVariables.set("amountIn", amountIn); - const targetChain = toChainId(toChainName); - const minAmountOut = BigInt(0); + localVariables.set("amountIn", amountIn); // Set the deadline to the current block timestamp. const currentBlock = await engineProvider.getBlockNumber(); const deadline = (await engineProvider.getBlock(currentBlock))!.timestamp; - const receipt = await fromTokenRouter - .placeFastMarketOrderTx( - amountIn, - targetChain, - Buffer.from(tryNativeToUint8Array(toWallet.address, toChainName)), - Buffer.from("All your base are belong to us."), - FEE_AMOUNT, - deadline!, - minAmountOut, - fromWallet.address, - ) - .then((txReq) => fromWallet.sendTransaction(txReq)) - .then((tx) => mineWait(fromProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const order: TokenRouter.OrderRequest = { + amountIn, + deadline, + minAmountOut: 0n, + redeemer: toUniversal(toChainName, toWallet.address), + maxFee: FEE_AMOUNT, + targetChain: toChainName, + redeemerMessage: encoding.bytes.encode("All your base are belong to us."), + refundAddress: toUniversal(fromChainName, fromWallet.address), + }; + + const txs = fromTokenRouter.placeFastMarketOrder(fromWallet.address, order); + const receipt = await signSendMineWait(txs, fromSigner); + const transactionResult = await fromTokenRouter.getTransactionResults( receipt!.hash, ); expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(fromEnv.tokenRouterAddress, fromChainName), + asUniversalBytes(fromEnv.tokenRouterAddress), ); expect(transactionResult.wormhole.message.body).has.property( "slowOrderResponse", @@ -1243,89 +1084,76 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc expect(signedVaas.length).to.eql(2); // The first message is the slow CCTP transfer. - const [slowOrderResponse, fastVaa] = signedVaas; - + const [encodedWormholeMessage, fastVaa] = signedVaas; const circleBridgeMessage = transactionResult.circleMessage!; const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - const redeemParameters: OrderResponse = { - encodedWormholeMessage: slowOrderResponse, - circleBridgeMessage, - circleAttestation, - }; - localVariables.set("redeemParameters", redeemParameters); - localVariables.set("fastVaa", fastVaa); + localVariables.set( + "redeemParameters", + decodedOrderResponse({ + encodedWormholeMessage, + circleBridgeMessage, + circleAttestation, + }), + ); + localVariables.set( + "fastVaa", + deserialize("FastTransfer:FastMarketOrder", fastVaa), + ); }); it(`Matching Engine -- Attempt to Start Auction After Deadline`, async () => { - const fastVaa = localVariables.get("fastVaa") as Uint8Array; - - // Parse the vaa, we will need the hash for later. - const parsedFastVaa = deserialize("Uint8Array", fastVaa); - localVariables.set("auctionId", keccak256(parsedFastVaa.hash)); - const fastOrder = MessageDecoder.decode(parsedFastVaa.payload).body - .fastMarketOrder; - - if (fastOrder === undefined) { - throw new Error("Fast order undefined"); - } + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; // Security deposit amount of the initial bid. - const initialDeposit = fastOrder.amountIn + fastOrder.maxFee; + const { amountIn, maxFee } = fastVaa.payload; + const initialDeposit = amountIn + maxFee; // Prepare usdc for the auction. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, initialBidder); - await mintNativeUsdc(usdc, await initialBidder.getAddress(), initialDeposit); + await mintNativeUsdc(usdc, initialBidder.address, initialDeposit); await usdc .approve(engine.address, initialDeposit) .then((tx) => mineWait(engineProvider, tx)); let failedGracefully = false; - const receipt = await engine + await engine .connect(initialBidder.provider!) - .placeInitialBid(fastVaa, fastOrder.maxFee) + .placeInitialBidTx(serialize(fastVaa), maxFee) .then(async (txReq) => await initialBidder.sendTransaction(txReq)) .catch((err) => { const error = errorDecoder(err); if (error.selector == "ErrDeadlineExceeded") { failedGracefully = true; } - - // We got a failed transaction so we need to - // reset the NonceManagers local tracker - initialBidder.reset(); }); expect(failedGracefully).is.true; }); it(`Matching Engine -- Execute Slow Vaa And Redeem`, async () => { - const fastVaa = localVariables.get("fastVaa") as Uint8Array; - const params = localVariables.get("redeemParameters") as OrderResponse; - expect(localVariables.delete("redeemParameters")).is.true; + const fastVaa = localVariables.get("fastVaa") as FastTransfer.Order; expect(localVariables.delete("fastVaa")).is.true; // NOTE: Imagine that several minutes have passed, and no auction has been started :). + const orderResponse = localVariables.get( + "redeemParameters", + ) as FastTransfer.Fill; + expect(localVariables.delete("redeemParameters")).is.true; + + if (orderResponse.vaa.payload.payload.id !== payloadIds("SlowOrderResponse")) + throw new Error("Invalid message type"); + // Parse the slow VAA for the baseFee and amount - const baseFee = MessageDecoder.decode( - deserialize("Uint8Array", params.encodedWormholeMessage).payload, - ).body.slowOrderResponse!.baseFee; + const { baseFee } = orderResponse.vaa.payload.payload; // Use player one as the relayer. const usdc = IERC20__factory.connect(engineEnv.tokenAddress, engineProvider); const feeRecipientBefore = await usdc.balanceOf(engineEnv.feeRecipient!); - const receipt = await engine - .connect(initialBidder.provider!) - .executeSlowOrderAndRedeem(fastVaa, params) - .then((txReq) => initialBidder.sendTransaction(txReq)) - .then((tx) => mineWait(engineProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = engine.settleOrder(initialBidder.address, fastVaa, orderResponse); + const receipt = await signSendMineWait(txs, initialBidderSigner); // Balance check. const feeRecipientAfter = await usdc.balanceOf(engineEnv.feeRecipient!); @@ -1335,16 +1163,13 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const transactionResult = await engine.getTransactionResults(receipt!.hash); + expect(transactionResult.wormhole.emitterAddress).to.eql( + asUniversalBytes(engine.address), + ); if (toChainName == MATCHING_ENGINE_NAME) { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fastFill"); expect(transactionResult.circleMessage).is.undefined; } else { - expect(transactionResult.wormhole.emitterAddress).to.eql( - tryNativeToUint8Array(engine.address, MATCHING_ENGINE_NAME), - ); expect(transactionResult.wormhole.message.body).has.property("fill"); expect(transactionResult.circleMessage).is.not.undefined; } @@ -1358,9 +1183,9 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc receipt!, ); - let orderResponse: OrderResponse; + let fastOrderResponse: OrderResponse; if (toChainName == MATCHING_ENGINE_NAME) { - orderResponse = { + fastOrderResponse = { encodedWormholeMessage: signedVaa, circleBridgeMessage: Buffer.from(""), circleAttestation: Buffer.from(""), @@ -1370,7 +1195,7 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc const circleAttestation = circleAttester.createAttestation(circleBridgeMessage); - orderResponse = { + fastOrderResponse = { encodedWormholeMessage: signedVaa, circleBridgeMessage, circleAttestation, @@ -1378,34 +1203,33 @@ describe("Fast Market Order Business Logic -- CCTP to CCTP", function (this: Moc } // Confirm that the auction was market as complete. - const auctionId = keccak256(deserialize("Uint8Array", fastVaa).hash); + const auctionId = keccak256(fastVaa.hash); const auctionStatus = await engine .liveAuctionInfo(auctionId) .then((info) => info.status); expect(auctionStatus).to.eql(2n); - localVariables.set("fastOrderResponse", orderResponse); + localVariables.set( + "fastOrderResponse", + decodedOrderResponse(fastOrderResponse), + ); localVariables.set("baseFee", baseFee); }); it(`To Network -- Redeem Fill`, async () => { - const orderResponse = localVariables.get("fastOrderResponse") as OrderResponse; - const baseFee = localVariables.get("baseFee") as bigint; + const orderResponse = localVariables.get( + "fastOrderResponse", + ) as FastTransfer.OrderResponse; expect(localVariables.delete("fastOrderResponse")).is.true; + + const baseFee = localVariables.get("baseFee") as bigint; expect(localVariables.delete("baseFee")).is.true; const usdc = IERC20__factory.connect(toEnv.tokenAddress, toProvider); const balanceBefore = await usdc.balanceOf(toWallet.address); - const receipt = await toTokenRouter - .redeemFillTx(orderResponse) - .then((txReq) => toWallet.sendTransaction(txReq)) - .then((tx) => mineWait(toProvider, tx)) - .catch((err) => { - console.log(err); - console.log(errorDecoder(err)); - throw err; - }); + const txs = toTokenRouter.redeemFill(toWallet.address, orderResponse); + await signSendMineWait(txs, toSigner); // Validate balance changes. const balanceAfter = await usdc.balanceOf(toWallet.address); diff --git a/package-lock.json b/package-lock.json index dca55b43..8b7386c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,9 @@ "license": "Apache-2.0", "dependencies": { "@wormhole-foundation/example-liquidity-layer-definitions": "0.0.1", - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-evm": "^0.7.0-beta.6", + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-evm": "^0.7.1-beta.2", "ethers": "^6.5.1" }, "devDependencies": { @@ -691,20 +691,20 @@ "link": true }, "node_modules/@wormhole-foundation/sdk-base": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-base/-/sdk-base-0.7.0-beta.6.tgz", - "integrity": "sha512-4kaGW9URQspd6uhMOYo18p8pmUD7cQglXcEFq9lhNlurRr/UV09SFVcH12+HXONRZFGxIt3TH73iEPnwKP0eBQ==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-base/-/sdk-base-0.7.1-beta.2.tgz", + "integrity": "sha512-dl2AV+u3eXVYgcSMsambjB944+NN4iMIIPIpu2/y8tjpeV837k4Ylzhmbu4O+lqnOXY+qUoGRF0Jr2WxVi6C+w==", "dependencies": { "@scure/base": "^1.1.3" } }, "node_modules/@wormhole-foundation/sdk-connect": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-connect/-/sdk-connect-0.7.0-beta.6.tgz", - "integrity": "sha512-ydoUboOJn8F+PhCCC8ZPzlMolBthjwG6orsyvGzkOj+c4gxCXgh/7DghognUkMTjTIUBwkZLURibbgaCvYwFBg==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-connect/-/sdk-connect-0.7.1-beta.2.tgz", + "integrity": "sha512-sgp3z6Y0H52ZrRCGzbZ8nKhR1Vgo0LR/RMJQrd7aGRhNh7zYZUgzi9NeAwmAXcr5NKlfPfoSlhZjgFssSWi1XA==", "dependencies": { - "@wormhole-foundation/sdk-base": "0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "0.7.0-beta.6", + "@wormhole-foundation/sdk-base": "0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "0.7.1-beta.2", "axios": "^1.4.0" }, "engines": { @@ -712,21 +712,21 @@ } }, "node_modules/@wormhole-foundation/sdk-definitions": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-definitions/-/sdk-definitions-0.7.0-beta.6.tgz", - "integrity": "sha512-Wpy3OHvuq5QMxP+vfcdWvIcVc7iXTnb1MS/RroCdYZ2XdikBp4Qv+iFk8AwO7wKdJiXeTWtW2etS9vcvaz9g6g==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-definitions/-/sdk-definitions-0.7.1-beta.2.tgz", + "integrity": "sha512-tn5sOWjswt180wjWKYCPXeeJhRIE5vv0GgFD6xLXPNchuuhB/XPbl8WG0yy8Rhe4Tj9tXioSFCwYA3onV2DWwA==", "dependencies": { "@noble/curves": "^1.4.0", "@noble/hashes": "^1.3.1", - "@wormhole-foundation/sdk-base": "0.7.0-beta.6" + "@wormhole-foundation/sdk-base": "0.7.1-beta.2" } }, "node_modules/@wormhole-foundation/sdk-evm": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-evm/-/sdk-evm-0.7.0-beta.6.tgz", - "integrity": "sha512-JXUvnWjngVtBCwFC41fTo4xO7vHb39KSsIUgE/UAcPFscoVv9DmktGR8jDm0jLXri4pUqaXLFEDO6K7AABjB6A==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-evm/-/sdk-evm-0.7.1-beta.2.tgz", + "integrity": "sha512-LTWrnMxSR1v4TiQKBUbQCVc72UrAFM0v0l78W81iptfksjzF3RhNC5ExBgGHgm0SsW276QpwlzBqd7agP41HIg==", "dependencies": { - "@wormhole-foundation/sdk-connect": "0.7.0-beta.6", + "@wormhole-foundation/sdk-connect": "0.7.1-beta.2", "ethers": "^6.5.1" }, "engines": { @@ -818,30 +818,30 @@ } }, "node_modules/@wormhole-foundation/sdk-solana": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana/-/sdk-solana-0.7.0-beta.6.tgz", - "integrity": "sha512-4+jpskWL6NLQ2d/qwD6k5BFi2dDrLM4V+GJD5/ZYUL4OZDubxS3jImIjfXK/Y/26OquytPpl/cfpahxNdo8pOg==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana/-/sdk-solana-0.7.1-beta.2.tgz", + "integrity": "sha512-4M/d7HEEnOL4j/c2uOH6N+QesnrVhduRqiq5dnUYYRp+lSA57xw8CmqX7lcVHMPro2KvZiBzrbE+UeLahBhSjA==", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@coral-xyz/borsh": "0.29.0", "@solana/spl-token": "0.3.9", "@solana/web3.js": "1.91.7", - "@wormhole-foundation/sdk-connect": "0.7.0-beta.6" + "@wormhole-foundation/sdk-connect": "0.7.1-beta.2" }, "engines": { "node": ">=16" } }, "node_modules/@wormhole-foundation/sdk-solana-core": { - "version": "0.7.0-beta.6", - "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana-core/-/sdk-solana-core-0.7.0-beta.6.tgz", - "integrity": "sha512-XtMWuuZXMDZdBKar352bB7C6j6Ua6Ltr5TDEUDElHlK37D+oD4Ss8Z2GXvVOLg4BbC9NWlYWCKrVQVbeQ3SeJw==", + "version": "0.7.1-beta.2", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana-core/-/sdk-solana-core-0.7.1-beta.2.tgz", + "integrity": "sha512-qmKQxKsrYYSfSg2UAqt/gvk2Ihx7Ybf/97Hp2ypN8sCqt96e/5Rp7Ou9mj5viopFQipZ75XYr3zOVe/LuJxa0A==", "dependencies": { "@coral-xyz/anchor": "0.29.0", "@coral-xyz/borsh": "0.29.0", "@solana/web3.js": "1.91.7", - "@wormhole-foundation/sdk-connect": "0.7.0-beta.6", - "@wormhole-foundation/sdk-solana": "0.7.0-beta.6" + "@wormhole-foundation/sdk-connect": "0.7.1-beta.2", + "@wormhole-foundation/sdk-solana": "0.7.1-beta.2" }, "engines": { "node": ">=16" @@ -3378,10 +3378,10 @@ "@solana/web3.js": "^1.91.7", "@types/node-fetch": "^2.6.11", "@wormhole-foundation/example-liquidity-layer-definitions": "0.0.1", - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana-core": "^0.7.0-beta.6", + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana-core": "^0.7.1-beta.2", "anchor-0.29.0": "npm:@coral-xyz/anchor@^0.29.0", "bn.js": "^5.2.1", "dotenv": "^16.4.1", @@ -3553,9 +3553,9 @@ "@solana/spl-token": "^0.4.6", "@wormhole-foundation/example-liquidity-layer-evm": "0.0.1", "@wormhole-foundation/example-liquidity-layer-solana": "0.0.1", - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana": "^0.7.0-beta.6" + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana": "^0.7.1-beta.2" } }, "solver/node_modules/@solana/spl-token": { @@ -3581,8 +3581,8 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6" + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2" }, "devDependencies": { "@types/chai": "^4.3.4", diff --git a/solana/package.json b/solana/package.json index f1559b91..b7e12d24 100644 --- a/solana/package.json +++ b/solana/package.json @@ -31,10 +31,10 @@ "dependencies": { "@certusone/wormhole-spydk": "^0.0.1", "@wormhole-foundation/example-liquidity-layer-definitions": "0.0.1", - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana-core": "^0.7.0-beta.6", + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana-core": "^0.7.1-beta.2", "@coral-xyz/anchor": "^0.30.0", "@solana/spl-token": "^0.4.6", "@solana/spl-token-group": "^0.0.4", diff --git a/solana/ts/src/protocol/matchingEngine.ts b/solana/ts/src/protocol/matchingEngine.ts index 908c45fc..b31d4d31 100644 --- a/solana/ts/src/protocol/matchingEngine.ts +++ b/solana/ts/src/protocol/matchingEngine.ts @@ -15,10 +15,8 @@ import { Chain, Network, Platform, toChainId } from "@wormhole-foundation/sdk-ba import { AccountAddress, ChainsConfig, - CircleAttestation, CircleBridge, Contracts, - VAA, } from "@wormhole-foundation/sdk-definitions"; import { AnySolanaAddress, @@ -28,9 +26,9 @@ import { SolanaTransaction, SolanaUnsignedTransaction, } from "@wormhole-foundation/sdk-solana"; +import { SolanaWormholeCore } from "@wormhole-foundation/sdk-solana-core"; import { vaaHash } from "../common"; import { AuctionParameters, MatchingEngineProgram } from "../matchingEngine"; -import { SolanaWormholeCore } from "@wormhole-foundation/sdk-solana-core"; export class SolanaMatchingEngine extends MatchingEngineProgram @@ -46,9 +44,7 @@ export class SolanaMatchingEngine ) { super(_connection, _contracts); - this.coreBridge = new SolanaWormholeCore(_network, _chain, _connection, { - ...this._contracts, - }); + this.coreBridge = new SolanaWormholeCore(_network, _chain, _connection, this._contracts); } static async fromRpc( @@ -161,30 +157,25 @@ export class SolanaMatchingEngine async *placeInitialOffer( sender: AnySolanaAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, + order: FastTransfer.Order, offerPrice: bigint, - totalDeposit?: bigint, ) { // If the VAA has not yet been posted, do so now - yield* this.postVaa(sender, vaa); + yield* this.postVaa(sender, order); const payer = new SolanaAddress(sender).unwrap(); - const vaaAddress = this.pdas.postedVaa(vaa); + const vaaAddress = this.pdas.postedVaa(order); const ixs = await this.placeInitialOfferCctpIx( { payer, fastVaa: vaaAddress }, - { offerPrice, totalDeposit }, + { offerPrice }, ); const transaction = this.createTx(payer, ixs); yield this.createUnsignedTx({ transaction }, "MatchingEngine.placeInitialOffer"); } - async *improveOffer( - sender: AnySolanaAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, - offer: bigint, - ) { + async *improveOffer(sender: AnySolanaAddress, vaa: FastTransfer.Order, offer: bigint) { const participant = new SolanaAddress(sender).unwrap(); const digest = vaaHash(vaa); @@ -202,7 +193,7 @@ export class SolanaMatchingEngine async *executeFastOrder( sender: AnySolanaAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, + vaa: FastTransfer.Order, participant?: AnySolanaAddress, ) { const payer = new SolanaAddress(sender).unwrap(); @@ -251,19 +242,15 @@ export class SolanaMatchingEngine private async _prepareOrderResponseIx( sender: AnySolanaAddress, - fast: VAA<"FastTransfer:FastMarketOrder">, - finalized: VAA<"FastTransfer:CctpDeposit">, - cctp: { - message: CircleBridge.Message; - attestation: CircleAttestation; - }, + order: FastTransfer.Order, + response: FastTransfer.Fill, ) { const payer = new SolanaAddress(sender).unwrap(); - const fastVaa = this.pdas.postedVaa(fast); - const finalizedVaa = this.pdas.postedVaa(finalized); + const fastVaa = this.pdas.postedVaa(order); + const finalizedVaa = this.pdas.postedVaa(response.vaa); - const digest = vaaHash(fast); + const digest = FastTransfer.auctionId(order); const preparedAddress = this.pdas.preparedOrderResponse(digest); try { @@ -275,8 +262,8 @@ export class SolanaMatchingEngine const ix = await this.prepareOrderResponseCctpIx( { payer, fastVaa, finalizedVaa }, { - encodedCctpMessage: Buffer.from(CircleBridge.serialize(cctp.message)), - cctpAttestation: Buffer.from(cctp.attestation, "hex"), + encodedCctpMessage: Buffer.from(CircleBridge.serialize(response.cctp!.message)), + cctpAttestation: Buffer.from(response.cctp!.attestation!, "hex"), }, ); @@ -285,16 +272,15 @@ export class SolanaMatchingEngine async *prepareOrderResponse( sender: AnySolanaAddress, - fast: VAA<"FastTransfer:FastMarketOrder">, - finalized: VAA<"FastTransfer:CctpDeposit">, - cctp: { - message: CircleBridge.Message; - attestation: CircleAttestation; - }, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, lookupTables?: AddressLookupTableAccount[], ) { + if (FastTransfer.isFastFill(response)) + throw "Invalid response type in prepareOrderResponse"; + const payer = new SolanaAddress(sender).unwrap(); - const ix = await this._prepareOrderResponseIx(sender, fast, finalized, cctp); + const ix = await this._prepareOrderResponseIx(sender, order, response); if (ix === undefined) return; const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }); @@ -305,63 +291,65 @@ export class SolanaMatchingEngine async *settleOrder( sender: AnySolanaAddress, - fast: VAA<"FastTransfer:FastMarketOrder">, - finalized?: VAA<"FastTransfer:CctpDeposit">, - cctp?: { - message: CircleBridge.Message; - attestation: CircleAttestation; - }, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, lookupTables?: AddressLookupTableAccount[], ) { + if (FastTransfer.isFastFill(response)) throw "Invalid response type in settleOrder"; + const payer = new SolanaAddress(sender).unwrap(); - // If the finalized VAA and CCTP message/attestation are passed - // we may try to prepare the order response - // this yields its own transaction const ixs = []; - if (finalized && cctp) { - // TODO: how do we decide? - const combine = true; - // try to include the prepare order instruction in the same transaction - if (combine) { - const ix = await this._prepareOrderResponseIx(sender, fast, finalized, cctp); - if (ix !== undefined) { - ixs.push(ix, ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 })); - } - } else { - yield* this.prepareOrderResponse(sender, fast, finalized, cctp, lookupTables); + if (response.cctp) { + const ix = await this._prepareOrderResponseIx(sender, order, response); + if (ix !== undefined) { + ixs.push(ix, ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 })); } } - const fastVaa = this.pdas.postedVaa(fast); + const fastVaa = this.pdas.postedVaa(order); - const digest = vaaHash(fast); + const digest = FastTransfer.auctionId(order); const preparedOrderResponse = this.pdas.preparedOrderResponse(digest); const auction = this.pdas.auction(digest); + let bestOfferToken; + let activeAuction = false; + try { + const { info } = await this.fetchAuction({ address: auction }); + if (!info) throw "No auction"; + activeAuction = true; + bestOfferToken = info.bestOfferToken; + } catch {} + const settleIx = await (async () => { - if (finalized && !cctp) { - if (fast.payload.targetChain === "Solana") { - const reservedSequence = this.pdas.reservedFastFillSequence(digest); - return await this.settleAuctionNoneLocalIx({ - payer, - reservedSequence, - preparedOrderResponse, - auction, - }); - } else { - return await this.settleAuctionNoneCctpIx( - { payer, fastVaa, preparedOrderResponse }, - { targetChain: toChainId(fast.payload.targetChain) }, - ); - } - } else { + if (activeAuction) { return await this.settleAuctionCompleteIx({ executor: payer, preparedOrderResponse, auction, + bestOfferToken, }); } + + // no auction, settle none + + const { targetChain } = order.payload; + + if (targetChain === "Solana") { + const reservedSequence = this.pdas.reservedFastFillSequence(digest); + return await this.settleAuctionNoneLocalIx({ + payer, + reservedSequence, + preparedOrderResponse, + auction, + }); + } + + return await this.settleAuctionNoneCctpIx( + { payer, fastVaa, preparedOrderResponse, auction }, + { targetChain: toChainId(targetChain) }, + ); })(); ixs.push(settleIx); diff --git a/solana/ts/src/protocol/tokenRouter.ts b/solana/ts/src/protocol/tokenRouter.ts index 6acdcff2..ed100741 100644 --- a/solana/ts/src/protocol/tokenRouter.ts +++ b/solana/ts/src/protocol/tokenRouter.ts @@ -9,7 +9,11 @@ import { TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; -import { Payload, TokenRouter } from "@wormhole-foundation/example-liquidity-layer-definitions"; +import { + FastTransfer, + Payload, + TokenRouter, +} from "@wormhole-foundation/example-liquidity-layer-definitions"; import { ChainId, Network, Platform, toChainId } from "@wormhole-foundation/sdk-base"; import { ChainsConfig, @@ -106,12 +110,10 @@ export class SolanaTokenRouter }, { amountIn: order.amountIn, - minAmountOut: order.minAmountOut !== undefined ? order.minAmountOut : null, + minAmountOut: order.minAmountOut ?? null, targetChain: toChainId(order.targetChain), redeemer: Array.from(order.redeemer.toUint8Array()), - redeemerMessage: order.redeemerMessage - ? Buffer.from(order.redeemerMessage) - : Buffer.from(""), + redeemerMessage: Buffer.from(order.redeemerMessage ?? ""), }, ); @@ -202,19 +204,19 @@ export class SolanaTokenRouter async *redeemFill( sender: AnySolanaAddress, - vaa: VAA<"FastTransfer:CctpDeposit">, - cctp: CircleBridge.Attestation, + orderResponse: FastTransfer.OrderResponse, lookupTables?: AddressLookupTableAccount[], ): AsyncGenerator, any, unknown> { - const payer = new SolanaAddress(sender).unwrap(); - - const postedVaaAddress = this.matchingEngine.pdas.postedVaa(vaa); + if (FastTransfer.isFastFill(orderResponse)) throw "Invalid order response"; - const fill = vaa.payload.payload; + const payer = new SolanaAddress(sender).unwrap(); + const { vaa, cctp } = orderResponse; // Must be a fill payload + const fill = vaa.payload.payload; if (!Payload.is(fill, "Fill")) throw new Error("Invalid VAA payload"); + const postedVaaAddress = this.matchingEngine.pdas.postedVaa(vaa); const ix = await this.redeemCctpFillIx( { payer: payer, @@ -224,8 +226,8 @@ export class SolanaTokenRouter ), }, { - encodedCctpMessage: Buffer.from(CircleBridge.serialize(cctp.message)), - cctpAttestation: Buffer.from(cctp.attestation!, "hex"), + encodedCctpMessage: Buffer.from(CircleBridge.serialize(cctp!.message)), + cctpAttestation: Buffer.from(cctp!.attestation!, "hex"), }, ); diff --git a/solana/ts/tests/01__matchingEngine.ts b/solana/ts/tests/01__matchingEngine.ts index 3b999819..2edf929a 100644 --- a/solana/ts/tests/01__matchingEngine.ts +++ b/solana/ts/tests/01__matchingEngine.ts @@ -3561,14 +3561,18 @@ describe("Matching Engine", function () { const { value: lookupTableAccount } = await connection.getAddressLookupTable( lookupTableAddress, ); - const tx2 = engine.settleOrder( - playerTwo.publicKey, - fast.vaaAccount.vaa("FastTransfer:FastMarketOrder"), - finalized!.vaaAccount.vaa("FastTransfer:CctpDeposit"), - { + + const response = { + vaa: finalized!.vaaAccount.vaa("FastTransfer:CctpDeposit"), + cctp: { message: cctpMessage, attestation: cctpAttestation.toString("hex"), }, + }; + const tx2 = engine.settleOrder( + playerTwo.publicKey, + fast.vaaAccount.vaa("FastTransfer:FastMarketOrder"), + response, [lookupTableAccount!], ); @@ -4157,10 +4161,9 @@ describe("Matching Engine", function () { // Place the initial offer. const txs = engine.placeInitialOffer( accounts.payer, - // @ts-expect-error -- may still be a Uint8array payload for testing invalid VAA + // @ts-ignore - may still be considered uint8array fastMarketOrderVAA, args.offerPrice, - args.totalDeposit, ); if (errorMsg !== null) { @@ -4406,8 +4409,10 @@ describe("Matching Engine", function () { const txs = engine.prepareOrderResponse( accounts.payer, fastVaaAccount.vaa("FastTransfer:FastMarketOrder"), - finalizedVaaAccount.vaa("FastTransfer:CctpDeposit"), - { message: cctpMessage, attestation: cctpAttestation }, + { + vaa: finalizedVaaAccount.vaa("FastTransfer:CctpDeposit"), + cctp: { message: cctpMessage, attestation: cctpAttestation }, + }, [lookupTableAccount!], ); @@ -4524,12 +4529,13 @@ describe("Matching Engine", function () { throw new Error("preparedInSameTransaction not implemented"); } - const { fastVaa, finalizedVaa, preparedOrderResponse } = await (async () => { + const { fastVaa, finalizedVaa, preparedOrderResponse, args } = await (async () => { if (accounts.preparedOrderResponse !== undefined) { return { fastVaa: null, finalizedVaa: null, preparedOrderResponse: accounts.preparedOrderResponse, + args: opts.args, }; } else { const result = await prepareOrderResponseCctpForTest( @@ -4550,13 +4556,12 @@ describe("Matching Engine", function () { const executor = accounts.executor ?? playerOne.publicKey; - const ix = await engine.settleAuctionCompleteIx({ - ...accounts, - executor, - preparedOrderResponse, - }); - if (errorMsg !== null) { + const ix = await engine.settleAuctionCompleteIx({ + ...accounts, + executor, + preparedOrderResponse, + }); return expectIxErr(connection, [ix], unwrapSigners(signers), errorMsg); } @@ -4607,9 +4612,12 @@ describe("Matching Engine", function () { .getAccountInfo(preparedCustodyToken) .then((info) => info!.lamports); + const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); + const txs = engine.settleOrder( executor, fastVaaAccount.vaa("FastTransfer:FastMarketOrder"), + { vaa: finalizedVaaAccount.vaa("FastTransfer:CctpDeposit") }, ); const signer = executorIsPreparer ? prepareSigners[0] : payerSigner; @@ -4629,7 +4637,6 @@ describe("Matching Engine", function () { bestOfferToken, ); - const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); const { deposit } = LiquidityLayerMessage.decode(finalizedVaaAccount.payload()); const { baseFee } = deposit!.message.payload! as SlowOrderResponse; @@ -4692,12 +4699,13 @@ describe("Matching Engine", function () { throw new Error("preparedInSameTransaction not implemented"); } - const { fastVaa, finalizedVaa, preparedOrderResponse } = await (async () => { + const { fastVaa, finalizedVaa, preparedOrderResponse, args } = await (async () => { if (accounts.fastVaa !== undefined && accounts.preparedOrderResponse !== undefined) { return { fastVaa: accounts.fastVaa, finalizedVaa: null, preparedOrderResponse: accounts.preparedOrderResponse, + args: opts.args, }; } else { const result = await prepareOrderResponseCctpForTest( @@ -4713,6 +4721,7 @@ describe("Matching Engine", function () { finalizedVaa: result!.finalizedVaa, preparedOrderResponse: accounts.preparedOrderResponse ?? result!.preparedOrderResponse, + args: result!.args ?? opts.args, }; } })(); @@ -4721,14 +4730,21 @@ describe("Matching Engine", function () { const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); expect(fastMarketOrder).is.not.undefined; - let finalizedVaaAccount = finalizedVaa - ? await VaaAccount.fetch(connection, finalizedVaa) + const cctp = args + ? { + message: CircleBridge.deserialize(args!.encodedCctpMessage!)[0], + attestation: encoding.hex.encode(args!.cctpAttestation, true), + } : undefined; + const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa!); const txs = engine.settleOrder( accounts.payer, fastVaaAccount.vaa("FastTransfer:FastMarketOrder"), - finalizedVaaAccount?.vaa("FastTransfer:CctpDeposit"), + { + vaa: finalizedVaaAccount.vaa("FastTransfer:CctpDeposit"), + cctp, + }, ); if (errorMsg !== null) { diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index 04b1396b..b05c351d 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -864,7 +864,6 @@ describe("Token Router", function () { ); }); - // TODO: it("Place Market Order", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; expect(localVariables.delete("preparedOrder")).is.true; @@ -1741,10 +1740,12 @@ describe("Token Router", function () { const txs = tokenRouter.redeemFill( payer.publicKey, - mockVaa, { - message: cctpMessage, - attestation: cctpAttestation.toString("hex"), + vaa: mockVaa, + cctp: { + message: cctpMessage, + attestation: cctpAttestation.toString("hex"), + }, }, [lookupTableAccount!], ); diff --git a/solver/package.json b/solver/package.json index 81189e1d..e26651d5 100644 --- a/solver/package.json +++ b/solver/package.json @@ -16,8 +16,8 @@ "@solana/spl-token": "^0.4.6", "@wormhole-foundation/example-liquidity-layer-evm": "0.0.1", "@wormhole-foundation/example-liquidity-layer-solana": "0.0.1", - "@wormhole-foundation/sdk-base": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6", - "@wormhole-foundation/sdk-solana": "^0.7.0-beta.6" + "@wormhole-foundation/sdk-base": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2", + "@wormhole-foundation/sdk-solana": "^0.7.1-beta.2" } } diff --git a/universal/ts/package.json b/universal/ts/package.json index aea861b5..200640d7 100644 --- a/universal/ts/package.json +++ b/universal/ts/package.json @@ -19,8 +19,8 @@ "clean":"rm -rf node_modules && rm -rf dist" }, "dependencies": { - "@wormhole-foundation/sdk-base":"^0.7.0-beta.6", - "@wormhole-foundation/sdk-definitions":"^0.7.0-beta.6" + "@wormhole-foundation/sdk-base":"^0.7.1-beta.2", + "@wormhole-foundation/sdk-definitions":"^0.7.1-beta.2" }, "devDependencies": { "envfile": "^6.18.0", diff --git a/universal/ts/src/protocol.ts b/universal/ts/src/protocol.ts index 794407b1..ec3d3235 100644 --- a/universal/ts/src/protocol.ts +++ b/universal/ts/src/protocol.ts @@ -8,6 +8,7 @@ import { ProtocolVAA, UnsignedTransaction, VAA, + keccak256, payloadDiscriminator, } from "@wormhole-foundation/sdk-definitions"; import { FastMarketOrder, MessageName, messageNames } from "./messages"; @@ -36,6 +37,16 @@ export namespace FastTransfer { cctp?: Contracts["cctp"] & { usdcMint: string }; }; + export type Order = VAA<"FastMarketOrder">; + export const auctionId = (vaa: Order) => keccak256(vaa.hash); + + export type Fill = { vaa: FastTransfer.VAA<"CctpDeposit">; cctp?: CircleBridge.Attestation }; + export type FastFill = { vaa: FastTransfer.VAA<"FastFill"> }; + + export type OrderResponse = Fill | FastFill; + export const isFastFill = (response: OrderResponse): response is FastFill => + response.vaa.payloadName === "FastFill"; + export const getPayloadDiscriminator = () => payloadDiscriminator([protocolName, messageNames]); } @@ -94,30 +105,29 @@ export interface MatchingEngine { // the first offer for the fast transfer and inits an auction placeInitialOffer( sender: AccountAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, + vaa: FastTransfer.Order, offerPrice: bigint, - totalDeposit?: bigint, ): AsyncGenerator>; + // improve the offer below previous offers improveOffer( sender: AccountAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, + order: FastTransfer.Order, offer: bigint, ): AsyncGenerator>; + // Order executeFastOrder( sender: AccountAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, + order: FastTransfer.Order, ): AsyncGenerator>; prepareOrderResponse( sender: AccountAddress, - vaa: VAA<"FastTransfer:FastMarketOrder">, - deposit: VAA<"FastTransfer:CctpDeposit">, - cctp: CircleBridge.Attestation, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, ): AsyncGenerator>; settleOrder( sender: AccountAddress, - fast: VAA<"FastTransfer:FastMarketOrder">, - deposit?: VAA<"FastTransfer:CctpDeposit">, - cctp?: CircleBridge.Attestation, + order: FastTransfer.Order, + response: FastTransfer.OrderResponse, ): AsyncGenerator>; } @@ -158,8 +168,7 @@ export interface TokenRouter { redeemFill( sender: AccountAddress, - vaa: FastTransfer.VAA, - cctp: CircleBridge.Attestation, + orderResponse: FastTransfer.OrderResponse, ): AsyncGenerator>; }