Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dotrain tasks #290

Merged
merged 8 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The app requires these arguments (all arguments can be set in env variables alte
- `--arb-address`, Address of the deployed arb contract, Will override the 'ARB_ADDRESS' in env variables
- `--bot-min-balance` The minimum gas token balance the bot wallet must have. Will override the 'BOT_MIN_BALANCE' in env variables
- `-s` or `--subgraph`, Subgraph URL(s) to read orders details from, can be used in combination with --orders, Will override the 'SUBGRAPH' in env variables
- `--dispair`, Address of dispair (ExpressionDeployer contract) to use for tasks, Will override the 'DISPAIR' in env variables

Other optional arguments are:
- `--generic-arb-address`, Address of the deployed generic arb contract to perform inter-orderbook clears, Will override the 'GENERIC_ARB_ADDRESS' in env variables
Expand Down Expand Up @@ -275,6 +276,9 @@ QUOTE_GAS=

# Only clear orders through RP4, excludes intra and inter orderbook clears
RP_ONLY="true"

# Address of dispair (ExpressionDeployer contract) to use for tasks
DISPAIR="address"
```
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 @@ -100,6 +100,9 @@ QUOTE_GAS=
# Only clear orders through RP4, excludes intra and inter orderbook clears
RP_ONLY="true"

# Address of dispair (ExpressionDeployer contract) to use for tasks
DISPAIR="address"


# test rpcs vars
TEST_POLYGON_RPC=
Expand Down
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@opentelemetry/resources": "^1.22.0",
"@opentelemetry/sdk-trace-base": "^1.22.0",
"@opentelemetry/semantic-conventions": "^1.22.0",
"@rainlanguage/dotrain": "^6.0.1-alpha.21",
"@rainlanguage/orderbook": "^0.0.1-alpha.6",
"axios": "^1.3.4",
"commander": "^11.0.0",
Expand Down
10 changes: 10 additions & 0 deletions src/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ export const routeProcessor3Abi = [
`function processRoute(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOutMin ,address to, bytes memory route) external payable returns (uint256 amountOut)`,
] as const;

/**
* ExpressionDeployerNPE2 minimal ABI
*/
export const deployerAbi = [
"function parse2(bytes memory data) external view returns (bytes memory bytecode)",
"function iStore() external view returns (address)",
"function iInterpreter() external view returns (address)",
"function iParser() external view returns (address)",
] as const;

/**
* Minimal ABI for Arb contract
*/
Expand Down
14 changes: 14 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { config } from "dotenv";
import { isAddress } from "viem";
import { getGasPrice } from "./gas";
import { Command } from "commander";
import { getMetaInfo } from "./config";
Expand Down Expand Up @@ -75,6 +76,7 @@ const ENV_OPTIONS = {
txGas: process?.env?.TX_GAS,
quoteGas: process?.env?.QUOTE_GAS,
route: process?.env?.ROUTE,
dispair: process?.env?.DISPAIR,
rpOnly: process?.env?.RP_ONLY?.toLowerCase() === "true" ? true : false,
ownerProfile: process?.env?.OWNER_PROFILE
? Array.from(process?.env?.OWNER_PROFILE.matchAll(/[^,\s]+/g)).map((v) => v[0])
Expand Down Expand Up @@ -120,6 +122,10 @@ const getOptions = async (argv: any, version?: string) => {
"--generic-arb-address <address>",
"Address of the deployed generic arb contract to perform inter-orderbook clears, Will override the 'GENERIC_ARB_ADDRESS' in env variables",
)
.option(
"--dispair <address>",
"Address of dispair (ExpressionDeployer contract) to use for tasks, Will override the 'DISPAIR' in env variables",
)
.option(
"-l, --lps <string>",
"List of liquidity providers (dex) to use by the router as one quoted string seperated by a comma for each, example: 'SushiSwapV2,UniswapV3', Will override the 'LIQUIDITY_PROVIDERS' in env variables, if unset will use all available liquidty providers",
Expand Down Expand Up @@ -260,6 +266,7 @@ const getOptions = async (argv: any, version?: string) => {
cmdOptions.route = cmdOptions.route || getEnv(ENV_OPTIONS.route);
cmdOptions.publicRpc = cmdOptions.publicRpc || getEnv(ENV_OPTIONS.publicRpc);
cmdOptions.rpOnly = cmdOptions.rpOnly || getEnv(ENV_OPTIONS.rpOnly);
cmdOptions.dispair = cmdOptions.dispair || getEnv(ENV_OPTIONS.dispair);
if (cmdOptions.ownerProfile) {
const profiles: Record<string, number> = {};
cmdOptions.ownerProfile.forEach((v: string) => {
Expand Down Expand Up @@ -469,6 +476,13 @@ export async function startup(argv: any, version?: string, tracer?: Tracer, ctx?
throw "invalid txGas value, must be an integer greater than zero optionally with appended percentage sign to apply as percentage to original gas";
}
}
if (options.dispair) {
if (typeof options.dispair !== "string" || !isAddress(options.dispair, { strict: false })) {
throw "expected dispair (ExpressionDeployer contract) address";
}
} else {
throw "undefined dispair address";
}
if (options.quoteGas) {
try {
options.quoteGas = BigInt(options.quoteGas);
Expand Down
80 changes: 1 addition & 79 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BigNumber } from "ethers";
import { getSgOrderbooks } from "./sg";
import { sendTransaction } from "./tx";
import { WNATIVE } from "sushi/currency";
import { ChainId, ChainKey } from "sushi/chain";
import { DataFetcher, LiquidityProviders } from "sushi/router";
Expand Down Expand Up @@ -30,7 +30,6 @@ import {
ROUTE_PROCESSOR_3_1_ADDRESS,
ROUTE_PROCESSOR_3_2_ADDRESS,
} from "sushi/config";
import { sendTransaction } from "./tx";

/**
* Get the chain config for a given chain id
Expand Down Expand Up @@ -221,83 +220,6 @@ export async function getDataFetcher(
}
}

/**
* Get the bounty check ensure task bytecode
* @param inputToEthPrice - Input token to Eth price
* @param outputToEthPrice - Output token to Eth price
* @param minimumExcepted - Minimum expected amount
*/
export function getBountyEnsureBytecode(
inputToEthPrice: BigNumber,
outputToEthPrice: BigNumber,
minimumExcepted: BigNumber,
sender: string,
): string {
const inputPrice = inputToEthPrice.toHexString().substring(2).padStart(64, "0");
const outputPrice = outputToEthPrice.toHexString().substring(2).padStart(64, "0");
const minimum = minimumExcepted.toHexString().substring(2).padStart(64, "0");
const msgSender = sender.substring(2).padStart(64, "0").toLowerCase();
// rainlang bytecode:
// :ensure(equal-to(sender context<0 0>()) \"unknown sender\"),
// :ensure(
// greater-than-or-equal-to(
// add(
// mul(inputToEthPrice context<1 0>())
// mul(outputToEthPrice context<1 1>())
// )
// minimumExcepted
// )
// \"minimum sender output\"
// );
return `0x0000000000000000000000000000000000000000000000000000000000000006${msgSender}8e756e6b6e6f776e2073656e6465720000000000000000000000000000000000${inputPrice}${outputPrice}${minimum}956d696e696d756d2073656e646572206f7574707574000000000000000000000000000000000000000000000000000000000000000000000000000000000047010000100500000110000103100000011000001e1200001d020000011000050110000403100101011000033d12000003100001011000023d1200002b120000211200001d020000`;
}

/**
* Get the bounty check ensure task bytecode for clear2 withdraw
* @param botAddress - Bot wallet address
* @param inputToken - Input token address
* @param outputToken - Output token address
* @param orgInputBalance - Input token original balance
* @param orgOutputBalance - Output token original balance
* @param inputToEthPrice - Input token to Eth price
* @param outputToEthPrice - Output token to Eth price
* @param minimumExcepted - Minimum expected amount
*/
export function getWithdrawEnsureBytecode(
botAddress: string,
inputToken: string,
outputToken: string,
orgInputBalance: BigNumber,
orgOutputBalance: BigNumber,
inputToEthPrice: BigNumber,
outputToEthPrice: BigNumber,
minimumExcepted: BigNumber,
sender: string,
): string {
const bot = botAddress.substring(2).padStart(64, "0");
const input = inputToken.substring(2).padStart(64, "0");
const output = outputToken.substring(2).padStart(64, "0");
const inputBalance = orgInputBalance.toHexString().substring(2).padStart(64, "0");
const outputBalance = orgOutputBalance.toHexString().substring(2).padStart(64, "0");
const inputPrice = inputToEthPrice.toHexString().substring(2).padStart(64, "0");
const outputPrice = outputToEthPrice.toHexString().substring(2).padStart(64, "0");
const minimum = minimumExcepted.toHexString().substring(2).padStart(64, "0");
const msgSender = sender.substring(2).padStart(64, "0").toLowerCase();
// rainlang bytecode:
// :ensure(equal-to(sender context<0 0>()) \"unknown sender\"),
// :ensure(
// greater-than-or-equal-to(
// add(
// mul(sub(erc20-balance-of(inputToken botAddress) originalInputBalance) inputToEthPrice)
// mul(sub(erc20-balance-of(outputToken botAddress) originalOutputBalance) outputToEthPrice)
// )
// minimumSenderOutput
// )
// \"minimumSenderOutput\"
// );
return `0x000000000000000000000000000000000000000000000000000000000000000b${msgSender}8e756e6b6e6f776e2073656e6465720000000000000000000000000000000000${input}${bot}${inputBalance}${inputPrice}${output}${outputBalance}${outputPrice}${minimum}936d696e696d756d53656e6465724f75747075740000000000000000000000000000000000000000000000000000000000000000000000000000000000000067010000180700000110000103100000011000001e1200001d0200000110000a011000090110000801100007011000030110000611120000471200003d1200000110000501100004011000030110000211120000471200003d1200002b120000211200001d020000`;
}

/**
* List of L2 chains that require SEPARATE L1 gas actions.
* other L2 chains that dont require separate L1 gas actions
Expand Down
31 changes: 30 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";
import { ethers } from "ethers";
import { ChainId } from "sushi";
import { versions } from "process";
import { PublicClient } from "viem";
import { parseAbi, PublicClient } from "viem";
import { processLps } from "./utils";
import { initAccounts } from "./account";
import { processOrders } from "./processOrders";
Expand All @@ -26,6 +26,7 @@ import {
onFetchResponse,
createViemClient,
} from "./config";
import { deployerAbi } from "./abis";

/**
* Get the order details from a source, i.e array of subgraphs and/or a local json file
Expand Down Expand Up @@ -196,6 +197,29 @@ export async function getConfig(
options.publicRpc,
);

const interpreter = await (async () => {
try {
return await viemClient.readContract({
address: options.dispair as `0x${string}`,
abi: parseAbi(deployerAbi),
functionName: "iInterpreter",
});
} catch {
throw "failed to get dispair interpreter address";
}
})();
const store = await (async () => {
try {
return await viemClient.readContract({
address: options.dispair as `0x${string}`,
abi: parseAbi(deployerAbi),
functionName: "iStore",
});
} catch {
throw "failed to get dispair store address";
}
})();

config.rpc = rpcUrls;
config.arbAddress = arbAddress;
config.genericArbAddress = options.genericArbAddress;
Expand All @@ -219,6 +243,11 @@ export async function getConfig(
config.txGas = options.txGas;
config.quoteGas = options.quoteGas;
config.rpOnly = options.rpOnly;
config.dispair = {
interpreter,
store,
deployer: options.dispair,
};

// init accounts
const { mainAccount, accounts } = await initAccounts(walletKey, config, options, tracer, ctx);
Expand Down
48 changes: 30 additions & 18 deletions src/modes/interOrderbook.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { orderbookAbi } from "../abis";
import { estimateGasCost } from "../gas";
import { BaseError, PublicClient } from "viem";
import { getBountyEnsureBytecode } from "../config";
import { BigNumber, Contract, ethers } from "ethers";
import { containsNodeError, errorSnapshot } from "../error";
import { getBountyEnsureRainlang, parseRainlang } from "../task";
import { BotConfig, BundledOrders, ViemClient, DryrunResult, SpanAttrs } from "../types";
import {
ONE18,
Expand Down Expand Up @@ -88,16 +88,20 @@ export async function dryrun({

const task = {
evaluable: {
interpreter: orderPairObject.takeOrders[0].takeOrder.order.evaluable.interpreter,
store: orderPairObject.takeOrders[0].takeOrder.order.evaluable.store,
interpreter: config.dispair.interpreter,
store: config.dispair.store,
bytecode:
config.gasCoveragePercentage === "0"
? "0x"
: getBountyEnsureBytecode(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
ethers.constants.Zero,
signer.account.address,
: await parseRainlang(
await getBountyEnsureRainlang(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
ethers.constants.Zero,
signer.account.address,
),
config.viemClient,
config.dispair,
),
},
signedContext: [],
Expand Down Expand Up @@ -149,11 +153,15 @@ export async function dryrun({
// sender output which is already called above
if (config.gasCoveragePercentage !== "0") {
const headroom = (Number(config.gasCoveragePercentage) * 1.03).toFixed();
task.evaluable.bytecode = getBountyEnsureBytecode(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(headroom).div("100"),
signer.account.address,
task.evaluable.bytecode = await parseRainlang(
await getBountyEnsureRainlang(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(headroom).div("100"),
signer.account.address,
),
config.viemClient,
config.dispair,
);
rawtx.data = arb.interface.encodeFunctionData("arb3", [
orderPairObject.orderbook,
Expand All @@ -169,11 +177,15 @@ export async function dryrun({
.div(100);
rawtx.gas = gasLimit.toBigInt();
gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost);
task.evaluable.bytecode = getBountyEnsureBytecode(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(config.gasCoveragePercentage).div("100"),
signer.account.address,
task.evaluable.bytecode = await parseRainlang(
await getBountyEnsureRainlang(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(config.gasCoveragePercentage).div("100"),
signer.account.address,
),
config.viemClient,
config.dispair,
);
rawtx.data = arb.interface.encodeFunctionData("arb3", [
orderPairObject.orderbook,
Expand Down
Loading
Loading