Skip to content

Commit

Permalink
Merge pull request #61 from VenusProtocol/various-improvements
Browse files Browse the repository at this point in the history
Various improvements
  • Loading branch information
coreyar authored Aug 29, 2024
2 parents 75278bc + ee90306 commit 1700fd8
Show file tree
Hide file tree
Showing 7 changed files with 693 additions and 582 deletions.
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

0 comments on commit 1700fd8

Please sign in to comment.