import { web3 } from "hardhat"; import History from "../interface/history"; import contracts from "../../config/contracts"; import logger from "../../utils/logger"; import {BigNumber} from "ethers"; // 初始化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 token0AndBaseTokenLp = this.allMaxValueLpGroupBySum[this.getHexSum(token0, baseTokenAddress.toLowerCase())] if (token0AndBaseTokenLp) { if (ethTokenAddressList.indexOf(baseTokenAddress.toLowerCase()) == -1) { logger.debug(`${lp}`) logger.debug(`0, ${token0AndBaseTokenLp.zeroToOnePrice}, ${this.baseTokenConvertEthValueMap[baseTokenAddress]}`) } return { '0': token0AndBaseTokenLp.zeroToOnePrice * this.baseTokenConvertEthValueMap[baseTokenAddress], '1': 1 / token0AndBaseTokenLp.zeroToOnePrice * this.baseTokenConvertEthValueMap[baseTokenAddress] } } const token1AndBaseTokenLp = this.allMaxValueLpGroupBySum[this.getHexSum(token1, baseTokenAddress.toLowerCase())] if (token1AndBaseTokenLp) { if (ethTokenAddressList.indexOf(baseTokenAddress.toLowerCase()) == -1) { logger.debug(`${lp}`) logger.debug(`1, ${token0AndBaseTokenLp.zeroToOnePrice}, ${this.baseTokenConvertEthValueMap[baseTokenAddress]}`) } return { '0': 1 / token1AndBaseTokenLp.oneToZeroPrice * this.baseTokenConvertEthValueMap[baseTokenAddress], '1': token1AndBaseTokenLp.oneToZeroPrice * this.baseTokenConvertEthValueMap[baseTokenAddress] } } } // 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的价格 let tokenConvertEthPrice: any = { '0': 1, '1': 1 } // token0和token1都不是eth的,分三种情况探讨 if (ethTokenAddressList.indexOf(lp.token0.toLowerCase()) == -1 && ethTokenAddressList.indexOf(lp.token1.toLowerCase()) == -1) { tokenConvertEthPrice = this.getLpEthValue(lp) } // 从高到低遍历过滤条件 const decimals0 = parseInt(lp.decimals0) const decimals1 = parseInt(lp.decimals0) 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 const notExist = !this.allMaxValueLpGroupBySum[lp.sum2] const moreValueThanNowMax = !notExist && (parseInt(lp.r0) > this.allMaxValueLpGroupBySum[lp.sum2].r0) if (notExist || moreValueThanNowMax) { // 计算0换1的价格和1换0的价格 lp.zeroToOnePrice = parseInt(lp.r1) / parseInt(lp.r0) // 一个token0 = zeroToOnePrice个token1 lp.oneToZeroPrice = parseInt(lp.r0) / parseInt(lp.r1) // 一个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.zeroToOnePrice } else if (baseTokenAddress.toLowerCase() == token1) { this.baseTokenConvertEthValueMap[baseTokenAddress] = maxValueLp.oneToZeroPrice } } } } } getHexSum(hex0: string, hex1: string) { return BigNumber.from(hex0).add(BigNumber.from(hex1))._hex } 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 allTypeLpList: any = topLpList.concat(lpList).concat(normalLpList) const lpAddressList: any = this.generateAddressListByLpList(allTypeLpList) // lp按sum分类 this.putAllLpGroupBySum(allTypeLpList) // logger.debug(`${lpAddressList.length}`) // 集中拉取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类型有变动的全部更新 for (const lpDbObj of allTypeLpList) { await this.handleLp(lpDbObj.dataObj, lpDbObj.block) } } catch (e) {} } } } async function main() { await new LpMaintenance().run() } main().catch((error) => { console.error(error); process.exitCode = 1; })