import { web3 } from "hardhat"; import History from "../interface/history"; import contracts from "../../config/contracts"; import logger from "../../utils/logger"; import {BigNumber} from "ethers"; import {replaceAll} from "hardhat/internal/util/strings"; // 初始化410 v2工具箱 const v2ToolBy410Abi = require('../../abi/410_V2_TOOLS.json') const v2ToolBy410 = new web3.eth.Contract(v2ToolBy410Abi, contracts.V2_TOOLS_BY_410) // basetoken const baseTokenMap = require('../../config/base-token.json') const baseTokenAddressList = Object.keys(baseTokenMap) const ethTokenAddressList = baseTokenAddressList.filter(baseTokenAddress => { return baseTokenMap[baseTokenAddress].name.indexOf('ETH') != -1 }) export class LpMaintenance { tokenInstance: any = {} maxMemoryOfByte: number = 0 maxMemoryChanged: boolean = true // 计算价格要用 allMaxValueLpGroupBySum: any = {} baseTokenConvertEthValueMap: any = {} async saveLp(lp: any, type='0') { await History.appendOrUpdate(type, lp.LP, lp) } getLpEthValue(lp: any) { const token0 = lp.token0.toLowerCase() const token1 = lp.token1.toLowerCase() // 1.两token之一能直接与ethw或wethw做直接池子的 // 2.两token之一能直接与baseToken做池子的 // 这两种情况直接带入baseTokenConvertEthValueMap计算 for (const baseTokenAddress of baseTokenAddressList) { const token0AndBaseTokenSum = this.getHexSum(token0, baseTokenAddress.toLowerCase()) const token0AndBaseTokenLp = this.allMaxValueLpGroupBySum[token0AndBaseTokenSum] if (token0AndBaseTokenLp) { const priceMap: any = {} if (token0AndBaseTokenLp.token0.toLowerCase() == token0) { priceMap['0'] = token0AndBaseTokenLp.in1Token0OutToken1 * this.baseTokenConvertEthValueMap[baseTokenAddress] } else { priceMap['0'] = token0AndBaseTokenLp.in1Token1OutToken0 * this.baseTokenConvertEthValueMap[baseTokenAddress] } priceMap['1'] = 0 return priceMap } const token1AndBaseTokenSum = this.getHexSum(token1, baseTokenAddress.toLowerCase()) const token1AndBaseTokenLp = this.allMaxValueLpGroupBySum[token1AndBaseTokenSum] if (token1AndBaseTokenLp) { const priceMap: any = {} if (token1AndBaseTokenLp.token0.toLowerCase() == token1) { priceMap['1'] = token1AndBaseTokenLp.in1Token0OutToken1 * this.baseTokenConvertEthValueMap[baseTokenAddress] } else { priceMap['1'] = token1AndBaseTokenLp.in1Token1OutToken0 * this.baseTokenConvertEthValueMap[baseTokenAddress] } priceMap['0'] = 0 return priceMap } // lp.LP.toLowerCase() === '0x23103F8a0A9B8585762Ab3cD79e1667A1f5C163a'.toLowerCase() } // 3.两token都不能与baseToken做池子的 // 这种情况则视为lp类型池子 return {'0': 0, '1': 0} } async checkLpType(lp: any) { // lp过滤后类型表 const filterTypeList = [ { type: 'topLp', limit: 1 }, { type: 'normalLp', limit: 0.1 }, { type: 'lp', limit: 0 }, ] // token折合成eth的价格,分三种情况探讨 // 0: token0与eth的价格 // 1: token1与eth的价格 let tokenConvertEthPrice: any = this.getLpEthValue(lp) // 从高到低遍历过滤条件 const decimals0 = parseInt(lp.decimals0) const decimals1 = parseInt(lp.decimals1) const realAmount0 = parseInt(lp.r0) * tokenConvertEthPrice['0'] / Math.pow(10, decimals0) const realAmount1 = parseInt(lp.r1) * tokenConvertEthPrice['1'] / Math.pow(10, decimals1) for (const filterType of filterTypeList) { const amountLimit = filterType.limit if (realAmount0 >= amountLimit || realAmount1 >= amountLimit) { return filterType.type } } } async handleLp(lp: any, oldType: any) { // 过滤Lp let lpType = await this.checkLpType(lp) // 一共12w个池子,非ethw的垃圾池子就不拉了 if (lpType == 'lp' && !lp.isEthW) { lpType = 'ethLp' } if (lpType != oldType) { // 保存变更之后的的Token // await this.saveToken(lp, lpType) // 保存变更后的Lp await this.saveLp(lp, lpType) logger.debug(`lp:${lp.LP},${oldType}->${lpType}.`) } } showMemory (mainInfo: string) { const memoryUsage = process.memoryUsage() if (this.maxMemoryOfByte < memoryUsage.rss) { this.maxMemoryOfByte = memoryUsage.rss this.maxMemoryChanged = true } if (this.maxMemoryChanged) { logger.debug(`${mainInfo} ${this.format(memoryUsage.rss)}/${this.format(this.maxMemoryOfByte)}`) this.maxMemoryChanged = false } } format (bytes: any) { return (bytes / 1024 / 1024).toFixed(2) + ' MB'; } generateAddressListByLpList (lpList: any) { const lpAddressList: any = [] for (const lp of lpList) { lp.dataObj = JSON.parse(lp.data) lpAddressList.push(lp.dataObj.LP) } return lpAddressList } updateLocalLpListR0R1(lpList: any, r0s: any, r1s: any) { for (let index = 0; index < lpList.length; index++) { lpList[index].dataObj.r0 = r0s[index] lpList[index].dataObj.r1 = r1s[index] } } putAllLpGroupBySum(lpList: any) { this.allMaxValueLpGroupBySum = {} for (const lpDbObj of lpList) { const lp = lpDbObj.dataObj lp.sum2 = this.getHexSum(lp.token0, lp.token1) const notExist = !this.allMaxValueLpGroupBySum[lp.sum2] const moreValueThanNowMax = !notExist && (parseInt(lp.r0) > this.allMaxValueLpGroupBySum[lp.sum2].r0) if (notExist || moreValueThanNowMax) { const r0RealAmount = parseInt(lp.r0) / Math.pow(10, parseInt(lp.decimals0)) const r1RealAmount = parseInt(lp.r1) / Math.pow(10, parseInt(lp.decimals1)) // 计算0换1的价格和1换0的价格 lp.in1Token0OutToken1 = r1RealAmount / r0RealAmount // 一个token0 = in1Token0OutToken1个token1 lp.in1Token1OutToken0 = r0RealAmount / r1RealAmount // 一个token1 = oneForZeroPrice个token0 this.allMaxValueLpGroupBySum[lp.sum2] = lp } } } calcBaseTokenCovertEthValue() { for (const baseTokenAddress of baseTokenAddressList) { const isEthToken = ethTokenAddressList.indexOf(baseTokenAddress) != -1 // eth兑换eth价格当然是1:1啦 if (isEthToken) { this.baseTokenConvertEthValueMap[baseTokenAddress] = 1 } else { // 在ETH token中查找适合兑换的 for (const ethTokenAddress of ethTokenAddressList) { const maxValueLp = this.allMaxValueLpGroupBySum[this.getHexSum(baseTokenAddress, ethTokenAddress)] if (!maxValueLp) continue const token0 = maxValueLp.token0.toLowerCase() const token1 = maxValueLp.token1.toLowerCase() if (baseTokenAddress.toLowerCase() == token0) { this.baseTokenConvertEthValueMap[baseTokenAddress] = maxValueLp.in1Token0OutToken1 } else if (baseTokenAddress.toLowerCase() == token1) { this.baseTokenConvertEthValueMap[baseTokenAddress] = maxValueLp.in1Token1OutToken0 } } } } } getHexSum(hex0: string, hex1: string) { return replaceAll(BigNumber.from(hex0).add(BigNumber.from(hex1))._hex, '0x0', '0x') } async run() { logger.debug('LP maintenance start.') while (true) { this.showMemory('a loop...') try { const topLpPullRst = await History.findByBlock('topLp') if (!topLpPullRst.state) continue const topLpList = topLpPullRst.data const lpPullRst = await History.findByBlock('lp') if (!lpPullRst.state) continue const lpList = lpPullRst.data const normalLpPullRst = await History.findByBlock('normalLp') if (!normalLpPullRst.state) continue const normalLpList = normalLpPullRst.data const ethLpPullRst = await History.findByBlock('ethLp') if (!ethLpPullRst.state) continue const ethLpList = ethLpPullRst.data // const ethLpList: any = [] const allTypeLpList: any = topLpList.concat(lpList).concat(normalLpList).concat(ethLpList) // const allTypeLpList: any = [].concat(normalLpList).concat(ethLpList) const lpAddressList: any = this.generateAddressListByLpList(allTypeLpList) // lp按sum分类 this.putAllLpGroupBySum(allTypeLpList) // 集中拉取r0,r1并更新本地的 const size = 2000 for (let from = 0; from < allTypeLpList.length; from += size) { // logger.debug(`${from}, ${allTypeLpList.length}`) const lpR0R1Info: any = await v2ToolBy410.methods.getPairSBalance(lpAddressList.slice(from, from + size)).call() const r0s = lpR0R1Info.amounts0 const r1s = lpR0R1Info.amounts1 this.updateLocalLpListR0R1(allTypeLpList.slice(from, from + size), r0s, r1s) } this.calcBaseTokenCovertEthValue() // 将lp类型有变动的全部更新 logger.debug(`${allTypeLpList.length}`) for (const lpDbObj of allTypeLpList) { await this.handleLp(lpDbObj.dataObj, lpDbObj.block) } } catch (e) { logger.debug(e) } } } } async function main() { await new LpMaintenance().run() } main().catch((error) => { console.error(error); process.exitCode = 1; })