lpMaintenance.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { web3 } from "hardhat";
  2. import History from "../interface/history";
  3. import contracts from "../../config/contracts";
  4. import logger from "../../utils/logger";
  5. import {BigNumber} from "ethers";
  6. import {replaceAll} from "hardhat/internal/util/strings";
  7. import Time from "../../utils/time";
  8. import MemoryUtils from "../../utils/memory";
  9. // 初始化410 v2工具箱
  10. const v2ToolBy410Abi = require('../../abi/410_V2_TOOLS.json')
  11. const v2ToolBy410 = new web3.eth.Contract(v2ToolBy410Abi, contracts.V2_TOOLS_BY_410)
  12. // basetoken
  13. const baseTokenMap = require('../../config/base-token.json')
  14. const baseTokenAddressList = Object.keys(baseTokenMap)
  15. const ethTokenAddressList = baseTokenAddressList.filter(baseTokenAddress => {
  16. return baseTokenMap[baseTokenAddress].name.indexOf('ETH') != -1
  17. })
  18. export class LpMaintenance {
  19. tokenAssembly: any = {}
  20. memoryUtils: MemoryUtils = new MemoryUtils()
  21. prevUpdateEthLpHours: number = -1
  22. // 计算价格要用
  23. allMaxValueLpGroupBySum: any = {}
  24. baseTokenConvertEthValueMap: any = {}
  25. async saveLp(lp: any, type='0') {
  26. await History.appendOrUpdate(type, lp.LP, lp)
  27. }
  28. getLpEthValue(lp: any) {
  29. const token0 = lp.token0.toLowerCase()
  30. const token1 = lp.token1.toLowerCase()
  31. if (ethTokenAddressList.indexOf(token0) !== -1 || ethTokenAddressList.indexOf(token1) !== -1) {
  32. return {
  33. '0': ethTokenAddressList.indexOf(token0) !== -1 ? 1 : 0,
  34. '1': ethTokenAddressList.indexOf(token1) !== -1 ? 1 : 0
  35. }
  36. }
  37. // 1.两token之一能直接与ethw或wethw做直接池子的
  38. // 2.两token之一能直接与baseToken做池子的
  39. // 这两种情况直接带入baseTokenConvertEthValueMap计算
  40. for (const baseTokenAddress of baseTokenAddressList) {
  41. const token0AndBaseTokenSum = this.getHexSum(token0, baseTokenAddress.toLowerCase())
  42. const token0AndBaseTokenLp = this.allMaxValueLpGroupBySum[token0AndBaseTokenSum]
  43. if (token0AndBaseTokenLp) {
  44. const priceMap: any = {}
  45. if (token0AndBaseTokenLp.token0.toLowerCase() == token0) {
  46. priceMap['0'] = token0AndBaseTokenLp.in1Token0OutToken1 * this.baseTokenConvertEthValueMap[baseTokenAddress]
  47. } else {
  48. priceMap['0'] = token0AndBaseTokenLp.in1Token1OutToken0 * this.baseTokenConvertEthValueMap[baseTokenAddress]
  49. }
  50. priceMap['1'] = 0
  51. return priceMap
  52. }
  53. const token1AndBaseTokenSum = this.getHexSum(token1, baseTokenAddress.toLowerCase())
  54. const token1AndBaseTokenLp = this.allMaxValueLpGroupBySum[token1AndBaseTokenSum]
  55. if (token1AndBaseTokenLp) {
  56. const priceMap: any = {}
  57. if (token1AndBaseTokenLp.token0.toLowerCase() == token1) {
  58. priceMap['1'] = token1AndBaseTokenLp.in1Token0OutToken1 * this.baseTokenConvertEthValueMap[baseTokenAddress]
  59. } else {
  60. priceMap['1'] = token1AndBaseTokenLp.in1Token1OutToken0 * this.baseTokenConvertEthValueMap[baseTokenAddress]
  61. }
  62. priceMap['0'] = 0
  63. return priceMap
  64. }
  65. // lp.LP.toLowerCase() === '0x23103F8a0A9B8585762Ab3cD79e1667A1f5C163a'.toLowerCase()
  66. }
  67. // 3.两token都不能与baseToken做池子的
  68. // 这种情况则视为lp类型池子
  69. return {'0': 0, '1': 0}
  70. }
  71. async checkLpType(lp: any) {
  72. // lp过滤后类型表
  73. const filterTypeList = [
  74. { type: 'topLp', limit: 10 },
  75. { type: 'normalLp', limit: 1 },
  76. { type: 'lp', limit: 0 },
  77. ]
  78. // token折合成eth的价格,分三种情况探讨
  79. // 0: token0与eth的价格
  80. // 1: token1与eth的价格
  81. let tokenConvertEthPrice: any = this.getLpEthValue(lp)
  82. // 从高到低遍历过滤条件
  83. const decimals0 = parseInt(lp.decimals0)
  84. const decimals1 = parseInt(lp.decimals1)
  85. const realAmount0 = parseInt(lp.r0) * tokenConvertEthPrice['0'] / Math.pow(10, decimals0)
  86. const realAmount1 = parseInt(lp.r1) * tokenConvertEthPrice['1'] / Math.pow(10, decimals1)
  87. for (const filterType of filterTypeList) {
  88. const amountLimit = filterType.limit
  89. if (realAmount0 >= amountLimit || realAmount1 >= amountLimit) {
  90. return filterType.type
  91. }
  92. }
  93. }
  94. async checkTokenByAddress(tokenAddress: any, newType: string) {
  95. const tokenDbOBj = this.tokenAssembly[tokenAddress]
  96. if (tokenDbOBj && this.needUpdate(tokenDbOBj.type, newType)) {
  97. logger.debug(`token:${tokenAddress},${tokenDbOBj.type}->${newType}.`)
  98. tokenDbOBj.type = newType
  99. await History.appendOrUpdate(tokenDbOBj.type, tokenDbOBj.hash, tokenDbOBj.data)
  100. }
  101. }
  102. async handleLp(lp: any, oldType: any) {
  103. // 过滤Lp
  104. let lpType: any = await this.checkLpType(lp)
  105. let tokenType = 'token'
  106. if (lpType != 'lp') tokenType = replaceAll(lpType, 'Lp', '') + 'Token'
  107. // 一共12w个池子,非ethw的垃圾池子就不拉了
  108. if (lpType == 'lp' && !lp.isEthW) {
  109. lpType = 'ethLp'
  110. }
  111. if (lpType != oldType) {
  112. // 保存变更后的Lp
  113. await this.saveLp(lp, lpType)
  114. logger.debug(`lp:${lp.LP},${oldType}->${lpType}\t[${lp.r0}, ${lp.r1}].`)
  115. }
  116. await this.checkTokenByAddress(lp.token0, tokenType)
  117. await this.checkTokenByAddress(lp.token1, tokenType)
  118. }
  119. generateAddressListByLpList (lpList: any) {
  120. const lpAddressList: any = []
  121. for (const lp of lpList) {
  122. if (!lp.dataObj) lp.dataObj = JSON.parse(lp.data)
  123. lpAddressList.push(lp.dataObj.LP)
  124. }
  125. return lpAddressList
  126. }
  127. updateLocalLpListR0R1(lpList: any, r0s: any, r1s: any) {
  128. for (let index = 0; index < lpList.length; index++) {
  129. const lp = lpList[index].dataObj
  130. if ((lp.symbol0 === 'ERR' && parseInt(lp.decimals0) === 0)
  131. || (lp.symbol1 === 'ERR' && parseInt(lp.decimals1) === 0)) {
  132. lp.r0 = 0
  133. lp.r1 = 0
  134. } else {
  135. lp.r0 = r0s[index]
  136. lp.r1 = r1s[index]
  137. }
  138. }
  139. }
  140. putAllLpGroupBySum(lpList: any) {
  141. this.allMaxValueLpGroupBySum = {}
  142. for (const lpDbObj of lpList) {
  143. const lp = lpDbObj.dataObj
  144. lp.sum2 = this.getHexSum(lp.token0, lp.token1)
  145. const notExist = !this.allMaxValueLpGroupBySum[lp.sum2]
  146. const moreValueThanNowMax = !notExist && (parseInt(lp.r0) > this.allMaxValueLpGroupBySum[lp.sum2].r0)
  147. if (notExist || moreValueThanNowMax) {
  148. const r0RealAmount = parseInt(lp.r0) / Math.pow(10, parseInt(lp.decimals0))
  149. const r1RealAmount = parseInt(lp.r1) / Math.pow(10, parseInt(lp.decimals1))
  150. // 计算0换1的价格和1换0的价格
  151. lp.in1Token0OutToken1 = r1RealAmount / r0RealAmount // 一个token0 = in1Token0OutToken1个token1
  152. lp.in1Token1OutToken0 = r0RealAmount / r1RealAmount // 一个token1 = oneForZeroPrice个token0
  153. this.allMaxValueLpGroupBySum[lp.sum2] = lp
  154. }
  155. }
  156. }
  157. needUpdate(oldType: string, newType: string) {
  158. const tokenTypeMap: any = {
  159. 'token': 1,
  160. 'normalToken': 2,
  161. 'topToken': 3
  162. }
  163. return tokenTypeMap[newType] > tokenTypeMap[oldType]
  164. }
  165. async pullAllToken(lpList: any) {
  166. for (const lpDbObj of lpList) {
  167. const lp = lpDbObj.dataObj
  168. const lpType = lpDbObj.block
  169. let tokenType = 'token'
  170. if (lpType != 'lp') tokenType = replaceAll(lpType, 'Lp', '') + 'Token'
  171. if (!this.tokenAssembly[lp.token0]) {
  172. const token0Rst = await History.findByHash(lp.token0)
  173. const token0DbObj = token0Rst.data[0]
  174. if (token0DbObj) {
  175. this.tokenAssembly[lp.token0] = {
  176. type: token0DbObj.block,
  177. hash: token0DbObj.hash,
  178. data: JSON.parse(token0DbObj.data)
  179. }
  180. }
  181. }
  182. if (!this.tokenAssembly[lp.token1]) {
  183. const token1Rst = await History.findByHash(lp.token1)
  184. const token1DbObj = token1Rst.data[0]
  185. if (token1DbObj) {
  186. this.tokenAssembly[lp.token1] = {
  187. type: token1DbObj.block,
  188. hash: token1DbObj.hash,
  189. data: JSON.parse(token1DbObj.data)
  190. }
  191. }
  192. }
  193. }
  194. }
  195. calcBaseTokenCovertEthValue() {
  196. for (const baseTokenAddress of baseTokenAddressList) {
  197. const isEthToken = ethTokenAddressList.indexOf(baseTokenAddress) != -1
  198. // eth兑换eth价格当然是1:1啦
  199. if (isEthToken) {
  200. this.baseTokenConvertEthValueMap[baseTokenAddress] = 1
  201. } else {
  202. // 在ETH token中查找适合兑换的
  203. for (const ethTokenAddress of ethTokenAddressList) {
  204. const maxValueLp = this.allMaxValueLpGroupBySum[this.getHexSum(baseTokenAddress, ethTokenAddress)]
  205. if (!maxValueLp) continue
  206. const token0 = maxValueLp.token0.toLowerCase()
  207. const token1 = maxValueLp.token1.toLowerCase()
  208. if (baseTokenAddress.toLowerCase() == token0) {
  209. this.baseTokenConvertEthValueMap[baseTokenAddress] = maxValueLp.in1Token0OutToken1
  210. } else if (baseTokenAddress.toLowerCase() == token1) {
  211. this.baseTokenConvertEthValueMap[baseTokenAddress] = maxValueLp.in1Token1OutToken0
  212. }
  213. }
  214. }
  215. }
  216. }
  217. getHexSum(hex0: string, hex1: string) {
  218. return replaceAll(BigNumber.from(hex0).add(BigNumber.from(hex1))._hex, '0x0', '0x')
  219. }
  220. needToCheckEthLp() {
  221. const hours = new Date().getHours()
  222. if (this.prevUpdateEthLpHours != hours) {
  223. this.prevUpdateEthLpHours = hours
  224. return true
  225. }
  226. return false
  227. }
  228. async run() {
  229. logger.debug('LP maintenance start.')
  230. while (true) {
  231. this.memoryUtils.logWithMemoryOnMemoryChange('a loop...')
  232. try {
  233. // toplp获取信息
  234. const topLpPullRst = await History.findByBlock('topLp')
  235. if (!topLpPullRst.state) continue
  236. const topLpList = topLpPullRst.data
  237. // lp获取信息
  238. const lpPullRst = await History.findByBlock('lp')
  239. if (!lpPullRst.state) continue
  240. const lpList = lpPullRst.data
  241. // normalLp获取息p
  242. const normalLpPullRst = await History.findByBlock('normalLp')
  243. if (!normalLpPullRst.state) continue
  244. const normalLpList = normalLpPullRst.data
  245. // 信息串联
  246. let allTypeLpList: any = topLpList.concat(lpList).concat(normalLpList)
  247. // 信息的ethLp获取需要是否
  248. const needToCheckEthLp = this.needToCheckEthLp()
  249. if (needToCheckEthLp) {
  250. const ethLpPullRst = await History.findByBlock('ethLp')
  251. if (!ethLpPullRst.state) continue
  252. const ethLpList = ethLpPullRst.data
  253. allTypeLpList = allTypeLpList.concat(ethLpList)
  254. logger.debug(`maintenance eth lp / hour.${allTypeLpList.length}`)
  255. }
  256. const lpAddressList: any = this.generateAddressListByLpList(allTypeLpList)
  257. // lp按sum分类
  258. this.putAllLpGroupBySum(allTypeLpList)
  259. // 获取所有lp的未拉取的token
  260. await this.pullAllToken(allTypeLpList)
  261. // 集中拉取r0,r1并更新本地的
  262. const size = needToCheckEthLp ? 500 : 2000
  263. for (let from = 0; from < allTypeLpList.length; from += size) {
  264. // logger.debug(`${from}, ${allTypeLpList.length}`)
  265. if (needToCheckEthLp) await Time.delay(168)
  266. const lpR0R1Info: any = await v2ToolBy410.methods.getPairSBalance(lpAddressList.slice(from, from + size)).call()
  267. const r0s = lpR0R1Info.amounts0
  268. const r1s = lpR0R1Info.amounts1
  269. this.updateLocalLpListR0R1(allTypeLpList.slice(from, from + size), r0s, r1s)
  270. }
  271. this.calcBaseTokenCovertEthValue()
  272. // 将lp类型有变动的全部更新
  273. // logger.debug(`${allTypeLpList.length}`)
  274. for (const lpDbObj of allTypeLpList) {
  275. await this.handleLp(lpDbObj.dataObj, lpDbObj.block)
  276. }
  277. } catch (e) {
  278. logger.debug(e)
  279. }
  280. await Time.delay(12000)
  281. }
  282. }
  283. }
  284. async function main() {
  285. await new LpMaintenance().run()
  286. }
  287. main().catch((error) => {
  288. console.error(error);
  289. process.exitCode = 1;
  290. })