Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
rouzwelt committed Nov 4, 2024
1 parent 173b2f7 commit 84b7c02
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 22 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Other optional arguments are:
- `--pool-update-interval`, Option to specify time (in minutes) between pools updates, default is 15 minutes, Will override the 'POOL_UPDATE_INTERVAL' in env variables
- `--self-fund-orders`, Specifies owned order to get funded once their vault goes below the specified threshold, example: token,vaultId,threshold,toptupamount;token,vaultId,threshold,toptupamount;... . Will override the 'SELF_FUND_ORDERS' in env variables
- `--route`, Specifies the routing mode 'multi' or 'single' or 'full', default is 'single'. Will override the 'ROUTE' in env variables
- `--exec-record-size`, Option for specifying the count of latest rounds reports are used for calculating avg execution performance, default is 50, Will override the 'EXEC_RECORD_SIZE' in env variables
- `-w` or `--wallet-count`, Number of wallet to submit transactions with, requirs `--mnemonic`. Will override the 'WALLET_COUNT' in env variables
- `-t` or `--topup-amount`, The initial topup amount of excess wallets, requirs `--mnemonic`. Will override the 'TOPUP_AMOUNT' in env variables
- `-V` or `--version`, output the version number
Expand Down Expand Up @@ -252,6 +253,9 @@ SELF_FUND_ORDERS=

# Specifies the routing mode 'multi' or 'single' or 'full', default is 'single'
ROUTE="single"

# Option for specifying the count of latest rounds reports are used for calculating avg execution performance, default is 50
EXEC_RECORD_SIZE=
```
If both env variables and CLI argument are set, the CLI arguments will be prioritized and override the env variables.

Expand Down
3 changes: 3 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ SELF_FUND_ORDERS=
# Specifies the routing mode 'multi' or 'single' or 'full', default is 'single'
ROUTE="single"

# Option for specifying the count of latest rounds reports are used for calculating avg execution performance, default is 50
EXEC_RECORD_SIZE=


# test rpcs vars
TEST_POLYGON_RPC=
Expand Down
89 changes: 69 additions & 20 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const ENV_OPTIONS = {
maxRatio: process?.env?.MAX_RATIO?.toLowerCase() === "true" ? true : false,
bundle: process?.env?.NO_BUNDLE?.toLowerCase() === "true" ? false : true,
timeout: process?.env?.TIMEOUT,
execRecordSize: process?.env?.EXEC_RECORD_SIZE,
flashbotRpc: process?.env?.FLASHBOT_RPC,
hops: process?.env?.HOPS,
retries: process?.env?.RETRIES,
Expand Down Expand Up @@ -163,6 +164,10 @@ const getOptions = async (argv: any, version?: string) => {
"--route <string>",
"Specifies the routing mode 'multi' or 'single' or 'full', default is 'single'. Will override the 'ROUTE' in env variables",
)
.option(
"--exec-record-size <integer>",
"Option for specifying the count of latest rounds reports are used for calculating avg execution performance, default is 50, Will override the 'EXEC_RECORD_SIZE' in env variables",
)
.description(
[
"A NodeJS app to find and take arbitrage trades for Rain Orderbook orders against some DeFi liquidity providers, requires NodeJS v18 or higher.",
Expand Down Expand Up @@ -191,6 +196,7 @@ const getOptions = async (argv: any, version?: string) => {
cmdOptions.maxRatio = cmdOptions.maxRatio || ENV_OPTIONS.maxRatio;
cmdOptions.flashbotRpc = cmdOptions.flashbotRpc || ENV_OPTIONS.flashbotRpc;
cmdOptions.timeout = cmdOptions.timeout || ENV_OPTIONS.timeout;
cmdOptions.execRecordSize = cmdOptions.execRecordSize || ENV_OPTIONS.execRecordSize || 50;
cmdOptions.hops = cmdOptions.hops || ENV_OPTIONS.hops;
cmdOptions.retries = cmdOptions.retries || ENV_OPTIONS.retries;
cmdOptions.poolUpdateInterval = cmdOptions.poolUpdateInterval || ENV_OPTIONS.poolUpdateInterval;
Expand Down Expand Up @@ -245,7 +251,7 @@ export const arbRound = async (
if (!ordersDetails.length) {
span.setStatus({ code: SpanStatusCode.OK, message: "found no orders" });
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], oppCount: 0, successCount: 0, avgGasCost: undefined };
}
} catch (e: any) {
const snapshot = errorSnapshot("", e);
Expand All @@ -254,40 +260,44 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], oppCount: 0, successCount: 0, avgGasCost: undefined };
}

try {
let txs;
let foundOpp = false;
let oppCount = 0;
let successCount = 0;
const txs: string[] = [];
const { reports = [], avgGasCost = undefined } = await clear(
config,
ordersDetails,
tracer,
ctx,
);
if (reports && reports.length) {
txs = reports.map((v) => v.txUrl).filter((v) => !!v);
reports.forEach((v) => {
if (v.txUrl) txs.push(v.txUrl);
if (v?.successfull) successCount++;
if (v.status === ProcessPairReportStatus.FoundOpportunity) oppCount++;
});
if (txs.length) {
foundOpp = true;
span.setAttribute("txUrls", txs);
}
if (successCount) {
span.setAttribute("didClear", true);
span.setAttribute("foundOpp", true);
} else if (
reports.some((v) => v.status === ProcessPairReportStatus.FoundOpportunity)
) {
foundOpp = true;
}
if (oppCount) {
span.setAttribute("foundOpp", true);
}
} else {
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
}
if (avgGasCost) {
span.setAttribute("avgGasCost", avgGasCost.toString());
}
span.setStatus({ code: SpanStatusCode.OK });
span.end();
return { txs, foundOpp, avgGasCost };
return { txs, oppCount, successCount, avgGasCost };
} catch (e: any) {
if (e?.startsWith?.("Failed to batch quote orders")) {
span.setAttribute("severity", ErrorSeverity.LOW);
Expand All @@ -301,7 +311,7 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], oppCount: 0, successCount: 0, avgGasCost: undefined };
}
} catch (e: any) {
const snapshot = errorSnapshot("Unexpected error occured", e);
Expand All @@ -311,7 +321,7 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], oppCount: 0, successCount: 0, avgGasCost: undefined };
}
});
};
Expand Down Expand Up @@ -351,6 +361,15 @@ export async function startup(argv: any, version?: string, tracer?: Tracer, ctx?
if (/^[0-9]+$/.test(options.sleep)) roundGap = Number(options.sleep) * 1000;
else throw "invalid sleep value, must be an integer greater than equal 0";
}
if (options.execRecordSize) {
if (typeof options.execRecordSize === "string") {
if (/^[0-9]+$/.test(options.sleep))
options.execRecordSize = Number(options.execRecordSize);
else throw "invalid Execution Record Size value, must be an integer greater than 0";
} else if (typeof options.execRecordSize !== "number") {
throw "invalid Execution Record Size value, must be an integer greater than 0";
}
}
if (options.poolUpdateInterval) {
if (typeof options.poolUpdateInterval === "number") {
_poolUpdateInterval = options.poolUpdateInterval;
Expand Down Expand Up @@ -404,6 +423,22 @@ export async function startup(argv: any, version?: string, tracer?: Tracer, ctx?
};
}

/**
* Calculates the opps count standard deviation from the avg of the given length of previous rounds records
*/
export const handleOppsRecord = (
recordSize: number,
previousRecords: number[],
oppCount: number,
): number => {
const avg = Math.floor(previousRecords.reduce((a, b) => a + b, 0) / previousRecords.length);
previousRecords.push(oppCount);
if (previousRecords.length > recordSize) {
previousRecords.splice(0, previousRecords.length - recordSize);
}
return oppCount - avg;
};

export const main = async (argv: any, version?: string) => {
// startup otel to collect span, logs, etc
// diag otel
Expand Down Expand Up @@ -473,6 +508,7 @@ export const main = async (argv: any, version?: string) => {
const wgc: ViemClient[] = [];
const wgcBuffer: { address: string; count: number }[] = [];
const botMinBalance = ethers.utils.parseUnits(options.botMinBalance);
const previousRoundsRecords: number[] = [];

// run bot's processing orders in a loop
// eslint-disable-next-line no-constant-condition
Expand Down Expand Up @@ -538,24 +574,37 @@ export const main = async (argv: any, version?: string) => {
try {
await rotateProviders(config, update);
const roundResult = await arbRound(tracer, roundCtx, options, config);
let txs, foundOpp, roundAvgGasCost;
let txs, roundAvgGasCost;
let oppCount = 0;
let successCount = 0;
if (roundResult) {
txs = roundResult.txs;
foundOpp = roundResult.foundOpp;
oppCount = roundResult.oppCount;
successCount = roundResult.successCount;
roundAvgGasCost = roundResult.avgGasCost;
}
if (txs && txs.length) {
roundSpan.setAttribute("txUrls", txs);
roundSpan.setAttribute("didClear", true);
roundSpan.setAttribute("foundOpp", true);
} else if (foundOpp) {
}
if (oppCount) {
roundSpan.setAttribute("foundOpp", true);
roundSpan.setAttribute("didClear", false);
} else {
roundSpan.setAttribute("foundOpp", false);
}
if (successCount) {
roundSpan.setAttribute("didClear", true);
} else {
roundSpan.setAttribute("didClear", false);
}

// record opps stdvs
const oppsCountStdvs = handleOppsRecord(
options.execRecordSize,
previousRoundsRecords,
oppCount,
);
roundSpan.setAttribute("opps-stdvs", oppsCountStdvs);

// keep avg gas cost
if (roundAvgGasCost) {
const _now = Date.now();
Expand Down
3 changes: 3 additions & 0 deletions src/processOrders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ export async function processPair(args: {
sellToken: orderPairObject.sellToken,
clearedAmount: clearActualAmount?.toString(),
actualGasCost: ethers.utils.formatUnits(actualGasCost),
successfull: true,
income,
inputTokenIncome: inputTokenIncome
? ethers.utils.formatUnits(inputTokenIncome, toToken.decimals)
Expand Down Expand Up @@ -735,6 +736,7 @@ export async function processPair(args: {
buyToken: orderPairObject.buyToken,
sellToken: orderPairObject.sellToken,
actualGasCost: ethers.utils.formatUnits(actualGasCost),
successfull: false,
};
result.reason = ProcessPairHaltReason.TxMineFailed;
return Promise.reject(result);
Expand All @@ -756,6 +758,7 @@ export async function processPair(args: {
tokenPair: pair,
buyToken: orderPairObject.buyToken,
sellToken: orderPairObject.sellToken,
successfull: false,
};
if (actualGasCost) {
result.report.actualGasCost = ethers.utils.formatUnits(actualGasCost);
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type CliOptions = {
selfFundOrders?: SelfFundOrder[];
tokens?: TokenDetails[];
route?: string;
execRecordSize: number;
};

export type TokenDetails = {
Expand Down Expand Up @@ -160,6 +161,7 @@ export type Report = {
clearedOrders?: string[];
income?: BigNumber;
netProfit?: BigNumber;
successfull?: boolean;
};

export type RoundReport = {
Expand Down
13 changes: 11 additions & 2 deletions test/cli.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require("dotenv").config();
const { assert } = require("chai");
const mockServer = require("mockttp").getLocal();
const { arbRound, startup } = require("../src/cli");
const { arbRound, startup, handleOppsRecord } = require("../src/cli");
const { trace, context } = require("@opentelemetry/api");
const { Resource } = require("@opentelemetry/resources");
const { BasicTracerProvider } = require("@opentelemetry/sdk-trace-base");
Expand Down Expand Up @@ -48,7 +48,7 @@ describe("Test cli", async function () {
};

const response = await arbRound(tracer, ctx, options, { mainAccount: {} });
const expected = { txs: [], foundOpp: false, avgGasCost: undefined };
const expected = { txs: [], oppCount: 0, successCount: 0, avgGasCost: undefined };
assert.deepEqual(response, expected);

testSpan.end();
Expand Down Expand Up @@ -216,4 +216,13 @@ describe("Test cli", async function () {
assert.deepEqual(result.config.rpcRecords, expected.config.rpcRecords);
assert.equal(result.options.botMinBalance, expected.options.botMinBalance);
});

it("test handleOppsRecord()", async function () {
const record = [3, 4, 5, 3, 3, 7, 4];
const size = 7;
const currentOppCount = 1;
const result = handleOppsRecord(size, record, currentOppCount);
const expected = -3;
assert.equal(result, expected);
});
});
3 changes: 3 additions & 0 deletions test/processPair.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ describe("Test process pair", async function () {
clearedOrders: [orderPairObject.takeOrders[0].id],
inputTokenIncome: undefined,
outputTokenIncome: undefined,
successfull: true,
},
reason: undefined,
error: undefined,
Expand Down Expand Up @@ -205,6 +206,7 @@ describe("Test process pair", async function () {
clearedOrders: [orderPairObject.takeOrders[0].id],
inputTokenIncome: undefined,
outputTokenIncome: undefined,
successfull: true,
},
reason: undefined,
error: undefined,
Expand Down Expand Up @@ -601,6 +603,7 @@ describe("Test process pair", async function () {
sellToken: orderPairObject.sellToken,
txUrl: scannerUrl + "/tx/" + txHash,
actualGasCost: formatUnits(effectiveGasPrice.mul(gasUsed)),
successfull: false,
},
reason: ProcessPairHaltReason.TxMineFailed,
error: undefined,
Expand Down

0 comments on commit 84b7c02

Please sign in to comment.