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

Various improvements #61

Merged
merged 7 commits into from
Aug 29, 2024
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
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@graphql-mesh/cli": "^0.90.5",
"@graphql-mesh/config": "^0.100.5",
"@graphql-mesh/graphql": "^0.98.4",
"@venusprotocol/keeper-bots": "1.1.0-dev.1",
"@venusprotocol/keeper-bots": "1.0.0-dev.5",
"@venusprotocol/venus-protocol": "^9.1.0",
"dotenv": "^16.3.1",
"graphql": "^16.8.1",
Expand Down
109 changes: 60 additions & 49 deletions packages/cli/source/commands/convert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { useEffect, useState, useReducer } from "react";
import { option } from "pastel";
import { Box, Spacer, Text, useApp, useStderr } from "ink";
import zod from "zod";
import { parseUnits } from "viem";
import { parseUnits, formatUnits } from "viem";
import { TokenConverter, PancakeSwapProvider, UniswapProvider } from "@venusprotocol/keeper-bots";
import { stringifyBigInt, getConverterConfigId } from "../utils/index.js";
import { Options, Title, BorderBox } from "../components/index.js";
import { reducer, defaultState } from "../state/convert.js";
import getEnvValue from "../utils/getEnvValue.js";
import FullScreenBox from "../components/fullScreenBox.js";
import { addressValidation } from "../utils/validation.js";

export const options = zod.object({
Expand Down Expand Up @@ -115,7 +114,7 @@ export const options = zod.object({
}),
)
.optional()
.default(30),
.default(3),
});

interface Props {
Expand Down Expand Up @@ -171,15 +170,15 @@ export default function Convert({ options }: Props) {
let amountOut = t.assetOut.balance;

const vTokenAddress = t.assetOutVTokens.core || t.assetOutVTokens.isolated![0]![1];
const { underlyingPriceUsd, underlyingUsdValue, underlyingDecimals } = await tokenConverter.getUsdValue(
const { assetOutPriceUsd, assetOutUsdValue, assetOutDecimals } = await tokenConverter.getUsdValue(
t.assetOut.address,
vTokenAddress,
amountOut,
);

if (+underlyingUsdValue > minTradeUsd) {
if (+underlyingUsdValue > maxTradeUsd) {
amountOut = parseUnits((maxTradeUsd / +underlyingPriceUsd.toString()).toString(), underlyingDecimals);
if (+assetOutUsdValue > minTradeUsd) {
if (+assetOutUsdValue > maxTradeUsd) {
amountOut = parseUnits((maxTradeUsd / +assetOutPriceUsd.toString()).toString(), assetOutDecimals);
}

const arbitrageArgs = await tokenConverter.prepareConversion(
Expand All @@ -196,7 +195,8 @@ export default function Convert({ options }: Props) {
minIncome: 0n,
};

const maxMinIncome = ((amount * BigInt(10000 + minIncomeBp)) / 10000n - amount) * -1n;
const minIncomeLimit = BigInt(Number(amount) * minIncomeBp) / 10000n;
const minIncomeUsdValue = +formatUnits(minIncome, assetOutDecimals) * +assetOutPriceUsd;

const context = {
converter: t.tokenConverter,
Expand All @@ -205,20 +205,26 @@ export default function Convert({ options }: Props) {
amount,
minIncome,
percentage: Number(minIncome) && Number(amount) && Number((minIncome * 10000000n) / amount) / 10000000,
maxMinIncome,
minIncomeLimit,
};
if (profitable && minIncome < 0) {
dispatch({
error: "Conversion is not profitable",
type: "ExecuteTrade",
context,
});
} else if (minIncome < 1 && minIncome * -1n > maxMinIncome * -1n) {
} else if (minIncome < 1 && minIncome * -1n > minIncomeLimit) {
dispatch({
type: "ExecuteTrade",
error: "Min income too high",
context,
});
} else if (profitable && +minIncomeUsdValue < 1) {
dispatch({
type: "ExecuteTrade",
error: "Min income too low",
context,
});
} else if (t.accountBalanceAssetOut < minIncome * -1n && !profitable) {
dispatch({
error: "Insufficient wallet balance to pay min income",
Expand Down Expand Up @@ -249,7 +255,7 @@ export default function Convert({ options }: Props) {
}

return (
<FullScreenBox flexDirection="column">
<Box flexDirection="column">
<Title />
{debug && <Options options={options} />}
<Box flexDirection="column" flexGrow={1}>
Expand Down Expand Up @@ -285,46 +291,51 @@ export default function Convert({ options }: Props) {
return null;
})}
<Spacer />
<Text bold>Logs</Text>
{messages.map((msg, idx) => {
const id = msg.type === "PotentialConversions" ? idx : getConverterConfigId(msg.context);
return (
<BorderBox
key={`${id}-${idx}`}
flexDirection="row"
borderStyle="doubleSingle"
borderColor="#3396FF"
borderTop
>
<Box flexGrow={1} flexDirection="column" marginLeft={1} marginRight={1} minWidth={60}>
<Text bold>{msg.type}</Text>
{"blockNumber" in msg && msg.blockNumber !== undefined && (
<Text bold>Block Number {msg.blockNumber?.toString()}</Text>
)}
{"error" in msg && msg.error && (
<>
<Text color="red">{msg.error}</Text>
</>
)}
{"pancakeSwapTrade" in msg.context && (
<Text>{JSON.stringify(msg.context.pancakeSwapTrade || " ", stringifyBigInt)}</Text>
)}
{(msg.type === "Arbitrage" || msg.type === "ExecuteTrade") && (
<Text>{JSON.stringify(msg.context || " ", stringifyBigInt)}</Text>
)}
{msg.type === "PotentialConversions" ? (
<Box flexGrow={1} flexDirection="column" minWidth={60} marginRight={1} marginLeft={1}>
<Text>
{msg.context.conversions.length} {msg.context.conversions.length > 1 ? "Trades" : "Trade"} found
</Text>
{debug && (
<Box flexDirection="column">
<Text bold>Logs</Text>
{messages.map((msg, idx) => {
const id = msg.type === "PotentialConversions" ? idx : getConverterConfigId(msg.context);
return (
<BorderBox
key={`${id}-${idx}`}
flexDirection="row"
borderStyle="doubleSingle"
borderColor="#3396FF"
borderTop
>
<Box flexGrow={1} flexDirection="column" marginLeft={1} marginRight={1} minWidth={60}>
<Text bold>{msg.type}</Text>
{"blockNumber" in msg && msg.blockNumber !== undefined && (
<Text bold>Block Number {msg.blockNumber?.toString()}</Text>
)}
{"error" in msg && msg.error && (
<>
<Text color="red">{msg.error}</Text>
</>
)}
{"pancakeSwapTrade" in msg.context && (
<Text>{JSON.stringify(msg.context.pancakeSwapTrade || " ", stringifyBigInt)}</Text>
)}
{(msg.type === "Arbitrage" || msg.type === "ExecuteTrade") && (
<Text>{JSON.stringify(msg.context || " ", stringifyBigInt)}</Text>
)}
{msg.type === "PotentialConversions" ? (
<Box flexGrow={1} flexDirection="column" minWidth={60} marginRight={1} marginLeft={1}>
<Text>
{msg.context.conversions.length} {msg.context.conversions.length > 1 ? "Trades" : "Trade"}{" "}
found
</Text>
</Box>
) : null}
</Box>
) : null}
</Box>
</BorderBox>
);
})}
</BorderBox>
);
})}
</Box>
)}
</Box>
{error ? <Text color="red">Error - {error}</Text> : null}
</FullScreenBox>
</Box>
);
}
4 changes: 2 additions & 2 deletions packages/cli/source/commands/releaseFunds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ const reduceToTokensWithBalances = async (
const token = tokenSetArray[idx]! as Address;
const vToken = markets.find(m => m.underlyingAddress === token);
if (result.result) {
const { underlyingUsdValue } = await tokenConverter.getUsdValue(
const { assetOutUsdValue } = await tokenConverter.getUsdValue(
vToken?.underlyingAddress!,
vToken?.vTokenAddress!,
result.result as bigint,
);

if (+underlyingUsdValue < 100) {
if (+assetOutUsdValue < 100) {
tokenSet.delete(token);
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/source/state/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ExecuteTradeMessage {
amount: bigint;
minIncome: bigint;
percentage: number;
maxMinIncome: bigint;
minIncomeLimit: bigint;
};
}

Expand Down
8 changes: 4 additions & 4 deletions packages/keeper-bots/src/converter-bot/TokenConverter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const createTokenConverterInstance = ({ simulate = false }: { simulate: boolean
(publicClient.waitForTransactionReceipt as unknown as jest.Mock).mockImplementation(
jest.fn(() => ({ blockNumber: 23486902n })),
);
(publicClient.estimateContractGas as unknown as jest.Mock).mockImplementation(jest.fn(() => {}));
(publicClient.estimateContractGas as unknown as jest.Mock).mockImplementation(jest.fn(() => 744684n));

const tokenConverter = new TokenConverter({
subscriber: subscriberMock,
Expand Down Expand Up @@ -678,9 +678,9 @@ describe("Token Converter", () => {
]);

expect(await tokenConverter.getUsdValue(addresses.USDC, addresses.vUSDC, 1n)).toEqual({
underlyingPriceUsd: "20000000000000000000",
underlyingUsdValue: "20",
underlyingDecimals: 18,
assetOutPriceUsd: "20000000000000000000",
assetOutUsdValue: "20",
assetOutDecimals: 18,
});
});
});
Expand Down
41 changes: 24 additions & 17 deletions packages/keeper-bots/src/converter-bot/TokenConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import { ConverterBotMessage, GetBestTradeMessage, MarketAddresses } from "./typ

const REVERT_IF_NOT_MINED_AFTER = 60n; // seconds

const CONFIRMATIONS = {
bscmainnet: 4,
bsctestnet: 4,
ethereum: 12,
sepolia: 12,
};

export class TokenConverter extends BotBase {
private addresses: ReturnType<typeof getAddresses>;
private operator: { address: Address; abi: typeof tokenConverterOperatorAbi };
Expand Down Expand Up @@ -241,13 +248,13 @@ export class TokenConverter extends BotBase {
* @param underlyingAddress Asset address
* @param vTokenAddress vToken market address for the asset
* @param value Amount of asset
* @returns {underlyingPriceUsd: string, underlyingUsdValue: string, underlyingDecimals: number}
* @returns {assetOutPriceUsd: string, assetOutUsdValue: string, assetOutDecimals: number}
*/
async getUsdValue(underlyingAddress: Address, vTokenAddress: Address, value: bigint) {
async getUsdValue(assetOutAddress: Address, vTokenAddress: Address, value: bigint) {
const result = await this.publicClient.multicall({
contracts: [
{
address: underlyingAddress,
address: assetOutAddress,
abi: erc20Abi,
functionName: "decimals",
args: [],
Expand All @@ -265,14 +272,14 @@ export class TokenConverter extends BotBase {

const [{ result: underlyingDecimals = 0 }, { result: { underlyingPrice } = { underlyingPrice: undefined } }] =
result;
let underlyingUsdValue = "0";
let assetOutUsdValue = "0";
if (underlyingPrice && underlyingDecimals) {
underlyingUsdValue = formatUnits(value * underlyingPrice, 36);
assetOutUsdValue = formatUnits(value * underlyingPrice, 36);
}
return {
underlyingPriceUsd: formatUnits(underlyingPrice || 0n, 36 - underlyingDecimals) || "0",
underlyingUsdValue,
underlyingDecimals,
assetOutPriceUsd: formatUnits(underlyingPrice || 0n, 36 - underlyingDecimals) || "0",
assetOutUsdValue,
assetOutDecimals: underlyingDecimals,
};
}

Expand All @@ -298,13 +305,7 @@ export class TokenConverter extends BotBase {
functionName: "approve",
args: [this.operator.address, amount],
});
const confirmations = {
bscmainnet: 4,
bsctestnet: 4,
ethereum: 12,
sepolia: 12,
};
await this.publicClient.waitForTransactionReceipt({ hash: trx, confirmations: confirmations[this.chainName] });
await this.publicClient.waitForTransactionReceipt({ hash: trx, confirmations: CONFIRMATIONS[this.chainName] });
}
}

Expand Down Expand Up @@ -360,15 +361,21 @@ export class TokenConverter extends BotBase {

if (!this.simulate) {
simulation = "Execution: ";
trx = await this.walletClient.writeContract({ ...convertTransaction, gas: gasEstimation });
({ blockNumber } = await this.publicClient.waitForTransactionReceipt({ hash: trx, confirmations: 4 }));
// Increasing gas limit because the conversion frequently runs out of gas
trx = await this.walletClient.writeContract({ ...convertTransaction, gas: (gasEstimation * 104n) / 100n });
({ blockNumber } = await this.publicClient.waitForTransactionReceipt({
hash: trx,
confirmations: CONFIRMATIONS[this.chainName],
}));
}
} catch (e) {
if (e instanceof BaseError) {
const revertError = e.walk(err => err instanceof ContractFunctionRevertedError);
if (revertError instanceof ContractFunctionRevertedError) {
// writeContract || simulateContract shapes
error = `${simulation}${revertError.reason || revertError.shortMessage}`;
} else {
error = `${simulation}${(e as Error).message}`;
}
} else {
error = `${simulation}${(e as Error).message}`;
Expand Down
Loading
Loading