Skip to content
Open
2 changes: 2 additions & 0 deletions src/cli/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SweepCmd } from "./sweep";
import { RainSolverCli } from "..";
import { DowntimeCmd } from "./downtime";
import { Command, Option, OptionValues } from "commander";
import { RaindexRouteCmd } from "./raindex-route";

export { sweepFunds, SweepOptions } from "./sweep";
export { main as downtimeReport, DowntimeOptions, DowntimeOptionsExtended } from "./downtime";
Expand All @@ -15,6 +16,7 @@ export const RainSolverCmd = new Command("node rain-solver")
.alias("rain-solver")
.addCommand(SweepCmd) // add sweep subcommand
.addCommand(DowntimeCmd) // add downtime subcommand
.addCommand(RaindexRouteCmd) // add raindex route check cmd
.addOption(
new Option(
"-c, --config <path>",
Expand Down
98 changes: 98 additions & 0 deletions src/cli/commands/raindex-route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { parseUnits } from "viem";
import { estimateProfit } from "../../core/modes/raindex";
import { describe, it, expect, vi, beforeEach, Mock } from "vitest";
import { RaindexRouteCmd, checkRaindexRoute, RaindexRouteLiveOptions } from "./raindex-route";

vi.mock("../../core/modes/raindex", () => ({
estimateProfit: vi.fn(),
}));

describe("Test raindex-route cli options", () => {
it("should get raindex-route cli options with all required parameters", () => {
// overwrite the action for testing
RaindexRouteCmd.action(function () {});

const expected: Record<string, any> = {
orderInMax: "100",
orderInRatio: "2",
orderInInputToEthPrice: "1.5",
orderInOutputToEthPrice: "2.5",
orderOutMax: "200",
orderOutRatio: "0.5",
externalRoutePrice: "1.1",
};

const result = RaindexRouteCmd.parse([
"",
"",
"--order-in-max",
"100",
"--order-in-ratio",
"2",
"--order-in-input-to-eth-price",
"1.5",
"--order-in-output-to-eth-price",
"2.5",
"--order-out-max",
"200",
"--order-out-ratio",
"0.5",
"--external-route-price",
"1.1",
]).opts();

expect(result).toEqual(expected);
});
});

describe("Test checkRaindexRoute", () => {
const mockOptions: RaindexRouteLiveOptions = {
orderInMax: "100",
orderInRatio: "2",
orderInInputToEthPrice: "1.5",
orderInOutputToEthPrice: "2.5",
orderOutMax: "200",
orderOutRatio: "0.5",
externalRoutePrice: "1.1",
};

beforeEach(() => {
vi.clearAllMocks();
});

it("should call estimateProfit with correct parameters", async () => {
const mockProfit = parseUnits("50", 18);
(estimateProfit as Mock).mockReturnValue({
profit: mockProfit,
counterpartyInputToEthPrice: parseUnits("1.5", 18),
counterpartyOutputToEthPrice: parseUnits("2.5", 18),
});

await checkRaindexRoute(mockOptions);

expect(estimateProfit).toHaveBeenCalledTimes(1);
expect(estimateProfit).toHaveBeenCalledWith(
{
takeOrder: {
quote: {
maxOutput: parseUnits("100", 18),
ratio: parseUnits("2", 18),
},
},
},
{
takeOrder: {
quote: {
maxOutput: parseUnits("200", 18),
ratio: parseUnits("0.5", 18),
},
},
},
{
price: parseUnits("1.1", 18),
},
"1.5",
"2.5",
);
});
});
107 changes: 107 additions & 0 deletions src/cli/commands/raindex-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* eslint-disable no-console */
import { Command, Option } from "commander";
import { formatUnits, parseUnits } from "viem";
import { estimateProfit } from "../../core/modes/raindex";

export type RaindexRouteLiveOptions = {
orderInMax: string;
orderInRatio: string;
orderInInputToEthPrice: string;
orderInOutputToEthPrice: string;
orderOutMax: string;
orderOutRatio: string;
externalRoutePrice: string;
};

/** Command-line interface for the checking Raindex route script */
export const RaindexRouteCmd = new Command("raindex-route")
.addOption(
new Option("--order-in-max <number>", "The input order max output")
.env("ORDER_IN_MAX")
.makeOptionMandatory(),
)
.addOption(
new Option("--order-in-ratio <number>", "The input order ratio")
.env("ORDER_IN_RATIO")
.makeOptionMandatory(),
)
.addOption(
new Option(
"--order-in-input-to-eth-price <number>",
"The input order input token to eth price, optional, if not provided will be deriven from order's ratio and external price",
).env("ORDER_IN_INPUT_TO_ETH_PRICE"),
)
.addOption(
new Option(
"--order-in-output-to-eth-price <number>",
"The input order output token to eth price",
)
.env("ORDER_IN_OUTPUT_TO_ETH_PRICE")
.makeOptionMandatory(),
)
.addOption(
new Option("--order-out-max <number>", "The output order max output")
.env("ORDER_OUT_MAX")
.makeOptionMandatory(),
)
.addOption(
new Option("--order-out-ratio <number>", "The output order ratio")
.env("ORDER_OUT_RATIO")
.makeOptionMandatory(),
)
.addOption(
new Option("--external-route-price <number>", "The external route price (sushi)")
.env("EXTERNAL_PRICE")
.makeOptionMandatory(),
)
.description(
"Checks profitability of a Raindex routeed trade, this is the initial off-chain possible profitability check",
)
.action(async (options: RaindexRouteLiveOptions) => {
await checkRaindexRoute(options);
console.log("\x1b[32m%s\x1b[0m", "Checking Raindex route process finished successfully!\n");
});

/**
* A script to check profitability of the a raindex route
* @param opts - RaindexRouteLiveOptions
*/
export async function checkRaindexRoute(opts: RaindexRouteLiveOptions) {
const {
orderInMax,
orderInRatio,
orderInInputToEthPrice,
orderInOutputToEthPrice,
orderOutMax,
orderOutRatio,
externalRoutePrice,
} = opts;

const orderIn = {
takeOrder: {
quote: {
maxOutput: parseUnits(orderInMax, 18),
ratio: parseUnits(orderInRatio, 18),
},
},
} as any;
const orderOut = {
takeOrder: {
quote: {
maxOutput: parseUnits(orderOutMax, 18),
ratio: parseUnits(orderOutRatio, 18),
},
},
} as any;
const result = estimateProfit(
orderIn,
orderOut,
{
price: parseUnits(externalRoutePrice, 18),
} as any,
orderInInputToEthPrice,
orderInOutputToEthPrice,
);

console.log("\nCalculated profit:", formatUnits(result.profit, 18), "ETH\n");
}
8 changes: 2 additions & 6 deletions src/core/modes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ vi.mock("./inter", () => ({
findBestInterOrderbookTrade: vi.fn(),
}));

// vi.mock("./balancer", () => ({
// findBestBalancerTrade: vi.fn(),
// }));

vi.mock("./raindex", () => ({
findBestRaindexRouterTrade: vi.fn(),
}));
Expand Down Expand Up @@ -321,7 +317,7 @@ describe("Test findBestTrade", () => {
oppBlockNumber: 123,
});
const raindexResult = Result.ok({
type: "interOrderbook",
type: "raindex",
spanAttributes: { foundOpp: true },
estimatedProfit: 120n,
oppBlockNumber: 123,
Expand Down Expand Up @@ -671,7 +667,7 @@ describe("Test getEnabledTrades", () => {
expect(result.findBestInterOrderbookTrade).toBe(findBestInterOrderbookTrade);
});

it("should return only inter-orderbook trade when orderbook is in raindex router set", () => {
it("should return only raindex routed trade when orderbook is in raindex router set", () => {
const orderbookTradeTypes: OrderbookTradeTypes = {
router: new Set(),
intraOrderbook: new Set(),
Expand Down
17 changes: 8 additions & 9 deletions src/core/modes/raindex/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
}),
},
router: {
cache: new Map(),
sushi: {
tryQuote: vi.fn(),
},
Expand Down Expand Up @@ -128,9 +129,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
expect(extendObjectWithHeader).not.toHaveBeenCalled();
expect(isV4OrderbookV6Spy).toHaveBeenCalledWith(orderDetails);
expect(solver.state.contracts.getAddressesForTrade).not.toHaveBeenCalledWith();
expect(
solver.orderManager.getCounterpartyOrdersAgainstBaseTokens,
).not.toHaveBeenCalledWith();
expect(solver.orderManager.getCounterpartyOrdersAgainstBaseTokens).not.toHaveBeenCalled();
expect(solver.state.router.sushi!.tryQuote).not.toHaveBeenCalledWith();
expect(routeProcessor4ParamsSpy).not.toHaveBeenCalled();
expect(visualizeRouteSpy).not.toHaveBeenCalled();
Expand Down Expand Up @@ -315,7 +314,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(routeProcessor4ParamsSpy).not.toHaveBeenCalled();
Expand Down Expand Up @@ -435,7 +434,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(routeProcessor4ParamsSpy).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -546,7 +545,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(routeProcessor4ParamsSpy).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -700,7 +699,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(solver.state.router.sushi!.tryQuote).toHaveBeenNthCalledWith(2, {
Expand All @@ -709,7 +708,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(routeProcessor4ParamsSpy).toHaveBeenCalledTimes(2);
Expand Down Expand Up @@ -861,7 +860,7 @@ describe("Test findBestRaindexRouterTrade function", () => {
amountIn: expect.any(BigInt),
gasPrice: solver.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: false,
sushiRouteType: solver.state.appOptions.route,
});
expect(routeProcessor4ParamsSpy).toHaveBeenCalledTimes(1);
Expand Down
5 changes: 3 additions & 2 deletions src/core/modes/raindex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export enum RouteLegType {
/**
* Tries to find the best raindex routed trade for the given order,
* it will simultaneously try to find the best trade against other
* orders routed through a middle base token swaped through sushi RP
* orders routed through a middle base token swapped through sushi RP
* @param this - RainSolver instance
* @param orderDetails - The details of the order to be processed
* @param signer - The signer to be used for the trade
Expand Down Expand Up @@ -87,13 +87,14 @@ export async function findBestRaindexRouterTrade(
});

// get route details from sushi dataFetcher
const key = `${fromToken.address.toLowerCase()}-${toToken.address.toLowerCase()}`;
const quoteResult = await this.state.router.sushi?.tryQuote({
fromToken,
toToken,
amountIn: maximumInput,
gasPrice: this.state.gasPrice,
blockNumber,
skipFetch: true,
skipFetch: this.state.router.cache.has(key),
sushiRouteType: this.state.appOptions.route,
});

Expand Down
Loading
Loading