diff --git a/.env.example b/.env.example index e91f87c2..69efb213 100644 --- a/.env.example +++ b/.env.example @@ -3,12 +3,12 @@ FORK=false FORKED_NETWORK=bscmainnet ## Archive nodes -#ARCHIVE_NODE_bsctestnet=https://bsc-testnet.nodereal.io/v1/ -#ARCHIVE_NODE_bscmainnet=https://bsc-mainnet.nodereal.io/v1/ -#ARCHIVE_NODE_bscmainnet=http://127.0.0.1:1248 -#ARCHIVE_NODE_sepolia=https://ethereum-sepolia.blockpi.network/v1/rpc/public -#ARCHIVE_NODE_ethereum=https://eth-mainnet.nodereal.io/v1/ -#ARCHIVE_NODE_ethereum=http://127.0.0.1:1248 +#LIVE_NETWORK_bsctestnet=https://bsc-testnet.nodereal.io/v1/ +#LIVE_NETWORK_bscmainnet=https://bsc-mainnet.nodereal.io/v1/ +#LIVE_NETWORK_bscmainnet=http://127.0.0.1:1248 +#LIVE_NETWORK_sepolia=https://ethereum-sepolia.blockpi.network/v1/rpc/public +#LIVE_NETWORK_ethereum=https://eth-mainnet.nodereal.io/v1/ +#LIVE_NETWORK_ethereum=http://127.0.0.1:1248 ETHERSCAN_API_KEY= REPORT_GAS= diff --git a/.eslint-tsconfig b/.eslint-tsconfig index ab94308c..a50a3038 100644 --- a/.eslint-tsconfig +++ b/.eslint-tsconfig @@ -1,8 +1,11 @@ { "extends": "./tsconfig.json", "include": [ - "tests", + "./src", + "./typechain", "docgen-templates", - "commitlint.config.js" + "commitlint.config.js", + "wagmi.config.ts", + "src/environment.d.ts" ] } diff --git a/.eslintignore b/.eslintignore index e843e6c4..02f86fb2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,5 @@ cache coverage dist typechain +src/config/abis/generated.ts +subgraph-client/.graphqlclient diff --git a/.eslintrc b/.eslintrc index 8c67a3d3..6f8b35e6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,6 +34,7 @@ "varsIgnorePattern": "_" } ], + "@typescript-eslint/ban-ts-comment": "warn", "spaced-comment": [ "error", "always", diff --git a/.gitignore b/.gitignore index 83eaa78a..3014371e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ artifacts !.yarn/plugins !.yarn/sdks !.yarn/versions + +# Generated files +src/config/abis/generated.ts +subgraph-client/.graphclient diff --git a/.prettierignore b/.prettierignore index aa66fcd1..6c8c6ac8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,6 @@ dist typechain contracts/third-party *.hbs + +src/config/abis/generated.ts +subgraph-client/.graphqlclient diff --git a/.prettierrc b/.prettierrc index 3aec6855..e794b58c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,7 @@ "bracketSpacing": true, "endOfLine": "auto", "importOrder": ["module-alias/register", "", "^[./]"], - "importOrderParserPlugins": ["typescript"], + "importOrderParserPlugins": ["typescript", "importAssertions"], "importOrderSeparation": true, "importOrderSortSpecifiers": true, "printWidth": 120, diff --git a/contracts/operators/TokenConverterOperator.sol b/contracts/operators/TokenConverterOperator.sol index d5afb6de..8dbef901 100644 --- a/contracts/operators/TokenConverterOperator.sol +++ b/contracts/operators/TokenConverterOperator.sol @@ -139,18 +139,6 @@ contract TokenConverterOperator is ExactOutputFlashSwap { _flashSwap(FlashSwapParams({ amountOut: amountToPay, path: params.path, data: abi.encode(data) })); } - function _validatePath(bytes calldata path, address expectedPathStart, address expectedPathEnd) internal pure { - address swapStart = path.toAddress(0); - if (swapStart != expectedPathStart) { - revert InvalidSwapStart(expectedPathStart, swapStart); - } - - address swapEnd = path.toAddress(path.length - 20); - if (swapEnd != expectedPathEnd) { - revert InvalidSwapEnd(expectedPathEnd, swapEnd); - } - } - function _onMoneyReceived(bytes memory data) internal override returns (IERC20 tokenIn, uint256 maxAmountIn) { ConversionData memory decoded = abi.decode(data, (ConversionData)); @@ -195,6 +183,18 @@ contract TokenConverterOperator is ExactOutputFlashSwap { return tokensReceived; } + function _validatePath(bytes calldata path, address expectedPathStart, address expectedPathEnd) internal pure { + address swapStart = path.toAddress(0); + if (swapStart != expectedPathStart) { + revert InvalidSwapStart(expectedPathStart, swapStart); + } + + address swapEnd = path.toAddress(path.length - 20); + if (swapEnd != expectedPathEnd) { + revert InvalidSwapEnd(expectedPathEnd, swapEnd); + } + } + function _u(int256 value) private pure returns (uint256) { if (value < 0) { revert Underflow(); diff --git a/package.json b/package.json index 37e90025..5ca82609 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "docgen": "hardhat docgen", "prepare": "husky install", "generate-abis": "yarn wagmi generate", - "postinstall": "yarn generate-abis" + "postinstall": "yarn generate-abis", + "generate-subgraph-types": "rm -rf /subgraph-client/.graphclient && npx graphclient build --dir ./subgraph-client" }, "keywords": [], "author": "Venus", @@ -92,6 +93,7 @@ "dependencies": { "@openzeppelin/contracts-upgradeable": "4.9.3", "@venusprotocol/governance-contracts": "^1.4.0", + "@venusprotocol/isolated-pools": "^2.3.0", "@venusprotocol/protocol-reserve": "^1.4.0", "@venusprotocol/venus-protocol": "^7.1.0", "viem": "^2.1.1" diff --git a/src/config/addresses.ts b/src/config/addresses.ts index 9499184e..e40cb0f4 100644 --- a/src/config/addresses.ts +++ b/src/config/addresses.ts @@ -1,29 +1,52 @@ import bscmainnetGovernance from "@venusprotocol/governance-contracts/deployments/bscmainnet_addresses.json"; import bsctestnetGovernance from "@venusprotocol/governance-contracts/deployments/bsctestnet_addresses.json"; +import bscmainnetIsolated from "@venusprotocol/isolated-pools/deployments/bscmainnet_addresses.json"; +import bsctestnetIsolated from "@venusprotocol/isolated-pools/deployments/bsctestnet_addresses.json"; import bscmainnetProtocolReserve from "@venusprotocol/protocol-reserve/deployments/bscmainnet_addresses.json"; import bsctestnetProtocolReserve from "@venusprotocol/protocol-reserve/deployments/bsctestnet_addresses.json"; import bscmainnetCore from "@venusprotocol/venus-protocol/deployments/bscmainnet_addresses.json"; import bsctestnetCore from "@venusprotocol/venus-protocol/deployments/bsctestnet_addresses.json"; -export const addresses = { +import { SUPPORTED_CHAINS } from "./chains"; + +const addresses = { bscmainnet: { ...bscmainnetCore.addresses, ...bscmainnetProtocolReserve.addresses, ...bscmainnetGovernance.addresses, + ...bscmainnetIsolated.addresses, BUSD: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", USDT: "0x55d398326f99059fF775485246999027B3197955", USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", PancakeSwapRouter: "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4", + + BTCBPrimeConverter: bscmainnetProtocolReserve.addresses.BTCBPrimeConverter, + ConverterNetwork: bscmainnetProtocolReserve.addresses.ConverterNetwork, + ETHPrimeConverter: bscmainnetProtocolReserve.addresses.ETHPrimeConverter, + RiskFundConverter: bscmainnetProtocolReserve.addresses.RiskFundConverter, + USDCPrimeConverter: bscmainnetProtocolReserve.addresses.USDCPrimeConverter, + USDTPrimeConverter: bscmainnetProtocolReserve.addresses.USDTPrimeConverter, + XVSVaultConverter: bscmainnetProtocolReserve.addresses.XVSVaultConverter, + TokenConverterOperator: "0x", }, bsctestnet: { ...bsctestnetCore.addresses, ...bsctestnetProtocolReserve.addresses, ...bsctestnetGovernance.addresses, + ...bsctestnetIsolated.addresses, PancakeSwapRouter: "0x1b81D678ffb9C0263b24A97847620C99d213eB14", xvsHolder: "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706", usdtHolder: "0x2Ce1d0ffD7E869D9DF33e28552b12DdDed326706", + BTCBPrimeConverter: bsctestnetProtocolReserve.addresses.BTCBPrimeConverter, + ConverterNetwork: bsctestnetProtocolReserve.addresses.ConverterNetwork, + ETHPrimeConverter: bsctestnetProtocolReserve.addresses.ETHPrimeConverter, + RiskFundConverter: bsctestnetProtocolReserve.addresses.RiskFundConverter, + USDCPrimeConverter: bsctestnetProtocolReserve.addresses.USDCPrimeConverter, + USDTPrimeConverter: bsctestnetProtocolReserve.addresses.USDTPrimeConverter, + XVSVaultConverter: bsctestnetProtocolReserve.addresses.XVSVaultConverter, + TokenConverterOperator: "0x9222F8b71603318d5EEbBf0074c2Da07fEbbB9eb", }, } as const; @@ -31,5 +54,17 @@ export const addresses = { type Addresses = typeof addresses; export type HasAddressFor = { - [ChainT in keyof Addresses]: Addresses[ChainT] extends Record ? ChainT : never; + [ChainT in keyof Addresses]: Addresses[ChainT] extends Record ? ChainT : never; }[keyof Addresses]; + +export type SupportedConverters = + | "BTCBPrimeConverter" + | "ETHPrimeConverter" + | "RiskFundConverter" + | "USDCPrimeConverter" + | "USDTPrimeConverter" + | "XVSVaultConverter"; + +const network = process.env.FORKED_NETWORK as SUPPORTED_CHAINS; + +export default addresses[network]; diff --git a/src/config/chains.ts b/src/config/chains.ts index 9a7edfe6..00465721 100644 --- a/src/config/chains.ts +++ b/src/config/chains.ts @@ -1,6 +1,10 @@ import { bsc, bscTestnet } from "viem/chains"; +import { HasAddressFor, SupportedConverters } from "./addresses"; + export const chains = { bscmainnet: bsc, bsctestnet: bscTestnet, } as const; + +export type SUPPORTED_CHAINS = HasAddressFor<"TokenConverterOperator" | SupportedConverters>; diff --git a/src/config/clients.ts b/src/config/clients.ts index aac88738..478a6a42 100644 --- a/src/config/clients.ts +++ b/src/config/clients.ts @@ -2,10 +2,11 @@ import { HttpTransport, PublicClient, WalletClient, createPublicClient, createWa import { PrivateKeyAccount, privateKeyToAccount } from "viem/accounts"; import { chains } from "./chains"; +import type { SUPPORTED_CHAINS } from "./chains"; -export const getPublicClient = ( - chainName: ChainT, -): PublicClient => { +export const getPublicClient = ( + chainName: SUPPORTED_CHAINS, +): PublicClient => { return createPublicClient({ chain: chains[chainName], transport: http(process.env[`LIVE_NETWORK_${chainName}`]), @@ -20,9 +21,9 @@ const readPrivateKeyFromEnv = (chainName: string): PrivateKeyAccount => { throw new Error(`Invalid private key for ${chainName}. Please specify PRIVATE_KEY_${chainName} env variable.`); }; -export const getWalletClient = ( - chainName: ChainT, -): WalletClient => { +export const getWalletClient = ( + chainName: SUPPORTED_CHAINS, +): WalletClient => { return createWalletClient({ chain: chains[chainName], transport: http(process.env[`LIVE_NETWORK_${chainName}`]), diff --git a/src/converter-bot/tokenConverterBot.ts b/src/converter-bot/tokenConverterBot.ts index 8a169668..49e4241f 100644 --- a/src/converter-bot/tokenConverterBot.ts +++ b/src/converter-bot/tokenConverterBot.ts @@ -1,10 +1,10 @@ -import { Address, parseAbi, parseUnits } from "viem"; +import { Address, parseAbi } from "viem"; import TokenConverterOperator from "../config/abis/TokenConverterOperator"; -import { type HasAddressFor, addresses } from "../config/addresses"; +import addresses, { HasAddressFor } from "../config/addresses"; import { chains } from "../config/chains"; import { getPublicClient, getWalletClient } from "../config/clients"; -import { Path, parsePath } from "./path"; +import { Path } from "./path"; type SupportedConverters = | "BTCBPrimeConverter" @@ -14,22 +14,22 @@ type SupportedConverters = | "USDTPrimeConverter" | "XVSVaultConverter"; -type SupportedChains = HasAddressFor<"TokenConverterOperator" | SupportedConverters>; +const REVERT_IF_NOT_MINED_AFTER = 60n; // seconds -const REVERT_IF_NOT_MINED_AFTER = 60n; //seconds +export type SUPPORTED_CHAINS = HasAddressFor<"TokenConverterOperator" | SupportedConverters>; class Bot { - private chainName: SupportedChains; + private chainName: SUPPORTED_CHAINS; private operator: { address: Address; abi: typeof TokenConverterOperator }; - private addresses: typeof addresses[SupportedChains]; - private _walletClient?: ReturnType>; - private _publicClient?: ReturnType>; + private addresses: typeof addresses; + private _walletClient?: ReturnType; + private _publicClient?: ReturnType; - constructor(chainName: SupportedChains) { + constructor(chainName: SUPPORTED_CHAINS) { this.chainName = chainName; this.addresses = addresses[chainName]; this.operator = { - address: addresses[chainName].TokenConverterOperator, + address: addresses.TokenConverterOperator, abi: TokenConverterOperator, }; } @@ -89,28 +89,4 @@ class Bot { } } -const main = async () => { - const bot = new Bot("bsctestnet"); - await bot.sanityCheck(); - - // Imagine the converter has LTC and wants USDT - // tokenToSendToConverter: USDT - // tokenToReceiveFromConverter: LTC - // We're swapping LTC to USDT on PCS, so - // the PCS reversed path should start with - // USDT (tokenToSendToConverter) and end - // with LTC (tokenToReceiveFromConverter) - // - // The income is paid out in LTC (if any) - await bot.arbitrage( - "RiskFundConverter", - parsePath([addresses.bsctestnet.USDT as Address, 500, addresses.bsctestnet.LTC as Address]), - parseUnits("1", 18), // 1 LTC - parseUnits("-0.1", 18), // We're ok with paying 0.1 LTC for this conversion - ); -}; - -main().catch(error => { - console.error(error); - process.exit(1); -}); +export default Bot; diff --git a/src/environment.d.ts b/src/environment.d.ts new file mode 100644 index 00000000..182915ea --- /dev/null +++ b/src/environment.d.ts @@ -0,0 +1,12 @@ +/* eslint-disable no-unused-vars */ +declare global { + namespace NodeJS { + interface ProcessEnv { + FORKED_NETWORK: "bsctestnet" | "bscmainnet"; + } + } +} + +// If this file has no import/export statements (i.e. is a script) +// convert it into a module by adding an empty export statement. +export {}; diff --git a/tsconfig.json b/tsconfig.json index 102c48f0..3624dc9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,6 @@ "@nomiclabs/hardhat-ethers": ["./node_modules/hardhat-deploy-ethers"] } }, - "include": ["./typechain", "./tests"], + "include": ["./typechain"], "files": ["./hardhat.config.ts"] } diff --git a/wagmi.config.ts b/wagmi.config.ts index 80ead800..1a99b8c8 100644 --- a/wagmi.config.ts +++ b/wagmi.config.ts @@ -1,54 +1,54 @@ -import { defineConfig } from '@wagmi/cli' -import { hardhat } from '@wagmi/cli/plugins' -import poolLensArtifact from '@venusprotocol/isolated-pools/artifacts/contracts/Lens/PoolLens.sol/PoolLens.json' assert { type: "json"}; -import diamondComptrollerArtifact from '@venusprotocol/venus-protocol/artifacts/contracts/Comptroller/Diamond/DiamondConsolidated.sol/DiamondConsolidated.json' assert { type: "json"}; -import ilComptrollerArtifact from '@venusprotocol/isolated-pools/artifacts/contracts/Comptroller.sol/Comptroller.json' assert { type: "json"}; -import coreVTokenArtifact from '@venusprotocol/venus-protocol/artifacts/contracts/Tokens/VTokens/VBep20Delegate.sol/VBep20Delegate.json' assert { type: "json"}; -import protocolShareReserveArtifact from '@venusprotocol/protocol-reserve/artifacts/contracts/ProtocolReserve/ProtocolShareReserve.sol/ProtocolShareReserve.json' assert { type: "json"}; -import vBnbAdminArtifact from '@venusprotocol/venus-protocol/artifacts/contracts/Admin/VBNBAdmin.sol/VBNBAdmin.json' assert { type: "json"}; -import vBNB from '@venusprotocol/venus-protocol/artifacts/contracts/Admin/VBNBAdminStorage.sol/VTokenInterface.json' assert { type: "json"}; +import ilComptrollerArtifact from "@venusprotocol/isolated-pools/artifacts/contracts/Comptroller.sol/Comptroller.json" assert { type: "json" }; +import poolLensArtifact from "@venusprotocol/isolated-pools/artifacts/contracts/Lens/PoolLens.sol/PoolLens.json" assert { type: "json" }; +import protocolShareReserveArtifact from "@venusprotocol/protocol-reserve/artifacts/contracts/ProtocolReserve/ProtocolShareReserve.sol/ProtocolShareReserve.json" assert { type: "json" }; +import vBnbAdminArtifact from "@venusprotocol/venus-protocol/artifacts/contracts/Admin/VBNBAdmin.sol/VBNBAdmin.json" assert { type: "json" }; +import vBNB from "@venusprotocol/venus-protocol/artifacts/contracts/Admin/VBNBAdminStorage.sol/VTokenInterface.json" assert { type: "json" }; +import diamondComptrollerArtifact from "@venusprotocol/venus-protocol/artifacts/contracts/Comptroller/Diamond/DiamondConsolidated.sol/DiamondConsolidated.json" assert { type: "json" }; +import coreVTokenArtifact from "@venusprotocol/venus-protocol/artifacts/contracts/Tokens/VTokens/VBep20Delegate.sol/VBep20Delegate.json" assert { type: "json" }; +import { defineConfig } from "@wagmi/cli"; +import { hardhat } from "@wagmi/cli/plugins"; +import { Abi } from "abitype"; - -const getExternalContracts = async (): Promise => [ +const getExternalContracts = async (): Promise<{ name: string; abi: Abi }[]> => [ { - abi: poolLensArtifact.abi, - name: 'PoolLens' + abi: poolLensArtifact.abi as Abi, + name: "PoolLens", }, { - abi: diamondComptrollerArtifact.abi, - name: 'CoreComptroller' + abi: diamondComptrollerArtifact.abi as Abi, + name: "CoreComptroller", }, { - abi: ilComptrollerArtifact.abi, - name: 'IlComptroller' + abi: ilComptrollerArtifact.abi as Abi, + name: "IlComptroller", }, { - abi: coreVTokenArtifact.abi, - name: 'CoreVToken' + abi: coreVTokenArtifact.abi as Abi, + name: "CoreVToken", }, { - abi: protocolShareReserveArtifact.abi, - name: 'ProtocolShareReserve' + abi: protocolShareReserveArtifact.abi as Abi, + name: "ProtocolShareReserve", }, { - abi: vBnbAdminArtifact.abi, - name: 'VBnbAdmin' + abi: vBnbAdminArtifact.abi as Abi, + name: "VBnbAdmin", }, { - abi: vBNB.abi, - name: 'vBNB' - } -] + abi: vBNB.abi as Abi, + name: "vBNB", + }, +]; export default defineConfig(async () => { - const externalContracts = await getExternalContracts() + const externalContracts = await getExternalContracts(); return { - out: 'src/config/abis/generated.ts', + out: "src/config/abis/generated.ts", contracts: externalContracts, plugins: [ hardhat({ - project: '.', + project: ".", }), ], - } -}) + }; +}); diff --git a/yarn.lock b/yarn.lock index ea18002c..78c58c71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3383,6 +3383,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^6.13.1 "@typescript-eslint/parser": ^6.13.1 "@venusprotocol/governance-contracts": ^1.4.0 + "@venusprotocol/isolated-pools": ^2.3.0 "@venusprotocol/oracle": ^1.7.3 "@venusprotocol/protocol-reserve": ^1.4.0 "@venusprotocol/solidity-utilities": ^1.1.0