From d90b321ea588275b3662ba85d05d9ef4d7debd77 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 3 May 2023 13:58:16 +0100 Subject: [PATCH 01/78] Basic addition of b-sdk. --- modules/sor/sor.gql | 19 +++ modules/sor/sor.resolvers.ts | 13 ++ modules/sor/sor.service.ts | 52 ++++++++ modules/sor/sorV1/sorV1.service.ts | 0 modules/sor/sorV2/sorV2.service.ts | 85 ++++++++++++ package.json | 1 + yarn.lock | 202 ++++++++++++++++++++++++++++- 7 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 modules/sor/sor.gql create mode 100644 modules/sor/sor.resolvers.ts create mode 100644 modules/sor/sor.service.ts create mode 100644 modules/sor/sorV1/sorV1.service.ts create mode 100644 modules/sor/sorV2/sorV2.service.ts diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql new file mode 100644 index 000000000..8175a87ee --- /dev/null +++ b/modules/sor/sor.gql @@ -0,0 +1,19 @@ +extend type Query { + sorGetSwapsNew( + tokenIn: String! + tokenOut: String! + swapType: GqlSorSwapType! + swapAmount: BigDecimal! #expected in raw amount + ): GqlSorGetSwapsResponseNew! +} + +enum GqlSorSwapType { + EXACT_IN + EXACT_OUT +} + +type GqlSorGetSwapsResponseNew { + tokenIn: String! + tokenOut: String! + result: String! +} diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts new file mode 100644 index 000000000..2b80e8494 --- /dev/null +++ b/modules/sor/sor.resolvers.ts @@ -0,0 +1,13 @@ +import { Resolvers } from '../../schema'; +import { sorService as sorService } from './sor.service'; +import { tokenService } from '../token/token.service'; + +const sorResolvers: Resolvers = { + Query: { + sorGetSwapsNew: async (parent, args, context) => { + return sorService.getSwaps({ ...args }); + }, + }, +}; + +export default sorResolvers; diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts new file mode 100644 index 000000000..dc7128285 --- /dev/null +++ b/modules/sor/sor.service.ts @@ -0,0 +1,52 @@ +import { GqlSorGetSwapsResponse, GqlSorGetSwapsResponseNew, GqlSorSwapOptionsInput, GqlSorSwapType } from '../../schema'; +import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { PrismaToken } from '@prisma/client'; +import { poolService } from '../pool/pool.service'; +import { oldBnum } from '../big-number/old-big-number'; +import axios from 'axios'; +import { FundManagement, SwapInfo, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; +import { replaceEthWithZeroAddress, replaceZeroAddressWithEth } from '../web3/addresses'; +import { BigNumber } from 'ethers'; +import { TokenAmountHumanReadable } from '../common/global-types'; +import { AddressZero } from '@ethersproject/constants'; +import { Contract } from '@ethersproject/contracts'; +import VaultAbi from '../pool/abi/Vault.json'; +import { env } from '../../app/env'; +import { networkContext } from '../network/network-context.service'; +import { DeploymentEnv } from '../network/network-config-types'; +import * as Sentry from '@sentry/node'; +import _ from 'lodash'; +import { Logger } from '@ethersproject/logger'; +import { sorV2Service } from './sorV2/sorV2.service'; + +export interface GetSwapsInput { + tokenIn: string; + tokenOut: string; + swapType: GqlSorSwapType; + swapAmount: string; +} + +export class SorService { + public async getSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }: GetSwapsInput): Promise { + console.log(`!!!!!!! getSwaps`); + const result = await sorV2Service.getSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }); + return { + tokenIn, + tokenOut, + result: result.result + } + + } +} + +export const sorService = new SorService(); diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts new file mode 100644 index 000000000..e69de29bb diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts new file mode 100644 index 000000000..5b83d44aa --- /dev/null +++ b/modules/sor/sorV2/sorV2.service.ts @@ -0,0 +1,85 @@ +import { BasePool, SubgraphPoolProvider, ChainId, OnChainPoolDataEnricher, SmartOrderRouter, sorGetSwapsWithPools, Token, Address, SwapKind } from '@balancer/sdk'; +import { GqlSorGetSwapsResponseNew } from '../../../schema'; +import { PrismaToken } from '@prisma/client'; +import { GetSwapsInput } from '../sor.service'; +import { tokenService } from '../../token/token.service'; +import { networkContext } from '../../network/network-context.service'; + +// TODO - Check if this has been deployed to same address across networks? +const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; + +export class SorV2Service { + private async getToken(tokenAddr: Address, chainId: ChainId): Promise { + const tokens = await tokenService.getTokens(); + const prismaToken = this.getPrismaToken(tokenAddr, tokens); + return new Token( + chainId, + tokenAddr, + prismaToken.decimals, + prismaToken.symbol, + ); + + } + + private getPrismaToken(tokenAddress: string, tokens: PrismaToken[]): PrismaToken { + tokenAddress = tokenAddress.toLowerCase(); + const match = tokens.find((token) => token.address === tokenAddress); + + if (!match) { + throw new Error('Unknown token: ' + tokenAddress); + } + return match; + } + + public async getSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }: GetSwapsInput): Promise { + const pools = await this.getPools(); + const chainId = networkContext.chainId as unknown as ChainId; + const tIn = await this.getToken(tokenIn as Address, chainId); + const tOut = await this.getToken(tokenOut as Address, chainId); + const swap = await sorGetSwapsWithPools( + tIn, + tOut, + SwapKind.GivenIn, + swapAmount, + pools, + // swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + console.log(`Swap`); + console.log(swap.swaps); + const result = swap.outputAmount.amount.toString(); + return { + tokenIn, + tokenOut, + result + } + } + + private async getPools(): Promise { + // TODO - Can map this from existing pool data? Or make separate cron job? + console.log(`Fetching pools...`); + const chainId = networkContext.chainId as unknown as ChainId; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); + const onChainPoolDataEnricher = new OnChainPoolDataEnricher( + networkContext.data.rpcUrl, + SOR_QUERIES, + ); + + const sor = new SmartOrderRouter({ + chainId: chainId, + poolDataProviders: subgraphPoolDataService, + poolDataEnrichers: onChainPoolDataEnricher, + rpcUrl: networkContext.data.rpcUrl, + }); + const pools = await sor.fetchAndCachePools(); + return pools; + } +} + +export const sorV2Service = new SorV2Service(); \ No newline at end of file diff --git a/package.json b/package.json index dbc1c7ffc..eee79e5cc 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", + "@balancer/sdk": "^0.0.1", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", diff --git a/yarn.lock b/yarn.lock index 2891218d7..23d5571c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d" + integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -2553,6 +2558,16 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" +"@balancer/sdk@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.0.1.tgz#484147f73c29d58a2df470e9a58a465e54d8e4c8" + integrity sha512-v2smtCc7QXHWysxLQXQlyfFflmIl3ZsP2NNVKLirjn6nTYmKJRBvphfhKdV7CmCHaTRAI/FTaxnQ7/Zirre2Ug== + dependencies: + async-retry "^1.3.3" + decimal.js-light "^2.5.1" + pino "^8.11.0" + viem "^0.2.1" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3716,6 +3731,25 @@ resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" integrity sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg== +"@noble/curves@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.9.0.tgz#d59713ecbb6a77381de84fb8969381fa85a7380b" + integrity sha512-OAdtHMXBp7Chl2lcTn/i7vnFX/q+hhTwDnek5NfYfZsY4LyaUuHCcoq2JlLY3BTFTLT+ZhYZalhF6ejlV7KnJQ== + dependencies: + "@noble/hashes" "1.3.0" + +"@noble/curves@~0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.8.3.tgz#ad6d48baf2599cf1d58dcb734c14d5225c8996e0" + integrity sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ== + dependencies: + "@noble/hashes" "1.3.0" + +"@noble/hashes@1.3.0", "@noble/hashes@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3863,6 +3897,28 @@ resolved "https://registry.yarnpkg.com/@sanity/timed-out/-/timed-out-4.0.2.tgz#c9f61f9a1609baa1eb3e4235a24ea2a775022cdf" integrity sha512-NBDKGj14g9Z+bopIvZcQKWCzJq5JSrdmzRR1CS+iyA3Gm8SnIWBfZa7I3mTg2X6Nu8LQXG0EPKXdOGozLS4i3w== +"@scure/base@~1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@scure/bip32@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.2.0.tgz#35692d8f8cc3207200239fc119f9e038e5f465df" + integrity sha512-O+vT/hBVk+ag2i6j2CDemwd1E1MtGt+7O1KzrPNsaNvSsiEK55MyPIxJIMI2PS8Ijj464B2VbQlpRoQXxw1uHg== + dependencies: + "@noble/curves" "~0.8.3" + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b" + integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@sentry/core@7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.29.0.tgz#bc4b54d56cf7652598d4430cf43ea97cc069f6fe" @@ -4388,6 +4444,11 @@ dependencies: "@types/yargs-parser" "*" +"@wagmi/chains@0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-0.2.16.tgz#a726716e4619ec1c192b312e23f9c38407617aa0" + integrity sha512-rkWaI2PxCnbD8G07ZZff5QXftnSkYL0h5f4DkHCG3fGYYr/ZDvmCL4bMae7j7A9sAif1csPPBmbCzHp3R5ogCQ== + "@xmldom/xmldom@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" @@ -4403,6 +4464,11 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abitype@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" + integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -4692,7 +4758,7 @@ asn1@^0.2.4: dependencies: safer-buffer "~2.1.0" -async-retry@^1.2.1: +async-retry@^1.2.1, async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -5073,6 +5139,14 @@ buffer@^5.5.0, buffer@^5.7.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -5703,6 +5777,11 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.3.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" @@ -6275,6 +6354,11 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-redact@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" + integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== + fast-text-encoding@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" @@ -6982,7 +7066,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7482,6 +7566,11 @@ isomorphic-ws@4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -8772,6 +8861,11 @@ obliterator@^2.0.2: resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== +on-exit-leak-free@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" + integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -9016,6 +9110,36 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@v1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" + integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-std-serializers@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.0.tgz#169048c0df3f61352fce56aeb7fb962f1b66ab43" + integrity sha512-IWgSzUL8X1w4BIWTwErRgtV8PyOGOOi60uqv0oKuS/fOA8Nco/OeI6lBuc4dyP8MMfdFwyHqTMcBIA7nDiqEqA== + +pino@^8.11.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.12.0.tgz#03039e1cbf42dee787d5f66d74ee88b16ab43990" + integrity sha512-qHiXP7x9hFnJb0EnrLzD4onHj82Vh91lEtx6UiPT2eNoHD7lwi50CUIz+s0Z7YXgtMc7IIjss4yIIxZu6T1Tdg== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.0.0 + pino-std-serializers "^6.0.0" + process-warning "^2.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.1.0" + thread-stream "^2.0.0" + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -9075,6 +9199,16 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-warning@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626" + integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + progress-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-2.0.0.tgz#fac63a0b3d11deacbb0969abcc93b214bce19ed5" @@ -9155,6 +9289,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + random-words@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/random-words/-/random-words-1.2.0.tgz#94d0cc8061965efe955d60b80ad93392a7edf2f5" @@ -9224,6 +9363,16 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" + integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -9238,6 +9387,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -9470,6 +9624,11 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -9675,6 +9834,13 @@ sonic-boom@^2.1.0: dependencies: atomic-sleep "^1.0.0" +sonic-boom@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c" + integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g== + dependencies: + atomic-sleep "^1.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -9716,6 +9882,11 @@ split-ca@^1.0.1: resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sponge-case@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c" @@ -10051,6 +10222,13 @@ testcontainers@^8.0.0: ssh-remote-port-forward "^1.0.4" tar-fs "^2.1.1" +thread-stream@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33" + integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA== + dependencies: + real-require "^0.2.0" + through2@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -10410,6 +10588,21 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +viem@^0.2.1: + version "0.2.14" + resolved "https://registry.yarnpkg.com/viem/-/viem-0.2.14.tgz#3a010b7bb6dedf83b0aa61a636e4c2c30d476029" + integrity sha512-hHlmzEvOQC/wDqcQlexjczxHSqcPlQrS/VKJwGv53Pp+Aap+SeULC3QBycW+YmQ2trh0RfV0xa53u7Qd/KDJog== + dependencies: + "@adraffy/ens-normalize" "1.9.0" + "@noble/curves" "0.9.0" + "@noble/hashes" "1.3.0" + "@scure/bip32" "1.2.0" + "@scure/bip39" "1.2.0" + "@wagmi/chains" "0.2.16" + abitype "0.7.1" + isomorphic-ws "5.0.0" + ws "8.12.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -10547,6 +10740,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" + integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== + ws@8.2.3: version "8.2.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" From 8f62a6c9dc2a9051d949e6020ee5247c30456cb0 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 8 May 2023 10:20:33 +0100 Subject: [PATCH 02/78] Uses pools from Prisma db. --- modules/sor/sorV2/sorV2.service.ts | 252 +++++++++++++++++++++++------ modules/sor/sorV2/temp.ts | 57 +++++++ prisma/prisma-types.ts | 18 +++ 3 files changed, 279 insertions(+), 48 deletions(-) create mode 100644 modules/sor/sorV2/temp.ts diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 5b83d44aa..dd271925d 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -1,85 +1,241 @@ -import { BasePool, SubgraphPoolProvider, ChainId, OnChainPoolDataEnricher, SmartOrderRouter, sorGetSwapsWithPools, Token, Address, SwapKind } from '@balancer/sdk'; -import { GqlSorGetSwapsResponseNew } from '../../../schema'; -import { PrismaToken } from '@prisma/client'; +import { + BasePool, + ChainId, + sorGetSwapsWithPools, + Token, + Address, + SwapKind, + sorParseRawPools, + RawStablePool, + RawLinearPool, + RawWeightedPool, + RawComposableStablePool, + RawMetaStablePool +} from '@balancer/sdk'; +import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../../schema'; +import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput } from '../sor.service'; import { tokenService } from '../../token/token.service'; import { networkContext } from '../../network/network-context.service'; +import { prisma } from '../../../prisma/prisma-client'; +import { PrismaPoolWithDynamic, prismaPoolWithDynamic } from '../../../prisma/prisma-types'; +import { RawPool } from '@balancer/sdk'; +import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; + +import { getSwapCompare } from './temp'; // TODO - Check if this has been deployed to same address across networks? -const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; +export const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; export class SorV2Service { - private async getToken(tokenAddr: Address, chainId: ChainId): Promise { - const tokens = await tokenService.getTokens(); - const prismaToken = this.getPrismaToken(tokenAddr, tokens); - return new Token( - chainId, - tokenAddr, - prismaToken.decimals, - prismaToken.symbol, - ); - - } - - private getPrismaToken(tokenAddress: string, tokens: PrismaToken[]): PrismaToken { - tokenAddress = tokenAddress.toLowerCase(); - const match = tokens.find((token) => token.address === tokenAddress); - - if (!match) { - throw new Error('Unknown token: ' + tokenAddress); - } - return match; - } - public async getSwaps({ tokenIn, tokenOut, swapType, swapAmount, }: GetSwapsInput): Promise { - const pools = await this.getPools(); + // TODO - This takes ~1.262s on my local machine. Is this fast enough? Any obvious ways to improve? + console.time('poolsFromDb'); + const poolsFromDb = await this.getBasePoolsFromDb(); + console.timeEnd('poolsFromDb'); const chainId = networkContext.chainId as unknown as ChainId; const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); const swap = await sorGetSwapsWithPools( tIn, tOut, - SwapKind.GivenIn, + this.mapSwapType(swapType), swapAmount, - pools, - // swapOptions, + poolsFromDb, + // swapOptions, // TODO - Handle properly ); if (!swap) throw new Error('Swap is undefined'); - console.log(`Swap`); + console.log(`Swap (db pools)`); + console.log(poolsFromDb.length); console.log(swap.swaps); - const result = swap.outputAmount.amount.toString(); + console.log(swap.outputAmount.amount.toString()); + console.log(`------------------`); + + // Just using to compare results against onchain pool calls + // await getSwapCompare(tIn, tOut, swapAmount); + + // TODO + // - Query SORV1 (via API call or using SOR/pools directly?) + // - Compare and choose best result + // - Log best result in Prisma + // - Return best result matching existing CowSwap SOR API format so its plug and play return { tokenIn, tokenOut, - result + result: swap.outputAmount.amount.toString() } } - private async getPools(): Promise { - // TODO - Can map this from existing pool data? Or make separate cron job? - console.log(`Fetching pools...`); - const chainId = networkContext.chainId as unknown as ChainId; - const subgraphPoolDataService = new SubgraphPoolProvider(chainId); - const onChainPoolDataEnricher = new OnChainPoolDataEnricher( - networkContext.data.rpcUrl, - SOR_QUERIES, + /** + * Gets a b-sdk Token based off tokenAddr. + * @param tokenAddr + * @param chainId + * @returns + */ + private async getToken(tokenAddr: Address, chainId: ChainId): Promise { + const tokens = await tokenService.getTokens(); + const prismaToken = this.getPrismaToken(tokenAddr, tokens); + return new Token( + chainId, + tokenAddr, + prismaToken.decimals, + prismaToken.symbol, ); + } + + private getPrismaToken(tokenAddress: string, tokens: PrismaToken[]): PrismaToken { + tokenAddress = tokenAddress.toLowerCase(); + const match = tokens.find((token) => token.address === tokenAddress); + + if (!match) { + throw new Error('Unknown token: ' + tokenAddress); + } + return match; + } + + private mapSwapType(swapType: GqlSorSwapType): SwapKind { + return swapType === "EXACT_IN" ? SwapKind.GivenIn : SwapKind.GivenOut; + } + + /** + * Fetch pools from Prisma and map to b-sdk BasePool. + * @returns + */ + private async getBasePoolsFromDb(): Promise { + const pools = await prisma.prismaPool.findMany({ + where: { + chain: networkContext.chain, + dynamicData: { + totalSharesNum: { + gt: 0.000000000001, + }, + swapEnabled: true, + }, + }, + include: prismaPoolWithDynamic.include + }); + const rawPools = this.mapToRawPools(pools); + // TODO - The pools below cause issue with b-sdk maths. Both have a token with balance = 0 so maybe that's issue? + const linearPoolsToIgnore = [ + "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", + "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", + ] - const sor = new SmartOrderRouter({ - chainId: chainId, - poolDataProviders: subgraphPoolDataService, - poolDataEnrichers: onChainPoolDataEnricher, - rpcUrl: networkContext.data.rpcUrl, + const basePools = this.mapToBasePools(rawPools.filter(p => { + if (p.poolType == 'Linear') { + return !linearPoolsToIgnore.includes(p.id); + } else return true; + })); + // const basePools = this.mapToBasePools(rawPools); + return basePools; + } + + /** + * Map Prisma pools to b-sdk RawPool. + * @param pools + * @returns + */ + private mapToRawPools(pools: PrismaPoolWithDynamic[]): RawPool[] { + return pools.map(prismaPool => { + // b-sdk: src/data/types.ts + let rawPool: RawPool = { + id: prismaPool.id as Address, + address: prismaPool.address as Address, + poolType: this.mapRawPoolType(prismaPool.type), + poolTypeVersion: 1, // TODO - Can we add this to Prisma?? + tokensList: prismaPool.tokens.map(t => t.address as Address), + swapEnabled: prismaPool.dynamicData!.swapEnabled, + swapFee: prismaPool.dynamicData!.swapFee as unknown as HumanAmount, + totalShares: prismaPool.dynamicData!.totalShares as unknown as HumanAmount, + liquidity: prismaPool.dynamicData!.totalLiquidity as unknown as HumanAmount, + tokens: prismaPool.tokens.map(t => { + return { + address: t.token.address as Address, + index: t.index, + symbol: t.token.symbol, + name: t.token.name, + decimals: t.token.decimals, + balance: t.dynamicData?.balance as unknown as HumanAmount + } + }), + }; + if(["Weighted", "Investment", "LiquidityBootstrapping"].includes(rawPool.poolType)) { + rawPool = { + ...rawPool, + tokens: rawPool.tokens.map((t, i) => { return {...t, weight: prismaPool.tokens[i].dynamicData?.weight} }), + } as RawWeightedPool; + } + if(rawPool.poolType === "Stable") { + rawPool = { + ...rawPool, + amp: prismaPool.stableDynamicData?.amp, + } as RawStablePool; + } + if(["MetaStable", "ComposableStable"].includes(rawPool.poolType)) { + rawPool = { + ...rawPool, + amp: prismaPool.stableDynamicData?.amp.split('.')[0], // Taken b-sdk onChainPoolDataEnricher.ts + tokens: rawPool.tokens.map((t, i) => { return {...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate} }), + } as RawMetaStablePool; + } + if(rawPool.poolType === "Linear") { + rawPool = { + ...rawPool, + mainIndex: prismaPool.linearData?.mainIndex, + wrappedIndex: prismaPool.linearData?.wrappedIndex, + lowerTarget: prismaPool.linearDynamicData?.lowerTarget, + upperTarget: prismaPool.linearDynamicData?.upperTarget, + tokens: rawPool.tokens.map((t, i) => { return {...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate} }), + } as RawLinearPool; + } + return rawPool; }); - const pools = await sor.fetchAndCachePools(); - return pools; } + + /** + * Map b-sdk RawPools to BasePools. + * @param pools + * @returns + */ + private mapToBasePools(pools: RawPool[]): BasePool[] { + const chainId = networkContext.chainId as unknown as ChainId; + return sorParseRawPools(chainId, pools); + } + + /** + * Map Prisma pool type to b-sdk Raw pool type. + * @param type + * @returns + */ + private mapRawPoolType(type: PrismaPoolType): SupportedRawPoolTypes | string { + // From b-sdk: + // - type LinearPoolType = `${string}Linear`; + // - LinearPoolType | 'Weighted' | 'Investment' | 'LiquidityBootstrapping' | 'Stable' | 'MetaStable' | 'ComposableStable' | 'StablePhantom' | 'Element'; + switch(type) { + case PrismaPoolType.WEIGHTED: + return 'Weighted'; + case PrismaPoolType.INVESTMENT: + return 'Investment'; + case PrismaPoolType.LIQUIDITY_BOOTSTRAPPING: + return 'LiquidityBootstrapping'; + case PrismaPoolType.STABLE: + return 'Stable'; // TODO - Is there a ComposableStable/Stable differentiation in Prisma? + case PrismaPoolType.META_STABLE: + return 'MetaStable'; + case PrismaPoolType.PHANTOM_STABLE: + return 'ComposableStable'; // b-sdk just treats these as ComposableStable + case PrismaPoolType.LINEAR: + return 'Linear'; + default: + return type; + } + } } export const sorV2Service = new SorV2Service(); \ No newline at end of file diff --git a/modules/sor/sorV2/temp.ts b/modules/sor/sorV2/temp.ts new file mode 100644 index 000000000..17fb5c821 --- /dev/null +++ b/modules/sor/sorV2/temp.ts @@ -0,0 +1,57 @@ +import { + BasePool, + SubgraphPoolProvider, + ChainId, + OnChainPoolDataEnricher, + SmartOrderRouter, + sorGetSwapsWithPools, + Token, + SwapKind, +} from '@balancer/sdk'; +import { networkContext } from '../../network/network-context.service'; + +import { SOR_QUERIES } from './sorV2.service'; + +// Just using to compare results +// Fetches pools using Subgraph/Onchain queries. +export async function getSwapCompare(tIn: Token, tOut: Token, swapAmount: any) { + console.time('poolsFromExternal'); + const poolsFromExternal = await getBasePoolsFromExternal(); + console.timeEnd('poolsFromExternal'); + const swap = await sorGetSwapsWithPools( + tIn, + tOut, + SwapKind.GivenIn, + swapAmount, + poolsFromExternal, + // swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + console.log(`Swap Compare:`); + console.log(`No of pools: `, poolsFromExternal.length); + console.log(swap.swaps); + console.log(swap.outputAmount.amount.toString()); +} + + /** + * Uses b-sdk to fetch/enrich pools. Uses Subgraph and Onchain calls. + * ! Used for test/compare right now. + * @returns + */ +async function getBasePoolsFromExternal(): Promise { + const chainId = networkContext.chainId as unknown as ChainId; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); + const onChainPoolDataEnricher = new OnChainPoolDataEnricher( + networkContext.data.rpcUrl, + SOR_QUERIES, + ); + const sor = new SmartOrderRouter({ + chainId: chainId, + poolDataProviders: subgraphPoolDataService, + poolDataEnrichers: onChainPoolDataEnricher, + rpcUrl: networkContext.data.rpcUrl, + }); + const pools = await sor.fetchAndCachePools(); + return pools; +} \ No newline at end of file diff --git a/prisma/prisma-types.ts b/prisma/prisma-types.ts index dbba9d699..37e75d4ed 100644 --- a/prisma/prisma-types.ts +++ b/prisma/prisma-types.ts @@ -273,3 +273,21 @@ export const prismaPoolBatchSwapWithSwaps = Prisma.validator; + +export const prismaPoolWithDynamic = Prisma.validator()({ + include: { + stableDynamicData: true, + dynamicData: true, + linearDynamicData: true, + linearData: true, + tokens: { + orderBy: { index: 'asc' }, + include: { + token: true, + dynamicData: true, + }, + }, + } +}); + +export type PrismaPoolWithDynamic = Prisma.PrismaPoolGetPayload; From 298eba2f0f58fff0072ecabbe185f43738d9b8cd Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 8 May 2023 12:09:11 +0100 Subject: [PATCH 03/78] Add TradeResults Prisma for recording results. --- modules/sor/sor.gql | 14 +++++++++++++ modules/sor/sor.prisma | 14 +++++++++++++ modules/sor/sor.resolvers.ts | 4 ++++ modules/sor/sor.service.ts | 20 +++++++++++++++++++ modules/sor/trade.service.ts | 19 ++++++++++++++++++ .../migration.sql | 15 ++++++++++++++ prisma/schema.prisma | 15 ++++++++++++++ 7 files changed, 101 insertions(+) create mode 100644 modules/sor/sor.prisma create mode 100644 modules/sor/trade.service.ts create mode 100644 prisma/migrations/20230508110533_trade_results/migration.sql diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql index 8175a87ee..5aea95190 100644 --- a/modules/sor/sor.gql +++ b/modules/sor/sor.gql @@ -5,6 +5,7 @@ extend type Query { swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in raw amount ): GqlSorGetSwapsResponseNew! + tradeResults: [GqlTradeResults]! } enum GqlSorSwapType { @@ -17,3 +18,16 @@ type GqlSorGetSwapsResponseNew { tokenOut: String! result: String! } + +type GqlTradeResults { + id: String! + timestamp: Int! + chain: String! + tokenIn: String! + tokenOut: String! + swapAmount: String! + swapType: String! + sorV1Result: String! + sorV2Result: String! + isSorV1: Boolean! +} diff --git a/modules/sor/sor.prisma b/modules/sor/sor.prisma new file mode 100644 index 000000000..eb7160a3f --- /dev/null +++ b/modules/sor/sor.prisma @@ -0,0 +1,14 @@ +model PrismaTradeResult { + @@id([id, chain]) + + id String + timestamp Int + chain Chain + tokenIn String + tokenOut String + swapAmount String + swapType String + sorV1Result String + sorV2Result String + isSorV1 Boolean +} \ No newline at end of file diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts index 2b80e8494..68969373e 100644 --- a/modules/sor/sor.resolvers.ts +++ b/modules/sor/sor.resolvers.ts @@ -1,5 +1,6 @@ import { Resolvers } from '../../schema'; import { sorService as sorService } from './sor.service'; +import { getTrades, TradeResults } from './trade.service'; import { tokenService } from '../token/token.service'; const sorResolvers: Resolvers = { @@ -7,6 +8,9 @@ const sorResolvers: Resolvers = { sorGetSwapsNew: async (parent, args, context) => { return sorService.getSwaps({ ...args }); }, + tradeResults: async () => { + return await getTrades(); + } }, }; diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index dc7128285..90d0d8340 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -18,6 +18,7 @@ import * as Sentry from '@sentry/node'; import _ from 'lodash'; import { Logger } from '@ethersproject/logger'; import { sorV2Service } from './sorV2/sorV2.service'; +import { prisma } from '../../prisma/prisma-client'; export interface GetSwapsInput { tokenIn: string; @@ -40,6 +41,25 @@ export class SorService { swapType, swapAmount, }); + + let isSorV1 = false; + + const timestamp = Math.floor(Date.now() / 1000); + + await prisma.prismaTradeResult.create({ + data: { + id: `${timestamp}-${tokenIn}-${tokenOut}`, + timestamp, + chain: networkContext.chain, + tokenIn, + tokenOut, + swapAmount, + swapType, + sorV1Result: 'TODO', + sorV2Result: result.result, + isSorV1 + } + }) return { tokenIn, tokenOut, diff --git a/modules/sor/trade.service.ts b/modules/sor/trade.service.ts new file mode 100644 index 000000000..0d35b7190 --- /dev/null +++ b/modules/sor/trade.service.ts @@ -0,0 +1,19 @@ +import { prisma } from '../../prisma/prisma-client'; + + +export interface TradeResults { + id: string; + timestamp: number; + chain: string; + tokenIn: string; + tokenOut: string; + swapAmount: string; + swapType: string; + sorV1Result: string; + sorV2Result: string; + isSorV1: boolean; +} + +export async function getTrades(): Promise { + return await prisma.prismaTradeResult.findMany(); +} \ No newline at end of file diff --git a/prisma/migrations/20230508110533_trade_results/migration.sql b/prisma/migrations/20230508110533_trade_results/migration.sql new file mode 100644 index 000000000..5ea9f1ce9 --- /dev/null +++ b/prisma/migrations/20230508110533_trade_results/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "PrismaTradeResult" ( + "id" TEXT NOT NULL, + "timestamp" INTEGER NOT NULL, + "chain" "Chain" NOT NULL, + "tokenIn" TEXT NOT NULL, + "tokenOut" TEXT NOT NULL, + "swapAmount" TEXT NOT NULL, + "swapType" TEXT NOT NULL, + "sorV1Result" TEXT NOT NULL, + "sorV2Result" TEXT NOT NULL, + "isSorV1" BOOLEAN NOT NULL, + + CONSTRAINT "PrismaTradeResult_pkey" PRIMARY KEY ("id","chain") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e1e521f11..a3a8af55c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -559,6 +559,21 @@ model PrismaReliquaryTokenBalanceSnapshot { } +model PrismaTradeResult { + @@id([id, chain]) + + id String + timestamp Int + chain Chain + tokenIn String + tokenOut String + swapAmount String + swapType String + sorV1Result String + sorV2Result String + isSorV1 Boolean +} + model PrismaToken { @@id([address, chain]) From 56b4a8df1edaeb5bb5ce5f7882318e76dff2b6a1 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 8 May 2023 12:24:25 +0100 Subject: [PATCH 04/78] Update SOR service to have placeholders for compare, etc. --- modules/sor/sor.service.ts | 42 +++++++++++------------------- modules/sor/sorV1/sorV1.service.ts | 1 + modules/sor/sorV2/sorV2.service.ts | 14 +++++----- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 90d0d8340..7ded4bf5c 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,22 +1,5 @@ -import { GqlSorGetSwapsResponse, GqlSorGetSwapsResponseNew, GqlSorSwapOptionsInput, GqlSorSwapType } from '../../schema'; -import { formatFixed, parseFixed } from '@ethersproject/bignumber'; -import { PrismaToken } from '@prisma/client'; -import { poolService } from '../pool/pool.service'; -import { oldBnum } from '../big-number/old-big-number'; -import axios from 'axios'; -import { FundManagement, SwapInfo, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; -import { replaceEthWithZeroAddress, replaceZeroAddressWithEth } from '../web3/addresses'; -import { BigNumber } from 'ethers'; -import { TokenAmountHumanReadable } from '../common/global-types'; -import { AddressZero } from '@ethersproject/constants'; -import { Contract } from '@ethersproject/contracts'; -import VaultAbi from '../pool/abi/Vault.json'; -import { env } from '../../app/env'; +import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../schema'; import { networkContext } from '../network/network-context.service'; -import { DeploymentEnv } from '../network/network-config-types'; -import * as Sentry from '@sentry/node'; -import _ from 'lodash'; -import { Logger } from '@ethersproject/logger'; import { sorV2Service } from './sorV2/sorV2.service'; import { prisma } from '../../prisma/prisma-client'; @@ -34,8 +17,12 @@ export class SorService { swapType, swapAmount, }: GetSwapsInput): Promise { - console.log(`!!!!!!! getSwaps`); - const result = await sorV2Service.getSwaps({ + const timestamp = Math.floor(Date.now() / 1000); + + // TODO - SORV1 result - via API call to current API or using SORV1/pools directly? + const sorV1Result = 'TODO'; + + const sorV2Result = await sorV2Service.getSwaps({ tokenIn, tokenOut, swapType, @@ -43,9 +30,9 @@ export class SorService { }); let isSorV1 = false; + // TODO - Compare V1 vs V2 result and return/log best - const timestamp = Math.floor(Date.now() / 1000); - + // Update db with best result so we can track performace await prisma.prismaTradeResult.create({ data: { id: `${timestamp}-${tokenIn}-${tokenOut}`, @@ -55,17 +42,18 @@ export class SorService { tokenOut, swapAmount, swapType, - sorV1Result: 'TODO', - sorV2Result: result.result, + sorV1Result, + sorV2Result: sorV2Result.result, isSorV1 } - }) + }); + + // TODO - Return in current CowSwap format so its plug and play return { tokenIn, tokenOut, - result: result.result + result: isSorV1 ? sorV1Result : sorV2Service.mapResultToCowSwap(sorV2Result.result) } - } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index e69de29bb..5f15687d4 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -0,0 +1 @@ +// This will hold logic for querying SORV1 trade. Either via call to existing API or SORV1 package directly. \ No newline at end of file diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index dd271925d..ac4f9ac65 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -10,7 +10,8 @@ import { RawLinearPool, RawWeightedPool, RawComposableStablePool, - RawMetaStablePool + RawMetaStablePool, + Swap } from '@balancer/sdk'; import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; @@ -60,11 +61,7 @@ export class SorV2Service { // Just using to compare results against onchain pool calls // await getSwapCompare(tIn, tOut, swapAmount); - // TODO - // - Query SORV1 (via API call or using SOR/pools directly?) - // - Compare and choose best result - // - Log best result in Prisma - // - Return best result matching existing CowSwap SOR API format so its plug and play + // TODO - Update with proper required result data return { tokenIn, tokenOut, @@ -72,6 +69,11 @@ export class SorV2Service { } } + public mapResultToCowSwap(swap: string): string { + // TODO - match existing CowSwap SOR API format so its plug and play + return swap; + } + /** * Gets a b-sdk Token based off tokenAddr. * @param tokenAddr From 1de2adc940df99942749a67ba23a416c98db1405 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 8 May 2023 16:00:23 +0100 Subject: [PATCH 05/78] Add placeholder for SORV1 service call to make more obvious. --- modules/sor/sor.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 7ded4bf5c..8f26fdfc5 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -20,6 +20,7 @@ export class SorService { const timestamp = Math.floor(Date.now() / 1000); // TODO - SORV1 result - via API call to current API or using SORV1/pools directly? + // const sorV1Result = await sorV1Service.getSwaps(...); const sorV1Result = 'TODO'; const sorV2Result = await sorV2Service.getSwaps({ From 31015897c19f47718eb9a047d61aaeaa43c091c0 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 9 May 2023 10:22:03 +0100 Subject: [PATCH 06/78] Move SOR_QUERIES in to temp file as only used there and can potentially remove. --- modules/sor/sorV2/sorV2.service.ts | 3 --- modules/sor/sorV2/temp.ts | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index ac4f9ac65..70987d18a 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -25,9 +25,6 @@ import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; import { getSwapCompare } from './temp'; -// TODO - Check if this has been deployed to same address across networks? -export const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; - export class SorV2Service { public async getSwaps({ tokenIn, diff --git a/modules/sor/sorV2/temp.ts b/modules/sor/sorV2/temp.ts index 17fb5c821..b42539c7e 100644 --- a/modules/sor/sorV2/temp.ts +++ b/modules/sor/sorV2/temp.ts @@ -10,7 +10,9 @@ import { } from '@balancer/sdk'; import { networkContext } from '../../network/network-context.service'; -import { SOR_QUERIES } from './sorV2.service'; +// TODO - Check if this has been deployed to same address across networks? +// - If we keep then can use: poolDataQueryContract: https://github.com/beethovenxfi/beethovenx-backend/blob/sor-v2/modules/network/network-config-types.ts#L87 +const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; // Just using to compare results // Fetches pools using Subgraph/Onchain queries. From 3884a9088979a43c20a317e047991c3afad51173 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 9 May 2023 10:28:21 +0100 Subject: [PATCH 07/78] Update Phantom/Composable pool comments. --- modules/sor/sorV2/sorV2.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 70987d18a..36a014ec7 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -224,11 +224,12 @@ export class SorV2Service { case PrismaPoolType.LIQUIDITY_BOOTSTRAPPING: return 'LiquidityBootstrapping'; case PrismaPoolType.STABLE: - return 'Stable'; // TODO - Is there a ComposableStable/Stable differentiation in Prisma? + return 'Stable'; case PrismaPoolType.META_STABLE: return 'MetaStable'; case PrismaPoolType.PHANTOM_STABLE: - return 'ComposableStable'; // b-sdk just treats these as ComposableStable + // Composablestables are PHANTOM_STABLE in Prisma. b-sdk treats Phantoms as ComposableStable. + return 'ComposableStable'; case PrismaPoolType.LINEAR: return 'Linear'; default: From 4e539c04dcbd8cc0659d5da300f738a3f143d4d1 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 9 May 2023 10:46:13 +0100 Subject: [PATCH 08/78] Add pool ids to ignore in to network config. --- modules/network/arbitrum.ts | 2 ++ modules/network/fantom.ts | 2 ++ modules/network/gnosis.ts | 2 ++ modules/network/mainnet.ts | 8 ++++++++ modules/network/network-config-types.ts | 1 + modules/network/optimism.ts | 2 ++ modules/network/polygon.ts | 2 ++ modules/sor/sorV2/sorV2.service.ts | 21 ++++++++------------- 8 files changed, 27 insertions(+), 13 deletions(-) diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 8c4d017c2..1693e2b23 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -106,6 +106,7 @@ const arbitrumNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -113,6 +114,7 @@ const arbitrumNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, yearn: { diff --git a/modules/network/fantom.ts b/modules/network/fantom.ts index f75657b1b..59b77e5e6 100644 --- a/modules/network/fantom.ts +++ b/modules/network/fantom.ts @@ -129,6 +129,7 @@ const fantomNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://mep53ds2noe6rhicd67q7raqhq0dkupc.lambda-url.eu-central-1.on.aws/', @@ -136,6 +137,7 @@ const fantomNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, yearn: { diff --git a/modules/network/gnosis.ts b/modules/network/gnosis.ts index c784e7a58..4d8d510d2 100644 --- a/modules/network/gnosis.ts +++ b/modules/network/gnosis.ts @@ -88,6 +88,7 @@ const gnosisNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: '', @@ -95,6 +96,7 @@ const gnosisNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, yearn: { diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 55fa0cfd4..b8f7cab28 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -126,6 +126,10 @@ const mainnetNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [ + "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", + "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", // Linear pools that cause issues with new b-sdk + ], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -133,6 +137,10 @@ const mainnetNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [ + "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", + "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", // Linear pools that cause issues with new b-sdk + ], }, }, yearn: { diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index 3c0ad438d..47f5e3aab 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -123,6 +123,7 @@ export interface NetworkData { forceRefresh: boolean; gasPrice: BigNumber; swapGas: BigNumber; + poolIdsToExclude: string[]; }; }; datastudio: { diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index 2c8e00867..5c4bcb325 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -126,6 +126,7 @@ const optimismNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://svlitjilcr5qtp7iolimlrlg7e0ipupj.lambda-url.eu-central-1.on.aws/', @@ -133,6 +134,7 @@ const optimismNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, yearn: { diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 180d50f15..9cd80506a 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -129,6 +129,7 @@ const polygonNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -136,6 +137,7 @@ const polygonNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, yearn: { diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 36a014ec7..bf5edef33 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -22,6 +22,8 @@ import { prisma } from '../../../prisma/prisma-client'; import { PrismaPoolWithDynamic, prismaPoolWithDynamic } from '../../../prisma/prisma-types'; import { RawPool } from '@balancer/sdk'; import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; +import { env } from '../../../app/env'; +import { DeploymentEnv } from '../../network/network-config-types'; import { getSwapCompare } from './temp'; @@ -116,23 +118,16 @@ export class SorV2Service { }, swapEnabled: true, }, + NOT: { + id: { + in: networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, + } + } }, include: prismaPoolWithDynamic.include }); const rawPools = this.mapToRawPools(pools); - // TODO - The pools below cause issue with b-sdk maths. Both have a token with balance = 0 so maybe that's issue? - const linearPoolsToIgnore = [ - "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", - "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", - ] - - const basePools = this.mapToBasePools(rawPools.filter(p => { - if (p.poolType == 'Linear') { - return !linearPoolsToIgnore.includes(p.id); - } else return true; - })); - // const basePools = this.mapToBasePools(rawPools); - return basePools; + return this.mapToBasePools(rawPools); } /** From 8b2ae686e5c69cc03fde37d714ea6f5fb8990d81 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 10 May 2023 13:36:53 +0100 Subject: [PATCH 09/78] WIP SORv1 API query. --- modules/sor/sor.service.ts | 14 +-- modules/sor/sorV1/sorV1.service.ts | 132 ++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 6 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 8f26fdfc5..ae9cc4ca5 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,5 +1,6 @@ import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../schema'; import { networkContext } from '../network/network-context.service'; +import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; import { prisma } from '../../prisma/prisma-client'; @@ -19,9 +20,12 @@ export class SorService { }: GetSwapsInput): Promise { const timestamp = Math.floor(Date.now() / 1000); - // TODO - SORV1 result - via API call to current API or using SORV1/pools directly? - // const sorV1Result = await sorV1Service.getSwaps(...); - const sorV1Result = 'TODO'; + const sorV1Result = await sorV1Service.getSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }); const sorV2Result = await sorV2Service.getSwaps({ tokenIn, @@ -43,7 +47,7 @@ export class SorService { tokenOut, swapAmount, swapType, - sorV1Result, + sorV1Result: sorV1Result.result, sorV2Result: sorV2Result.result, isSorV1 } @@ -53,7 +57,7 @@ export class SorService { return { tokenIn, tokenOut, - result: isSorV1 ? sorV1Result : sorV2Service.mapResultToCowSwap(sorV2Result.result) + result: isSorV1 ? sorV1Result.result : sorV2Service.mapResultToCowSwap(sorV2Result.result) } } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 5f15687d4..36d85915d 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -1 +1,131 @@ -// This will hold logic for querying SORV1 trade. Either via call to existing API or SORV1 package directly. \ No newline at end of file +import axios, { AxiosError } from 'axios'; +import { SwapKind } from '@balancer/sdk'; +import { GqlSorGetSwapsResponseNew, GqlSorSwapType, GqlSorSwapOptionsInput } from '../../../schema'; +import { GetSwapsInput } from '../sor.service'; +import { FundManagement, SwapInfo, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; +import { env } from '../../../app/env'; +import { networkContext } from '../../network/network-context.service'; +import { DeploymentEnv } from '../../network/network-config-types'; +import { BigNumber } from 'ethers'; + +interface ApiResponse { + tokenAddresses: string[]; + swapAmount: string; + swapAmountForSwaps: string; + returnAmount: string; + returnAmountFromSwaps: string; + returnAmountConsideringFees: string; + tokenIn: string; + tokenOut: string; + marketSp: string; +} + +export class SorV1Service { + public async getSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }: GetSwapsInput): Promise { + const query = await this.querySorBeets(swapType, tokenIn, tokenOut, swapAmount, {}); + + // TODO - Update with proper required result data + return { + tokenIn, + tokenOut, + result: '4', + } + } + + private async querySorBeets( + swapType: string, + tokenIn: string, + tokenOut: string, + swapAmountScaled: string, + swapOptions: GqlSorSwapOptionsInput, + ) { + // Taken from: modules/beethoven/balancer-sor.service.ts + // TODO - Currently don't get a swap from mainnet. Need an example curl? + const { data } = await axios.post<{ swapInfo: SwapInfo }>( + networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].url, + { + swapType, + tokenIn, + tokenOut, + swapAmountScaled, + swapOptions: { + maxPools: + swapOptions.maxPools || networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].maxPools, + forceRefresh: + swapOptions.forceRefresh || + networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].forceRefresh, + }, + }, + ); + const swapInfo = data.swapInfo; + console.log(`BEETS API SwapInfo:`) + console.log(swapInfo); + return swapInfo; + } + + private async querySorBalancer( + swapType: string, + tokenIn: string, + tokenOut: string, + swapAmountScaled: string, + swapOptions: GqlSorSwapOptionsInput, + ) { + // TODO - Waiting for auth token from Tim + /* + curl -X POST -H "Content-Type: application/json" \ + -d '{"sellToken":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","buyToken":"0x6b175474e89094c44da98b954eedeac495271d0f","orderKind":"sell", "amount":"100000", "gasPrice":"40000000000"}' \ + https://api.balancer.fi/sor/1 + + Example response: + { + "tokenAddresses":[ + "0xba100000625a3754423978a60c9317c58a424e3d", + "0x6b175474e89094c44da98b954eedeac495271d0f" + ], + "swaps":[ + { + "poolId":"0x148ce9b50be946a96e94a4f5479b771bab9b1c59000100000000000000000054", + "assetInIndex":0, + "assetOutIndex":1, + "amount":"1000000000000000000", + "userData":"0x", + "returnAmount":"6196869182476993074" + } + ], + "swapAmount":"1000000000000000000", + "swapAmountForSwaps":"1000000000000000000", + "returnAmount":"6196869182476993074", + "returnAmountFromSwaps":"6196869182476993074", + "returnAmountConsideringFees":"-65828817523006926", + "tokenIn":"0xba100000625a3754423978a60c9317c58a424e3d", + "tokenOut":"0x6b175474e89094c44da98b954eedeac495271d0f", + "marketSp":"0.15959797648561186" + } + */ + const endPoint = `https://api.balancer.fi/sor/`; + + const { data } = await axios.post<{ swapInfo: SwapInfo }>( + `${endPoint}/sor/1`, + { + orderKind: 'sell', + sellToken: tokenIn, + buyToken: tokenOut, + amount: swapAmountScaled, + gasPrice: '40000000000' + }, + ); + const swapInfo = data.swapInfo; + return swapInfo; + } + + private mapSwapType(swapType: GqlSorSwapType): SwapKind { + return swapType === "EXACT_IN" ? SwapKind.GivenIn : SwapKind.GivenOut; + } +} + +export const sorV1Service = new SorV1Service(); \ No newline at end of file From 92d269ce808de87736b0a78209c76b9764521afa Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 10 May 2023 13:48:21 +0100 Subject: [PATCH 10/78] Use cache for pools. --- modules/sor/sorV2/sorV2.service.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index bf5edef33..8fac284f9 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -24,10 +24,18 @@ import { RawPool } from '@balancer/sdk'; import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; +import { Cache, CacheClass } from 'memory-cache'; import { getSwapCompare } from './temp'; +const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; + export class SorV2Service { + cache: CacheClass; + + constructor() { + this.cache = new Cache(); + } public async getSwaps({ tokenIn, tokenOut, @@ -35,9 +43,9 @@ export class SorV2Service { swapAmount, }: GetSwapsInput): Promise { // TODO - This takes ~1.262s on my local machine. Is this fast enough? Any obvious ways to improve? - console.time('poolsFromDb'); - const poolsFromDb = await this.getBasePoolsFromDb(); - console.timeEnd('poolsFromDb'); + console.time('getBasePools'); + const poolsFromDb = await this.getBasePools(); + console.timeEnd('getBasePools'); const chainId = networkContext.chainId as unknown as ChainId; const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); @@ -104,6 +112,16 @@ export class SorV2Service { return swapType === "EXACT_IN" ? SwapKind.GivenIn : SwapKind.GivenOut; } + private async getBasePools(): Promise { + let basePools: BasePool[] | null = this.cache.get(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`); + if (!basePools) { + console.log('Fetching pools from db as cache expired...') + basePools = await this.getBasePoolsFromDb(); + this.cache.put(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`, basePools, 5 * 60 * 1000); + } + return basePools; + } + /** * Fetch pools from Prisma and map to b-sdk BasePool. * @returns From 1f9bcff59355be1dc9d4d63ab0d2487ff84df7fc Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 10:24:59 +0100 Subject: [PATCH 11/78] Query API (Currently Balancers) for SORV1. --- modules/sor/sor.service.ts | 4 +- modules/sor/sorV1/sorV1.service.ts | 143 ++++++++++++----------------- modules/sor/sorV1/types.ts | 23 +++++ 3 files changed, 83 insertions(+), 87 deletions(-) create mode 100644 modules/sor/sorV1/types.ts diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index ae9cc4ca5..d90ec2622 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -47,7 +47,7 @@ export class SorService { tokenOut, swapAmount, swapType, - sorV1Result: sorV1Result.result, + sorV1Result: sorV1Result.returnAmount, sorV2Result: sorV2Result.result, isSorV1 } @@ -57,7 +57,7 @@ export class SorService { return { tokenIn, tokenOut, - result: isSorV1 ? sorV1Result.result : sorV2Service.mapResultToCowSwap(sorV2Result.result) + result: isSorV1 ? sorV1Result.returnAmount : sorV2Service.mapResultToCowSwap(sorV2Result.result) } } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 36d85915d..110bf863c 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -1,24 +1,24 @@ import axios, { AxiosError } from 'axios'; -import { SwapKind } from '@balancer/sdk'; -import { GqlSorGetSwapsResponseNew, GqlSorSwapType, GqlSorSwapOptionsInput } from '../../../schema'; +import { GqlSorSwapType, GqlSorSwapOptionsInput } from '../../../schema'; import { GetSwapsInput } from '../sor.service'; -import { FundManagement, SwapInfo, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; +import { SwapInfo } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; -import { BigNumber } from 'ethers'; +import { ApiResponse, CowSwapSwapType } from './types'; -interface ApiResponse { - tokenAddresses: string[]; - swapAmount: string; - swapAmountForSwaps: string; - returnAmount: string; - returnAmountFromSwaps: string; - returnAmountConsideringFees: string; - tokenIn: string; - tokenOut: string; - marketSp: string; -} +const EMPTY_APIRESPONSE: ApiResponse = { + tokenAddresses: [], + swaps: [], + swapAmount: '0', + swapAmountForSwaps: '0', + returnAmount: '0', + returnAmountFromSwaps: '0', + returnAmountConsideringFees: '0', + tokenIn: '', + tokenOut: '', + marketSp: '0', +}; export class SorV1Service { public async getSwaps({ @@ -26,18 +26,52 @@ export class SorV1Service { tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { - const query = await this.querySorBeets(swapType, tokenIn, tokenOut, swapAmount, {}); + }: GetSwapsInput): Promise { + return await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); + } + + /** + * Query Balancer API CowSwap/SOR endpoint. + * @param swapType + * @param tokenIn + * @param tokenOut + * @param swapAmountScaled + * @param swapOptions + * @returns + */ + private async querySorBalancer( + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmountScaled: string, + ): Promise { + const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; + const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); + const swapData = { + orderKind: this.mapSwapType(swapType), + sellToken: tokenIn, + buyToken: tokenOut, + amount: swapAmountScaled, + gasPrice + }; - // TODO - Update with proper required result data - return { - tokenIn, - tokenOut, - result: '4', - } + try { + const { data } = await axios.post( + endPoint, + swapData, + ); + return data; + } catch (err) { + console.log(`sorV1 Service Error`, err); + return EMPTY_APIRESPONSE; + } } - private async querySorBeets( + private mapSwapType(swapType: GqlSorSwapType): CowSwapSwapType { + return swapType === "EXACT_IN" ? 'sell' : 'buy'; + } + + private async querySorBeets( swapType: string, tokenIn: string, tokenOut: string, @@ -63,69 +97,8 @@ export class SorV1Service { }, ); const swapInfo = data.swapInfo; - console.log(`BEETS API SwapInfo:`) - console.log(swapInfo); - return swapInfo; - } - - private async querySorBalancer( - swapType: string, - tokenIn: string, - tokenOut: string, - swapAmountScaled: string, - swapOptions: GqlSorSwapOptionsInput, - ) { - // TODO - Waiting for auth token from Tim - /* - curl -X POST -H "Content-Type: application/json" \ - -d '{"sellToken":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","buyToken":"0x6b175474e89094c44da98b954eedeac495271d0f","orderKind":"sell", "amount":"100000", "gasPrice":"40000000000"}' \ - https://api.balancer.fi/sor/1 - - Example response: - { - "tokenAddresses":[ - "0xba100000625a3754423978a60c9317c58a424e3d", - "0x6b175474e89094c44da98b954eedeac495271d0f" - ], - "swaps":[ - { - "poolId":"0x148ce9b50be946a96e94a4f5479b771bab9b1c59000100000000000000000054", - "assetInIndex":0, - "assetOutIndex":1, - "amount":"1000000000000000000", - "userData":"0x", - "returnAmount":"6196869182476993074" - } - ], - "swapAmount":"1000000000000000000", - "swapAmountForSwaps":"1000000000000000000", - "returnAmount":"6196869182476993074", - "returnAmountFromSwaps":"6196869182476993074", - "returnAmountConsideringFees":"-65828817523006926", - "tokenIn":"0xba100000625a3754423978a60c9317c58a424e3d", - "tokenOut":"0x6b175474e89094c44da98b954eedeac495271d0f", - "marketSp":"0.15959797648561186" - } - */ - const endPoint = `https://api.balancer.fi/sor/`; - - const { data } = await axios.post<{ swapInfo: SwapInfo }>( - `${endPoint}/sor/1`, - { - orderKind: 'sell', - sellToken: tokenIn, - buyToken: tokenOut, - amount: swapAmountScaled, - gasPrice: '40000000000' - }, - ); - const swapInfo = data.swapInfo; return swapInfo; } - - private mapSwapType(swapType: GqlSorSwapType): SwapKind { - return swapType === "EXACT_IN" ? SwapKind.GivenIn : SwapKind.GivenOut; - } } export const sorV1Service = new SorV1Service(); \ No newline at end of file diff --git a/modules/sor/sorV1/types.ts b/modules/sor/sorV1/types.ts new file mode 100644 index 000000000..1281227f0 --- /dev/null +++ b/modules/sor/sorV1/types.ts @@ -0,0 +1,23 @@ +export interface ApiResponse { + tokenAddresses: string[]; + swaps: Swap[]; + swapAmount: string; + swapAmountForSwaps: string; + returnAmount: string; + returnAmountFromSwaps: string; + returnAmountConsideringFees: string; + tokenIn: string; + tokenOut: string; + marketSp: string; +} + +export interface Swap { + poolId: string; + assetInIndex: number; + assetOutIndex: number; + amount: string; + userData: string; + returnAmount: string; +} + +export type CowSwapSwapType = 'buy' | 'sell'; \ No newline at end of file From 1725a259fbe2510738b1077d427339f7aeec87ab Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 11:14:50 +0100 Subject: [PATCH 12/78] Add basic compare, WIP. --- modules/sor/sor.service.ts | 24 +++++++++++-- modules/sor/sorV1/sorV1.service.ts | 10 +++--- modules/sor/sorV1/types.ts | 2 +- modules/sor/sorV2/sorV2.service.ts | 55 +++++++++++------------------- 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index d90ec2622..4546d8d08 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -3,6 +3,8 @@ import { networkContext } from '../network/network-context.service'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; import { prisma } from '../../prisma/prisma-client'; +import { CowSwapApiResponse } from './sorV1/types'; +import { Swap } from '@balancer/sdk'; export interface GetSwapsInput { tokenIn: string; @@ -34,7 +36,7 @@ export class SorService { swapAmount, }); - let isSorV1 = false; + const bestSwap = this.getBestSwap(sorV1Result, sorV2Result); // TODO - Compare V1 vs V2 result and return/log best // Update db with best result so we can track performace @@ -57,7 +59,25 @@ export class SorService { return { tokenIn, tokenOut, - result: isSorV1 ? sorV1Result.returnAmount : sorV2Service.mapResultToCowSwap(sorV2Result.result) + result: bestSwap + } + } + + private getBestSwap(v1: CowSwapApiResponse, v2: Swap | null): CowSwapApiResponse { + if(!v2) { + if(v1.returnAmount === '0') { + console.log('NO RESULT'); + } + else { + console.log('V1'); + } + return v1; + } + if(v2.outputAmount.amount < BigInt(v1.returnAmount)) { + console.log('V1'); + return v1; + } else { + return sorV2Service.mapResultToCowSwap(v2); } } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 110bf863c..7edf856d3 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -5,9 +5,9 @@ import { SwapInfo } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; -import { ApiResponse, CowSwapSwapType } from './types'; +import { CowSwapApiResponse, CowSwapSwapType } from './types'; -const EMPTY_APIRESPONSE: ApiResponse = { +const EMPTY_APIRESPONSE: CowSwapApiResponse = { tokenAddresses: [], swaps: [], swapAmount: '0', @@ -26,7 +26,7 @@ export class SorV1Service { tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { + }: GetSwapsInput): Promise { return await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); } @@ -44,7 +44,7 @@ export class SorV1Service { tokenIn: string, tokenOut: string, swapAmountScaled: string, - ): Promise { + ): Promise { const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); const swapData = { @@ -56,7 +56,7 @@ export class SorV1Service { }; try { - const { data } = await axios.post( + const { data } = await axios.post( endPoint, swapData, ); diff --git a/modules/sor/sorV1/types.ts b/modules/sor/sorV1/types.ts index 1281227f0..be7ad1a37 100644 --- a/modules/sor/sorV1/types.ts +++ b/modules/sor/sorV1/types.ts @@ -1,4 +1,4 @@ -export interface ApiResponse { +export interface CowSwapApiResponse { tokenAddresses: string[]; swaps: Swap[]; swapAmount: string; diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 8fac284f9..1c044671f 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -11,23 +11,21 @@ import { RawWeightedPool, RawComposableStablePool, RawMetaStablePool, - Swap + Swap, + RawPool } from '@balancer/sdk'; -import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../../schema'; +import { GqlSorSwapType } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput } from '../sor.service'; import { tokenService } from '../../token/token.service'; import { networkContext } from '../../network/network-context.service'; import { prisma } from '../../../prisma/prisma-client'; import { PrismaPoolWithDynamic, prismaPoolWithDynamic } from '../../../prisma/prisma-types'; -import { RawPool } from '@balancer/sdk'; import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; -import { getSwapCompare } from './temp'; - const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; export class SorV2Service { @@ -41,44 +39,32 @@ export class SorV2Service { tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { - // TODO - This takes ~1.262s on my local machine. Is this fast enough? Any obvious ways to improve? + }: GetSwapsInput): Promise { console.time('getBasePools'); const poolsFromDb = await this.getBasePools(); console.timeEnd('getBasePools'); const chainId = networkContext.chainId as unknown as ChainId; const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); - const swap = await sorGetSwapsWithPools( - tIn, - tOut, - this.mapSwapType(swapType), - swapAmount, - poolsFromDb, - // swapOptions, // TODO - Handle properly - ); - - if (!swap) throw new Error('Swap is undefined'); - console.log(`Swap (db pools)`); - console.log(poolsFromDb.length); - console.log(swap.swaps); - console.log(swap.outputAmount.amount.toString()); - console.log(`------------------`); - - // Just using to compare results against onchain pool calls - // await getSwapCompare(tIn, tOut, swapAmount); - - // TODO - Update with proper required result data - return { - tokenIn, - tokenOut, - result: swap.outputAmount.amount.toString() - } + try { + const swap = await sorGetSwapsWithPools( + tIn, + tOut, + this.mapSwapType(swapType), + swapAmount, + poolsFromDb, + // swapOptions, // TODO - Handle properly + ); + return swap; + } catch (err) { + console.log(`sorV2 Service Error`, err); + return null; + } } - public mapResultToCowSwap(swap: string): string { + public mapResultToCowSwap(swap: Swap): string { // TODO - match existing CowSwap SOR API format so its plug and play - return swap; + return 'TODO'; } /** @@ -115,7 +101,6 @@ export class SorV2Service { private async getBasePools(): Promise { let basePools: BasePool[] | null = this.cache.get(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`); if (!basePools) { - console.log('Fetching pools from db as cache expired...') basePools = await this.getBasePoolsFromDb(); this.cache.put(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`, basePools, 5 * 60 * 1000); } From 5c3b6aa6d891c66ff0a5815cedaa283247c65de4 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 11:19:37 +0100 Subject: [PATCH 13/78] Remove Prisma trade result in favour of cloudwatch implementation. --- modules/sor/sor.gql | 14 -------------- modules/sor/sor.prisma | 14 -------------- modules/sor/sor.resolvers.ts | 4 ---- modules/sor/sor.service.ts | 16 ---------------- modules/sor/trade.service.ts | 19 ------------------- .../migration.sql | 8 ++++++++ prisma/schema.prisma | 15 --------------- 7 files changed, 8 insertions(+), 82 deletions(-) delete mode 100644 modules/sor/sor.prisma delete mode 100644 modules/sor/trade.service.ts create mode 100644 prisma/migrations/20230519101752_remove_trade_result/migration.sql diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql index 5aea95190..8175a87ee 100644 --- a/modules/sor/sor.gql +++ b/modules/sor/sor.gql @@ -5,7 +5,6 @@ extend type Query { swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in raw amount ): GqlSorGetSwapsResponseNew! - tradeResults: [GqlTradeResults]! } enum GqlSorSwapType { @@ -18,16 +17,3 @@ type GqlSorGetSwapsResponseNew { tokenOut: String! result: String! } - -type GqlTradeResults { - id: String! - timestamp: Int! - chain: String! - tokenIn: String! - tokenOut: String! - swapAmount: String! - swapType: String! - sorV1Result: String! - sorV2Result: String! - isSorV1: Boolean! -} diff --git a/modules/sor/sor.prisma b/modules/sor/sor.prisma deleted file mode 100644 index eb7160a3f..000000000 --- a/modules/sor/sor.prisma +++ /dev/null @@ -1,14 +0,0 @@ -model PrismaTradeResult { - @@id([id, chain]) - - id String - timestamp Int - chain Chain - tokenIn String - tokenOut String - swapAmount String - swapType String - sorV1Result String - sorV2Result String - isSorV1 Boolean -} \ No newline at end of file diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts index 68969373e..2b80e8494 100644 --- a/modules/sor/sor.resolvers.ts +++ b/modules/sor/sor.resolvers.ts @@ -1,6 +1,5 @@ import { Resolvers } from '../../schema'; import { sorService as sorService } from './sor.service'; -import { getTrades, TradeResults } from './trade.service'; import { tokenService } from '../token/token.service'; const sorResolvers: Resolvers = { @@ -8,9 +7,6 @@ const sorResolvers: Resolvers = { sorGetSwapsNew: async (parent, args, context) => { return sorService.getSwaps({ ...args }); }, - tradeResults: async () => { - return await getTrades(); - } }, }; diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 4546d8d08..dd69dfa5b 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -39,22 +39,6 @@ export class SorService { const bestSwap = this.getBestSwap(sorV1Result, sorV2Result); // TODO - Compare V1 vs V2 result and return/log best - // Update db with best result so we can track performace - await prisma.prismaTradeResult.create({ - data: { - id: `${timestamp}-${tokenIn}-${tokenOut}`, - timestamp, - chain: networkContext.chain, - tokenIn, - tokenOut, - swapAmount, - swapType, - sorV1Result: sorV1Result.returnAmount, - sorV2Result: sorV2Result.result, - isSorV1 - } - }); - // TODO - Return in current CowSwap format so its plug and play return { tokenIn, diff --git a/modules/sor/trade.service.ts b/modules/sor/trade.service.ts deleted file mode 100644 index 0d35b7190..000000000 --- a/modules/sor/trade.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { prisma } from '../../prisma/prisma-client'; - - -export interface TradeResults { - id: string; - timestamp: number; - chain: string; - tokenIn: string; - tokenOut: string; - swapAmount: string; - swapType: string; - sorV1Result: string; - sorV2Result: string; - isSorV1: boolean; -} - -export async function getTrades(): Promise { - return await prisma.prismaTradeResult.findMany(); -} \ No newline at end of file diff --git a/prisma/migrations/20230519101752_remove_trade_result/migration.sql b/prisma/migrations/20230519101752_remove_trade_result/migration.sql new file mode 100644 index 000000000..694852ca6 --- /dev/null +++ b/prisma/migrations/20230519101752_remove_trade_result/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the `PrismaTradeResult` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +DROP TABLE "PrismaTradeResult"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a3a8af55c..e1e521f11 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -559,21 +559,6 @@ model PrismaReliquaryTokenBalanceSnapshot { } -model PrismaTradeResult { - @@id([id, chain]) - - id String - timestamp Int - chain Chain - tokenIn String - tokenOut String - swapAmount String - swapType String - sorV1Result String - sorV2Result String - isSorV1 Boolean -} - model PrismaToken { @@id([address, chain]) From 243d85515330027b09b06da66811b0bc97149ba7 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 11:43:51 +0100 Subject: [PATCH 14/78] Find best and log result via cloudwatch. --- modules/sor/sor.service.ts | 34 +++++++++++++++++++----------- modules/sor/sorV1/constants.ts | 14 ++++++++++++ modules/sor/sorV1/sorV1.service.ts | 16 ++------------ modules/sor/sorV2/sorV2.service.ts | 6 ++++-- 4 files changed, 42 insertions(+), 28 deletions(-) create mode 100644 modules/sor/sorV1/constants.ts diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index dd69dfa5b..d175c82f7 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,8 +1,6 @@ import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../schema'; -import { networkContext } from '../network/network-context.service'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { prisma } from '../../prisma/prisma-client'; import { CowSwapApiResponse } from './sorV1/types'; import { Swap } from '@balancer/sdk'; @@ -20,50 +18,62 @@ export class SorService { swapType, swapAmount, }: GetSwapsInput): Promise { - const timestamp = Math.floor(Date.now() / 1000); - + console.time('sorV1'); const sorV1Result = await sorV1Service.getSwaps({ tokenIn, tokenOut, swapType, swapAmount, }); - + console.timeEnd('sorV1'); + console.time('sorV2'); const sorV2Result = await sorV2Service.getSwaps({ tokenIn, tokenOut, swapType, swapAmount, }); + console.timeEnd('sorV2'); - const bestSwap = this.getBestSwap(sorV1Result, sorV2Result); - // TODO - Compare V1 vs V2 result and return/log best + const bestSwap = this.getBestSwap(sorV1Result, sorV2Result, swapType); // TODO - Return in current CowSwap format so its plug and play return { tokenIn, tokenOut, - result: bestSwap + result: bestSwap.returnAmount } } - private getBestSwap(v1: CowSwapApiResponse, v2: Swap | null): CowSwapApiResponse { + /** + * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. + * @param v1 + * @param v2 + * @param swapType + * @returns + */ + private getBestSwap(v1: CowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType): CowSwapApiResponse { if(!v2) { if(v1.returnAmount === '0') { - console.log('NO RESULT'); + this.logResult(`No Result`, v1, v2, swapType); } else { - console.log('V1'); + this.logResult(`V1 (No V2)`, v1, v2, swapType); } return v1; } if(v2.outputAmount.amount < BigInt(v1.returnAmount)) { - console.log('V1'); + this.logResult(`V1`, v1, v2, swapType); return v1; } else { return sorV2Service.mapResultToCowSwap(v2); } } + + private logResult(logType: string, v1: CowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType) { + // console.log() will log to cloudwatch + console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2?.outputAmount.amount.toString()); + } } export const sorService = new SorService(); diff --git a/modules/sor/sorV1/constants.ts b/modules/sor/sorV1/constants.ts new file mode 100644 index 000000000..75f429559 --- /dev/null +++ b/modules/sor/sorV1/constants.ts @@ -0,0 +1,14 @@ +import { CowSwapApiResponse } from '../sorV1/types'; + +export const EMPTY_COWSWAP_RESPONSE: CowSwapApiResponse = { + tokenAddresses: [], + swaps: [], + swapAmount: '0', + swapAmountForSwaps: '0', + returnAmount: '0', + returnAmountFromSwaps: '0', + returnAmountConsideringFees: '0', + tokenIn: '', + tokenOut: '', + marketSp: '0', +}; \ No newline at end of file diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 7edf856d3..4e1eec98c 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -6,19 +6,7 @@ import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; import { CowSwapApiResponse, CowSwapSwapType } from './types'; - -const EMPTY_APIRESPONSE: CowSwapApiResponse = { - tokenAddresses: [], - swaps: [], - swapAmount: '0', - swapAmountForSwaps: '0', - returnAmount: '0', - returnAmountFromSwaps: '0', - returnAmountConsideringFees: '0', - tokenIn: '', - tokenOut: '', - marketSp: '0', -}; +import { EMPTY_COWSWAP_RESPONSE } from './constants'; export class SorV1Service { public async getSwaps({ @@ -63,7 +51,7 @@ export class SorV1Service { return data; } catch (err) { console.log(`sorV1 Service Error`, err); - return EMPTY_APIRESPONSE; + return EMPTY_COWSWAP_RESPONSE; } } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 1c044671f..6c8c6592b 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -25,6 +25,8 @@ import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; +import { CowSwapApiResponse } from '../sorV1/types'; +import { EMPTY_COWSWAP_RESPONSE } from '../sorV1/constants'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -62,9 +64,9 @@ export class SorV2Service { } } - public mapResultToCowSwap(swap: Swap): string { + public mapResultToCowSwap(swap: Swap): CowSwapApiResponse { // TODO - match existing CowSwap SOR API format so its plug and play - return 'TODO'; + return EMPTY_COWSWAP_RESPONSE; } /** From 5cc16679b8f293260ee0c16eafa2c98b4902043d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 12:06:46 +0100 Subject: [PATCH 15/78] Change response to be in API format. Use generated types. --- modules/sor/sor.gql | 22 +++++++++++++++++++--- modules/sor/sor.service.ts | 18 +++++------------- modules/sor/sorV1/constants.ts | 4 ++-- modules/sor/sorV1/sorV1.service.ts | 12 ++++++------ modules/sor/sorV1/types.ts | 23 ----------------------- modules/sor/sorV2/sorV2.service.ts | 4 ++-- 6 files changed, 34 insertions(+), 49 deletions(-) delete mode 100644 modules/sor/sorV1/types.ts diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql index 8175a87ee..e095cfcb4 100644 --- a/modules/sor/sor.gql +++ b/modules/sor/sor.gql @@ -4,7 +4,7 @@ extend type Query { tokenOut: String! swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in raw amount - ): GqlSorGetSwapsResponseNew! + ): GqlCowSwapApiResponse! } enum GqlSorSwapType { @@ -12,8 +12,24 @@ enum GqlSorSwapType { EXACT_OUT } -type GqlSorGetSwapsResponseNew { +type GqlCowSwapApiResponse { + tokenAddresses: [String!]! + swaps: [GqlSwap!]! + swapAmount: String! + swapAmountForSwaps: String! + returnAmount: String! + returnAmountFromSwaps: String! + returnAmountConsideringFees: String! tokenIn: String! tokenOut: String! - result: String! + marketSp: String! +} + +type GqlSwap { + poolId: String! + assetInIndex: Int! + assetOutIndex: Int! + amount: String! + userData: String! + returnAmount: String! } diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index d175c82f7..fc2f4de15 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,7 +1,6 @@ -import { GqlSorGetSwapsResponseNew, GqlSorSwapType } from '../../schema'; +import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { CowSwapApiResponse } from './sorV1/types'; import { Swap } from '@balancer/sdk'; export interface GetSwapsInput { @@ -17,7 +16,7 @@ export class SorService { tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { + }: GetSwapsInput): Promise { console.time('sorV1'); const sorV1Result = await sorV1Service.getSwaps({ tokenIn, @@ -35,14 +34,7 @@ export class SorService { }); console.timeEnd('sorV2'); - const bestSwap = this.getBestSwap(sorV1Result, sorV2Result, swapType); - - // TODO - Return in current CowSwap format so its plug and play - return { - tokenIn, - tokenOut, - result: bestSwap.returnAmount - } + return this.getBestSwap(sorV1Result, sorV2Result, swapType); } /** @@ -52,7 +44,7 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap(v1: CowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType): CowSwapApiResponse { + private getBestSwap(v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType): GqlCowSwapApiResponse { if(!v2) { if(v1.returnAmount === '0') { this.logResult(`No Result`, v1, v2, swapType); @@ -70,7 +62,7 @@ export class SorService { } } - private logResult(logType: string, v1: CowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType) { + private logResult(logType: string, v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType) { // console.log() will log to cloudwatch console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2?.outputAmount.amount.toString()); } diff --git a/modules/sor/sorV1/constants.ts b/modules/sor/sorV1/constants.ts index 75f429559..12626f829 100644 --- a/modules/sor/sorV1/constants.ts +++ b/modules/sor/sorV1/constants.ts @@ -1,6 +1,6 @@ -import { CowSwapApiResponse } from '../sorV1/types'; +import { GqlCowSwapApiResponse } from '../../../schema'; -export const EMPTY_COWSWAP_RESPONSE: CowSwapApiResponse = { +export const EMPTY_COWSWAP_RESPONSE: GqlCowSwapApiResponse = { tokenAddresses: [], swaps: [], swapAmount: '0', diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 4e1eec98c..26fee0147 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -1,20 +1,20 @@ -import axios, { AxiosError } from 'axios'; -import { GqlSorSwapType, GqlSorSwapOptionsInput } from '../../../schema'; +import axios from 'axios'; +import { GqlSorSwapType, GqlSorSwapOptionsInput, GqlCowSwapApiResponse } from '../../../schema'; import { GetSwapsInput } from '../sor.service'; import { SwapInfo } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; -import { CowSwapApiResponse, CowSwapSwapType } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; +type CowSwapSwapType = 'buy' | 'sell'; export class SorV1Service { public async getSwaps({ tokenIn, tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { + }: GetSwapsInput): Promise { return await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); } @@ -32,7 +32,7 @@ export class SorV1Service { tokenIn: string, tokenOut: string, swapAmountScaled: string, - ): Promise { + ): Promise { const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); const swapData = { @@ -44,7 +44,7 @@ export class SorV1Service { }; try { - const { data } = await axios.post( + const { data } = await axios.post( endPoint, swapData, ); diff --git a/modules/sor/sorV1/types.ts b/modules/sor/sorV1/types.ts deleted file mode 100644 index be7ad1a37..000000000 --- a/modules/sor/sorV1/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface CowSwapApiResponse { - tokenAddresses: string[]; - swaps: Swap[]; - swapAmount: string; - swapAmountForSwaps: string; - returnAmount: string; - returnAmountFromSwaps: string; - returnAmountConsideringFees: string; - tokenIn: string; - tokenOut: string; - marketSp: string; -} - -export interface Swap { - poolId: string; - assetInIndex: number; - assetOutIndex: number; - amount: string; - userData: string; - returnAmount: string; -} - -export type CowSwapSwapType = 'buy' | 'sell'; \ No newline at end of file diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 6c8c6592b..a921ff6e0 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -25,7 +25,7 @@ import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; -import { CowSwapApiResponse } from '../sorV1/types'; +import { GqlCowSwapApiResponse } from '../../../schema'; import { EMPTY_COWSWAP_RESPONSE } from '../sorV1/constants'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -64,7 +64,7 @@ export class SorV2Service { } } - public mapResultToCowSwap(swap: Swap): CowSwapApiResponse { + public mapResultToCowSwap(swap: Swap): GqlCowSwapApiResponse { // TODO - match existing CowSwap SOR API format so its plug and play return EMPTY_COWSWAP_RESPONSE; } From be6bcfaedd3934cc2f9c63b12b9966fc9369598b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 12:08:33 +0100 Subject: [PATCH 16/78] Remove temp. --- modules/sor/sorV2/temp.ts | 59 --------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 modules/sor/sorV2/temp.ts diff --git a/modules/sor/sorV2/temp.ts b/modules/sor/sorV2/temp.ts deleted file mode 100644 index b42539c7e..000000000 --- a/modules/sor/sorV2/temp.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - BasePool, - SubgraphPoolProvider, - ChainId, - OnChainPoolDataEnricher, - SmartOrderRouter, - sorGetSwapsWithPools, - Token, - SwapKind, -} from '@balancer/sdk'; -import { networkContext } from '../../network/network-context.service'; - -// TODO - Check if this has been deployed to same address across networks? -// - If we keep then can use: poolDataQueryContract: https://github.com/beethovenxfi/beethovenx-backend/blob/sor-v2/modules/network/network-config-types.ts#L87 -const SOR_QUERIES = '0x1814a3b3e4362caf4eb54cd85b82d39bd7b34e41'; - -// Just using to compare results -// Fetches pools using Subgraph/Onchain queries. -export async function getSwapCompare(tIn: Token, tOut: Token, swapAmount: any) { - console.time('poolsFromExternal'); - const poolsFromExternal = await getBasePoolsFromExternal(); - console.timeEnd('poolsFromExternal'); - const swap = await sorGetSwapsWithPools( - tIn, - tOut, - SwapKind.GivenIn, - swapAmount, - poolsFromExternal, - // swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - console.log(`Swap Compare:`); - console.log(`No of pools: `, poolsFromExternal.length); - console.log(swap.swaps); - console.log(swap.outputAmount.amount.toString()); -} - - /** - * Uses b-sdk to fetch/enrich pools. Uses Subgraph and Onchain calls. - * ! Used for test/compare right now. - * @returns - */ -async function getBasePoolsFromExternal(): Promise { - const chainId = networkContext.chainId as unknown as ChainId; - const subgraphPoolDataService = new SubgraphPoolProvider(chainId); - const onChainPoolDataEnricher = new OnChainPoolDataEnricher( - networkContext.data.rpcUrl, - SOR_QUERIES, - ); - const sor = new SmartOrderRouter({ - chainId: chainId, - poolDataProviders: subgraphPoolDataService, - poolDataEnrichers: onChainPoolDataEnricher, - rpcUrl: networkContext.data.rpcUrl, - }); - const pools = await sor.fetchAndCachePools(); - return pools; -} \ No newline at end of file From a01a9fba78e36fd6c54fb19a5e6ce02c8e7c80cb Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 13:49:21 +0100 Subject: [PATCH 17/78] Implement mapResultToCowSwap. --- modules/sor/sor.gql | 1 - modules/sor/sorV2/sorV2.service.ts | 42 +++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql index e095cfcb4..54dba163f 100644 --- a/modules/sor/sor.gql +++ b/modules/sor/sor.gql @@ -31,5 +31,4 @@ type GqlSwap { assetOutIndex: Int! amount: String! userData: String! - returnAmount: String! } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index a921ff6e0..f3b90be8d 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -14,7 +14,7 @@ import { Swap, RawPool } from '@balancer/sdk'; -import { GqlSorSwapType } from '../../../schema'; +import { GqlSorSwapType, GqlSwap } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput } from '../sor.service'; import { tokenService } from '../../token/token.service'; @@ -64,9 +64,45 @@ export class SorV2Service { } } + /** + * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. + * @param swap + * @returns + */ public mapResultToCowSwap(swap: Swap): GqlCowSwapApiResponse { - // TODO - match existing CowSwap SOR API format so its plug and play - return EMPTY_COWSWAP_RESPONSE; + let swaps: GqlSwap[]; + if (swap.swaps instanceof Array) { + swaps = swap.swaps.map(swap => { + return { + ...swap, + amount: swap.amount.toString(), + assetInIndex: Number(swap.assetInIndex), + assetOutIndex: Number(swap.assetOutIndex), + } + }); + } else { + swaps = [{ + amount: swap.inputAmount.amount.toString(), + assetInIndex: swap.assets.indexOf(swap.swaps.assetIn), + assetOutIndex: swap.assets.indexOf(swap.swaps.assetOut), + poolId: swap.swaps.poolId, + userData: swap.swaps.userData + }]; + } + const returnAmount = swap.swapKind === SwapKind.GivenIn ? swap.outputAmount.amount.toString() : swap.inputAmount.amount.toString(); + const swapAmount = swap.swapKind === SwapKind.GivenIn ? swap.inputAmount.amount.toString() : swap.outputAmount.amount.toString(); + return { + marketSp: '', // TODO - Could this be calculate using out/in? + returnAmount, + returnAmountConsideringFees: returnAmount, // TODO - Check if CowSwap actually use this? + returnAmountFromSwaps: returnAmount, // TODO - Check if CowSwap actually use this? + swapAmount, + swapAmountForSwaps: swapAmount, // TODO - Check if CowSwap actually use this? + swaps, + tokenAddresses: swap.assets, + tokenIn: swap.inputAmount.token.address, + tokenOut: swap.outputAmount.token.address, + } } /** From 554a26e3dd826a24262e2534254219ce49265144 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 14:07:17 +0100 Subject: [PATCH 18/78] Compare correctly for ExactOut. --- modules/sor/sor.service.ts | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index fc2f4de15..a04c6c2ec 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,7 +1,7 @@ import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { Swap } from '@balancer/sdk'; +import { Swap, SwapKind } from '@balancer/sdk'; export interface GetSwapsInput { tokenIn: string; @@ -33,7 +33,6 @@ export class SorService { swapAmount, }); console.timeEnd('sorV2'); - return this.getBestSwap(sorV1Result, sorV2Result, swapType); } @@ -44,27 +43,35 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap(v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType): GqlCowSwapApiResponse { + private getBestSwap(v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType, debugOut=false): GqlCowSwapApiResponse { + // Useful for comparing + if(debugOut && v2) { + console.log(v1); + console.log(sorV2Service.mapResultToCowSwap(v2)); + } + if(!v2) { - if(v1.returnAmount === '0') { - this.logResult(`No Result`, v1, v2, swapType); - } - else { - this.logResult(`V1 (No V2)`, v1, v2, swapType); - } + if(v1.returnAmount === '0') this.logResult(`No Result`, v1, v2, swapType); + else this.logResult(`V1 (No V2)`, v1, v2, swapType); return v1; } - if(v2.outputAmount.amount < BigInt(v1.returnAmount)) { + + let isV1 = false; + if(v2.swapKind === SwapKind.GivenIn) { + if(v2.outputAmount.amount < BigInt(v1.returnAmount)) isV1 = true; + } else { + if(v2.inputAmount.amount > BigInt(v1.returnAmount)) isV1 = true; + } + if (isV1) { this.logResult(`V1`, v1, v2, swapType); return v1; - } else { + } else return sorV2Service.mapResultToCowSwap(v2); - } } private logResult(logType: string, v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType) { // console.log() will log to cloudwatch - console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2?.outputAmount.amount.toString()); + console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2?.inputAmount.amount.toString(), v2?.outputAmount.amount.toString()); } } From 388772e8ae8af873273d0c4343e30aa735cf6389 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 14:17:05 +0100 Subject: [PATCH 19/78] Use cloneDeep for b-sdk as it mutates pools. --- modules/sor/sorV2/sorV2.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index f3b90be8d..3a274f22c 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -26,7 +26,7 @@ import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; import { GqlCowSwapApiResponse } from '../../../schema'; -import { EMPTY_COWSWAP_RESPONSE } from '../sorV1/constants'; +import cloneDeep from 'lodash/cloneDeep'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -49,12 +49,13 @@ export class SorV2Service { const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); try { + // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, tOut, this.mapSwapType(swapType), swapAmount, - poolsFromDb, + cloneDeep(poolsFromDb), // swapOptions, // TODO - Handle properly ); return swap; From 3578f01e8e3346af5bb933537c544223af6fe797 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 14:20:29 +0100 Subject: [PATCH 20/78] Remove TODO on swapOptions as I believe we dont need it. --- modules/sor/sorV2/sorV2.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 3a274f22c..a92bbc2ab 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -56,7 +56,7 @@ export class SorV2Service { this.mapSwapType(swapType), swapAmount, cloneDeep(poolsFromDb), - // swapOptions, // TODO - Handle properly + // swapOptions, // I don't think we need specific swapOptions for this? ); return swap; } catch (err) { From a0ec5c3bdc93bc7934b08bf4f87f901e6ba80e09 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 19 May 2023 14:35:06 +0100 Subject: [PATCH 21/78] Remove old gql. --- modules/beethoven/balancer-sdk.gql | 7 ------- modules/beethoven/balancer-sdk.resolvers.ts | 5 ----- modules/sor/sor.gql | 2 +- modules/sor/sor.resolvers.ts | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index e4ab912fc..9b22e1782 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -1,11 +1,4 @@ extend type Query { - sorGetSwaps( - tokenIn: String! - tokenOut: String! - swapType: GqlSorSwapType! - swapAmount: BigDecimal! #expected in human readable form - swapOptions: GqlSorSwapOptionsInput! - ): GqlSorGetSwapsResponse! sorGetBatchSwapForTokensIn( tokensIn: [GqlTokenAmountHumanReadable!]! tokenOut: String! diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index 3f889b5af..ec2333ce8 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -4,11 +4,6 @@ import { tokenService } from '../token/token.service'; const balancerSdkResolvers: Resolvers = { Query: { - sorGetSwaps: async (parent, args, context) => { - const tokens = await tokenService.getTokens(); - - return balancerSorService.getSwaps({ ...args, tokens }); - }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { const tokens = await tokenService.getTokens(); diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql index 54dba163f..6e2c43c97 100644 --- a/modules/sor/sor.gql +++ b/modules/sor/sor.gql @@ -1,5 +1,5 @@ extend type Query { - sorGetSwapsNew( + sorGetSwaps( tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts index 2b80e8494..2d7402224 100644 --- a/modules/sor/sor.resolvers.ts +++ b/modules/sor/sor.resolvers.ts @@ -4,7 +4,7 @@ import { tokenService } from '../token/token.service'; const sorResolvers: Resolvers = { Query: { - sorGetSwapsNew: async (parent, args, context) => { + sorGetSwaps: async (parent, args, context) => { return sorService.getSwaps({ ...args }); }, }, From 65f84adeacec39c268701a071efcdbe2ca7ef2f0 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 25 May 2023 14:12:23 +0100 Subject: [PATCH 22/78] Refactor to common Swap result interface. --- modules/sor/sor.service.ts | 43 +++++------- modules/sor/sorV1/sorV1.service.ts | 31 +++++++-- modules/sor/sorV2/sorV2.service.ts | 103 +++++++++++++++++++---------- modules/sor/types.ts | 19 ++++++ 4 files changed, 130 insertions(+), 66 deletions(-) create mode 100644 modules/sor/types.ts diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index a04c6c2ec..02d50df1d 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,14 +1,7 @@ import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { Swap, SwapKind } from '@balancer/sdk'; - -export interface GetSwapsInput { - tokenIn: string; - tokenOut: string; - swapType: GqlSorSwapType; - swapAmount: string; -} +import { GetSwapsInput, Swap } from './types'; export class SorService { public async getSwaps({ @@ -18,7 +11,7 @@ export class SorService { swapAmount, }: GetSwapsInput): Promise { console.time('sorV1'); - const sorV1Result = await sorV1Service.getSwaps({ + const sorV1Result = await sorV1Service.getSwap({ tokenIn, tokenOut, swapType, @@ -26,7 +19,7 @@ export class SorService { }); console.timeEnd('sorV1'); console.time('sorV2'); - const sorV2Result = await sorV2Service.getSwaps({ + const sorV2Result = await sorV2Service.getSwap({ tokenIn, tokenOut, swapType, @@ -43,35 +36,35 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap(v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType, debugOut=false): GqlCowSwapApiResponse { + private getBestSwap(v1: Swap, v2: Swap, swapType: GqlSorSwapType, debugOut=false): GqlCowSwapApiResponse { // Useful for comparing - if(debugOut && v2) { + if(debugOut) { console.log(v1); - console.log(sorV2Service.mapResultToCowSwap(v2)); + console.log(v2); } - if(!v2) { - if(v1.returnAmount === '0') this.logResult(`No Result`, v1, v2, swapType); - else this.logResult(`V1 (No V2)`, v1, v2, swapType); - return v1; + if(v1.outputAmount === BigInt(0) && v2.outputAmount === BigInt(0)) { + this.logResult(`No Result`, v1.getSwap(), v2.getSwap(), swapType); + // TODO - What should we return as a good response here? + return v1.getSwap(); } let isV1 = false; - if(v2.swapKind === SwapKind.GivenIn) { - if(v2.outputAmount.amount < BigInt(v1.returnAmount)) isV1 = true; + if(swapType === 'EXACT_IN') { + if(v2.outputAmount < v1.outputAmount) isV1 = true; } else { - if(v2.inputAmount.amount > BigInt(v1.returnAmount)) isV1 = true; + if(v2.inputAmount > v1.inputAmount) isV1 = true; } if (isV1) { - this.logResult(`V1`, v1, v2, swapType); - return v1; + this.logResult(`V1`, v1.getSwap(), v2.getSwap(), swapType); + return v1.getSwap(); } else - return sorV2Service.mapResultToCowSwap(v2); + return v2.getSwap(); } - private logResult(logType: string, v1: GqlCowSwapApiResponse, v2: Swap | null, swapType: GqlSorSwapType) { + private logResult(logType: string, v1: GqlCowSwapApiResponse, v2: GqlCowSwapApiResponse, swapType: GqlSorSwapType) { // console.log() will log to cloudwatch - console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2?.inputAmount.amount.toString(), v2?.outputAmount.amount.toString()); + console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2.swapAmount, v2.returnAmount); } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 26fee0147..31f3fb544 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { GqlSorSwapType, GqlSorSwapOptionsInput, GqlCowSwapApiResponse } from '../../../schema'; -import { GetSwapsInput } from '../sor.service'; +import { GetSwapsInput, SwapService, Swap } from '../types'; import { SwapInfo } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; @@ -8,15 +8,34 @@ import { DeploymentEnv } from '../../network/network-config-types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; type CowSwapSwapType = 'buy' | 'sell'; -export class SorV1Service { - public async getSwaps({ + +class SwapResult implements Swap { + + constructor(private swap: GqlCowSwapApiResponse, public inputAmount: bigint, public outputAmount: bigint) { + } + + getSwap(): GqlCowSwapApiResponse { + return this.swap; + } + + async queryAndUpdate(): Promise { + // TODO + return this.swap; + } +} +export class SorV1Service implements SwapService { + + public async getSwap({ tokenIn, tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { - return await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); - } + }: GetSwapsInput): Promise { + const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); + const inputAmout = swapType === 'EXACT_IN' ? swapAmount : swap.returnAmount; + const outputAmout = swapType === 'EXACT_IN' ? swap.returnAmount : swapAmount; + return new SwapResult(swap, BigInt(inputAmout), BigInt(outputAmout)); + }; /** * Query Balancer API CowSwap/SOR endpoint. diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index a92bbc2ab..d3d583c24 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -11,12 +11,12 @@ import { RawWeightedPool, RawComposableStablePool, RawMetaStablePool, - Swap, + Swap as SwapSdk, RawPool } from '@balancer/sdk'; import { GqlSorSwapType, GqlSwap } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; -import { GetSwapsInput } from '../sor.service'; +import { GetSwapsInput, Swap } from '../types'; import { tokenService } from '../../token/token.service'; import { networkContext } from '../../network/network-context.service'; import { prisma } from '../../../prisma/prisma-client'; @@ -30,47 +30,43 @@ import cloneDeep from 'lodash/cloneDeep'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; -export class SorV2Service { - cache: CacheClass; +class SwapResult implements Swap { - constructor() { - this.cache = new Cache(); - } - public async getSwaps({ - tokenIn, - tokenOut, - swapType, - swapAmount, - }: GetSwapsInput): Promise { - console.time('getBasePools'); - const poolsFromDb = await this.getBasePools(); - console.timeEnd('getBasePools'); - const chainId = networkContext.chainId as unknown as ChainId; - const tIn = await this.getToken(tokenIn as Address, chainId); - const tOut = await this.getToken(tokenOut as Address, chainId); - try { - // Constructing a Swap mutates the pools so I used cloneDeep - const swap = await sorGetSwapsWithPools( - tIn, - tOut, - this.mapSwapType(swapType), - swapAmount, - cloneDeep(poolsFromDb), - // swapOptions, // I don't think we need specific swapOptions for this? - ); - return swap; - } catch (err) { - console.log(`sorV2 Service Error`, err); - return null; + private swap: SwapSdk | null; + public inputAmount: bigint; + public outputAmount: bigint; + + constructor(swap: SwapSdk | null) { + if(swap === null) { + this.swap = null; + this.inputAmount = BigInt(0); + this.outputAmount = BigInt(0); + } else { + this.swap = swap; + this.inputAmount = swap.inputAmount.amount; + this.outputAmount = swap.outputAmount.amount; } } - /** + getSwap(): GqlCowSwapApiResponse { + if(this.swap === null) + return {} as GqlCowSwapApiResponse; + return this.mapResultToCowSwap(this.swap); + } + + async queryAndUpdate(): Promise { + // TODO + if(this.swap === null) + return {} as GqlCowSwapApiResponse; + return this.mapResultToCowSwap(this.swap); + } + + /** * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. * @param swap * @returns */ - public mapResultToCowSwap(swap: Swap): GqlCowSwapApiResponse { + private mapResultToCowSwap(swap: SwapSdk): GqlCowSwapApiResponse { let swaps: GqlSwap[]; if (swap.swaps instanceof Array) { swaps = swap.swaps.map(swap => { @@ -105,6 +101,43 @@ export class SorV2Service { tokenOut: swap.outputAmount.token.address, } } +} + +export class SorV2Service { + cache: CacheClass; + + constructor() { + this.cache = new Cache(); + } + + public async getSwap({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }: GetSwapsInput): Promise { + console.time('getBasePools'); + const poolsFromDb = await this.getBasePools(); + console.timeEnd('getBasePools'); + const chainId = networkContext.chainId as unknown as ChainId; + const tIn = await this.getToken(tokenIn as Address, chainId); + const tOut = await this.getToken(tokenOut as Address, chainId); + try { + // Constructing a Swap mutates the pools so I used cloneDeep + const swap = await sorGetSwapsWithPools( + tIn, + tOut, + this.mapSwapType(swapType), + swapAmount, + cloneDeep(poolsFromDb), + // swapOptions, // I don't think we need specific swapOptions for this? + ); + return new SwapResult(swap); + } catch (err) { + console.log(`sorV2 Service Error`, err); + return new SwapResult(null); + } + }; /** * Gets a b-sdk Token based off tokenAddr. diff --git a/modules/sor/types.ts b/modules/sor/types.ts new file mode 100644 index 000000000..775c9c0cd --- /dev/null +++ b/modules/sor/types.ts @@ -0,0 +1,19 @@ +import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; + +export interface GetSwapsInput { + tokenIn: string; + tokenOut: string; + swapType: GqlSorSwapType; + swapAmount: string; +} + +export interface Swap { + getSwap(): GqlCowSwapApiResponse; + queryAndUpdate(): Promise; + outputAmount: bigint; + inputAmount: bigint; +} + +export interface SwapService { + getSwap(inputs: GetSwapsInput): Promise; +} \ No newline at end of file From 946a6ed0e2937ae965ee28fc2031df68cbb91fc6 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 25 May 2023 15:20:46 +0100 Subject: [PATCH 23/78] Update SDK package. --- package.json | 2 +- yarn.lock | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index eee79e5cc..26333de6b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", - "@balancer/sdk": "^0.0.1", + "@balancer/sdk": "^0.0.3", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", diff --git a/yarn.lock b/yarn.lock index 23d5571c9..d1ab30cec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2558,10 +2558,10 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" -"@balancer/sdk@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.0.1.tgz#484147f73c29d58a2df470e9a58a465e54d8e4c8" - integrity sha512-v2smtCc7QXHWysxLQXQlyfFflmIl3ZsP2NNVKLirjn6nTYmKJRBvphfhKdV7CmCHaTRAI/FTaxnQ7/Zirre2Ug== +"@balancer/sdk@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.0.3.tgz#3579f4ab9f3b918d70bd1ac280fcf49e8bf83616" + integrity sha512-L2/5n5AFM2Ci4YMcHGeq7cZO0xJCDA2VPoBulTv5YgZrr1LPE37vq9pLmqdAjvyKBM6xN6nqWXEXEOfQD40Kow== dependencies: async-retry "^1.3.3" decimal.js-light "^2.5.1" @@ -6355,9 +6355,9 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-redact@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" - integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== + version "3.2.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.2.0.tgz#b1e2d39bc731376d28bde844454fa23e26919987" + integrity sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw== fast-text-encoding@^1.0.0: version "1.0.6" @@ -9119,14 +9119,14 @@ pino-abstract-transport@v1.0.0: split2 "^4.0.0" pino-std-serializers@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.0.tgz#169048c0df3f61352fce56aeb7fb962f1b66ab43" - integrity sha512-IWgSzUL8X1w4BIWTwErRgtV8PyOGOOi60uqv0oKuS/fOA8Nco/OeI6lBuc4dyP8MMfdFwyHqTMcBIA7nDiqEqA== + version "6.2.1" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz#369f4ae2a19eb6d769ddf2c88a2164b76879a284" + integrity sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ== pino@^8.11.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.12.0.tgz#03039e1cbf42dee787d5f66d74ee88b16ab43990" - integrity sha512-qHiXP7x9hFnJb0EnrLzD4onHj82Vh91lEtx6UiPT2eNoHD7lwi50CUIz+s0Z7YXgtMc7IIjss4yIIxZu6T1Tdg== + version "8.14.1" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.14.1.tgz#bb38dcda8b500dd90c1193b6c9171eb777a47ac8" + integrity sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw== dependencies: atomic-sleep "^1.0.0" fast-redact "^3.1.1" @@ -9364,9 +9364,9 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable util-deprecate "^1.0.1" readable-stream@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" - integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" + integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" From f14a2039705b977c37871eb0ede6258e6c3eba46 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 25 May 2023 15:21:18 +0100 Subject: [PATCH 24/78] Implement onchain query method for V2 service. --- modules/sor/sor.service.ts | 34 ++++++++++++++----------- modules/sor/sorV1/sorV1.service.ts | 11 ++++---- modules/sor/sorV2/sorV2.service.ts | 40 +++++++++++++++++++----------- modules/sor/types.ts | 5 ++-- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 02d50df1d..898e3c91f 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -26,7 +26,17 @@ export class SorService { swapAmount, }); console.timeEnd('sorV2'); - return this.getBestSwap(sorV1Result, sorV2Result, swapType); + const bestSwap = this.getBestSwap(sorV1Result, sorV2Result, swapType); + if(bestSwap === null) return {} as GqlCowSwapApiResponse; // TODO - Whats the best response here? + + try { + // Updates with latest onchain data before returning + return await bestSwap.getSwap(true); + } catch (err) { + console.log(`Error Retrieving QuerySwap`); + console.log(err); + return {} as GqlCowSwapApiResponse; // TODO - Whats the best response here? + } } /** @@ -36,18 +46,15 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap(v1: Swap, v2: Swap, swapType: GqlSorSwapType, debugOut=false): GqlCowSwapApiResponse { + private getBestSwap(v1: Swap, v2: Swap, swapType: GqlSorSwapType, debugOut=false): Swap | null { // Useful for comparing if(debugOut) { console.log(v1); console.log(v2); } - if(v1.outputAmount === BigInt(0) && v2.outputAmount === BigInt(0)) { - this.logResult(`No Result`, v1.getSwap(), v2.getSwap(), swapType); - // TODO - What should we return as a good response here? - return v1.getSwap(); - } + if(v1.outputAmount === BigInt(0) && v2.outputAmount === BigInt(0)) + return null let isV1 = false; if(swapType === 'EXACT_IN') { @@ -55,16 +62,15 @@ export class SorService { } else { if(v2.inputAmount > v1.inputAmount) isV1 = true; } - if (isV1) { - this.logResult(`V1`, v1.getSwap(), v2.getSwap(), swapType); - return v1.getSwap(); - } else - return v2.getSwap(); + if(isV1 === true) { + this.logResult(`V1`, v1, v2, swapType); + return v1; + } else return v2; } - private logResult(logType: string, v1: GqlCowSwapApiResponse, v2: GqlCowSwapApiResponse, swapType: GqlSorSwapType) { + private logResult(logType: string, v1: Swap, v2: Swap, swapType: GqlSorSwapType) { // console.log() will log to cloudwatch - console.log('SOR Service', logType, swapType, v1.tokenIn, v1.tokenOut, v1.swapAmount, v1.returnAmount, v2.swapAmount, v2.returnAmount); + console.log('SOR Service', logType, swapType, v1.assetIn, v1.assetOut, v1.inputAmount, v1.outputAmount, v2.inputAmount, v2.outputAmount); } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 31f3fb544..166dd536f 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -10,16 +10,15 @@ import { EMPTY_COWSWAP_RESPONSE } from './constants'; type CowSwapSwapType = 'buy' | 'sell'; class SwapResult implements Swap { + public assetIn: string; + public assetOut: string; constructor(private swap: GqlCowSwapApiResponse, public inputAmount: bigint, public outputAmount: bigint) { + this.assetIn = swap.tokenIn; + this.assetOut = swap.tokenOut; } - getSwap(): GqlCowSwapApiResponse { - return this.swap; - } - - async queryAndUpdate(): Promise { - // TODO + async getSwap(queryFirst = false): Promise { return this.swap; } } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index d3d583c24..5e2faeed5 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -12,7 +12,8 @@ import { RawComposableStablePool, RawMetaStablePool, Swap as SwapSdk, - RawPool + RawPool, + TokenAmount } from '@balancer/sdk'; import { GqlSorSwapType, GqlSwap } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; @@ -31,42 +32,51 @@ import cloneDeep from 'lodash/cloneDeep'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; class SwapResult implements Swap { - private swap: SwapSdk | null; public inputAmount: bigint; public outputAmount: bigint; + public assetIn: string; + public assetOut: string; constructor(swap: SwapSdk | null) { if(swap === null) { this.swap = null; this.inputAmount = BigInt(0); this.outputAmount = BigInt(0); + this.assetIn = ''; + this.assetOut = ''; } else { this.swap = swap; + this.assetIn = swap.inputAmount.token.address; + this.assetOut = swap.outputAmount.token.address; this.inputAmount = swap.inputAmount.amount; this.outputAmount = swap.outputAmount.amount; } } - getSwap(): GqlCowSwapApiResponse { + async getSwap(queryFirst = false): Promise { if(this.swap === null) return {} as GqlCowSwapApiResponse; - return this.mapResultToCowSwap(this.swap); - } + if(!queryFirst) + return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + else { + // Needs node >= 18 (https://github.com/wagmi-dev/viem/discussions/147) + const updatedResult = await this.swap.query(networkContext.data.rpcUrl); + console.log(`UPDATE:`, this.swap.quote.amount.toString(), updatedResult.amount.toString()); - async queryAndUpdate(): Promise { - // TODO - if(this.swap === null) - return {} as GqlCowSwapApiResponse; - return this.mapResultToCowSwap(this.swap); + const ip = this.swap.swapKind === SwapKind.GivenIn ? this.swap.inputAmount : updatedResult; + const op = this.swap.swapKind === SwapKind.GivenIn ? updatedResult : this.swap.outputAmount; + + return this.mapResultToCowSwap(this.swap, ip, op); + } } - /** + /** * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. * @param swap * @returns */ - private mapResultToCowSwap(swap: SwapSdk): GqlCowSwapApiResponse { + private mapResultToCowSwap(swap: SwapSdk, inputAmount: TokenAmount, outputAmount: TokenAmount): GqlCowSwapApiResponse { let swaps: GqlSwap[]; if (swap.swaps instanceof Array) { swaps = swap.swaps.map(swap => { @@ -79,15 +89,15 @@ class SwapResult implements Swap { }); } else { swaps = [{ - amount: swap.inputAmount.amount.toString(), + amount: inputAmount.amount.toString(), assetInIndex: swap.assets.indexOf(swap.swaps.assetIn), assetOutIndex: swap.assets.indexOf(swap.swaps.assetOut), poolId: swap.swaps.poolId, userData: swap.swaps.userData }]; } - const returnAmount = swap.swapKind === SwapKind.GivenIn ? swap.outputAmount.amount.toString() : swap.inputAmount.amount.toString(); - const swapAmount = swap.swapKind === SwapKind.GivenIn ? swap.inputAmount.amount.toString() : swap.outputAmount.amount.toString(); + const returnAmount = swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); + const swapAmount = swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); return { marketSp: '', // TODO - Could this be calculate using out/in? returnAmount, diff --git a/modules/sor/types.ts b/modules/sor/types.ts index 775c9c0cd..521b11c7e 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -8,8 +8,9 @@ export interface GetSwapsInput { } export interface Swap { - getSwap(): GqlCowSwapApiResponse; - queryAndUpdate(): Promise; + getSwap(queryFirst: boolean): Promise; + assetIn: string; + assetOut: string; outputAmount: bigint; inputAmount: bigint; } From 311b82d294eaa69830b4a3d1e92a4d4f549d74d3 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 25 May 2023 16:04:44 +0100 Subject: [PATCH 25/78] Implement onchain query method for V1 service. --- modules/sor/sorV1/sorV1.service.ts | 39 +++++++++++++++++++++++++++--- modules/sor/sorV2/sorV2.service.ts | 2 +- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index 166dd536f..dc7635aeb 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -1,26 +1,59 @@ import axios from 'axios'; +import { AddressZero } from '@ethersproject/constants'; +import { Contract } from '@ethersproject/contracts'; import { GqlSorSwapType, GqlSorSwapOptionsInput, GqlCowSwapApiResponse } from '../../../schema'; import { GetSwapsInput, SwapService, Swap } from '../types'; -import { SwapInfo } from '@balancer-labs/sdk'; +import { SwapInfo, FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; +import VaultAbi from '../../pool/abi/Vault.json'; +import { BigNumber } from 'ethers'; + type CowSwapSwapType = 'buy' | 'sell'; class SwapResult implements Swap { public assetIn: string; public assetOut: string; - constructor(private swap: GqlCowSwapApiResponse, public inputAmount: bigint, public outputAmount: bigint) { + constructor(private swap: GqlCowSwapApiResponse, public inputAmount: bigint, public outputAmount: bigint, private swapType: GqlSorSwapType) { this.assetIn = swap.tokenIn; this.assetOut = swap.tokenOut; } async getSwap(queryFirst = false): Promise { + if(queryFirst) { + const swapType = this.mapSwapType(this.swapType); + const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses); + const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.assetIn)].toString(); + const tokenOutAmount = deltas[this.swap.tokenAddresses.indexOf(this.assetOut)].abs().toString(); + // console.log(`UPDATE:`, this.inputAmount, this.outputAmount, tokenInAmount, tokenOutAmount, deltas.toString()); + return { + ...this.swap, + returnAmount: swapType === SwapTypes.SwapExactIn ? tokenOutAmount : tokenInAmount, + swapAmount: swapType === SwapTypes.SwapExactIn ? tokenInAmount : tokenOutAmount, + } + } return this.swap; } + + private queryBatchSwap(swapType: SwapTypes, swaps: SwapV2[], assets: string[]): Promise { + const vaultContract = new Contract(networkContext.data.balancer.vault, VaultAbi, networkContext.provider); + const funds: FundManagement = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, + }; + + return vaultContract.queryBatchSwap(swapType, swaps, assets, funds); + } + + private mapSwapType(swapType: GqlSorSwapType): SwapTypes { + return swapType === "EXACT_IN" ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut; + } } export class SorV1Service implements SwapService { @@ -33,7 +66,7 @@ export class SorV1Service implements SwapService { const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); const inputAmout = swapType === 'EXACT_IN' ? swapAmount : swap.returnAmount; const outputAmout = swapType === 'EXACT_IN' ? swap.returnAmount : swapAmount; - return new SwapResult(swap, BigInt(inputAmout), BigInt(outputAmout)); + return new SwapResult(swap, BigInt(inputAmout), BigInt(outputAmout), swapType); }; /** diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 5e2faeed5..752f9e0be 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -62,7 +62,7 @@ class SwapResult implements Swap { else { // Needs node >= 18 (https://github.com/wagmi-dev/viem/discussions/147) const updatedResult = await this.swap.query(networkContext.data.rpcUrl); - console.log(`UPDATE:`, this.swap.quote.amount.toString(), updatedResult.amount.toString()); + // console.log(`UPDATE:`, this.swap.quote.amount.toString(), updatedResult.amount.toString()); const ip = this.swap.swapKind === SwapKind.GivenIn ? this.swap.inputAmount : updatedResult; const op = this.swap.swapKind === SwapKind.GivenIn ? updatedResult : this.swap.outputAmount; From 5d04f38db215632ac25ef1840e02715e70d5599b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 30 May 2023 10:00:23 +0100 Subject: [PATCH 26/78] Handle empty responses. --- modules/sor/constants.ts | 19 ++++++++++ modules/sor/sor.service.ts | 28 +++++++------- modules/sor/sorV1/constants.ts | 14 ------- modules/sor/sorV1/sorV1.service.ts | 60 ++++++++++++++++-------------- modules/sor/sorV2/sorV2.service.ts | 42 ++++++++++----------- modules/sor/types.ts | 9 ++--- 6 files changed, 89 insertions(+), 83 deletions(-) create mode 100644 modules/sor/constants.ts delete mode 100644 modules/sor/sorV1/constants.ts diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts new file mode 100644 index 000000000..fe027999e --- /dev/null +++ b/modules/sor/constants.ts @@ -0,0 +1,19 @@ +import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; + +export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, swapType: GqlSorSwapType, amount: string): GqlCowSwapApiResponse => { + const returnAmount = swapType === "EXACT_IN" ? '0' : amount; + const swapAmount = swapType === "EXACT_IN" ? amount : '0'; + + return { + marketSp: '0', + returnAmount, + returnAmountConsideringFees: '0', + returnAmountFromSwaps: '0', + swapAmount, + swapAmountForSwaps: '0', + swaps: [], + tokenAddresses: [], + tokenIn: assetIn, + tokenOut: assetOut, + } +} \ No newline at end of file diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 898e3c91f..865b4670d 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,7 +1,8 @@ import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { GetSwapsInput, Swap } from './types'; +import { GetSwapsInput, SwapResult } from './types'; +import { EMPTY_COWSWAP_RESPONSE } from './constants'; export class SorService { public async getSwaps({ @@ -11,7 +12,7 @@ export class SorService { swapAmount, }: GetSwapsInput): Promise { console.time('sorV1'); - const sorV1Result = await sorV1Service.getSwap({ + const swapV1 = await sorV1Service.getSwapResult({ tokenIn, tokenOut, swapType, @@ -19,23 +20,25 @@ export class SorService { }); console.timeEnd('sorV1'); console.time('sorV2'); - const sorV2Result = await sorV2Service.getSwap({ + const swapV2 = await sorV2Service.getSwapResult({ tokenIn, tokenOut, swapType, swapAmount, }); console.timeEnd('sorV2'); - const bestSwap = this.getBestSwap(sorV1Result, sorV2Result, swapType); - if(bestSwap === null) return {} as GqlCowSwapApiResponse; // TODO - Whats the best response here? + if(!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapType, swapAmount); + + const bestSwap = this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut, true); + try { // Updates with latest onchain data before returning - return await bestSwap.getSwap(true); + return await bestSwap.getSwapResponse(true); } catch (err) { console.log(`Error Retrieving QuerySwap`); console.log(err); - return {} as GqlCowSwapApiResponse; // TODO - Whats the best response here? + return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapType, swapAmount); } } @@ -46,16 +49,13 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap(v1: Swap, v2: Swap, swapType: GqlSorSwapType, debugOut=false): Swap | null { + private getBestSwap(v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string, debugOut=false): SwapResult { // Useful for comparing if(debugOut) { console.log(v1); console.log(v2); } - if(v1.outputAmount === BigInt(0) && v2.outputAmount === BigInt(0)) - return null - let isV1 = false; if(swapType === 'EXACT_IN') { if(v2.outputAmount < v1.outputAmount) isV1 = true; @@ -63,14 +63,14 @@ export class SorService { if(v2.inputAmount > v1.inputAmount) isV1 = true; } if(isV1 === true) { - this.logResult(`V1`, v1, v2, swapType); + this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); return v1; } else return v2; } - private logResult(logType: string, v1: Swap, v2: Swap, swapType: GqlSorSwapType) { + private logResult(logType: string, v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string) { // console.log() will log to cloudwatch - console.log('SOR Service', logType, swapType, v1.assetIn, v1.assetOut, v1.inputAmount, v1.outputAmount, v2.inputAmount, v2.outputAmount); + console.log('SOR Service', logType, swapType, assetIn, assetOut, v1.inputAmount, v1.outputAmount, v2.inputAmount, v2.outputAmount); } } diff --git a/modules/sor/sorV1/constants.ts b/modules/sor/sorV1/constants.ts deleted file mode 100644 index 12626f829..000000000 --- a/modules/sor/sorV1/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { GqlCowSwapApiResponse } from '../../../schema'; - -export const EMPTY_COWSWAP_RESPONSE: GqlCowSwapApiResponse = { - tokenAddresses: [], - swaps: [], - swapAmount: '0', - swapAmountForSwaps: '0', - returnAmount: '0', - returnAmountFromSwaps: '0', - returnAmountConsideringFees: '0', - tokenIn: '', - tokenOut: '', - marketSp: '0', -}; \ No newline at end of file diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index dc7635aeb..bc346d934 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -2,33 +2,41 @@ import axios from 'axios'; import { AddressZero } from '@ethersproject/constants'; import { Contract } from '@ethersproject/contracts'; import { GqlSorSwapType, GqlSorSwapOptionsInput, GqlCowSwapApiResponse } from '../../../schema'; -import { GetSwapsInput, SwapService, Swap } from '../types'; +import { GetSwapsInput, SwapService, SwapResult } from '../types'; import { SwapInfo, FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; -import { EMPTY_COWSWAP_RESPONSE } from './constants'; import VaultAbi from '../../pool/abi/Vault.json'; import { BigNumber } from 'ethers'; type CowSwapSwapType = 'buy' | 'sell'; -class SwapResult implements Swap { - public assetIn: string; - public assetOut: string; +class SwapResultV1 implements SwapResult { + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; - constructor(private swap: GqlCowSwapApiResponse, public inputAmount: bigint, public outputAmount: bigint, private swapType: GqlSorSwapType) { - this.assetIn = swap.tokenIn; - this.assetOut = swap.tokenOut; + constructor(private swap: GqlCowSwapApiResponse | null, private swapType: GqlSorSwapType) { + if(swap === null) { + this.isValid = false; + this.swap = null; + } else { + this.inputAmount = swapType === 'EXACT_IN' ? BigInt(swap.swapAmount) : BigInt(swap.returnAmount); + this.outputAmount = swapType === 'EXACT_IN' ? BigInt(swap.returnAmount) : BigInt(swap.swapAmount); + this.isValid = swap.swaps.length === 0 ? false : true; + } } - async getSwap(queryFirst = false): Promise { + async getSwapResponse(queryFirst = false): Promise { + if(!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap') + if(queryFirst) { const swapType = this.mapSwapType(this.swapType); const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses); - const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.assetIn)].toString(); - const tokenOutAmount = deltas[this.swap.tokenAddresses.indexOf(this.assetOut)].abs().toString(); + const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenIn)].toString(); + const tokenOutAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenOut)].abs().toString(); // console.log(`UPDATE:`, this.inputAmount, this.outputAmount, tokenInAmount, tokenOutAmount, deltas.toString()); return { ...this.swap, @@ -57,16 +65,19 @@ class SwapResult implements Swap { } export class SorV1Service implements SwapService { - public async getSwap({ + public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { - const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); - const inputAmout = swapType === 'EXACT_IN' ? swapAmount : swap.returnAmount; - const outputAmout = swapType === 'EXACT_IN' ? swap.returnAmount : swapAmount; - return new SwapResult(swap, BigInt(inputAmout), BigInt(outputAmout), swapType); + }: GetSwapsInput): Promise { + try { + const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); + return new SwapResultV1(swap, swapType); + } catch (err) { + console.log(`sorV1 Service Error`, err); + return new SwapResultV1(null, swapType); + } }; /** @@ -94,16 +105,11 @@ export class SorV1Service implements SwapService { gasPrice }; - try { - const { data } = await axios.post( - endPoint, - swapData, - ); - return data; - } catch (err) { - console.log(`sorV1 Service Error`, err); - return EMPTY_COWSWAP_RESPONSE; - } + const { data } = await axios.post( + endPoint, + swapData, + ); + return data; } private mapSwapType(swapType: GqlSorSwapType): CowSwapSwapType { diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 752f9e0be..4fbc8aef6 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -15,9 +15,10 @@ import { RawPool, TokenAmount } from '@balancer/sdk'; +import cloneDeep from 'lodash/cloneDeep'; import { GqlSorSwapType, GqlSwap } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; -import { GetSwapsInput, Swap } from '../types'; +import { GetSwapsInput, SwapResult, SwapService } from '../types'; import { tokenService } from '../../token/token.service'; import { networkContext } from '../../network/network-context.service'; import { prisma } from '../../../prisma/prisma-client'; @@ -27,36 +28,30 @@ import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; import { GqlCowSwapApiResponse } from '../../../schema'; -import cloneDeep from 'lodash/cloneDeep'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; -class SwapResult implements Swap { +class SwapResultV2 implements SwapResult { private swap: SwapSdk | null; - public inputAmount: bigint; - public outputAmount: bigint; - public assetIn: string; - public assetOut: string; + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; constructor(swap: SwapSdk | null) { if(swap === null) { + this.isValid = false; this.swap = null; - this.inputAmount = BigInt(0); - this.outputAmount = BigInt(0); - this.assetIn = ''; - this.assetOut = ''; } else { + this.isValid = true; this.swap = swap; - this.assetIn = swap.inputAmount.token.address; - this.assetOut = swap.outputAmount.token.address; this.inputAmount = swap.inputAmount.amount; this.outputAmount = swap.outputAmount.amount; } } - async getSwap(queryFirst = false): Promise { - if(this.swap === null) - return {} as GqlCowSwapApiResponse; + async getSwapResponse(queryFirst = false): Promise { + if(!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap') + if(!queryFirst) return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); else { @@ -99,7 +94,7 @@ class SwapResult implements Swap { const returnAmount = swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); const swapAmount = swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); return { - marketSp: '', // TODO - Could this be calculate using out/in? + marketSp: '', // TODO - Check if CowSwap actually use this? Could this be calculate using out/in? returnAmount, returnAmountConsideringFees: returnAmount, // TODO - Check if CowSwap actually use this? returnAmountFromSwaps: returnAmount, // TODO - Check if CowSwap actually use this? @@ -113,39 +108,40 @@ class SwapResult implements Swap { } } -export class SorV2Service { +export class SorV2Service implements SwapService { cache: CacheClass; constructor() { this.cache = new Cache(); } - public async getSwap({ + public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount, - }: GetSwapsInput): Promise { + }: GetSwapsInput): Promise { console.time('getBasePools'); const poolsFromDb = await this.getBasePools(); console.timeEnd('getBasePools'); const chainId = networkContext.chainId as unknown as ChainId; const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); + const swapKind = this.mapSwapType(swapType); try { // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, tOut, - this.mapSwapType(swapType), + swapKind, swapAmount, cloneDeep(poolsFromDb), // swapOptions, // I don't think we need specific swapOptions for this? ); - return new SwapResult(swap); + return new SwapResultV2(swap); } catch (err) { console.log(`sorV2 Service Error`, err); - return new SwapResult(null); + return new SwapResultV2(null); } }; diff --git a/modules/sor/types.ts b/modules/sor/types.ts index 521b11c7e..d30635245 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -7,14 +7,13 @@ export interface GetSwapsInput { swapAmount: string; } -export interface Swap { - getSwap(queryFirst: boolean): Promise; - assetIn: string; - assetOut: string; +export interface SwapResult { + getSwapResponse(queryFirst: boolean): Promise; + isValid: boolean; outputAmount: bigint; inputAmount: bigint; } export interface SwapService { - getSwap(inputs: GetSwapsInput): Promise; + getSwapResult(inputs: GetSwapsInput): Promise; } \ No newline at end of file From 633c6740dba871db5b7c32faa4920fd64c0c1299 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 30 May 2023 11:08:11 +0100 Subject: [PATCH 27/78] Return correct empty response. --- modules/sor/constants.ts | 9 +++------ modules/sor/sor.service.ts | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts index fe027999e..1770a2f07 100644 --- a/modules/sor/constants.ts +++ b/modules/sor/constants.ts @@ -1,15 +1,12 @@ import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; -export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, swapType: GqlSorSwapType, amount: string): GqlCowSwapApiResponse => { - const returnAmount = swapType === "EXACT_IN" ? '0' : amount; - const swapAmount = swapType === "EXACT_IN" ? amount : '0'; - +export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: string): GqlCowSwapApiResponse => { return { marketSp: '0', - returnAmount, + returnAmount: '0', returnAmountConsideringFees: '0', returnAmountFromSwaps: '0', - swapAmount, + swapAmount: amount, swapAmountForSwaps: '0', swaps: [], tokenAddresses: [], diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 865b4670d..065d93606 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -28,9 +28,9 @@ export class SorService { }); console.timeEnd('sorV2'); - if(!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapType, swapAmount); + if(!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); - const bestSwap = this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut, true); + const bestSwap = this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut); try { // Updates with latest onchain data before returning @@ -38,7 +38,7 @@ export class SorService { } catch (err) { console.log(`Error Retrieving QuerySwap`); console.log(err); - return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapType, swapAmount); + return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); } } @@ -52,6 +52,7 @@ export class SorService { private getBestSwap(v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string, debugOut=false): SwapResult { // Useful for comparing if(debugOut) { + console.log(`------ DEBUG`) console.log(v1); console.log(v2); } From fbc8553e82c40fb6d53f2530945359b2e0145a48 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 30 May 2023 11:20:33 +0100 Subject: [PATCH 28/78] Use prisma pool version. --- modules/sor/sorV2/sorV2.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 4fbc8aef6..bc495c05f 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -223,7 +223,7 @@ export class SorV2Service implements SwapService { id: prismaPool.id as Address, address: prismaPool.address as Address, poolType: this.mapRawPoolType(prismaPool.type), - poolTypeVersion: 1, // TODO - Can we add this to Prisma?? + poolTypeVersion: prismaPool.version, tokensList: prismaPool.tokens.map(t => t.address as Address), swapEnabled: prismaPool.dynamicData!.swapEnabled, swapFee: prismaPool.dynamicData!.swapFee as unknown as HumanAmount, From 67fce9aaa0f0673836380ce7fe9235c057a83fea Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 30 May 2023 13:47:21 +0100 Subject: [PATCH 29/78] Handle invalid swap case correctly. --- modules/sor/sor.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 065d93606..59737141e 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -58,11 +58,15 @@ export class SorService { } let isV1 = false; - if(swapType === 'EXACT_IN') { + if(!v1.isValid || !v2.isValid) { + isV1 = v1.isValid ? true : false; + } + else if(swapType === 'EXACT_IN') { if(v2.outputAmount < v1.outputAmount) isV1 = true; } else { if(v2.inputAmount > v1.inputAmount) isV1 = true; } + if(isV1 === true) { this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); return v1; From a91db31b01d52935415d22a6b70e0c33fd9af856 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 May 2023 11:57:23 +0100 Subject: [PATCH 30/78] Filtering out Linear pools with 0 price rate which causes issues on b-sdk. --- modules/sor/sorV2/sorV2.service.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index bc495c05f..7d482f8a1 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -15,6 +15,7 @@ import { RawPool, TokenAmount } from '@balancer/sdk'; +import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; import { GqlSorSwapType, GqlSwap } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; @@ -211,13 +212,27 @@ export class SorV2Service implements SwapService { return this.mapToBasePools(rawPools); } + /** + * Remove linear pools that cause issues because of price rate. + * @param pools + */ + private filterLinearPoolsWithZeroRate(pools: PrismaPoolWithDynamic[]): PrismaPoolWithDynamic[] { + // 0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b is a linear pool with priceRate = 0.0 for some tokens which causes issues with b-sdk + return pools.filter((p) => { + const isLinearPriceRateOk = p.type === "LINEAR" ? !p.tokens.some(t => t.dynamicData?.priceRate === '0.0') : true; + return isLinearPriceRateOk; + }) + } + /** * Map Prisma pools to b-sdk RawPool. * @param pools * @returns */ private mapToRawPools(pools: PrismaPoolWithDynamic[]): RawPool[] { - return pools.map(prismaPool => { + + const filteredPools = this.filterLinearPoolsWithZeroRate(pools); + return filteredPools.map(prismaPool => { // b-sdk: src/data/types.ts let rawPool: RawPool = { id: prismaPool.id as Address, From b0b140967cc57a84224a3da686357c80f8d8c857 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 7 Jun 2023 10:37:06 +0100 Subject: [PATCH 31/78] Update logs with chainId. Fix prettier. --- modules/sor/constants.ts | 6 +- modules/sor/sor.service.ts | 74 +++++++----- modules/sor/sorV1/sorV1.service.ts | 53 ++++---- modules/sor/sorV2/sorV2.service.ts | 187 +++++++++++++++-------------- modules/sor/types.ts | 2 +- 5 files changed, 167 insertions(+), 155 deletions(-) diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts index 1770a2f07..8a7a820ab 100644 --- a/modules/sor/constants.ts +++ b/modules/sor/constants.ts @@ -1,6 +1,6 @@ import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; -export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: string): GqlCowSwapApiResponse => { +export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: string): GqlCowSwapApiResponse => { return { marketSp: '0', returnAmount: '0', @@ -12,5 +12,5 @@ export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount tokenAddresses: [], tokenIn: assetIn, tokenOut: assetOut, - } -} \ No newline at end of file + }; +}; diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 59737141e..1eb1ad9db 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,3 +1,4 @@ +import { networkContext } from '../network/network-context.service'; import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; import { sorV1Service } from './sorV1/sorV1.service'; import { sorV2Service } from './sorV2/sorV2.service'; @@ -5,33 +6,28 @@ import { GetSwapsInput, SwapResult } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; export class SorService { - public async getSwaps({ - tokenIn, - tokenOut, - swapType, - swapAmount, - }: GetSwapsInput): Promise { - console.time('sorV1'); + public async getSwaps({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + console.time(`sorV1-${networkContext.chain}`); const swapV1 = await sorV1Service.getSwapResult({ tokenIn, tokenOut, swapType, swapAmount, }); - console.timeEnd('sorV1'); - console.time('sorV2'); + console.timeEnd(`sorV1-${networkContext.chain}`); + console.time(`sorV2-${networkContext.chain}`); const swapV2 = await sorV2Service.getSwapResult({ tokenIn, tokenOut, swapType, swapAmount, }); - console.timeEnd('sorV2'); + console.timeEnd(`sorV2-${networkContext.chain}`); - if(!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); + if (!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); const bestSwap = this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut); - + try { // Updates with latest onchain data before returning return await bestSwap.getSwapResponse(true); @@ -44,38 +40,62 @@ export class SorService { /** * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. - * @param v1 - * @param v2 - * @param swapType - * @returns + * @param v1 + * @param v2 + * @param swapType + * @returns */ - private getBestSwap(v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string, debugOut=false): SwapResult { + private getBestSwap( + v1: SwapResult, + v2: SwapResult, + swapType: GqlSorSwapType, + assetIn: string, + assetOut: string, + debugOut = false, + ): SwapResult { // Useful for comparing - if(debugOut) { - console.log(`------ DEBUG`) + if (debugOut) { + console.log(`------ DEBUG`); console.log(v1); console.log(v2); } let isV1 = false; - if(!v1.isValid || !v2.isValid) { + if (!v1.isValid || !v2.isValid) { isV1 = v1.isValid ? true : false; - } - else if(swapType === 'EXACT_IN') { - if(v2.outputAmount < v1.outputAmount) isV1 = true; + } else if (swapType === 'EXACT_IN') { + if (v2.outputAmount < v1.outputAmount) isV1 = true; } else { - if(v2.inputAmount > v1.inputAmount) isV1 = true; + if (v2.inputAmount > v1.inputAmount) isV1 = true; } - if(isV1 === true) { + if (isV1 === true) { this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); return v1; } else return v2; } - private logResult(logType: string, v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string) { + private logResult( + logType: string, + v1: SwapResult, + v2: SwapResult, + swapType: GqlSorSwapType, + assetIn: string, + assetOut: string, + ) { // console.log() will log to cloudwatch - console.log('SOR Service', logType, swapType, assetIn, assetOut, v1.inputAmount, v1.outputAmount, v2.inputAmount, v2.outputAmount); + console.log( + 'SOR Service', + networkContext.chain, + logType, + swapType, + assetIn, + assetOut, + v1.inputAmount, + v1.outputAmount, + v2.inputAmount, + v2.outputAmount, + ); } } diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1/sorV1.service.ts index bc346d934..222f38a7a 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1/sorV1.service.ts @@ -19,7 +19,7 @@ class SwapResultV1 implements SwapResult { public isValid: boolean; constructor(private swap: GqlCowSwapApiResponse | null, private swapType: GqlSorSwapType) { - if(swap === null) { + if (swap === null) { this.isValid = false; this.swap = null; } else { @@ -30,9 +30,9 @@ class SwapResultV1 implements SwapResult { } async getSwapResponse(queryFirst = false): Promise { - if(!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap') + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); - if(queryFirst) { + if (queryFirst) { const swapType = this.mapSwapType(this.swapType); const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses); const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenIn)].toString(); @@ -42,7 +42,7 @@ class SwapResultV1 implements SwapResult { ...this.swap, returnAmount: swapType === SwapTypes.SwapExactIn ? tokenOutAmount : tokenInAmount, swapAmount: swapType === SwapTypes.SwapExactIn ? tokenInAmount : tokenOutAmount, - } + }; } return this.swap; } @@ -60,17 +60,11 @@ class SwapResultV1 implements SwapResult { } private mapSwapType(swapType: GqlSorSwapType): SwapTypes { - return swapType === "EXACT_IN" ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut; + return swapType === 'EXACT_IN' ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut; } } export class SorV1Service implements SwapService { - - public async getSwapResult({ - tokenIn, - tokenOut, - swapType, - swapAmount, - }: GetSwapsInput): Promise { + public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { try { const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); return new SwapResultV1(swap, swapType); @@ -78,16 +72,16 @@ export class SorV1Service implements SwapService { console.log(`sorV1 Service Error`, err); return new SwapResultV1(null, swapType); } - }; + } /** * Query Balancer API CowSwap/SOR endpoint. - * @param swapType - * @param tokenIn - * @param tokenOut - * @param swapAmountScaled - * @param swapOptions - * @returns + * @param swapType + * @param tokenIn + * @param tokenOut + * @param swapAmountScaled + * @param swapOptions + * @returns */ private async querySorBalancer( swapType: GqlSorSwapType, @@ -98,22 +92,19 @@ export class SorV1Service implements SwapService { const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); const swapData = { - orderKind: this.mapSwapType(swapType), - sellToken: tokenIn, - buyToken: tokenOut, - amount: swapAmountScaled, - gasPrice - }; + orderKind: this.mapSwapType(swapType), + sellToken: tokenIn, + buyToken: tokenOut, + amount: swapAmountScaled, + gasPrice, + }; - const { data } = await axios.post( - endPoint, - swapData, - ); + const { data } = await axios.post(endPoint, swapData); return data; } private mapSwapType(swapType: GqlSorSwapType): CowSwapSwapType { - return swapType === "EXACT_IN" ? 'sell' : 'buy'; + return swapType === 'EXACT_IN' ? 'sell' : 'buy'; } private async querySorBeets( @@ -146,4 +137,4 @@ export class SorV1Service implements SwapService { } } -export const sorV1Service = new SorV1Service(); \ No newline at end of file +export const sorV1Service = new SorV1Service(); diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 7d482f8a1..5c3dadde7 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -1,19 +1,19 @@ -import { - BasePool, - ChainId, - sorGetSwapsWithPools, - Token, - Address, - SwapKind, - sorParseRawPools, - RawStablePool, - RawLinearPool, - RawWeightedPool, - RawComposableStablePool, +import { + BasePool, + ChainId, + sorGetSwapsWithPools, + Token, + Address, + SwapKind, + sorParseRawPools, + RawStablePool, + RawLinearPool, + RawWeightedPool, + RawComposableStablePool, RawMetaStablePool, Swap as SwapSdk, RawPool, - TokenAmount + TokenAmount, } from '@balancer/sdk'; import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; @@ -39,7 +39,7 @@ class SwapResultV2 implements SwapResult { public isValid: boolean; constructor(swap: SwapSdk | null) { - if(swap === null) { + if (swap === null) { this.isValid = false; this.swap = null; } else { @@ -51,10 +51,9 @@ class SwapResultV2 implements SwapResult { } async getSwapResponse(queryFirst = false): Promise { - if(!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap') + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); - if(!queryFirst) - return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + if (!queryFirst) return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); else { // Needs node >= 18 (https://github.com/wagmi-dev/viem/discussions/147) const updatedResult = await this.swap.query(networkContext.data.rpcUrl); @@ -69,31 +68,39 @@ class SwapResultV2 implements SwapResult { /** * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. - * @param swap - * @returns + * @param swap + * @returns */ - private mapResultToCowSwap(swap: SwapSdk, inputAmount: TokenAmount, outputAmount: TokenAmount): GqlCowSwapApiResponse { + private mapResultToCowSwap( + swap: SwapSdk, + inputAmount: TokenAmount, + outputAmount: TokenAmount, + ): GqlCowSwapApiResponse { let swaps: GqlSwap[]; if (swap.swaps instanceof Array) { - swaps = swap.swaps.map(swap => { + swaps = swap.swaps.map((swap) => { return { ...swap, amount: swap.amount.toString(), assetInIndex: Number(swap.assetInIndex), assetOutIndex: Number(swap.assetOutIndex), - } + }; }); } else { - swaps = [{ - amount: inputAmount.amount.toString(), - assetInIndex: swap.assets.indexOf(swap.swaps.assetIn), - assetOutIndex: swap.assets.indexOf(swap.swaps.assetOut), - poolId: swap.swaps.poolId, - userData: swap.swaps.userData - }]; + swaps = [ + { + amount: inputAmount.amount.toString(), + assetInIndex: swap.assets.indexOf(swap.swaps.assetIn), + assetOutIndex: swap.assets.indexOf(swap.swaps.assetOut), + poolId: swap.swaps.poolId, + userData: swap.swaps.userData, + }, + ]; } - const returnAmount = swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); - const swapAmount = swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); + const returnAmount = + swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); + const swapAmount = + swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); return { marketSp: '', // TODO - Check if CowSwap actually use this? Could this be calculate using out/in? returnAmount, @@ -105,7 +112,7 @@ class SwapResultV2 implements SwapResult { tokenAddresses: swap.assets, tokenIn: swap.inputAmount.token.address, tokenOut: swap.outputAmount.token.address, - } + }; } } @@ -116,15 +123,8 @@ export class SorV2Service implements SwapService { this.cache = new Cache(); } - public async getSwapResult({ - tokenIn, - tokenOut, - swapType, - swapAmount, - }: GetSwapsInput): Promise { - console.time('getBasePools'); + public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { const poolsFromDb = await this.getBasePools(); - console.timeEnd('getBasePools'); const chainId = networkContext.chainId as unknown as ChainId; const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); @@ -132,35 +132,30 @@ export class SorV2Service implements SwapService { try { // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( - tIn, - tOut, - swapKind, - swapAmount, - cloneDeep(poolsFromDb), - // swapOptions, // I don't think we need specific swapOptions for this? - ); + tIn, + tOut, + swapKind, + swapAmount, + cloneDeep(poolsFromDb), + // swapOptions, // I don't think we need specific swapOptions for this? + ); return new SwapResultV2(swap); } catch (err) { console.log(`sorV2 Service Error`, err); return new SwapResultV2(null); } - }; + } /** * Gets a b-sdk Token based off tokenAddr. - * @param tokenAddr - * @param chainId - * @returns + * @param tokenAddr + * @param chainId + * @returns */ private async getToken(tokenAddr: Address, chainId: ChainId): Promise { const tokens = await tokenService.getTokens(); const prismaToken = this.getPrismaToken(tokenAddr, tokens); - return new Token( - chainId, - tokenAddr, - prismaToken.decimals, - prismaToken.symbol, - ); + return new Token(chainId, tokenAddr, prismaToken.decimals, prismaToken.symbol); } private getPrismaToken(tokenAddress: string, tokens: PrismaToken[]): PrismaToken { @@ -174,7 +169,7 @@ export class SorV2Service implements SwapService { } private mapSwapType(swapType: GqlSorSwapType): SwapKind { - return swapType === "EXACT_IN" ? SwapKind.GivenIn : SwapKind.GivenOut; + return swapType === 'EXACT_IN' ? SwapKind.GivenIn : SwapKind.GivenOut; } private async getBasePools(): Promise { @@ -188,11 +183,11 @@ export class SorV2Service implements SwapService { /** * Fetch pools from Prisma and map to b-sdk BasePool. - * @returns + * @returns */ private async getBasePoolsFromDb(): Promise { const pools = await prisma.prismaPool.findMany({ - where: { + where: { chain: networkContext.chain, dynamicData: { totalSharesNum: { @@ -203,10 +198,10 @@ export class SorV2Service implements SwapService { NOT: { id: { in: networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, - } - } + }, + }, }, - include: prismaPoolWithDynamic.include + include: prismaPoolWithDynamic.include, }); const rawPools = this.mapToRawPools(pools); return this.mapToBasePools(rawPools); @@ -214,74 +209,80 @@ export class SorV2Service implements SwapService { /** * Remove linear pools that cause issues because of price rate. - * @param pools + * @param pools */ private filterLinearPoolsWithZeroRate(pools: PrismaPoolWithDynamic[]): PrismaPoolWithDynamic[] { // 0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b is a linear pool with priceRate = 0.0 for some tokens which causes issues with b-sdk return pools.filter((p) => { - const isLinearPriceRateOk = p.type === "LINEAR" ? !p.tokens.some(t => t.dynamicData?.priceRate === '0.0') : true; + const isLinearPriceRateOk = + p.type === 'LINEAR' ? !p.tokens.some((t) => t.dynamicData?.priceRate === '0.0') : true; return isLinearPriceRateOk; - }) + }); } /** * Map Prisma pools to b-sdk RawPool. - * @param pools - * @returns + * @param pools + * @returns */ private mapToRawPools(pools: PrismaPoolWithDynamic[]): RawPool[] { - const filteredPools = this.filterLinearPoolsWithZeroRate(pools); - return filteredPools.map(prismaPool => { + return filteredPools.map((prismaPool) => { // b-sdk: src/data/types.ts let rawPool: RawPool = { id: prismaPool.id as Address, address: prismaPool.address as Address, poolType: this.mapRawPoolType(prismaPool.type), poolTypeVersion: prismaPool.version, - tokensList: prismaPool.tokens.map(t => t.address as Address), + tokensList: prismaPool.tokens.map((t) => t.address as Address), swapEnabled: prismaPool.dynamicData!.swapEnabled, swapFee: prismaPool.dynamicData!.swapFee as unknown as HumanAmount, totalShares: prismaPool.dynamicData!.totalShares as unknown as HumanAmount, liquidity: prismaPool.dynamicData!.totalLiquidity as unknown as HumanAmount, - tokens: prismaPool.tokens.map(t => { + tokens: prismaPool.tokens.map((t) => { return { - address: t.token.address as Address, - index: t.index, - symbol: t.token.symbol, - name: t.token.name, - decimals: t.token.decimals, - balance: t.dynamicData?.balance as unknown as HumanAmount - } + address: t.token.address as Address, + index: t.index, + symbol: t.token.symbol, + name: t.token.name, + decimals: t.token.decimals, + balance: t.dynamicData?.balance as unknown as HumanAmount, + }; }), }; - if(["Weighted", "Investment", "LiquidityBootstrapping"].includes(rawPool.poolType)) { + if (['Weighted', 'Investment', 'LiquidityBootstrapping'].includes(rawPool.poolType)) { rawPool = { ...rawPool, - tokens: rawPool.tokens.map((t, i) => { return {...t, weight: prismaPool.tokens[i].dynamicData?.weight} }), + tokens: rawPool.tokens.map((t, i) => { + return { ...t, weight: prismaPool.tokens[i].dynamicData?.weight }; + }), } as RawWeightedPool; } - if(rawPool.poolType === "Stable") { + if (rawPool.poolType === 'Stable') { rawPool = { ...rawPool, amp: prismaPool.stableDynamicData?.amp, } as RawStablePool; } - if(["MetaStable", "ComposableStable"].includes(rawPool.poolType)) { + if (['MetaStable', 'ComposableStable'].includes(rawPool.poolType)) { rawPool = { ...rawPool, amp: prismaPool.stableDynamicData?.amp.split('.')[0], // Taken b-sdk onChainPoolDataEnricher.ts - tokens: rawPool.tokens.map((t, i) => { return {...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate} }), + tokens: rawPool.tokens.map((t, i) => { + return { ...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate }; + }), } as RawMetaStablePool; } - if(rawPool.poolType === "Linear") { + if (rawPool.poolType === 'Linear') { rawPool = { ...rawPool, - mainIndex: prismaPool.linearData?.mainIndex, + mainIndex: prismaPool.linearData?.mainIndex, wrappedIndex: prismaPool.linearData?.wrappedIndex, lowerTarget: prismaPool.linearDynamicData?.lowerTarget, upperTarget: prismaPool.linearDynamicData?.upperTarget, - tokens: rawPool.tokens.map((t, i) => { return {...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate} }), + tokens: rawPool.tokens.map((t, i) => { + return { ...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate }; + }), } as RawLinearPool; } return rawPool; @@ -291,7 +292,7 @@ export class SorV2Service implements SwapService { /** * Map b-sdk RawPools to BasePools. * @param pools - * @returns + * @returns */ private mapToBasePools(pools: RawPool[]): BasePool[] { const chainId = networkContext.chainId as unknown as ChainId; @@ -300,14 +301,14 @@ export class SorV2Service implements SwapService { /** * Map Prisma pool type to b-sdk Raw pool type. - * @param type - * @returns + * @param type + * @returns */ private mapRawPoolType(type: PrismaPoolType): SupportedRawPoolTypes | string { // From b-sdk: // - type LinearPoolType = `${string}Linear`; // - LinearPoolType | 'Weighted' | 'Investment' | 'LiquidityBootstrapping' | 'Stable' | 'MetaStable' | 'ComposableStable' | 'StablePhantom' | 'Element'; - switch(type) { + switch (type) { case PrismaPoolType.WEIGHTED: return 'Weighted'; case PrismaPoolType.INVESTMENT: @@ -326,7 +327,7 @@ export class SorV2Service implements SwapService { default: return type; } - } + } } -export const sorV2Service = new SorV2Service(); \ No newline at end of file +export const sorV2Service = new SorV2Service(); diff --git a/modules/sor/types.ts b/modules/sor/types.ts index d30635245..c78aae8b4 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -16,4 +16,4 @@ export interface SwapResult { export interface SwapService { getSwapResult(inputs: GetSwapsInput): Promise; -} \ No newline at end of file +} From c5f1ed5d6c1d427869d74c6542e2532a3d395319 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 7 Jun 2023 11:13:51 +0100 Subject: [PATCH 32/78] Initial restructure for Balancer/Beets separation. --- modules/sor/sor.resolvers.ts | 2 +- modules/sor/sor.service.ts | 8 ++++---- .../sorV1Balancer.service.ts} | 6 +++--- modules/sor/sorV2/sorV2.service.ts | 2 +- modules/sor/types.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename modules/sor/{sorV1/sorV1.service.ts => sorV1Balancer/sorV1Balancer.service.ts} (96%) diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts index 2d7402224..4e8335bd8 100644 --- a/modules/sor/sor.resolvers.ts +++ b/modules/sor/sor.resolvers.ts @@ -5,7 +5,7 @@ import { tokenService } from '../token/token.service'; const sorResolvers: Resolvers = { Query: { sorGetSwaps: async (parent, args, context) => { - return sorService.getSwaps({ ...args }); + return sorService.getCowSwaps({ ...args }); }, }, }; diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 1eb1ad9db..df53119a8 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,14 +1,14 @@ import { networkContext } from '../network/network-context.service'; import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; -import { sorV1Service } from './sorV1/sorV1.service'; +import { sorV1BalancerService } from './sorV1Balancer/sorV1Balancer.service'; import { sorV2Service } from './sorV2/sorV2.service'; import { GetSwapsInput, SwapResult } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; export class SorService { - public async getSwaps({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + public async getCowSwaps({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { console.time(`sorV1-${networkContext.chain}`); - const swapV1 = await sorV1Service.getSwapResult({ + const swapV1 = await sorV1BalancerService.getSwapResult({ tokenIn, tokenOut, swapType, @@ -30,7 +30,7 @@ export class SorService { try { // Updates with latest onchain data before returning - return await bestSwap.getSwapResponse(true); + return await bestSwap.getCowSwapResponse(true); } catch (err) { console.log(`Error Retrieving QuerySwap`); console.log(err); diff --git a/modules/sor/sorV1/sorV1.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts similarity index 96% rename from modules/sor/sorV1/sorV1.service.ts rename to modules/sor/sorV1Balancer/sorV1Balancer.service.ts index 222f38a7a..3d64d8c6f 100644 --- a/modules/sor/sorV1/sorV1.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -29,7 +29,7 @@ class SwapResultV1 implements SwapResult { } } - async getSwapResponse(queryFirst = false): Promise { + async getCowSwapResponse(queryFirst = false): Promise { if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); if (queryFirst) { @@ -63,7 +63,7 @@ class SwapResultV1 implements SwapResult { return swapType === 'EXACT_IN' ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut; } } -export class SorV1Service implements SwapService { +export class SorV1BalancerService implements SwapService { public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { try { const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); @@ -137,4 +137,4 @@ export class SorV1Service implements SwapService { } } -export const sorV1Service = new SorV1Service(); +export const sorV1BalancerService = new SorV1BalancerService(); diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 5c3dadde7..54a5dec0f 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -50,7 +50,7 @@ class SwapResultV2 implements SwapResult { } } - async getSwapResponse(queryFirst = false): Promise { + async getCowSwapResponse(queryFirst = false): Promise { if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); if (!queryFirst) return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); diff --git a/modules/sor/types.ts b/modules/sor/types.ts index c78aae8b4..7cf319544 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -8,7 +8,7 @@ export interface GetSwapsInput { } export interface SwapResult { - getSwapResponse(queryFirst: boolean): Promise; + getCowSwapResponse(queryFirst: boolean): Promise; isValid: boolean; outputAmount: bigint; inputAmount: bigint; From 87a6b705764f302ffab57f5e2ac1a5813aed9d97 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 7 Jun 2023 16:10:51 +0100 Subject: [PATCH 33/78] Split resolvers. --- modules/balancer/balancer.gql | 38 +++++++++++++++++++-- modules/balancer/balancer.resolvers.ts | 6 ++++ modules/beethoven/balancer-sdk.gql | 30 ++++++++++++++++ modules/beethoven/balancer-sdk.resolvers.ts | 8 ++++- modules/sor/sor.gql | 34 ------------------ modules/sor/sor.resolvers.ts | 13 ------- 6 files changed, 79 insertions(+), 50 deletions(-) delete mode 100644 modules/sor/sor.gql delete mode 100644 modules/sor/sor.resolvers.ts diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index 284b6d89d..8e02b3366 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -1,7 +1,41 @@ +extend type Mutation { + balancerMutationTest: String! +} + +union SorResponse = GqlSorGetSwapsResponse | GqlCowSwapApiResponse + extend type Query { balancerQueryTest: String! + sorGetSwaps( + tokenIn: String! + tokenOut: String! + swapType: GqlSorSwapType! + swapAmount: BigDecimal! #expected in raw amount + ): SorResponse! } -extend type Mutation { - balancerMutationTest: String! +enum GqlSorSwapType { + EXACT_IN + EXACT_OUT +} + +type GqlCowSwapApiResponse { + tokenAddresses: [String!]! + swaps: [GqlSwap!]! + swapAmount: String! + swapAmountForSwaps: String! + returnAmount: String! + returnAmountFromSwaps: String! + returnAmountConsideringFees: String! + tokenIn: String! + tokenOut: String! + marketSp: String! +} + +type GqlSwap { + poolId: String! + assetInIndex: Int! + assetOutIndex: Int! + amount: String! + userData: String! } diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index ba97d0c87..2d72cefd0 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,10 +1,16 @@ import { Resolvers } from '../../schema'; +import { sorService as sorService } from '../sor/sor.service'; + const balancerResolvers: Resolvers = { Query: { balancerQueryTest: async (parent, {}, context) => { return 'test'; }, + sorGetSwaps: async (parent, args, context) => { + const swaps = await sorService.getCowSwaps({ ...args }); + return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; + }, }, Mutation: { balancerMutationTest: async (parent, {}, context) => { diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index 9b22e1782..22e652db3 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -1,4 +1,13 @@ +union SorResponse = GqlSorGetSwapsResponse | GqlCowSwapApiResponse + extend type Query { + sorGetSwaps( + tokenIn: String! + tokenOut: String! + swapType: GqlSorSwapType! + swapAmount: BigDecimal! #expected in human readable form + swapOptions: GqlSorSwapOptionsInput! + ): SorResponse! sorGetBatchSwapForTokensIn( tokensIn: [GqlTokenAmountHumanReadable!]! tokenOut: String! @@ -70,3 +79,24 @@ type GqlSorGetBatchSwapForTokensInResponse { swaps: [GqlSorSwap!]! assets: [String!]! } + +type GqlCowSwapApiResponse { + tokenAddresses: [String!]! + swaps: [GqlSwap!]! + swapAmount: String! + swapAmountForSwaps: String! + returnAmount: String! + returnAmountFromSwaps: String! + returnAmountConsideringFees: String! + tokenIn: String! + tokenOut: String! + marketSp: String! +} + +type GqlSwap { + poolId: String! + assetInIndex: Int! + assetOutIndex: Int! + amount: String! + userData: String! +} diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index ec2333ce8..1f1a6c3b2 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -1,9 +1,15 @@ -import { Resolvers } from '../../schema'; +import { Resolvers, GqlSorGetSwapsResponse } from '../../schema'; import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; const balancerSdkResolvers: Resolvers = { Query: { + sorGetSwaps: async (parent, args, context) => { + const tokens = await tokenService.getTokens(); + + const swaps = await balancerSorService.getSwaps({ ...args, tokens }); + return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; + }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { const tokens = await tokenService.getTokens(); diff --git a/modules/sor/sor.gql b/modules/sor/sor.gql deleted file mode 100644 index 6e2c43c97..000000000 --- a/modules/sor/sor.gql +++ /dev/null @@ -1,34 +0,0 @@ -extend type Query { - sorGetSwaps( - tokenIn: String! - tokenOut: String! - swapType: GqlSorSwapType! - swapAmount: BigDecimal! #expected in raw amount - ): GqlCowSwapApiResponse! -} - -enum GqlSorSwapType { - EXACT_IN - EXACT_OUT -} - -type GqlCowSwapApiResponse { - tokenAddresses: [String!]! - swaps: [GqlSwap!]! - swapAmount: String! - swapAmountForSwaps: String! - returnAmount: String! - returnAmountFromSwaps: String! - returnAmountConsideringFees: String! - tokenIn: String! - tokenOut: String! - marketSp: String! -} - -type GqlSwap { - poolId: String! - assetInIndex: Int! - assetOutIndex: Int! - amount: String! - userData: String! -} diff --git a/modules/sor/sor.resolvers.ts b/modules/sor/sor.resolvers.ts deleted file mode 100644 index 4e8335bd8..000000000 --- a/modules/sor/sor.resolvers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Resolvers } from '../../schema'; -import { sorService as sorService } from './sor.service'; -import { tokenService } from '../token/token.service'; - -const sorResolvers: Resolvers = { - Query: { - sorGetSwaps: async (parent, args, context) => { - return sorService.getCowSwaps({ ...args }); - }, - }, -}; - -export default sorResolvers; From 1dafb8e6d5361802290d37a0b554fa314b2c9406 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 8 Jun 2023 10:31:18 +0100 Subject: [PATCH 34/78] Add getBeetsSwapResponse type. --- .../sorV1Balancer/sorV1Balancer.service.ts | 37 +++---------------- modules/sor/sorV2/sorV2.service.ts | 16 +++++++- modules/sor/types.ts | 4 +- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts index 3d64d8c6f..2c0d2ee8c 100644 --- a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -1,9 +1,9 @@ import axios from 'axios'; import { AddressZero } from '@ethersproject/constants'; import { Contract } from '@ethersproject/contracts'; -import { GqlSorSwapType, GqlSorSwapOptionsInput, GqlCowSwapApiResponse } from '../../../schema'; +import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse } from '../../../schema'; import { GetSwapsInput, SwapService, SwapResult } from '../types'; -import { SwapInfo, FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; +import { FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; import { DeploymentEnv } from '../../network/network-config-types'; @@ -47,6 +47,10 @@ class SwapResultV1 implements SwapResult { return this.swap; } + async getBeetsSwapResponse(queryFirst: boolean): Promise { + throw new Error('Use Beets service.'); + } + private queryBatchSwap(swapType: SwapTypes, swaps: SwapV2[], assets: string[]): Promise { const vaultContract = new Contract(networkContext.data.balancer.vault, VaultAbi, networkContext.provider); const funds: FundManagement = { @@ -106,35 +110,6 @@ export class SorV1BalancerService implements SwapService { private mapSwapType(swapType: GqlSorSwapType): CowSwapSwapType { return swapType === 'EXACT_IN' ? 'sell' : 'buy'; } - - private async querySorBeets( - swapType: string, - tokenIn: string, - tokenOut: string, - swapAmountScaled: string, - swapOptions: GqlSorSwapOptionsInput, - ) { - // Taken from: modules/beethoven/balancer-sor.service.ts - // TODO - Currently don't get a swap from mainnet. Need an example curl? - const { data } = await axios.post<{ swapInfo: SwapInfo }>( - networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].url, - { - swapType, - tokenIn, - tokenOut, - swapAmountScaled, - swapOptions: { - maxPools: - swapOptions.maxPools || networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].maxPools, - forceRefresh: - swapOptions.forceRefresh || - networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].forceRefresh, - }, - }, - ); - const swapInfo = data.swapInfo; - return swapInfo; - } } export const sorV1BalancerService = new SorV1BalancerService(); diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 54a5dec0f..0fe673300 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -17,7 +17,7 @@ import { } from '@balancer/sdk'; import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; -import { GqlSorSwapType, GqlSwap } from '../../../schema'; +import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput, SwapResult, SwapService } from '../types'; import { tokenService } from '../../token/token.service'; @@ -66,6 +66,20 @@ class SwapResultV2 implements SwapResult { } } + async getBeetsSwapResponse(queryFirst: boolean): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + + return this.mapResultToBeetsSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + } + + private mapResultToBeetsSwap( + swap: SwapSdk, + inputAmount: TokenAmount, + outputAmount: TokenAmount, + ): GqlSorGetSwapsResponse { + throw new Error('NOT IMPLEMENTED YET'); + } + /** * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. * @param swap diff --git a/modules/sor/types.ts b/modules/sor/types.ts index 7cf319544..56c034410 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -1,5 +1,4 @@ -import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; - +import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse } from '../../schema'; export interface GetSwapsInput { tokenIn: string; tokenOut: string; @@ -9,6 +8,7 @@ export interface GetSwapsInput { export interface SwapResult { getCowSwapResponse(queryFirst: boolean): Promise; + getBeetsSwapResponse(queryFirst: boolean): Promise; isValid: boolean; outputAmount: bigint; inputAmount: bigint; From aa0636120a9b78a83550db8b59b7fadac6a87ba2 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 8 Jun 2023 11:33:29 +0100 Subject: [PATCH 35/78] Add reusable formatResponse function. --- modules/beethoven/balancer-sor.service.ts | 102 +++++--- modules/beethoven/balancer-sor.test.ts | 299 ++++++++++++++++++++++ 2 files changed, 371 insertions(+), 30 deletions(-) create mode 100644 modules/beethoven/balancer-sor.test.ts diff --git a/modules/beethoven/balancer-sor.service.ts b/modules/beethoven/balancer-sor.service.ts index 8f1f5888e..79a4e1ffa 100644 --- a/modules/beethoven/balancer-sor.service.ts +++ b/modules/beethoven/balancer-sor.service.ts @@ -1,4 +1,4 @@ -import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType } from '../../schema'; +import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType, GqlPoolMinimal } from '../../schema'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; import { PrismaToken } from '@prisma/client'; import { poolService } from '../pool/pool.service'; @@ -17,6 +17,7 @@ import { DeploymentEnv } from '../network/network-config-types'; import * as Sentry from '@sentry/node'; import _ from 'lodash'; import { Logger } from '@ethersproject/logger'; +import { SwapInfoRoute } from '@balancer-labs/sor'; interface GetSwapsInput { tokenIn: string; @@ -142,27 +143,70 @@ export class BalancerSorService { const tokenInAmount = BigNumber.from(deltas[swapInfo.tokenAddresses.indexOf(tokenIn)]); const tokenOutAmount = BigNumber.from(deltas[swapInfo.tokenAddresses.indexOf(tokenOut)]).abs(); - const swapAmountQuery = swapType === 'EXACT_OUT' ? tokenOutAmount : tokenInAmount; - const returnAmount = swapType === 'EXACT_IN' ? tokenOutAmount : tokenInAmount; - - const returnAmountFixed = formatFixed( - returnAmount, - this.getTokenDecimals(swapType === 'EXACT_IN' ? tokenOut : tokenIn, tokens), - ); - - const swapAmountQueryFixed = formatFixed( - swapAmountQuery, - this.getTokenDecimals(swapType === 'EXACT_OUT' ? tokenOut : tokenIn, tokens), - ); + return this.formatResponse({ + tokenIn: swapInfo.tokenIn, + tokenOut: swapInfo.tokenOut, + tokens, + tokenInAmtEvm: tokenInAmount.toString(), + tokenOutAmtEvm: tokenOutAmount.toString(), + swapAmountForSwaps: BigNumber.from(swapInfo.swapAmountForSwaps).toString(), + returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), + returnAmountFromSwaps: BigNumber.from(swapInfo.returnAmountFromSwaps).toString(), + routes: swapInfo.routes, + pools, + marketSp: swapInfo.marketSp, + swaps: swapInfo.swaps, + tokenAddresses: swapInfo.tokenAddresses, + swapType, + }); + } - const tokenInAmountFixed = formatFixed(tokenInAmount, this.getTokenDecimals(tokenIn, tokens)); - const tokenOutAmountFixed = formatFixed(tokenOutAmount, this.getTokenDecimals(tokenOut, tokens)); + formatResponse(swapData: { + tokenIn: string; + tokenOut: string; + swapType: GqlSorSwapType; + tokens: PrismaToken[]; + tokenInAmtEvm: string; + tokenOutAmtEvm: string; + swapAmountForSwaps: string; + returnAmountConsideringFees: string; + returnAmountFromSwaps: string; + routes: SwapInfoRoute[]; + pools: GqlPoolMinimal[]; + marketSp: string; + swaps: SwapV2[]; + tokenAddresses: string[]; + }): GqlSorGetSwapsResponse { + const { + tokenIn, + tokenOut, + swapType, + tokens, + tokenInAmtEvm, + tokenOutAmtEvm, + swapAmountForSwaps, + returnAmountConsideringFees, + returnAmountFromSwaps, + routes, + pools, + marketSp, + swaps, + tokenAddresses, + } = swapData; + + const tokenInAmountFixed = formatFixed(tokenInAmtEvm, this.getTokenDecimals(tokenIn, tokens)); + const tokenOutAmountFixed = formatFixed(tokenOutAmtEvm, this.getTokenDecimals(tokenOut, tokens)); + + const swapAmountQuery = swapType === 'EXACT_OUT' ? tokenOutAmtEvm : tokenInAmtEvm; + const returnAmount = swapType === 'EXACT_IN' ? tokenOutAmtEvm : tokenInAmtEvm; + const swapAmountQueryFixed = swapType === 'EXACT_OUT' ? tokenOutAmountFixed : tokenInAmountFixed; + const returnAmountFixed = swapType === 'EXACT_IN' ? tokenOutAmountFixed : tokenInAmountFixed; const effectivePrice = oldBnum(tokenInAmountFixed).div(tokenOutAmountFixed); const effectivePriceReversed = oldBnum(tokenOutAmountFixed).div(tokenInAmountFixed); - const priceImpact = effectivePrice.div(swapInfo.marketSp).minus(1); + const priceImpact = effectivePrice.div(marketSp).minus(1); - for (const route of swapInfo.routes) { + for (const route of routes) { route.tokenInAmount = oldBnum(tokenInAmountFixed) .multipliedBy(route.share) .dp(this.getTokenDecimals(tokenIn, tokens)) @@ -174,24 +218,22 @@ export class BalancerSorService { } return { - ...swapInfo, - tokenIn: replaceZeroAddressWithEth(swapInfo.tokenIn), - tokenOut: replaceZeroAddressWithEth(swapInfo.tokenOut), + swaps, + marketSp, + tokenAddresses, + tokenIn: replaceZeroAddressWithEth(tokenIn), + tokenOut: replaceZeroAddressWithEth(tokenOut), swapType, tokenInAmount: tokenInAmountFixed, tokenOutAmount: tokenOutAmountFixed, swapAmount: swapAmountQueryFixed, - swapAmountScaled: swapAmountQuery.toString(), - swapAmountForSwaps: swapInfo.swapAmountForSwaps - ? BigNumber.from(swapInfo.swapAmountForSwaps).toString() - : undefined, + swapAmountScaled: swapAmountQuery, + swapAmountForSwaps: swapAmountForSwaps ? BigNumber.from(swapAmountForSwaps).toString() : undefined, returnAmount: returnAmountFixed, - returnAmountScaled: returnAmount.toString(), - returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), - returnAmountFromSwaps: swapInfo.returnAmountFromSwaps - ? BigNumber.from(swapInfo.returnAmountFromSwaps).toString() - : undefined, - routes: swapInfo.routes.map((route) => ({ + returnAmountScaled: returnAmount, + returnAmountConsideringFees: BigNumber.from(returnAmountConsideringFees).toString(), + returnAmountFromSwaps: returnAmountFromSwaps ? BigNumber.from(returnAmountFromSwaps).toString() : undefined, + routes: routes.map((route) => ({ ...route, hops: route.hops.map((hop) => ({ ...hop, diff --git a/modules/beethoven/balancer-sor.test.ts b/modules/beethoven/balancer-sor.test.ts new file mode 100644 index 000000000..0a7261f9e --- /dev/null +++ b/modules/beethoven/balancer-sor.test.ts @@ -0,0 +1,299 @@ +import { BalancerSorService } from './balancer-sor.service'; +import { tokenService } from '../token/token.service'; +import { poolService } from '../pool/pool.service'; + +import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType, GqlPoolMinimal } from '../../schema'; + +// npx jest --testPathPattern=modules/beethoven/balancer-sor.test.ts +describe('SmartOrderRouter', () => { + test('swap with mixed decimals', async () => { + const tokens = await tokenService.getTokens(); + const pools = await poolService.getGqlPools({ + where: { idIn: ['0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015'] }, + }); + + const sor = new BalancerSorService(); + const out = { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + swapType: 'EXACT_IN' as GqlSorSwapType, + tokens, + tokenInAmtEvm: '1000000000000000000000', + tokenOutAmtEvm: '21524991', + swapAmountForSwaps: '1000000000000000000000', + returnAmountConsideringFees: '21524991', + returnAmountFromSwaps: '21524991', + routes: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + share: 1, + hops: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + }, + ], + }, + ], + pools, + marketSp: '46.44498985968269', + swaps: [ + { + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + assetInIndex: 0, + assetOutIndex: 1, + amount: '1000000000000000000000', + userData: '0x', + returnAmount: '21524991', + }, + ], + tokenAddresses: [ + '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + ], + }; + const result = sor.formatResponse(out); + const actual = { + swapAmount: '1000.0', + swapAmountForSwaps: '1000000000000000000000', + returnAmount: '21.524991', + returnAmountFromSwaps: '21524991', + returnAmountConsideringFees: '21524991', + swaps: [ + { + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + assetInIndex: 0, + assetOutIndex: 1, + amount: '1000000000000000000000', + userData: '0x', + returnAmount: '21524991', + }, + ], + tokenAddresses: [ + '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + ], + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + marketSp: '46.44498985968269', + routes: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991', + share: 1, + hops: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + pool: pools[0], + }, + ], + }, + ], + swapType: 'EXACT_IN', + tokenInAmount: '1000.0', + tokenOutAmount: '21.524991', + swapAmountScaled: '1000000000000000000000', + returnAmountScaled: '21524991', + effectivePrice: '46.45762685800890694914', + effectivePriceReversed: '0.021524991', + priceImpact: '0.00027208528550431865', + }; + expect(result).toEqual(actual); + }); + test('swap with native asset', async () => { + const tokens = await tokenService.getTokens(); + const pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + ], + }, + }); + + const sor = new BalancerSorService(); + const out = { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x0000000000000000000000000000000000000000', + swapType: 'EXACT_IN' as GqlSorSwapType, + tokens, + tokenInAmtEvm: '10000000', + tokenOutAmtEvm: '34434611675857195780', + swapAmountForSwaps: '10000000', + returnAmountConsideringFees: '34434650939050394265', + returnAmountFromSwaps: '34434650939080394265', + routes: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '10', + tokenOutAmount: '34.434650939080394265', + share: 1, + hops: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenInAmount: '10', + tokenOutAmount: '9.953043613661588446', + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + }, + { + tokenIn: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenOut: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenInAmount: '9.953043613661588446', + tokenOutAmount: '34.355661787346070666', + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + }, + { + tokenIn: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '34.355661787346070666', + tokenOutAmount: '34.434650939080394265', + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + }, + ], + }, + ], + pools, + marketSp: '0.29021486756308521004963850902453996332730905029153527', + swaps: [ + { + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + assetInIndex: 0, + assetOutIndex: 1, + amount: '10000000', + userData: '0x', + returnAmount: '9953043613661588446', + }, + { + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + assetInIndex: 1, + assetOutIndex: 2, + amount: '0', + userData: '0x', + returnAmount: '34355661787346070666', + }, + { + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + assetInIndex: 2, + assetOutIndex: 3, + amount: '0', + userData: '0x', + returnAmount: '34434650939080394265', + }, + ], + tokenAddresses: [ + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + '0x0000000000000000000000000000000000000000', + ], + }; + const result = sor.formatResponse(out); + const actual = { + swapAmount: '10.0', + swapAmountForSwaps: '10000000', + returnAmount: '34.43461167585719578', + returnAmountFromSwaps: '34434650939080394265', + returnAmountConsideringFees: '34434650939050394265', + swaps: [ + { + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + assetInIndex: 0, + assetOutIndex: 1, + amount: '10000000', + userData: '0x', + returnAmount: '9953043613661588446', + }, + { + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + assetInIndex: 1, + assetOutIndex: 2, + amount: '0', + userData: '0x', + returnAmount: '34355661787346070666', + }, + { + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + assetInIndex: 2, + assetOutIndex: 3, + amount: '0', + userData: '0x', + returnAmount: '34434650939080394265', + }, + ], + tokenAddresses: [ + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + '0x0000000000000000000000000000000000000000', + ], + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + marketSp: '0.29021486756308521004963850902453996332730905029153527', + routes: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '10', + tokenOutAmount: '34.43461167585719578', + share: 1, + hops: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenInAmount: '10', + tokenOutAmount: '9.953043613661588446', + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + pool: pools.find( + (p) => p.id === '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + ), + }, + { + tokenIn: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenOut: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenInAmount: '9.953043613661588446', + tokenOutAmount: '34.355661787346070666', + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + pool: pools.find( + (p) => p.id === '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + ), + }, + { + tokenIn: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '34.355661787346070666', + tokenOutAmount: '34.434650939080394265', + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + pool: pools.find( + (p) => p.id === '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + ), + }, + ], + }, + ], + swapType: 'EXACT_IN', + tokenInAmount: '10.0', + tokenOutAmount: '34.43461167585719578', + swapAmountScaled: '10000000', + returnAmountScaled: '34434611675857195780', + effectivePrice: '0.29040548196485696673', + effectivePriceReversed: '3.443461167585719578', + priceImpact: '0.00065680439934843116', + }; + expect(result).toEqual(actual); + }); +}); From bdf75917c7b59900e5255c3993b64b30c7044e85 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 8 Jun 2023 14:28:52 +0100 Subject: [PATCH 36/78] Add mapResultToBeetsSwap - still need to add routes. --- modules/sor/sorV2/sorV2.service.ts | 118 +++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 5 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 0fe673300..3165beecd 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -17,7 +17,7 @@ import { } from '@balancer/sdk'; import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; -import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse } from '../../../schema'; +import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput, SwapResult, SwapService } from '../types'; import { tokenService } from '../../token/token.service'; @@ -29,6 +29,13 @@ import { env } from '../../../app/env'; import { DeploymentEnv } from '../../network/network-config-types'; import { Cache, CacheClass } from 'memory-cache'; import { GqlCowSwapApiResponse } from '../../../schema'; +import { BalancerSorService } from '../../beethoven/balancer-sor.service'; +import { poolService } from '../../pool/pool.service'; +import { BatchSwapStep } from '@balancer/sdk'; +import { SingleSwap } from '@balancer/sdk'; +import { SwapInfoRoute, SwapTypes, Swap, bnum, SwapInfoRouteHop } from '@balancer-labs/sor'; +import { BigNumber } from 'ethers'; +import { oldBnumScale } from '../../big-number/old-big-number'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -69,15 +76,116 @@ class SwapResultV2 implements SwapResult { async getBeetsSwapResponse(queryFirst: boolean): Promise { if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); - return this.mapResultToBeetsSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + return await this.mapResultToBeetsSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); } - private mapResultToBeetsSwap( + private async mapResultToBeetsSwap( swap: SwapSdk, inputAmount: TokenAmount, outputAmount: TokenAmount, - ): GqlSorGetSwapsResponse { - throw new Error('NOT IMPLEMENTED YET'); + ): Promise { + const sor = new BalancerSorService(); + const tokens = await tokenService.getTokens(); + let poolIds: string[]; + if (swap.isBatchSwap) { + const swaps = swap.swaps as BatchSwapStep[]; + poolIds = swaps.map((swap) => swap.poolId); + } else { + const singleSwap = swap.swaps as SingleSwap; + poolIds = [singleSwap.poolId]; + } + const pools = await poolService.getGqlPools({ + where: { idIn: poolIds }, + }); + + const swapAmountForSwaps = + swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); + const returnAmountFromSwaps = + swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); + + const swapData = { + tokenIn: inputAmount.token.address.toString(), + tokenOut: outputAmount.token.address.toString(), + tokens, + swapType: this.mapSwapKind(swap.swapKind), + tokenInAmtEvm: inputAmount.amount.toString(), + tokenOutAmtEvm: outputAmount.amount.toString(), + swapAmountForSwaps, + returnAmountFromSwaps, + returnAmountConsideringFees: returnAmountFromSwaps, + routes: [], // TODO + pools, + marketSp: '', // TODO + swaps: this.mapSwaps(swap.swaps, swap.assets), + tokenAddresses: swap.assets, + }; + return sor.formatResponse(swapData); + } + + private mapSwaps(swaps: BatchSwapStep[] | SingleSwap, assets: string[]): GqlSwap[] { + if (Array.isArray(swaps)) { + return swaps.map((swap) => { + return { + ...swap, + assetInIndex: Number(swap.assetInIndex.toString()), + assetOutIndex: Number(swap.assetOutIndex.toString()), + amount: swap.amount.toString(), + }; + }); + } else { + const assetInIndex = assets.indexOf(swaps.assetIn); + const assetOutIndex = assets.indexOf(swaps.assetOut); + return [ + { + ...swaps, + assetInIndex, + assetOutIndex, + amount: swaps.amount.toString(), + }, + ]; + } + } + + private mapSwapKind(kind: SwapKind): GqlSorSwapType { + return kind === SwapKind.GivenIn ? 'EXACT_IN' : 'EXACT_OUT'; + } + + /** + * Formats a sequence of swaps to a format that is useful for displaying the routes in user interfaces. + * Taken directly from Beets SOR: https://github.com/beethovenxfi/balancer-sor/blob/beethovenx-master/src/formatSwaps.ts#L167 + * @dev The swaps are converted to an array of routes, where each route has an array of hops + * @param swapType - exact in or exact out + * @param routes - The original Swaps + * @param swapAmount - The total amount being swapped + * @returns SwapInfoRoute[] - The swaps formatted as routes with hops + */ + private formatRoutesSOR(swapType: SwapTypes, routes: Swap[][], swapAmount: BigNumber): SwapInfoRoute[] { + const exactIn = swapType === SwapTypes.SwapExactIn; + + return routes.map((swaps) => { + const first = swaps[0]; + const last = swaps[swaps.length - 1]; + const tokenInAmount = (exactIn ? first.swapAmount : last.swapAmountOut) || '0'; + const tokenOutAmount = (exactIn ? last.swapAmountOut : first.swapAmount) || '0'; + const tokenInAmountScaled = oldBnumScale(bnum(tokenInAmount), first.tokenInDecimals); + + return { + tokenIn: first.tokenIn, + tokenOut: last.tokenOut, + tokenInAmount, + tokenOutAmount, + share: tokenInAmountScaled.div(bnum(swapAmount.toString())).toNumber(), + hops: swaps.map((swap): SwapInfoRouteHop => { + return { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + tokenInAmount: (exactIn ? swap.swapAmount : swap.swapAmountOut) || '0', + tokenOutAmount: (exactIn ? swap.swapAmountOut : swap.swapAmount) || '0', + poolId: swap.pool, + }; + }), + }; + }); } /** From 78e46ad6f1a66a2908c6bbbdbdca55e3ccc3e71e Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 8 Jun 2023 15:00:55 +0100 Subject: [PATCH 37/78] Add sorV1Beets service and hook up to resolver. --- modules/balancer/balancer.resolvers.ts | 3 +- modules/beethoven/balancer-sdk.resolvers.ts | 7 ++- modules/sor/sor.service.ts | 19 ++++++- modules/sor/sorV1Beets/sorV1Beets.service.ts | 53 ++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 modules/sor/sorV1Beets/sorV1Beets.service.ts diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 2d72cefd0..558706ebe 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,6 +1,5 @@ import { Resolvers } from '../../schema'; -import { sorService as sorService } from '../sor/sor.service'; - +import { sorService } from '../sor/sor.service'; const balancerResolvers: Resolvers = { Query: { diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index 1f1a6c3b2..e2fa4e974 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -1,13 +1,12 @@ -import { Resolvers, GqlSorGetSwapsResponse } from '../../schema'; +import { Resolvers } from '../../schema'; import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; +import { sorService } from '../sor/sor.service'; const balancerSdkResolvers: Resolvers = { Query: { sorGetSwaps: async (parent, args, context) => { - const tokens = await tokenService.getTokens(); - - const swaps = await balancerSorService.getSwaps({ ...args, tokens }); + const swaps = await sorService.getBeetsSwaps({ ...args }); return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index df53119a8..1e14a537a 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,12 +1,18 @@ import { networkContext } from '../network/network-context.service'; -import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; +import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../schema'; import { sorV1BalancerService } from './sorV1Balancer/sorV1Balancer.service'; +import { sorV1BeetsService } from './sorV1Beets/sorV1Beets.service'; import { sorV2Service } from './sorV2/sorV2.service'; import { GetSwapsInput, SwapResult } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; export class SorService { - public async getCowSwaps({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + public async getCowSwaps({ + tokenIn, + tokenOut, + swapType, + swapAmount, + }: GetSwapsInput): Promise { console.time(`sorV1-${networkContext.chain}`); const swapV1 = await sorV1BalancerService.getSwapResult({ tokenIn, @@ -38,6 +44,15 @@ export class SorService { } } + public async getBeetsSwaps( + input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, + ): Promise { + console.time(`sorV1-${networkContext.chain}`); + const swapV1 = await sorV1BeetsService.getSwapResult(input); + console.timeEnd(`sorV1-${networkContext.chain}`); + return swapV1.getBeetsSwapResponse(false); + } + /** * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. * @param v1 diff --git a/modules/sor/sorV1Beets/sorV1Beets.service.ts b/modules/sor/sorV1Beets/sorV1Beets.service.ts new file mode 100644 index 000000000..939299b07 --- /dev/null +++ b/modules/sor/sorV1Beets/sorV1Beets.service.ts @@ -0,0 +1,53 @@ +import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../../schema'; +import { GetSwapsInput, SwapService, SwapResult } from '../types'; +import { BalancerSorService } from '../../beethoven/balancer-sor.service'; +import { tokenService } from '../../token/token.service'; + +class SwapResultV1 implements SwapResult { + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; + + constructor(private swap: GqlSorGetSwapsResponse | null, private swapType: GqlSorSwapType) { + if (swap === null) { + this.isValid = false; + this.swap = null; + } else { + this.inputAmount = swapType === 'EXACT_IN' ? BigInt(swap.swapAmountScaled) : BigInt(swap.returnAmountScaled); + this.outputAmount = + swapType === 'EXACT_IN' ? BigInt(swap.returnAmountScaled) : BigInt(swap.swapAmountScaled); + this.isValid = swap.swaps.length === 0 ? false : true; + } + } + + async getCowSwapResponse(queryFirst = false): Promise { + throw new Error('Use Balancer Service'); + } + + async getBeetsSwapResponse(queryFirst: boolean): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + // Beets service is already querying onchain + return this.swap; + } +} +export class SorV1BeetsService implements SwapService { + public async getSwapResult(input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }): Promise { + try { + const swap = await this.querySorBeets(input); + return new SwapResultV1(swap, input.swapType); + } catch (err) { + console.log(`sorV1 Service Error`, err); + return new SwapResultV1(null, input.swapType); + } + } + + private async querySorBeets( + input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, + ): Promise { + const tokens = await tokenService.getTokens(); + const sorService = new BalancerSorService(); + return await sorService.getSwaps({ ...input, tokens }); + } +} + +export const sorV1BeetsService = new SorV1BeetsService(); From c9f75cd6dd94325dcf9a90b7f9cb5a113a083f4f Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Jun 2023 08:48:54 +0100 Subject: [PATCH 38/78] Best swap for beets endpoint. Zero response. --- modules/beethoven/balancer-sor.service.ts | 54 +++++++++----------- modules/sor/sor.service.ts | 18 ++++++- modules/sor/sorV1Beets/sorV1Beets.service.ts | 18 ++++++- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/modules/beethoven/balancer-sor.service.ts b/modules/beethoven/balancer-sor.service.ts index 79a4e1ffa..e541b199e 100644 --- a/modules/beethoven/balancer-sor.service.ts +++ b/modules/beethoven/balancer-sor.service.ts @@ -56,35 +56,7 @@ export class BalancerSorService { let swapInfo = await this.querySor(swapType, tokenIn, tokenOut, swapAmountScaled, swapOptions); // no swaps found, return 0 if (swapInfo.swaps.length === 0) { - return { - ...swapInfo, - tokenIn: replaceZeroAddressWithEth(swapInfo.tokenIn), - tokenOut: replaceZeroAddressWithEth(swapInfo.tokenOut), - swapType, - tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : BigNumber.from('0').toString(), - tokenOutAmount: swapType === 'EXACT_IN' ? BigNumber.from('0').toString() : swapAmount, - swapAmount: swapType === 'EXACT_IN' ? BigNumber.from('0').toString() : swapAmount, - swapAmountScaled: BigNumber.from('0').toString(), - swapAmountForSwaps: swapInfo.swapAmountForSwaps - ? BigNumber.from(swapInfo.swapAmountForSwaps).toString() - : undefined, - returnAmount: BigNumber.from('0').toString(), - returnAmountScaled: BigNumber.from('0').toString(), - returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), - returnAmountFromSwaps: swapInfo.returnAmountFromSwaps - ? BigNumber.from(swapInfo.returnAmountFromSwaps).toString() - : undefined, - routes: swapInfo.routes.map((route) => ({ - ...route, - hops: route.hops.map((hop) => ({ - ...hop, - pool: pools.find((pool) => pool.id === hop.poolId)!, - })), - })), - effectivePrice: BigNumber.from('0').toString(), - effectivePriceReversed: BigNumber.from('0').toString(), - priceImpact: BigNumber.from('0').toString(), - }; + return this.zeroResponse(swapType, tokenIn, tokenOut, swapAmount); } let deltas: string[] = []; @@ -246,6 +218,30 @@ export class BalancerSorService { }; } + zeroResponse(swapType: GqlSorSwapType, tokenIn: string, tokenOut: string, swapAmount: string): GqlSorGetSwapsResponse { + return { + marketSp: '0', + tokenAddresses: [], + swaps: [], + tokenIn: replaceZeroAddressWithEth(tokenIn), + tokenOut: replaceZeroAddressWithEth(tokenOut), + swapType, + tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : '0', + tokenOutAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmountScaled: '0', + swapAmountForSwaps: '0', + returnAmount: '0', + returnAmountScaled: '0', + returnAmountConsideringFees: '0', + returnAmountFromSwaps: '0', + routes: [], + effectivePrice:'0', + effectivePriceReversed: '0', + priceImpact: '0', + }; + } + private async querySor( swapType: string, tokenIn: string, diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 1e14a537a..fb5ff1ebd 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -50,7 +50,23 @@ export class SorService { console.time(`sorV1-${networkContext.chain}`); const swapV1 = await sorV1BeetsService.getSwapResult(input); console.timeEnd(`sorV1-${networkContext.chain}`); - return swapV1.getBeetsSwapResponse(false); + console.time(`sorV2-${networkContext.chain}`); + const swapV2 = await sorV2Service.getSwapResult(input); + console.timeEnd(`sorV2-${networkContext.chain}`); + + if (!swapV1.isValid && !swapV2.isValid) + return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); + + const bestSwap = this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut); + + try { + // Updates with latest onchain data before returning + return await bestSwap.getBeetsSwapResponse(true); + } catch (err) { + console.log(`Error Retrieving QuerySwap`); + console.log(err); + return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); + } } /** diff --git a/modules/sor/sorV1Beets/sorV1Beets.service.ts b/modules/sor/sorV1Beets/sorV1Beets.service.ts index 939299b07..b54914ae5 100644 --- a/modules/sor/sorV1Beets/sorV1Beets.service.ts +++ b/modules/sor/sorV1Beets/sorV1Beets.service.ts @@ -31,6 +31,12 @@ class SwapResultV1 implements SwapResult { } } export class SorV1BeetsService implements SwapService { + sorService: BalancerSorService; + + constructor() { + this.sorService = new BalancerSorService(); + } + public async getSwapResult(input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }): Promise { try { const swap = await this.querySorBeets(input); @@ -41,12 +47,20 @@ export class SorV1BeetsService implements SwapService { } } + public zeroResponse( + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmount: string, + ): GqlSorGetSwapsResponse { + return this.sorService.zeroResponse(swapType, tokenIn, tokenOut, swapAmount); + } + private async querySorBeets( input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, ): Promise { const tokens = await tokenService.getTokens(); - const sorService = new BalancerSorService(); - return await sorService.getSwaps({ ...input, tokens }); + return await this.sorService.getSwaps({ ...input, tokens }); } } From 585bda3bc38b68b6dbc5e800d12cf9349187d112 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Jun 2023 10:28:01 +0100 Subject: [PATCH 39/78] Map Routes for SingleSwap. --- modules/sor/sorV2/beetsHelpers.test.ts | 70 ++++++++++++++++++++++++++ modules/sor/sorV2/beetsHelpers.ts | 38 ++++++++++++++ modules/sor/sorV2/sorV2.service.ts | 14 +++++- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 modules/sor/sorV2/beetsHelpers.test.ts create mode 100644 modules/sor/sorV2/beetsHelpers.ts diff --git a/modules/sor/sorV2/beetsHelpers.test.ts b/modules/sor/sorV2/beetsHelpers.test.ts new file mode 100644 index 000000000..b1838b95d --- /dev/null +++ b/modules/sor/sorV2/beetsHelpers.test.ts @@ -0,0 +1,70 @@ +import { SingleSwap, SwapKind } from '@balancer/sdk'; +import { GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; +import { mapRoutes } from './beetsHelpers'; +import { poolService } from '../../pool/pool.service'; + +// npx jest --testPathPattern=modules/sor/sorV2/beetsHelpers.test.ts +describe('sorV2 Service - Routes', () => { + describe('SingleSwap', () => { + let singleSwap: SingleSwap; + let pools: GqlPoolMinimal[]; + let expectedRoute: GqlSorSwapRoute[]; + + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { idIn: ['0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c'] }, + }); + singleSwap = { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + kind: SwapKind.GivenIn, + assetIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + assetOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + amount: BigInt(0), + userData: '0x', + }; + expectedRoute = [ + { + hops: [ + { + pool: pools[0], + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: '', + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '', + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: '', + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '', + }, + ]; + }); + test('GivenIn', () => { + const amountIn = '123456789112345678'; + const amountOut = '876543210987654321'; + singleSwap.kind = SwapKind.GivenIn; + singleSwap.amount = BigInt(amountIn); + expectedRoute[0].tokenInAmount = amountIn; + expectedRoute[0].tokenOutAmount = amountOut; + expectedRoute[0].hops[0].tokenInAmount = amountIn; + expectedRoute[0].hops[0].tokenOutAmount = amountOut; + const mappedRoute = mapRoutes(singleSwap, amountIn, amountOut, pools); + expect(mappedRoute).toEqual(expectedRoute); + }); + test('GivenOut', () => { + const amountIn = '876543210987654321'; + const amountOut = '123456789112345678'; + singleSwap.kind = SwapKind.GivenOut; + singleSwap.amount = BigInt(amountOut); + expectedRoute[0].tokenInAmount = amountIn; + expectedRoute[0].tokenOutAmount = amountOut; + expectedRoute[0].hops[0].tokenInAmount = amountIn; + expectedRoute[0].hops[0].tokenOutAmount = amountOut; + const mappedRoute = mapRoutes(singleSwap, amountIn, amountOut, pools); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); +}); diff --git a/modules/sor/sorV2/beetsHelpers.ts b/modules/sor/sorV2/beetsHelpers.ts new file mode 100644 index 000000000..8cff66c8c --- /dev/null +++ b/modules/sor/sorV2/beetsHelpers.ts @@ -0,0 +1,38 @@ +import { BatchSwapStep, SingleSwap } from '@balancer/sdk'; +import { GqlPoolMinimal, GqlSorSwapRoute, GqlSorSwapRouteHop } from '../../../schema'; + +export function mapRoutes( + swaps: BatchSwapStep[] | SingleSwap, + amountIn: string, + amountOut: string, + pools: GqlPoolMinimal[], +): GqlSorSwapRoute[] { + const isBatchSwap = Array.isArray(swaps); + if (!isBatchSwap) { + const pool = pools.find((p) => p.id === swaps.poolId); + if (!pool) throw new Error('Pool not found while mapping route'); + return [mapSingleSwap(swaps, amountIn, amountOut, pool)]; + } + + const route = {} as GqlSorSwapRoute; + return [route]; +} + +function mapSingleSwap(swap: SingleSwap, amountIn: string, amountOut: string, pool: GqlPoolMinimal): GqlSorSwapRoute { + const hop: GqlSorSwapRouteHop = { + pool, + poolId: swap.poolId, + tokenIn: swap.assetIn, + tokenInAmount: amountIn, + tokenOut: swap.assetOut, + tokenOutAmount: amountOut, + }; + return { + share: 1, + tokenIn: swap.assetIn, + tokenOut: swap.assetOut, + tokenInAmount: amountIn, + tokenOutAmount: amountOut, + hops: [hop] + } as GqlSorSwapRoute; +} diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 3165beecd..61af29451 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -17,7 +17,7 @@ import { } from '@balancer/sdk'; import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; -import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal } from '../../../schema'; +import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; import { GetSwapsInput, SwapResult, SwapService } from '../types'; import { tokenService } from '../../token/token.service'; @@ -36,6 +36,7 @@ import { SingleSwap } from '@balancer/sdk'; import { SwapInfoRoute, SwapTypes, Swap, bnum, SwapInfoRouteHop } from '@balancer-labs/sor'; import { BigNumber } from 'ethers'; import { oldBnumScale } from '../../big-number/old-big-number'; +import { mapRoutes } from './beetsHelpers'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -113,7 +114,7 @@ class SwapResultV2 implements SwapResult { swapAmountForSwaps, returnAmountFromSwaps, returnAmountConsideringFees: returnAmountFromSwaps, - routes: [], // TODO + routes: this.mapRoutes(swap.swaps, inputAmount.amount.toString(), outputAmount.amount.toString(), pools), // TODO pools, marketSp: '', // TODO swaps: this.mapSwaps(swap.swaps, swap.assets), @@ -150,6 +151,15 @@ class SwapResultV2 implements SwapResult { return kind === SwapKind.GivenIn ? 'EXACT_IN' : 'EXACT_OUT'; } + private mapRoutes( + swaps: BatchSwapStep[] | SingleSwap, + inputAmount: string, + outputAmount: string, + pools: GqlPoolMinimal[], + ): GqlSorSwapRoute[] { + return mapRoutes(swaps, inputAmount, outputAmount, pools); + } + /** * Formats a sequence of swaps to a format that is useful for displaying the routes in user interfaces. * Taken directly from Beets SOR: https://github.com/beethovenxfi/balancer-sor/blob/beethovenx-master/src/formatSwaps.ts#L167 From bd036a27395823bc926f5c1c75b3b3ca5ec36726 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Jun 2023 14:56:20 +0100 Subject: [PATCH 40/78] Route mapping. --- modules/sor/sorV2/beetsHelpers.test.ts | 221 ++++++++++++++++++++++++- modules/sor/sorV2/beetsHelpers.ts | 74 ++++++++- modules/sor/sorV2/sorV2.service.ts | 17 +- 3 files changed, 301 insertions(+), 11 deletions(-) diff --git a/modules/sor/sorV2/beetsHelpers.test.ts b/modules/sor/sorV2/beetsHelpers.test.ts index b1838b95d..1732db8dc 100644 --- a/modules/sor/sorV2/beetsHelpers.test.ts +++ b/modules/sor/sorV2/beetsHelpers.test.ts @@ -1,6 +1,6 @@ -import { SingleSwap, SwapKind } from '@balancer/sdk'; +import { SingleSwap, SwapKind, BatchSwapStep } from '@balancer/sdk'; import { GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; -import { mapRoutes } from './beetsHelpers'; +import { mapBatchSwap, mapRoutes, splitPaths } from './beetsHelpers'; import { poolService } from '../../pool/pool.service'; // npx jest --testPathPattern=modules/sor/sorV2/beetsHelpers.test.ts @@ -9,6 +9,9 @@ describe('sorV2 Service - Routes', () => { let singleSwap: SingleSwap; let pools: GqlPoolMinimal[]; let expectedRoute: GqlSorSwapRoute[]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7'; + const assets = ['0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7']; beforeAll(async () => { pools = await poolService.getGqlPools({ @@ -51,7 +54,16 @@ describe('sorV2 Service - Routes', () => { expectedRoute[0].tokenOutAmount = amountOut; expectedRoute[0].hops[0].tokenInAmount = amountIn; expectedRoute[0].hops[0].tokenOutAmount = amountOut; - const mappedRoute = mapRoutes(singleSwap, amountIn, amountOut, pools); + const mappedRoute = mapRoutes( + singleSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); expect(mappedRoute).toEqual(expectedRoute); }); test('GivenOut', () => { @@ -63,8 +75,209 @@ describe('sorV2 Service - Routes', () => { expectedRoute[0].tokenOutAmount = amountOut; expectedRoute[0].hops[0].tokenInAmount = amountIn; expectedRoute[0].hops[0].tokenOutAmount = amountOut; - const mappedRoute = mapRoutes(singleSwap, amountIn, amountOut, pools); + const mappedRoute = mapRoutes( + singleSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); expect(mappedRoute).toEqual(expectedRoute); }); }); + describe('BatchSwap', () => { + describe('Single Path, Exact In', () => { + let pools: GqlPoolMinimal[]; + let paths: BatchSwapStep[][]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; + const amountIn = '123456789112345678'; + const amountOut = '876543210987654321'; + const batchSwap: BatchSwapStep[] = [ + { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(1), + amount: BigInt(amountIn), + userData: '0x', + }, + { + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(1), + assetOutIndex: BigInt(2), + amount: BigInt(0), + userData: '0x', + }, + ]; + const assets = [ + '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + '0x8159462d255C1D24915CB51ec361F700174cD994', + ]; + const expectedRoute: GqlSorSwapRoute[] = [ + { + hops: [ + { + pool: {} as GqlPoolMinimal, + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '0', // TODO - Are we expecting to get the values for intermmediate steps? May need b-sdk to return that? + }, + { + pool: {} as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenInAmount: '0', + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ]; + + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ], + }, + }); + expectedRoute[0].hops[0].pool = pools.find( + (p) => p.id === '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + ) as GqlPoolMinimal; + expectedRoute[0].hops[1].pool = pools.find( + (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal; + }); + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenIn, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenIn', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + describe('Single Path, Exact Out', () => { + let pools: GqlPoolMinimal[]; + let paths: BatchSwapStep[][]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; + const amountIn = '123456789112345678'; + const amountOut = '876543210987654321'; + const batchSwap: BatchSwapStep[] = [ + { + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(1), + assetOutIndex: BigInt(2), + amount: BigInt(amountOut), + userData: '0x', + }, + { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(1), + amount: BigInt(0), + userData: '0x', + }, + ]; + const assets = [ + '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + '0x8159462d255C1D24915CB51ec361F700174cD994', + ]; + const expectedRoute: GqlSorSwapRoute[] = [ + { + hops: [ + { + pool: {} as GqlPoolMinimal, + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '0', // TODO - Are we expecting to get the values for intermmediate steps? May need b-sdk to return that? + }, + { + pool: {} as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenInAmount: '0', + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ]; + + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ], + }, + }); + expectedRoute[0].hops[0].pool = pools.find( + (p) => p.id === '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + ) as GqlPoolMinimal; + expectedRoute[0].hops[1].pool = pools.find( + (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal; + }); + test('split paths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenOut, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenOut', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + }); }); diff --git a/modules/sor/sorV2/beetsHelpers.ts b/modules/sor/sorV2/beetsHelpers.ts index 8cff66c8c..79bcd2984 100644 --- a/modules/sor/sorV2/beetsHelpers.ts +++ b/modules/sor/sorV2/beetsHelpers.ts @@ -1,4 +1,4 @@ -import { BatchSwapStep, SingleSwap } from '@balancer/sdk'; +import { BatchSwapStep, SingleSwap, SwapKind } from '@balancer/sdk'; import { GqlPoolMinimal, GqlSorSwapRoute, GqlSorSwapRouteHop } from '../../../schema'; export function mapRoutes( @@ -6,6 +6,10 @@ export function mapRoutes( amountIn: string, amountOut: string, pools: GqlPoolMinimal[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind ): GqlSorSwapRoute[] { const isBatchSwap = Array.isArray(swaps); if (!isBatchSwap) { @@ -13,12 +17,72 @@ export function mapRoutes( if (!pool) throw new Error('Pool not found while mapping route'); return [mapSingleSwap(swaps, amountIn, amountOut, pool)]; } + const paths = splitPaths(swaps, assetIn, assetOut, assets, kind); + return paths.map((p) => mapBatchSwap(p, amountIn, amountOut, kind, assets, pools)); +} + +export function mapBatchSwap( + swaps: BatchSwapStep[], + amountIn: string, + amountOut: string, + kind: SwapKind, + assets: string[], + pools: GqlPoolMinimal[], +): GqlSorSwapRoute { + const exactIn = kind === SwapKind.GivenIn; + const first = swaps[0]; + const last = swaps[swaps.length - 1]; + const share = 1; // tokenInAmountScaled.div(bnum(swapAmount.toString())).toNumber() + const tokenInAmount = exactIn ? first.amount.toString() : amountIn; // * share + const tokenOutAmount = exactIn ? amountOut : last.amount.toString(); // * share + + return { + tokenIn: assets[Number(first.assetInIndex)], + tokenOut: assets[Number(last.assetOutIndex)], + tokenInAmount, + tokenOutAmount, + share, + hops: swaps.map((swap, i) => { + return { + tokenIn: assets[Number(swap.assetInIndex)], + tokenOut: assets[Number(swap.assetOutIndex)], + tokenInAmount: i === 0 ? tokenInAmount : '0', + tokenOutAmount: i === swaps.length - 1 ? tokenOutAmount : '0', + poolId: swap.poolId, + pool: pools.find((p) => p.id === swap.poolId) as GqlPoolMinimal, + }; + }), + }; +} - const route = {} as GqlSorSwapRoute; - return [route]; +export function splitPaths( + swaps: BatchSwapStep[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind, +): BatchSwapStep[][] { + const swapsCopy = [...swaps]; + if (kind === SwapKind.GivenOut) swapsCopy.reverse(); + console.log(swapsCopy); + const assetInIndex = BigInt(assets.indexOf(assetIn)); + const assetOutIndex = BigInt(assets.indexOf(assetOut)); + let path: BatchSwapStep[]; + let paths: BatchSwapStep[][] = []; + swapsCopy.forEach((swap) => { + if (swap.assetInIndex === assetInIndex) { + path = [swap]; + } else if (swap.assetOutIndex === assetOutIndex) { + path.push(swap); + paths.push(path); + } else { + path.push(swap); + } + }); + return paths; } -function mapSingleSwap(swap: SingleSwap, amountIn: string, amountOut: string, pool: GqlPoolMinimal): GqlSorSwapRoute { +function mapSingleSwap(swap: SingleSwap, amountIn: string, amountOut: string, pool: GqlPoolMinimal): GqlSorSwapRoute { const hop: GqlSorSwapRouteHop = { pool, poolId: swap.poolId, @@ -33,6 +97,6 @@ function mapSingleSwap(swap: SingleSwap, amountIn: string, amountOut: string, p tokenOut: swap.assetOut, tokenInAmount: amountIn, tokenOutAmount: amountOut, - hops: [hop] + hops: [hop], } as GqlSorSwapRoute; } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 61af29451..1e391ca92 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -114,7 +114,16 @@ class SwapResultV2 implements SwapResult { swapAmountForSwaps, returnAmountFromSwaps, returnAmountConsideringFees: returnAmountFromSwaps, - routes: this.mapRoutes(swap.swaps, inputAmount.amount.toString(), outputAmount.amount.toString(), pools), // TODO + routes: this.mapRoutes( + swap.swaps, + inputAmount.amount.toString(), + outputAmount.amount.toString(), + pools, + swap.inputAmount.token.address, + swap.outputAmount.token.address, + swap.assets, + swap.swapKind, + ), pools, marketSp: '', // TODO swaps: this.mapSwaps(swap.swaps, swap.assets), @@ -156,8 +165,12 @@ class SwapResultV2 implements SwapResult { inputAmount: string, outputAmount: string, pools: GqlPoolMinimal[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind, ): GqlSorSwapRoute[] { - return mapRoutes(swaps, inputAmount, outputAmount, pools); + return mapRoutes(swaps, inputAmount, outputAmount, pools, assetIn, assetOut, assets, kind); } /** From 4e6a2ada120aa65756590c87cae1a414b6b291f0 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Jun 2023 16:23:32 +0100 Subject: [PATCH 41/78] Route mapping for batchSwap with paths > 1. --- modules/sor/sorV2/beetsHelpers.test.ts | 243 ++++++++++++++++++++----- modules/sor/sorV2/beetsHelpers.ts | 25 ++- 2 files changed, 215 insertions(+), 53 deletions(-) diff --git a/modules/sor/sorV2/beetsHelpers.test.ts b/modules/sor/sorV2/beetsHelpers.test.ts index 1732db8dc..cdd0621f9 100644 --- a/modules/sor/sorV2/beetsHelpers.test.ts +++ b/modules/sor/sorV2/beetsHelpers.test.ts @@ -89,13 +89,13 @@ describe('sorV2 Service - Routes', () => { }); }); describe('BatchSwap', () => { - describe('Single Path, Exact In', () => { + describe('ExactIn', () => { let pools: GqlPoolMinimal[]; let paths: BatchSwapStep[][]; const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; - const amountIn = '123456789112345678'; - const amountOut = '876543210987654321'; + const amountIn = '111111111111111111'; + const amountOut = '222222222222222222'; const batchSwap: BatchSwapStep[] = [ { poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', @@ -144,7 +144,6 @@ describe('sorV2 Service - Routes', () => { tokenOutAmount: amountOut, }, ]; - beforeAll(async () => { pools = await poolService.getGqlPools({ where: { @@ -161,36 +160,111 @@ describe('sorV2 Service - Routes', () => { (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', ) as GqlPoolMinimal; }); - test('splitPaths', () => { - paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); - expect(paths.length).toEqual(1); - expect(paths[0].length).toEqual(2); - }); - test('mapBatchSwap', () => { - const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenIn, assets, pools); - expect(route).toEqual(expectedRoute[0]); + describe('Single Path', () => { + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenIn, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenIn', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); }); - test('GivenIn', () => { - const mappedRoute = mapRoutes( - batchSwap, - amountIn, - amountOut, - pools, - assetIn, - assetOut, - assets, - SwapKind.GivenIn, - ); - expect(mappedRoute).toEqual(expectedRoute); + describe('Multiple Paths', () => { + beforeAll(() => { + batchSwap.push({ + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(2), + amount: BigInt(amountIn), + userData: '0x', + }); + expectedRoute[0].share = 0.5; + expectedRoute.push({ + hops: [ + { + pool: pools.find( + (p) => + p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 0.5, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }); + }); + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); + expect(paths.length).toEqual(2); + expect(paths[0].length).toEqual(2); + expect(paths[1].length).toEqual(1); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[0], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenIn, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[0]); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[1], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenIn, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[1]); + }); + test('GivenIn', () => { + const mappedRoute = mapRoutes( + batchSwap, + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); }); }); - describe('Single Path, Exact Out', () => { + describe('ExactOut', () => { let pools: GqlPoolMinimal[]; let paths: BatchSwapStep[][]; const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; - const amountIn = '123456789112345678'; - const amountOut = '876543210987654321'; + const amountIn = '111111111111111111'; + const amountOut = '222222222222222222'; const batchSwap: BatchSwapStep[] = [ { poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', @@ -256,27 +330,102 @@ describe('sorV2 Service - Routes', () => { (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', ) as GqlPoolMinimal; }); - test('split paths', () => { - paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); - expect(paths.length).toEqual(1); - expect(paths[0].length).toEqual(2); - }); - test('mapBatchSwap', () => { - const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenOut, assets, pools); - expect(route).toEqual(expectedRoute[0]); + describe('Single Path', () => { + test('split paths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenOut, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenOut', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); }); - test('GivenOut', () => { - const mappedRoute = mapRoutes( - batchSwap, - amountIn, - amountOut, - pools, - assetIn, - assetOut, - assets, - SwapKind.GivenOut, - ); - expect(mappedRoute).toEqual(expectedRoute); + describe('Multi Paths', () => { + beforeAll(() => { + batchSwap.push({ + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(2), + amount: BigInt(amountOut), + userData: '0x', + }); + expectedRoute[0].share = 0.5; + expectedRoute.unshift({ + hops: [ + { + pool: pools.find( + (p) => + p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 0.5, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }); + }); + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); + expect(paths.length).toEqual(2); + expect(paths[0].length).toEqual(1); + expect(paths[1].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[0], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenOut, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[0]); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[1], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenOut, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[1]); + }); + test('GivenOut', () => { + const mappedRoute = mapRoutes( + batchSwap, + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); }); }); }); diff --git a/modules/sor/sorV2/beetsHelpers.ts b/modules/sor/sorV2/beetsHelpers.ts index 79bcd2984..5dc457367 100644 --- a/modules/sor/sorV2/beetsHelpers.ts +++ b/modules/sor/sorV2/beetsHelpers.ts @@ -1,5 +1,6 @@ import { BatchSwapStep, SingleSwap, SwapKind } from '@balancer/sdk'; import { GqlPoolMinimal, GqlSorSwapRoute, GqlSorSwapRouteHop } from '../../../schema'; +import { formatFixed } from '@ethersproject/bignumber'; export function mapRoutes( swaps: BatchSwapStep[] | SingleSwap, @@ -9,7 +10,7 @@ export function mapRoutes( assetIn: string, assetOut: string, assets: string[], - kind: SwapKind + kind: SwapKind, ): GqlSorSwapRoute[] { const isBatchSwap = Array.isArray(swaps); if (!isBatchSwap) { @@ -32,16 +33,26 @@ export function mapBatchSwap( const exactIn = kind === SwapKind.GivenIn; const first = swaps[0]; const last = swaps[swaps.length - 1]; - const share = 1; // tokenInAmountScaled.div(bnum(swapAmount.toString())).toNumber() - const tokenInAmount = exactIn ? first.amount.toString() : amountIn; // * share - const tokenOutAmount = exactIn ? amountOut : last.amount.toString(); // * share + let tokenInAmount: string; + let tokenOutAmount: string; + let share: bigint; + const one = BigInt(1e18); + if (exactIn) { + tokenInAmount = first.amount.toString(); + share = (BigInt(tokenInAmount) * one) / BigInt(amountIn); + tokenOutAmount = ((BigInt(amountOut) * share) / one).toString(); + } else { + tokenOutAmount = last.amount.toString(); + share = (BigInt(tokenOutAmount) * one) / BigInt(amountOut); + tokenInAmount = ((BigInt(amountIn) * share) / one).toString(); + } return { tokenIn: assets[Number(first.assetInIndex)], tokenOut: assets[Number(last.assetOutIndex)], tokenInAmount, tokenOutAmount, - share, + share: Number(formatFixed(share.toString(), 18)), hops: swaps.map((swap, i) => { return { tokenIn: assets[Number(swap.assetInIndex)], @@ -70,7 +81,9 @@ export function splitPaths( let path: BatchSwapStep[]; let paths: BatchSwapStep[][] = []; swapsCopy.forEach((swap) => { - if (swap.assetInIndex === assetInIndex) { + if (swap.assetInIndex === assetInIndex && swap.assetOutIndex === assetOutIndex) { + paths.push([swap]); + } else if (swap.assetInIndex === assetInIndex) { path = [swap]; } else if (swap.assetOutIndex === assetOutIndex) { path.push(swap); From be0516a93e767d157a6c389f22e068a124ed3a3e Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 14 Jun 2023 13:39:08 +0100 Subject: [PATCH 42/78] Wider catch in V2 service. (Catches b-sdk/Fantom issue so response is still returned). --- modules/sor/sorV2/sorV2.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 1e391ca92..97a6cd9e3 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -269,12 +269,12 @@ export class SorV2Service implements SwapService { } public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { - const poolsFromDb = await this.getBasePools(); - const chainId = networkContext.chainId as unknown as ChainId; - const tIn = await this.getToken(tokenIn as Address, chainId); - const tOut = await this.getToken(tokenOut as Address, chainId); - const swapKind = this.mapSwapType(swapType); try { + const poolsFromDb = await this.getBasePools(); + const chainId = networkContext.chainId as unknown as ChainId; + const tIn = await this.getToken(tokenIn as Address, chainId); + const tOut = await this.getToken(tokenOut as Address, chainId); + const swapKind = this.mapSwapType(swapType); // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, From ad9059242c83331dcbf8caf5e865c41e77a68fce Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 14 Jun 2023 13:50:11 +0100 Subject: [PATCH 43/78] Remove balancerQueryTest query. --- modules/balancer/balancer.gql | 1 - modules/balancer/balancer.resolvers.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index 8e02b3366..df797a997 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -5,7 +5,6 @@ extend type Mutation { union SorResponse = GqlSorGetSwapsResponse | GqlCowSwapApiResponse extend type Query { - balancerQueryTest: String! sorGetSwaps( tokenIn: String! tokenOut: String! diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 558706ebe..8f0f28602 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -3,9 +3,6 @@ import { sorService } from '../sor/sor.service'; const balancerResolvers: Resolvers = { Query: { - balancerQueryTest: async (parent, {}, context) => { - return 'test'; - }, sorGetSwaps: async (parent, args, context) => { const swaps = await sorService.getCowSwaps({ ...args }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; From 2b7821ab54c96d3b2099ce1b6661733f972604af Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 14 Jun 2023 14:08:08 +0100 Subject: [PATCH 44/78] Remove union type in sorGetSwaps query. --- modules/balancer/balancer.gql | 6 ++---- modules/beethoven/balancer-sdk.gql | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index df797a997..9140d1f71 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -2,15 +2,13 @@ extend type Mutation { balancerMutationTest: String! } -union SorResponse = GqlSorGetSwapsResponse | GqlCowSwapApiResponse - extend type Query { sorGetSwaps( tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in raw amount - ): SorResponse! + ): GqlCowSwapApiResponse! } enum GqlSorSwapType { @@ -24,7 +22,7 @@ type GqlCowSwapApiResponse { swapAmount: String! swapAmountForSwaps: String! returnAmount: String! - returnAmountFromSwaps: String! + returnAmountFromSwaps: String! returnAmountConsideringFees: String! tokenIn: String! tokenOut: String! diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index 22e652db3..f099bccf2 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -1,5 +1,3 @@ -union SorResponse = GqlSorGetSwapsResponse | GqlCowSwapApiResponse - extend type Query { sorGetSwaps( tokenIn: String! @@ -7,7 +5,7 @@ extend type Query { swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in human readable form swapOptions: GqlSorSwapOptionsInput! - ): SorResponse! + ): GqlSorGetSwapsResponse! sorGetBatchSwapForTokensIn( tokensIn: [GqlTokenAmountHumanReadable!]! tokenOut: String! @@ -86,7 +84,7 @@ type GqlCowSwapApiResponse { swapAmount: String! swapAmountForSwaps: String! returnAmount: String! - returnAmountFromSwaps: String! + returnAmountFromSwaps: String! returnAmountConsideringFees: String! tokenIn: String! tokenOut: String! From fefc348857bdb7174dc98b6efaff400af5e97efc Mon Sep 17 00:00:00 2001 From: franz Date: Wed, 14 Jun 2023 15:16:52 +0200 Subject: [PATCH 45/78] fix schema exclusion --- codegen.yml | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/codegen.yml b/codegen.yml index fbf30c21e..9c585d6e1 100644 --- a/codegen.yml +++ b/codegen.yml @@ -140,7 +140,7 @@ generates: graphql_schema_generated_balancer.ts: schema: - ./modules/**/*.gql - - '!./modules/beethoven/beets.gql' + - '!./modules/beethoven/*.gql' - '!./modules/**/*.beets.gql' plugins: - add: @@ -155,7 +155,7 @@ generates: graphql_schema_generated_beethoven.ts: schema: - ./modules/**/*.gql - - '!./modules/balancer/balancer.gql' + - '!./modules/balancer/*.gql' - '!./modules/**/*.balancer.gql' plugins: - add: @@ -167,28 +167,28 @@ generates: - add: placement: 'append' content: '`;' - schema.ts: - schema: ./modules/**/*.gql - plugins: - - add: - content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" - - typescript - - typescript-resolvers + # schema.ts: + # schema: ./modules/**/*.gql + # plugins: + # - add: + # content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" + # - typescript + # - typescript-resolvers - config: - declarationKind: interface - immutableTypes: false - useIndexSignature: true - enumsAsTypes: true - contextType: ./app/gql/gql-context#Context - #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier - scalars: - # Binary: Buffer - # Cursor: string - Date: Date - UUID: string - GqlBigNumber: string - Bytes: string - BigDecimal: string - BigInt: string - AmountHumanReadable: string + # config: + # declarationKind: interface + # immutableTypes: false + # useIndexSignature: true + # enumsAsTypes: true + # contextType: ./app/gql/gql-context#Context + # #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier + # scalars: + # # Binary: Buffer + # # Cursor: string + # Date: Date + # UUID: string + # GqlBigNumber: string + # Bytes: string + # BigDecimal: string + # BigInt: string + # AmountHumanReadable: string From 16fdead7f2f9de375f4da35bcf65d529d34166bb Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 20 Jun 2023 09:22:30 +0100 Subject: [PATCH 46/78] Return marketSp of 0 for beets. --- modules/sor/sorV2/sorV2.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 97a6cd9e3..1f43bc36c 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -125,7 +125,7 @@ class SwapResultV2 implements SwapResult { swap.swapKind, ), pools, - marketSp: '', // TODO + marketSp: '0', // Daniel confirmed returning 0 should be fine here swaps: this.mapSwaps(swap.swaps, swap.assets), tokenAddresses: swap.assets, }; @@ -357,6 +357,7 @@ export class SorV2Service implements SwapService { * @param pools */ private filterLinearPoolsWithZeroRate(pools: PrismaPoolWithDynamic[]): PrismaPoolWithDynamic[] { + // TODO Is this still an issue? // 0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b is a linear pool with priceRate = 0.0 for some tokens which causes issues with b-sdk return pools.filter((p) => { const isLinearPriceRateOk = From 1221d7d806e51b214528c644b4240aef21e1922f Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 29 Aug 2023 09:17:22 +0100 Subject: [PATCH 47/78] Revert commented out section in codegen.yml. --- codegen.yml | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/codegen.yml b/codegen.yml index 9d87786e6..958b0e043 100644 --- a/codegen.yml +++ b/codegen.yml @@ -140,7 +140,7 @@ generates: graphql_schema_generated_balancer.ts: schema: - ./modules/**/*.gql - - '!./modules/beethoven/*.gql' + - '!./modules/beethoven/beets.gql' - '!./modules/**/*.beets.gql' plugins: - add: @@ -155,7 +155,7 @@ generates: graphql_schema_generated_beethoven.ts: schema: - ./modules/**/*.gql - - '!./modules/balancer/*.gql' + - '!./modules/balancer/balancer.gql' - '!./modules/**/*.balancer.gql' plugins: - add: @@ -167,28 +167,28 @@ generates: - add: placement: 'append' content: '`;' - # schema.ts: - # schema: ./modules/**/*.gql - # plugins: - # - add: - # content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" - # - typescript - # - typescript-resolvers + schema.ts: + schema: ./modules/**/*.gql + plugins: + - add: + content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" + - typescript + - typescript-resolvers - # config: - # declarationKind: interface - # immutableTypes: false - # useIndexSignature: true - # enumsAsTypes: true - # contextType: ./app/gql/gql-context#Context - # #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier - # scalars: - # # Binary: Buffer - # # Cursor: string - # Date: Date - # UUID: string - # GqlBigNumber: string - # Bytes: string - # BigDecimal: string - # BigInt: string - # AmountHumanReadable: string + config: + declarationKind: interface + immutableTypes: false + useIndexSignature: true + enumsAsTypes: true + contextType: ./app/gql/gql-context#Context + #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier + scalars: + # Binary: Buffer + # Cursor: string + Date: Date + UUID: string + GqlBigNumber: string + Bytes: string + BigDecimal: string + BigInt: string + AmountHumanReadable: string From ae35e8306e6c6afc77683ce25f370c1bab27f821 Mon Sep 17 00:00:00 2001 From: franz Date: Tue, 29 Aug 2023 10:50:28 +0200 Subject: [PATCH 48/78] exclude all in protocol specific folder --- codegen.yml | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/codegen.yml b/codegen.yml index 958b0e043..9d87786e6 100644 --- a/codegen.yml +++ b/codegen.yml @@ -140,7 +140,7 @@ generates: graphql_schema_generated_balancer.ts: schema: - ./modules/**/*.gql - - '!./modules/beethoven/beets.gql' + - '!./modules/beethoven/*.gql' - '!./modules/**/*.beets.gql' plugins: - add: @@ -155,7 +155,7 @@ generates: graphql_schema_generated_beethoven.ts: schema: - ./modules/**/*.gql - - '!./modules/balancer/balancer.gql' + - '!./modules/balancer/*.gql' - '!./modules/**/*.balancer.gql' plugins: - add: @@ -167,28 +167,28 @@ generates: - add: placement: 'append' content: '`;' - schema.ts: - schema: ./modules/**/*.gql - plugins: - - add: - content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" - - typescript - - typescript-resolvers + # schema.ts: + # schema: ./modules/**/*.gql + # plugins: + # - add: + # content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" + # - typescript + # - typescript-resolvers - config: - declarationKind: interface - immutableTypes: false - useIndexSignature: true - enumsAsTypes: true - contextType: ./app/gql/gql-context#Context - #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier - scalars: - # Binary: Buffer - # Cursor: string - Date: Date - UUID: string - GqlBigNumber: string - Bytes: string - BigDecimal: string - BigInt: string - AmountHumanReadable: string + # config: + # declarationKind: interface + # immutableTypes: false + # useIndexSignature: true + # enumsAsTypes: true + # contextType: ./app/gql/gql-context#Context + # #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier + # scalars: + # # Binary: Buffer + # # Cursor: string + # Date: Date + # UUID: string + # GqlBigNumber: string + # Bytes: string + # BigDecimal: string + # BigInt: string + # AmountHumanReadable: string From de6ea10048acfcc301fff30b4c3f0f9010bc3583 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 1 Sep 2023 16:31:05 +0100 Subject: [PATCH 49/78] Revert "exclude all in protocol specific folder" This reverts commit ae35e8306e6c6afc77683ce25f370c1bab27f821. --- codegen.yml | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/codegen.yml b/codegen.yml index 9d87786e6..958b0e043 100644 --- a/codegen.yml +++ b/codegen.yml @@ -140,7 +140,7 @@ generates: graphql_schema_generated_balancer.ts: schema: - ./modules/**/*.gql - - '!./modules/beethoven/*.gql' + - '!./modules/beethoven/beets.gql' - '!./modules/**/*.beets.gql' plugins: - add: @@ -155,7 +155,7 @@ generates: graphql_schema_generated_beethoven.ts: schema: - ./modules/**/*.gql - - '!./modules/balancer/*.gql' + - '!./modules/balancer/balancer.gql' - '!./modules/**/*.balancer.gql' plugins: - add: @@ -167,28 +167,28 @@ generates: - add: placement: 'append' content: '`;' - # schema.ts: - # schema: ./modules/**/*.gql - # plugins: - # - add: - # content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" - # - typescript - # - typescript-resolvers + schema.ts: + schema: ./modules/**/*.gql + plugins: + - add: + content: "/*\n * THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n */" + - typescript + - typescript-resolvers - # config: - # declarationKind: interface - # immutableTypes: false - # useIndexSignature: true - # enumsAsTypes: true - # contextType: ./app/gql/gql-context#Context - # #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier - # scalars: - # # Binary: Buffer - # # Cursor: string - # Date: Date - # UUID: string - # GqlBigNumber: string - # Bytes: string - # BigDecimal: string - # BigInt: string - # AmountHumanReadable: string + config: + declarationKind: interface + immutableTypes: false + useIndexSignature: true + enumsAsTypes: true + contextType: ./app/gql/gql-context#Context + #defaultMapper: Partial<{T}> #uncomment this to handle partial query resolution easier + scalars: + # Binary: Buffer + # Cursor: string + Date: Date + UUID: string + GqlBigNumber: string + Bytes: string + BigDecimal: string + BigInt: string + AmountHumanReadable: string From 1688a411cb69090823e4b8cf791db85953df5c0d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 1 Sep 2023 16:51:29 +0100 Subject: [PATCH 50/78] Change to use 2 different query types for Beets/Cow swaps. Add missing config to new networks. --- modules/balancer/balancer.gql | 2 +- modules/balancer/balancer.resolvers.ts | 2 +- modules/network/avalanche.ts | 2 ++ modules/network/base.ts | 2 ++ modules/network/zkevm.ts | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index 9140d1f71..fa952c7a7 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -3,7 +3,7 @@ extend type Mutation { } extend type Query { - sorGetSwaps( + sorGetCowSwaps( tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 8f0f28602..13d38e39a 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -3,7 +3,7 @@ import { sorService } from '../sor/sor.service'; const balancerResolvers: Resolvers = { Query: { - sorGetSwaps: async (parent, args, context) => { + sorGetCowSwaps: async (parent, args, context) => { const swaps = await sorService.getCowSwaps({ ...args }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index 20b42578b..c69499452 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -86,6 +86,7 @@ const avalancheNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -93,6 +94,7 @@ const avalancheNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, monitoring: { diff --git a/modules/network/base.ts b/modules/network/base.ts index ec4eea965..67c1973d1 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -87,6 +87,7 @@ const baseNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -94,6 +95,7 @@ const baseNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, lido: { diff --git a/modules/network/zkevm.ts b/modules/network/zkevm.ts index b349fa943..d7d084b8a 100644 --- a/modules/network/zkevm.ts +++ b/modules/network/zkevm.ts @@ -89,6 +89,7 @@ const zkevmNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -96,6 +97,7 @@ const zkevmNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, monitoring: { From fd52d50a9a7230aab174f10a9d02b265fbef750b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 4 Sep 2023 11:55:15 +0100 Subject: [PATCH 51/78] fix: Filter out Linear pools. Filter out pools from vulnerability/mitigation. --- modules/sor/constants.ts | 338 +++++++++++++++++++++++++++++ modules/sor/sorV2/sorV2.service.ts | 38 +--- 2 files changed, 342 insertions(+), 34 deletions(-) diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts index 8a7a820ab..ed1f5b2f2 100644 --- a/modules/sor/constants.ts +++ b/modules/sor/constants.ts @@ -14,3 +14,341 @@ export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount tokenOut: assetOut, }; }; + +// These are pools related to Aug `23 vulnerability/mitigation and should be ignored for swaps +export const poolsToIgnore = [ + '0x00c2a4be503869fa751c2dbcb7156cc970b5a8da000000000000000000000477', + '0x02d928e68d8f10c0358566152677db51e1e2dc8c00000000000000000000051e', + '0x04248aabca09e9a1a3d5129a7ba05b7f17de768400000000000000000000050e', + '0x05513ca725b6ce035ca2641075474eb469f05f4c00020000000000000000041f', + '0x0a0fb4ff697de5ac5b6770cd8ee1b72af80b57cf000000000000000000000496', + '0x0afbd58beca09545e4fb67772faf3858e610bcd00000000000000000000004b9', + '0x0d05aac44ac7dd3c7ba5d50be93eb884a057d23400000000000000000000051c', + '0x11839d635e2f0270da37e8ef4324d4d5d54329570002000000000000000004d8', + '0x126e7643235ec0ab9c103c507642dc3f4ca23c66000000000000000000000468', + '0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476', + '0x159cb00338fb63f263fd6f621df619cef71da9540000000000000000000004d5', + '0x173063a30e095313eee39411f07e95a8a806014e0002000000000000000003ab', + '0x1bd2f176a812e312077bca87e37c08432bb09f3e0000000000000000000005a1', + '0x20b156776114e8a801e9767d90c6ccccc8adf398000000000000000000000499', + '0x246ffb4d928e394a02e45761fecdba6c2e79b8eb000000000000000000000541', + '0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387', + '0x26c2b83fc8535deead276f5cc3ad9c1a2192e02700020000000000000000056b', + '0x2b218683178d029bab6c9789b1073aa6c96e517600000000000000000000058c', + '0x2ba7aa2213fa2c909cd9e46fed5a0059542b36b00000000000000000000003a3', + '0x2bbf681cc4eb09218bee85ea2a5d3d13fa40fc0c0000000000000000000000fd', + '0x2e52c64fd319e380cdbcfc4577ea1fda558a32e40002000000000000000005ba', + '0x2f4eb100552ef93840d5adc30560e5513dfffacb000000000000000000000334', + '0x2ff1a9dbdacd55297452cfd8a4d94724bc22a5f7000000000000000000000484', + '0x3035917be42af437cbdd774be26b9ec90a2bd677000200000000000000000543', + '0x331d50e0b00fc1c32742f151e56b9b616227e23e00000000000000000000047c', + '0x334c96d792e4b26b841d28f53235281cec1be1f200020000000000000000038a', + '0x335d1709d4da9aca59d16328db5cd4ea66bfe06b0000000000000000000004d6', + '0x395d8a1d9ad82b5abe558f8abbfe183b27138af40000000000000000000004e5', + '0x3bb22fc9033b802f2ac47c18885f63476f158afc000000000000000000000483', + '0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b', + '0x3cdae4f12a67ba563499e102f309c73213cb241c000000000000000000000335', + '0x3dbb8d974b82e82ce79c20c0f5995f4f1f533ede000000000000000000000470', + '0x3f7a7fd7f214be45ec26820fd01ac3be4fc75aa70002000000000000000004c5', + '0x3fcb7085b8f2f473f80bf6d879cae99ea4de934400000000000000000000056d', + '0x41503c9d499ddbd1dcdf818a1b05e9774203bf46000000000000000000000594', + '0x4228290ee9cab692938ff0b4ba303fbcdb68e9f200020000000000000000057d', + '0x454ed96955d04d2f5cdd05e0fd1c77975bfe5307000000000000000000000410', + '0x481c5fc05d63a58aa2f0f2aa417c021b5d419cb200000000000000000000056a', + '0x483006684f422a9448023b2382615c57c5ecf18f000000000000000000000488', + '0x4a82b580365cff9b146281ab72500957a849abdc000000000000000000000494', + '0x4c81255cc9ed7062180ea99962fe05ac0d57350b0000000000000000000005a3', + '0x4c8d2e60863e8d7e1033eda2b3d84e92a641802000000000000000000000040f', + '0x4cbde5c4b4b53ebe4af4adb85404725985406163000000000000000000000595', + '0x4ce0bd7debf13434d3ae127430e9bd4291bfb61f00020000000000000000038b', + '0x4ce277df0feb5b4d07a0ca2adcf5326e4005239d000000000000000000000518', + '0x4fd4687ec38220f805b6363c3c1e52d0df3b5023000200000000000000000473', + '0x4fd63966879300cafafbb35d157dc5229278ed230000000000000000000000e9', + '0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f', + '0x53bc3cba3832ebecbfa002c12023f8ab1aa3a3a0000000000000000000000411', + '0x5a6a8cffb4347ff7fc484bf5f0f8a2e234d34255000200000000000000000275', + '0x5b3240b6be3e7487d61cd1afdfc7fe4fa1d81e6400000000000000000000037b', + '0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489', + '0x60d604890feaa0b5460b28a424407c24fe89374a0000000000000000000004fc', + '0x639883476960a23b38579acfd7d71561a0f408cf000200000000000000000505', + '0x652d486b80c461c397b0d95612a404da936f3db30000000000000000000000e7', + '0x6667c6fa9f2b3fc1cc8d85320b62703d938e43850000000000000000000004fb', + '0x6a1eb2e9b45e772f55bd9a34659a04b6f75da68700000000000000000000040d', + '0x6c56e72c551b5ac4bf54a620a76077ca768c8fe40002000000000000000004da', + '0x70b7d3b3209a59fb0400e17f67f3ee8c37363f4900020000000000000000018f', + '0x7337224d59cb16c2dc6938cd45a7b2c60c865d6a0000000000000000000004d4', + '0x74cbfaf94a3577c539a9dcee9870a6349a33b34f000000000000000000000534', + '0x779d01f939d78a918a3de18cc236ee89221dfd4e0000000000000000000004c7', + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', + '0x804cdb9116a10bb78768d3252355a1b18067bf8f0000000000000000000000fb', + '0x813e3fe1761f714c502d1d2d3a7cceb33f37f59d00000000000000000000040c', + '0x82698aecc9e28e9bb27608bd52cf57f704bd1b83000000000000000000000336', + '0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8', + '0x8e6ec57a822c2f527f2df7c7d7d361df3e7530a1000000000000000000000498', + '0x8f4063446f5011bc1c9f79a819efe87776f23704000000000000000000000197', + '0x9001cbbd96f54a658ff4e6e65ab564ded76a543100000000000000000000050a', + '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc', + '0x9516a2d25958edb8da246a320f2c7d94a0dbe25d000000000000000000000519', + '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + '0x968024662b9566b42d78af23a0f441bc8723fa83000200000000000000000418', + '0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a', + '0x9b1c8407a360443a9e5eca004713e4088fab8ac0000000000000000000000497', + '0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d', + '0x9d7f992c900fbea0ec314bdd71b7cc1becf76a33000200000000000000000573', + '0x9fb771d530b0ceba5160f7bfe2dd1e8b8aa1340300000000000000000000040e', + '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + '0xa1697f9af0875b63ddc472d6eebada8c1fab85680000000000000000000004f9', + '0xa3823e50f20982656557a4a6a9c06ba5467ae9080000000000000000000000e6', + '0xa718042e5622099e5f0ace4e7122058ab39e1bbe000200000000000000000475', + '0xa8b103a10a94f4f2d7ed2fdcd5545e807557330700000000000000000000048e', + '0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f00000000000000000000051d', + '0xac976bb42cb0c85635644e8c7c74d0e0286aa61c0000000000000000000003cb', + '0xae37d54ae477268b9997d4161b96b8200755935c000000000000000000000337', + '0xae8535c23afedda9304b03c68a3563b75fc8f92b0000000000000000000005a0', + '0xb0f75e97a114a4eb4a425edc48990e6760726709000000000000000000000198', + '0xb5e3de837f869b0248825e0175da73d4e8c3db6b000200000000000000000474', + '0xb841b062ea8ccf5c4cb78032e91de4ae875560420002000000000000000005b7', + '0xb9bd68a77ccf8314c0dfe51bc291c77590c4e9e6000200000000000000000385', + '0xbb6881874825e60e1160416d6c426eae65f2459e000000000000000000000592', + '0xbc0f2372008005471874e426e86ccfae7b4de79d000000000000000000000485', + '0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d', + '0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3', + '0xc2b021133d1b0cf07dba696fd5dd89338428225b000000000000000000000598', + '0xc443c15033fcb6cf72cc24f1bda0db070ddd9786000000000000000000000593', + '0xc50d4347209f285247bda8a09fc1c12ce42031c3000000000000000000000590', + '0xc5dc1316ab670a2eed5716d7f19ced321191f38200000000000000000000056e', + '0xc8c79fcd0e859e7ec81118e91ce8e4379a481ee6000000000000000000000196', + '0xcaa052584b462198a5a9356c28bce0634d65f65c0000000000000000000004db', + '0xcbfa4532d8b2ade2c261d3dd5ef2a2284f7926920000000000000000000004fa', + '0xcfae6e251369467f465f13836ac8135bd42f8a56000000000000000000000591', + '0xd4e7c1f3da1144c9e2cfd1b015eda7652b4a439900000000000000000000046a', + '0xd6e355036f41dc261b3f1ed3bbc6003e87aadb4f000000000000000000000495', + '0xd7edb56f63b2a0191742aea32df1f98ca81ed9c600000000000000000000058e', + '0xd997f35c9b1281b82c8928039d14cddab5e13c2000000000000000000000019c', + '0xdba274b4d04097b90a72b62467d828cefd708037000000000000000000000486', + '0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4', + '0xdec02e6642e2c999af429f5ce944653cad15e093000000000000000000000469', + '0xe03af00fabe8401560c1ff7d242d622a5b601573000000000000000000000493', + '0xe0fcbf4d98f0ad982db260f86cf28b49845403c5000000000000000000000504', + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7', + '0xe4dc3c1998ac693d68f4c77476d7c815694c3e94000200000000000000000416', + '0xe6bcc79f328eec93d4ec8f7ed35534d9ab549faa0000000000000000000000e8', + '0xe8c56405bc405840154d9b572927f4197d110de10000000000000000000005a4', + '0xeb486af868aeb3b6e53066abc9623b1041b42bc000000000000000000000046c', + '0xeb567dde03f3da7fe185bdacd5ab495ab220769d000000000000000000000548', + '0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d', + '0xf22ff21e17157340575158ad7394e068048dd98b0000000000000000000004b8', + '0xf57c794f42da72b38c8f610ff3b5e8502e48cbde00000000000000000000055c', + '0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e', + '0xfa24a90a3f2bbe5feea92b95cd0d14ce709649f900000000000000000000058f', + '0xfd11ccdbdb7ab91cb9427a6d6bf570c95876d1950000000000000000000004c2', + '0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502', + '0xfef969638c52899f91781f1be594af6f40b99bad00000000000000000000047b', + '0x02e139d53ebf4033bf78ab66c6a1e7f1f204487f0002000000000000000009f9', + '0x03090a9811181a2afe830a3a0b467698ccf3a8b1000000000000000000000bf5', + '0x0320c1c5b6df19a194d48882aaec1c72940081d9000000000000000000000a7d', + '0x04b54ea92d73de2d62d651db7d9778f0c49157d8000200000000000000000ba2', + '0x0503dd6b2d3dd463c9bef67fb5156870af63393e00000000000000000000042e', + '0x0889b240a5876aae745ac19f1771853671dc5d36000000000000000000000b3f', + '0x0bc54e914f53f98d16035f4f0d948f3e09c2fac0000200000000000000000bac', + '0x0c06e87c7b88d998f645b91c1f53b51294b12bca000100000000000000000bb9', + '0x10b040038f87219d9b42e025e3bd9b8095c87dd9000000000000000000000b11', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000aca', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000a5f', + '0x1379b816b9be611431d693290289c204720ca56d000100000000000000000b6f', + '0x150e7b885bdfce974f2abe88a72fdbd692175c6f0002000000000000000009fd', + '0x178e029173417b1f9c8bc16dcec6f697bc323746000000000000000000000758', + '0x1aafc31091d93c3ff003cff5d2d8f7ba2e7284250000000000000000000003b3', + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x216d6db0c28204014618482c369d7fbf0a8f3232000100000000000000000b60', + '0x230ecdb2a7cee56d6889965a023aa0473d6da507000000000000000000000bf3', + '0x252ff6a3a6fd7b5e8e999de8e3f5c3b306ed1401000200000000000000000bec', + '0x25e57f4612912614e6c99616bd2abb9b5ae71e99000000000000000000000bf0', + '0x2645b13fd2c5295296e94a76280b968bdcbbdfed000000000000000000000c11', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000ac9', + '0x2c8dbe8eb86135d9f2f26d196748c088d47f73e7000200000000000000000a29', + '0x31bccf9e28b94e5dacebaa67fe8bc1603cecd904000000000000000000000a01', + '0x341068a547c3cde3c09e338714010dd01b32f93f000200000000000000000a34', + '0x3db543faf7a92052de7860c5c9debabee59ed5bd000000000000000000000a62', + '0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd00000000000000000000070d', + '0x3efb91c4f9b103ee45885695c67794591916f34e000200000000000000000b43', + '0x402cfdb7781fa85d52f425352661128250b79e12000000000000000000000be3', + '0x43894de14462b421372bcfe445fa51b1b4a0ff3d000000000000000000000b36', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000b10', + '0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b', + '0x4a0b73f0d13ff6d43e304a174697e3d5cfd310a400020000000000000000091c', + '0x4a77ef015ddcd972fd9ba2c7d5d658689d090f1a000000000000000000000b38', + '0x4ae3661afa119892f0cc8c43edaf6a94989ac171000000000000000000000c06', + '0x4ccb966d8246240afb7a1a24628efb930870b1c40002000000000000000009fc', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000b0f', + '0x5b77107fcdf2b41903bab2bc555d4fc14cf7667d000000000000000000000b32', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000ac6', + '0x600bd01b6526611079e12e1ff93aba7a3e34226f0000000000000000000009e4', + '0x63ce19ccd39930725b8a3d2733627804718ab83d000000000000000000000bf2', + '0x64efad69f099813021b41f4cac6e749fd55e188f000000000000000000000b39', + '0x6933ec1ca55c06a894107860c92acdfd2dd8512f000000000000000000000428', + '0x6abe4e7a497b8293c258389c1b00d177e4f257ed00010000000000000000080d', + '0x6c8c7fc50247a47038015eb1fd5dc105d05dafba000200000000000000000ba0', + '0x7079a25dec33be61bbd81b2fb69b468e80d3e72c0000000000000000000009ff', + '0x71bd10c2a590b5858f5576550c163976a48af906000000000000000000000b27', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000acd', + '0x7f4f4942f2a14b6ab7b08b10ada1aacede4ee8d4000200000000000000000b44', + '0x86aef31951e0a3a54333bd9e72f9a95587d058c5000200000000000000000912', + '0x882c7a84231484b3e9f3fd45ac04b1eb5d35b076000200000000000000000a91', + '0x894c82800526e0391e709c0983a5aea3718b7f6d000000000000000000000ac5', + '0x89b28a9494589b09dbccb69911c189f74fdadc5a000000000000000000000b33', + '0x89bb15076c9f2d86aa98ec6cffc1a71e31c38953000000000000000000000bf1', + '0x89f1146fee52b5d9166e9c83cc388b6d8f69f1380001000000000000000009e7', + '0x8a819a4cabd6efcb4e5504fe8679a1abd831dd8f00000000000000000000042d', + '0x8b58a1e7fff52001c22386c2918d45938a6a9be30001000000000000000008d9', + '0x8b8225bfedebaf1708c55743acb4ad43fd4d0f21000200000000000000000918', + '0x8fbd0f8e490735cfc3abf4f29cbddd5c3289b9a7000000000000000000000b5b', + '0x8fd39252d683fdb60bddd4df4b53c9380b496d59000200000000000000000b45', + '0x9321e2250767d79bab5aa06daa8606a2b3b7b4c5000000000000000000000bf4', + '0x949a12b95ec5b80c375b98963a5d6b33b0d0efff0002000000000000000009fe', + '0x9a020bdc2faff5bd24c6acc2020d01ff9f2c627a000000000000000000000ae2', + '0x9cf9358300e34bf9373d30129a1e718d8d058b54000200000000000000000913', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000ad5', + '0xa5a935833f6a5312715f182733eab088452335d7000100000000000000000bee', + '0xa5fe91dde37d8bf2dacacc0168b115d28ed03f84000000000000000000000b35', + '0xa8bf1c584519be0184311c48adbdc4c15cb2e8c1000000000000000000000bf6', + '0xab269164a10fab22bc87c39946da06c870b172d6000000000000000000000bfc', + '0xac2cae8d2f78a4a8f92f20dbe74042cd0a8d5af3000000000000000000000be2', + '0xae646817e458c0be890b81e8d880206710e3c44e000000000000000000000acb', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000ac8', + '0xb0c830dceb4ef55a60192472c20c8bf19df03488000000000000000000000be1', + '0xb266ac3b7c98d7bcb28731dac0ef42dba1b276be000000000000000000000be4', + '0xb371aa09f5a110ab69b39a84b5469d29f9b22b76000000000000000000000b37', + '0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace', + '0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e', + '0xb59be8f3c85a9dd6e2899103b6fbf6ea405b99a4000000000000000000000b34', + '0xb878ecce26838fbba4f78cb5b791a0e09152c067000000000000000000000427', + '0xb973ca96a3f0d61045f53255e319aedb6ed4924000000000000000000000042f', + '0xbd4e35784c832d0f9049b54cb3609e5907c5b495000100000000000000000b14', + '0xc55ec796a4debe625d95436a3531f4950b11bdcf000000000000000000000b3e', + '0xc7e6389e364f4275eb442ef215ed21877028e2af000000000000000000000ac7', + '0xc83b55bbd005f1f84906545fcdb145dee53523e0000200000000000000000b30', + '0xcb21a9e647c95127ed784626485b3104cb28d0e7000000000000000000000425', + '0xd00f9ca46ce0e4a63067c4657986f0167b0de1e5000000000000000000000b42', + '0xd2f3b9e67c69762dd1c88f1d3dadd1649a190761000200000000000000000bf7', + '0xd4accb350f9cf59fe3cf7a5ee6ed9ace6a568ea9000200000000000000000b75', + '0xda1cd1711743e57dd57102e9e61b75f3587703da000000000000000000000acc', + '0xdae301690004946424e41051ace1791083be42a1000000000000000000000b40', + '0xde0a77ab6689b980c30306b10f9131a007e1af81000200000000000000000ba1', + '0xe051605a83deae38d26a7346b100ef1ac2ef8a0b0000000000000000000003ce', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b01000000000000000000000ac4', + '0xe2272cddb2cc408e79e02a73d1db9acc24a843d5000200000000000000000ba7', + '0xe2dc0e0f2c358d6e31836dee69a558ab8d1390e70000000000000000000009fa', + '0xe4885ed2818cc9e840a25f94f9b2a28169d1aea7000000000000000000000b29', + '0xe6909c2f18a29d97217a6146f045e1780606991f000100000000000000000bfe', + '0xe78b25c06db117fdf8f98583cdaaa6c92b79e917000000000000000000000b2b', + '0xea11645ac7d8f2def94c9d8d86bd766296c9b6b6000000000000000000000b3a', + '0xeb480dbbdd921cd6c359e4cc4c65ddea6395e2a1000200000000000000000946', + '0xed35f28f837e96f81240ebb82e0e3f518c7e8a2f000100000000000000000bb5', + '0xf0211cceebe6fcc45052b4e57ee95d233f5669d2000100000000000000000c01', + '0xf22a66046b5307842f21b311ecb4c462c24c0635000000000000000000000b15', + '0xf28f17be00f8ca3c9b7f66a4aad5513757fb3341000200000000000000000b5a', + '0xf42ed61450458ee4620f5ef4f29adb25a6ef0fb6000000000000000000000bf8', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000000000000000000000445', + '0xf93579002dbe8046c43fefe86ec78b1112247bb8000000000000000000000759', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca000000000000000000000a59', + '0xfa2c0bd8327c99db5bde4c9e9e5cbf30946351bb000000000000000000000948', + '0xff4ce5aaab5a627bf82f4a571ab1ce94aa365ea600000000000000000000075a', + '0x1ac55c31dac78ca943cb8ebfca5945ce09e036e2000000000000000000000024', + '0x225e0047671939a8d78e08ebd692788abe63f15c000000000000000000000009', + '0x41211bba6d37f5a74b22e667533f080c7c7f3f1300000000000000000000000b', + '0x4de21b365d6543661d0e105e579a34b963862497000200000000000000000045', + '0x581ec1f5e7ced12b186deae32256adb53bdd5b08000000000000000000000001', + '0x66f33ae36dd80327744207a48122f874634b3ada000100000000000000000013', + '0xa3ed6f78edc29f69df8a0d16b1d1ccf9871f918c000000000000000000000032', + '0xa611a551b95b205ccd9490657acf7899daee5db700000000000000000000002e', + '0xb95829adbacd8af89e291dee78bc09e24de51d6b000000000000000000000043', + '0xb973ca96a3f0d61045f53255e319aedb6ed49240000200000000000000000011', + '0xba1a5b19d09a79dada039b1f974015c5a989d5fd000100000000000000000046', + '0xbb9cd48d33033f5effbedec9dd700c7d7e1dcf5000000000000000000000000e', + '0xd16f72b02da5f51231fde542a8b9e2777a478c8800000000000000000000000f', + '0xd4015683b8153666190e0b2bec352580ebc4caca00000000000000000000000d', + '0xe15cac1df3621e001f76210ab12a7f1a1691481f000000000000000000000044', + '0xe7f88d7d4ef2eb18fcf9dd7216ba7da1c46f3dd600000000000000000000000a', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000200000000000000000012', + '0xfedb19ec000d38d92af4b21436870f115db22725000000000000000000000010', + '0xffff76a3280e95dc855696111c2562da09db2ac000000000000000000000000c', + '0x00fcd3d55085e998e291a0005cedecf58ac14c4000020000000000000000047f', + '0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000381', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000353', + '0x19b1c92631405a0a9495ccba0becf4f2e8e908bd000000000000000000000410', + '0x1e550b7764da9638fdd32c8a701364de31f45ee800000000000000000000047c', + '0x1fa7f727934226aedab636d62a084931b97d366b000000000000000000000411', + '0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000c9', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000380', + '0x2a96254ca32020b20ed3506f8f75318da24709f9000200000000000000000456', + '0x36942963e3b6f37ecc45a4e72349558514233f0000000000000000000000048a', + '0x3f53a862919ccfa023cb6ace91378a79fb0f6bf500000000000000000000040f', + '0x40af308e3d07ec769d85eb80afb116525ff4ac99000000000000000000000485', + '0x418de00ae109e6f874d872658767866d680eaa1900000000000000000000047d', + '0x45c4d1376943ab28802b995acffc04903eb5223f000000000000000000000470', + '0x4689122d360c4725d244c5cfea22861333d862e6000100000000000000000468', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000409', + '0x49a0e3334496442a9706e481617724e7e37eaa080000000000000000000003ff', + '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000408', + '0x567ecfcb22205d279bb8eed3e066989902bf03d5000000000000000000000452', + '0x585d95df0231fa08aeee35ff0c16b92fd0ecdc3300020000000000000000045f', + '0x5a7f39435fd9c381e4932fa2047c9a5136a5e3e7000000000000000000000400', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000350', + '0x6cb787a419c3e6ee2e9ff365856c29cd10659113000000000000000000000474', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000406', + '0x81fc12c60ee5b753cf5fd0adc342dfb5f3817e3200000000000000000000035d', + '0x894c82800526e0391e709c0983a5aea3718b7f6d00000000000000000000034f', + '0x970712708a08e8fb152be4d81b2dc586923f5369000200000000000000000479', + '0x9bf7c3b63c77b4b4f2717776f15a4bec1b532a280000000000000000000000c8', + '0x9cebf13bb702f253abf1579294694a1edad00eaa000000000000000000000486', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000407', + '0x9fb7d6dcac7b6aa20108bad226c35b85a9e31b63000200000000000000000412', + '0xa1ea76c42b2938cfa9abea12357881006c52851300000000000000000000048f', + '0xa50f89e9f439fde2a6fe05883721a00475da3c4500000000000000000000048b', + '0xa612b6aed2e7ca1a3a4f23fbca9128461bbb7718000000000000000000000274', + '0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b', + '0xad28940024117b442a9efb6d0f25c8b59e1c950b00000000000000000000046f', + '0xae646817e458c0be890b81e8d880206710e3c44e00000000000000000000039d', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000351', + '0xbbf9d705b75f408cfcaee91da32966124d2c6f7d00000000000000000000047e', + '0xbd724eb087d4cc0f61a5fed1fffaf937937e14de000000000000000000000473', + '0xbe0f30217be1e981add883848d0773a86d2d2cd4000000000000000000000471', + '0xc46be4b8bb6b5a3d3120660efae9c5416318ed40000000000000000000000472', + '0xc69771058481551261709d8db44977e9afde645000010000000000000000042a', + '0xc6eee8cb7643ec2f05f46d569e9ec8ef8b41b389000000000000000000000475', + '0xcba9ff45cfb9ce238afde32b0148eb82cbe635620000000000000000000003fd', + '0xcf8b555b7754556cf2ac2165e77ee23ed8517d7900020000000000000000045e', + '0xd0dc20e6342db2de82692b8dc842301ff9121805000200000000000000000454', + '0xd3d5d45f4edf82ba0dfaf061d230766032a10e07000200000000000000000413', + '0xd6d20527c7b0669989ee082b9d3a1c63af742290000000000000000000000483', + '0xda1cd1711743e57dd57102e9e61b75f3587703da0000000000000000000003fc', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b0100000000000000000000034e', + '0xee02583596aee94cccb7e8ccd3921d955f17982a00000000000000000000040a', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca00000000000000000000034d', + '0xff8f84e8c87532af96aef5582ee451572233678b000200000000000000000478', + '0x054e7b0c73e1ee5aed6864fa511658fc2b54bcaa000000000000000000000015', + '0x3f1a2c4a3a751f6626bd90ef16e104f0772d4d6b00020000000000000000001b', + '0x7275c131b1f67e8b53b4691f92b0e35a4c1c6e22000000000000000000000010', + '0xa154009870e9b6431305f19b09f9cfd7284d4e7a000000000000000000000013', + '0xa1d14d922a575232066520eda11e27760946c991000000000000000000000012', + '0xa826a114b0c7db4d1ff4a4be845a78998c64564c000000000000000000000008', + '0xea67626e1f0b59e0d172a04f5702ef90bcdf440c00000000000000000000000f', + '0xeb496161099d45b3ea4892408ef745c6182eb56e00000000000000000000000e', + '0xece571847897fd61e764d455dc15cf1cd9de8d6f000000000000000000000014', + '0xed3e2f496cbcd8e212192fb8d1499842f04a0d19000000000000000000000009', + '0x02c9dcb975262719a61f9b40bdf0987ead9add3a000000000000000000000006', + '0x16c9a4d841e88e52b51936106010f27085a529ec00000000000000000000000c', + '0x32be2d0ddeaf3333501b24a28668ce373ba8e763000200000000000000000014', + '0x32f03464fdf909fdf3798f87ff3712b10c59bd86000000000000000000000005', + '0x4b718e0e2fea1da68b763cd50c446fba03ceb2ea00000000000000000000000b', + '0x68a69c596b3839023c0e08d09682314f582314e5000200000000000000000011', + '0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015', + '0x9e2d87f904862671eb49cb358e74284762cc9f42000200000000000000000013', + '0xac4b72c01072a52b73ca71105504f1372efcce0d000000000000000000000003', + '0xbfd65c6160cfd638a85c645e6e6d8acac5dac935000000000000000000000004', + '0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d', +]; + diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 1f43bc36c..3b849016a 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -7,15 +7,12 @@ import { SwapKind, sorParseRawPools, RawStablePool, - RawLinearPool, RawWeightedPool, - RawComposableStablePool, RawMetaStablePool, Swap as SwapSdk, RawPool, TokenAmount, } from '@balancer/sdk'; -import { parseFixed } from '@ethersproject/bignumber'; import cloneDeep from 'lodash/cloneDeep'; import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; import { PrismaPoolType, PrismaToken } from '@prisma/client'; @@ -37,6 +34,7 @@ import { SwapInfoRoute, SwapTypes, Swap, bnum, SwapInfoRouteHop } from '@balance import { BigNumber } from 'ethers'; import { oldBnumScale } from '../../big-number/old-big-number'; import { mapRoutes } from './beetsHelpers'; +import { poolsToIgnore } from '../constants'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -342,8 +340,9 @@ export class SorV2Service implements SwapService { }, NOT: { id: { - in: networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, + in: [...networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, ...poolsToIgnore], }, + type: 'LINEAR' // Linear pools are sunset so ignore to avoid issues related to lack of support }, }, include: prismaPoolWithDynamic.include, @@ -352,28 +351,13 @@ export class SorV2Service implements SwapService { return this.mapToBasePools(rawPools); } - /** - * Remove linear pools that cause issues because of price rate. - * @param pools - */ - private filterLinearPoolsWithZeroRate(pools: PrismaPoolWithDynamic[]): PrismaPoolWithDynamic[] { - // TODO Is this still an issue? - // 0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b is a linear pool with priceRate = 0.0 for some tokens which causes issues with b-sdk - return pools.filter((p) => { - const isLinearPriceRateOk = - p.type === 'LINEAR' ? !p.tokens.some((t) => t.dynamicData?.priceRate === '0.0') : true; - return isLinearPriceRateOk; - }); - } - /** * Map Prisma pools to b-sdk RawPool. * @param pools * @returns */ private mapToRawPools(pools: PrismaPoolWithDynamic[]): RawPool[] { - const filteredPools = this.filterLinearPoolsWithZeroRate(pools); - return filteredPools.map((prismaPool) => { + return pools.map((prismaPool) => { // b-sdk: src/data/types.ts let rawPool: RawPool = { id: prismaPool.id as Address, @@ -419,18 +403,6 @@ export class SorV2Service implements SwapService { }), } as RawMetaStablePool; } - if (rawPool.poolType === 'Linear') { - rawPool = { - ...rawPool, - mainIndex: prismaPool.linearData?.mainIndex, - wrappedIndex: prismaPool.linearData?.wrappedIndex, - lowerTarget: prismaPool.linearDynamicData?.lowerTarget, - upperTarget: prismaPool.linearDynamicData?.upperTarget, - tokens: rawPool.tokens.map((t, i) => { - return { ...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate }; - }), - } as RawLinearPool; - } return rawPool; }); } @@ -468,8 +440,6 @@ export class SorV2Service implements SwapService { case PrismaPoolType.PHANTOM_STABLE: // Composablestables are PHANTOM_STABLE in Prisma. b-sdk treats Phantoms as ComposableStable. return 'ComposableStable'; - case PrismaPoolType.LINEAR: - return 'Linear'; default: return type; } From 061e5248c684614b4a8629ce0acf102642713e2d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 4 Sep 2023 12:03:33 +0100 Subject: [PATCH 52/78] chore: Remove unneccesary Migration folders. --- .../20230508110533_trade_results/migration.sql | 15 --------------- .../migration.sql | 8 -------- 2 files changed, 23 deletions(-) delete mode 100644 prisma/migrations/20230508110533_trade_results/migration.sql delete mode 100644 prisma/migrations/20230519101752_remove_trade_result/migration.sql diff --git a/prisma/migrations/20230508110533_trade_results/migration.sql b/prisma/migrations/20230508110533_trade_results/migration.sql deleted file mode 100644 index 5ea9f1ce9..000000000 --- a/prisma/migrations/20230508110533_trade_results/migration.sql +++ /dev/null @@ -1,15 +0,0 @@ --- CreateTable -CREATE TABLE "PrismaTradeResult" ( - "id" TEXT NOT NULL, - "timestamp" INTEGER NOT NULL, - "chain" "Chain" NOT NULL, - "tokenIn" TEXT NOT NULL, - "tokenOut" TEXT NOT NULL, - "swapAmount" TEXT NOT NULL, - "swapType" TEXT NOT NULL, - "sorV1Result" TEXT NOT NULL, - "sorV2Result" TEXT NOT NULL, - "isSorV1" BOOLEAN NOT NULL, - - CONSTRAINT "PrismaTradeResult_pkey" PRIMARY KEY ("id","chain") -); diff --git a/prisma/migrations/20230519101752_remove_trade_result/migration.sql b/prisma/migrations/20230519101752_remove_trade_result/migration.sql deleted file mode 100644 index 694852ca6..000000000 --- a/prisma/migrations/20230519101752_remove_trade_result/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - You are about to drop the `PrismaTradeResult` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -DROP TABLE "PrismaTradeResult"; From 2ea2442168bde1ad806363503c5ef007005b13d7 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 4 Sep 2023 15:34:57 +0100 Subject: [PATCH 53/78] chore: Update sdk to 0.1.1 and add missing RawPool fields. --- modules/sor/sorV2/sorV2.service.ts | 3 + package.json | 2 +- yarn.lock | 160 ++++++++++++++--------------- 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 3b849016a..52ed8baaa 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -379,6 +379,9 @@ export class SorV2Service implements SwapService { balance: t.dynamicData?.balance as unknown as HumanAmount, }; }), + isPaused: !!prismaPool.dynamicData?.isPaused, + inRecoveryMode: !!prismaPool.dynamicData?.isInRecoveryMode, + name: 'n/a' }; if (['Weighted', 'Investment', 'LiquidityBootstrapping'].includes(rawPool.poolType)) { rawPool = { diff --git a/package.json b/package.json index 123b0d2dc..49a54f31e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", - "@balancer/sdk": "^0.0.3", + "@balancer/sdk": "^0.1.1", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", diff --git a/yarn.lock b/yarn.lock index fa094b064..709c198cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d" - integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ== +"@adraffy/ens-normalize@1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" + integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== "@ampproject/remapping@^2.1.0": version "2.2.0" @@ -2456,15 +2456,15 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" -"@balancer/sdk@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.0.3.tgz#3579f4ab9f3b918d70bd1ac280fcf49e8bf83616" - integrity sha512-L2/5n5AFM2Ci4YMcHGeq7cZO0xJCDA2VPoBulTv5YgZrr1LPE37vq9pLmqdAjvyKBM6xN6nqWXEXEOfQD40Kow== +"@balancer/sdk@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.1.1.tgz#5449f76757812f11ffee06b2d3f6019c791f0c45" + integrity sha512-qCibAQJ3hiZcQYL3Y/qhedn8Ro1BszIDL0i/SroALLN9RlhtWni2lNHu9vCNSgneJbvlFfLxQ+NNJ6dyOv67HQ== dependencies: async-retry "^1.3.3" decimal.js-light "^2.5.1" pino "^8.11.0" - viem "^0.2.1" + viem "^1.4.1" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -3751,24 +3751,17 @@ resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" integrity sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg== -"@noble/curves@0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.9.0.tgz#d59713ecbb6a77381de84fb8969381fa85a7380b" - integrity sha512-OAdtHMXBp7Chl2lcTn/i7vnFX/q+hhTwDnek5NfYfZsY4LyaUuHCcoq2JlLY3BTFTLT+ZhYZalhF6ejlV7KnJQ== - dependencies: - "@noble/hashes" "1.3.0" - -"@noble/curves@~0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.8.3.tgz#ad6d48baf2599cf1d58dcb734c14d5225c8996e0" - integrity sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ== +"@noble/curves@1.2.0", "@noble/curves@~1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: - "@noble/hashes" "1.3.0" + "@noble/hashes" "1.3.2" -"@noble/hashes@1.3.0", "@noble/hashes@~1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== +"@noble/hashes@1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -3917,24 +3910,24 @@ resolved "https://registry.yarnpkg.com/@sanity/timed-out/-/timed-out-4.0.2.tgz#c9f61f9a1609baa1eb3e4235a24ea2a775022cdf" integrity sha512-NBDKGj14g9Z+bopIvZcQKWCzJq5JSrdmzRR1CS+iyA3Gm8SnIWBfZa7I3mTg2X6Nu8LQXG0EPKXdOGozLS4i3w== -"@scure/base@~1.1.0": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.2.tgz#ff0cf51874aaf176490c9cb46e4df807a2e581d2" - integrity sha512-sSCrnIdaUZQHhBxZThMuk7Wm1TWzMD3uJNdGgx3JS23xSqevu0tAOsg8k66nL3R2NwQe65AI9GgqpPOgZys/eA== +"@scure/base@~1.1.0", "@scure/base@~1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== -"@scure/bip32@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.2.0.tgz#35692d8f8cc3207200239fc119f9e038e5f465df" - integrity sha512-O+vT/hBVk+ag2i6j2CDemwd1E1MtGt+7O1KzrPNsaNvSsiEK55MyPIxJIMI2PS8Ijj464B2VbQlpRoQXxw1uHg== +"@scure/bip32@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" + integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== dependencies: - "@noble/curves" "~0.8.3" - "@noble/hashes" "~1.3.0" - "@scure/base" "~1.1.0" + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.2" -"@scure/bip39@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b" - integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg== +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== dependencies: "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" @@ -4819,6 +4812,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.5": + version "8.5.5" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" + integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -4881,11 +4881,6 @@ loupe "^2.3.6" pretty-format "^29.5.0" -"@wagmi/chains@0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-0.2.16.tgz#a726716e4619ec1c192b312e23f9c38407617aa0" - integrity sha512-rkWaI2PxCnbD8G07ZZff5QXftnSkYL0h5f4DkHCG3fGYYr/ZDvmCL4bMae7j7A9sAif1csPPBmbCzHp3R5ogCQ== - "@xmldom/xmldom@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" @@ -4901,10 +4896,10 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abitype@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" - integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== +abitype@0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" + integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== abort-controller@^3.0.0: version "3.0.0" @@ -6865,9 +6860,9 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-redact@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.2.0.tgz#b1e2d39bc731376d28bde844454fa23e26919987" - integrity sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" + integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== fast-text-encoding@^1.0.0: version "1.0.6" @@ -9697,14 +9692,14 @@ pino-abstract-transport@v1.0.0: split2 "^4.0.0" pino-std-serializers@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.1.tgz#369f4ae2a19eb6d769ddf2c88a2164b76879a284" - integrity sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ== + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== pino@^8.11.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.14.1.tgz#bb38dcda8b500dd90c1193b6c9171eb777a47ac8" - integrity sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw== + version "8.15.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.15.0.tgz#67c61d5e397bf297e5a0433976a7f7b8aa6f876b" + integrity sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ== dependencies: atomic-sleep "^1.0.0" fast-redact "^3.1.1" @@ -9969,14 +9964,15 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable util-deprecate "^1.0.1" readable-stream@^4.0.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" - integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== + version "4.4.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.2.tgz#e6aced27ad3b9d726d8308515b9a1b98dc1b9d13" + integrity sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" events "^3.3.0" process "^0.11.10" + string_decoder "^1.3.0" readdir-glob@^1.0.0: version "1.1.1" @@ -10655,7 +10651,7 @@ string.prototype.trimstart@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -10862,9 +10858,9 @@ testcontainers@^8.0.0: tar-fs "^2.1.1" thread-stream@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33" - integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA== + version "2.4.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.0.tgz#5def29598d1d4171ba3bace7e023a71d87d99c07" + integrity sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw== dependencies: real-require "^0.2.0" @@ -11252,20 +11248,20 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -viem@^0.2.1: - version "0.2.14" - resolved "https://registry.yarnpkg.com/viem/-/viem-0.2.14.tgz#3a010b7bb6dedf83b0aa61a636e4c2c30d476029" - integrity sha512-hHlmzEvOQC/wDqcQlexjczxHSqcPlQrS/VKJwGv53Pp+Aap+SeULC3QBycW+YmQ2trh0RfV0xa53u7Qd/KDJog== - dependencies: - "@adraffy/ens-normalize" "1.9.0" - "@noble/curves" "0.9.0" - "@noble/hashes" "1.3.0" - "@scure/bip32" "1.2.0" - "@scure/bip39" "1.2.0" - "@wagmi/chains" "0.2.16" - abitype "0.7.1" +viem@^1.4.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.10.1.tgz#b91a2e5e583e94d61c210391322de01692f19a07" + integrity sha512-rN2GhbEElz47z0aOXly4A+XLGNNQJkCUR03pL9EoRVbMpAPf7mK3Pk0m7crRvBesb0xiS8rgIg0Ip50fq9vWIw== + dependencies: + "@adraffy/ens-normalize" "1.9.4" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + "@types/ws" "^8.5.5" + abitype "0.9.8" isomorphic-ws "5.0.0" - ws "8.12.0" + ws "8.13.0" vite-node@0.32.4: version "0.32.4" @@ -11472,10 +11468,10 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" - integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== +ws@8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== ws@8.2.3: version "8.2.3" From aae9bd89ab09d6cdb0a99aeab46daa18ecf36631 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 5 Sep 2023 09:50:31 +0100 Subject: [PATCH 54/78] feat: Add poolGetGyroPools query. --- modules/pool/lib/pool-gql-loader.service.ts | 11 +++++++++++ modules/pool/pool.gql | 1 + modules/pool/pool.resolvers.ts | 3 +++ modules/pool/pool.service.ts | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index f6d97f1ee..8cad321f3 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -13,6 +13,7 @@ import { GqlBalancePoolAprSubItem, GqlPoolDynamicData, GqlPoolFeaturedPoolGroup, + GqlPoolGyro, GqlPoolInvestConfig, GqlPoolInvestOption, GqlPoolLinear, @@ -75,6 +76,16 @@ export class PoolGqlLoaderService { return pools.map((pool) => this.mapPoolToGqlPool(pool)) as GqlPoolLinear[]; } + public async getGyroPools(): Promise { + const pools = await prisma.prismaPool.findMany({ + where: { type: {in: ["GYRO", "GYRO3", "GYROE"]}, chain: networkContext.chain }, + orderBy: { dynamicData: { totalLiquidity: 'desc' } }, + include: prismaPoolWithExpandedNesting.include, + }); + + return pools.map((pool) => this.mapPoolToGqlPool(pool)) as GqlPoolGyro[]; + } + public mapToMinimalGqlPool(pool: PrismaPoolMinimal): GqlPoolMinimal { return { ...pool, diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index c168048ae..3f3579485 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -25,6 +25,7 @@ extend type Query { poolGetSnapshots(id: String!, range: GqlPoolSnapshotDataRange!): [GqlPoolSnapshot!]! poolGetAllPoolsSnapshots(range: GqlPoolSnapshotDataRange!): [GqlPoolSnapshot!]! poolGetLinearPools: [GqlPoolLinear!]! + poolGetGyroPools: [GqlPoolGyro!]! } extend type Mutation { diff --git a/modules/pool/pool.resolvers.ts b/modules/pool/pool.resolvers.ts index 19878217c..439133a76 100644 --- a/modules/pool/pool.resolvers.ts +++ b/modules/pool/pool.resolvers.ts @@ -66,6 +66,9 @@ const balancerResolvers: Resolvers = { poolGetLinearPools: async () => { return poolService.getGqlLinearPools(); }, + poolGetGyroPools: async () => { + return poolService.getGqlGyroPools(); + } }, Mutation: { poolSyncAllPoolsFromSubgraph: async (parent, {}, context) => { diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index f1e42fcd9..23a6bae15 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -6,6 +6,7 @@ import { prisma } from '../../prisma/prisma-client'; import { GqlPoolBatchSwap, GqlPoolFeaturedPoolGroup, + GqlPoolGyro, GqlPoolJoinExit, GqlPoolLinear, GqlPoolMinimal, @@ -73,6 +74,10 @@ export class PoolService { return this.poolGqlLoaderService.getLinearPools(); } + public async getGqlGyroPools(): Promise { + return this.poolGqlLoaderService.getGyroPools(); + } + public async getPoolsCount(args: QueryPoolGetPoolsArgs): Promise { return this.poolGqlLoaderService.getPoolsCount(args); } From 5d6ade7f14829524f0a3c7dd2ea51be120cab764 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 5 Sep 2023 12:02:04 +0100 Subject: [PATCH 55/78] chore: Add new Gyro fields for SOR calcs. --- modules/pool/lib/pool-creator.service.ts | 12 +++++++ modules/pool/lib/pool-gql-loader.service.ts | 36 +++++++++++++++++++ modules/pool/pool.gql | 12 +++++++ modules/pool/pool.prisma | 12 +++++++ .../balancer-subgraph-queries.graphql | 12 +++++++ .../migration.sql | 13 +++++++ prisma/schema.prisma | 12 +++++++ 7 files changed, 109 insertions(+) create mode 100644 prisma/migrations/20230905102723_gyro_sor_fields/migration.sql diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index cf035e527..9005b13ca 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -249,6 +249,18 @@ export class PoolCreatorService { id: pool.id, alpha: pool.alpha || '', beta: pool.beta || '', + c: pool.c || '', + s: pool.s || '', + lambda: pool.lambda || '', + tauAlphaX: pool.tauAlphaX || '', + tauAlphaY: pool.tauAlphaY || '', + tauBetaX: pool.tauBetaX || '', + tauBetaY: pool.tauBetaY || '', + u: pool.u || '', + v: pool.v || '', + w: pool.w || '', + z: pool.z || '', + dSq: pool.dSq || '', }, } : undefined, diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index 8cad321f3..268a1d6ee 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -386,6 +386,18 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; case 'GYRO3': return { @@ -394,6 +406,18 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; case 'GYROE': return { @@ -402,6 +426,18 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; } diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index 3f3579485..33e176a24 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -237,6 +237,18 @@ type GqlPoolGyro implements GqlPoolBase { alpha: String! beta: String! + c: String! + s: String! + lambda: String! + tauAlphaX: String! + tauAlphaY: String! + tauBetaX: String! + tauBetaY: String! + u: String! + v: String! + w: String! + z: String! + dSq: String! tokens: [GqlPoolTokenUnion!]! nestingType: GqlPoolNestingType! diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index 174d3ed07..bf32bbb2d 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -95,6 +95,18 @@ model PrismaPoolGyroData{ alpha String beta String + c String? + s String? + lambda String? + tauAlphaX String? + tauAlphaY String? + tauBetaX String? + tauBetaY String? + u String? + v String? + w String? + z String? + dSq String? } model PrismaPoolDynamicData { diff --git a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql index 8c37e682f..f0f3012a2 100644 --- a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql +++ b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql @@ -182,6 +182,18 @@ fragment BalancerPool on Pool { amp alpha beta + c + s + lambda + tauAlphaX + tauAlphaY + tauBetaX + tauBetaY + u + v + w + z + dSq tokens { ...BalancerPoolToken diff --git a/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql b/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql new file mode 100644 index 000000000..4202ecbdf --- /dev/null +++ b/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql @@ -0,0 +1,13 @@ +-- AlterTable +ALTER TABLE "PrismaPoolGyroData" ADD COLUMN "c" TEXT, +ADD COLUMN "dSq" TEXT, +ADD COLUMN "lambda" TEXT, +ADD COLUMN "s" TEXT, +ADD COLUMN "tauAlphaX" TEXT, +ADD COLUMN "tauAlphaY" TEXT, +ADD COLUMN "tauBetaX" TEXT, +ADD COLUMN "tauBetaY" TEXT, +ADD COLUMN "u" TEXT, +ADD COLUMN "v" TEXT, +ADD COLUMN "w" TEXT, +ADD COLUMN "z" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 296704b77..1340d7527 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -141,6 +141,18 @@ model PrismaPoolGyroData{ alpha String beta String + c String? + s String? + lambda String? + tauAlphaX String? + tauAlphaY String? + tauBetaX String? + tauBetaY String? + u String? + v String? + w String? + z String? + dSq String? } model PrismaPoolDynamicData { From 6390fe45c0f6b149f53be3c965c371882489d419 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 7 Sep 2023 15:01:30 +0100 Subject: [PATCH 56/78] chore: More Gyro fields for SOR. --- modules/pool/lib/pool-creator.service.ts | 3 +++ modules/pool/lib/pool-gql-loader.service.ts | 9 +++++++++ modules/pool/pool.gql | 3 +++ modules/pool/pool.prisma | 3 +++ .../balancer-subgraph/balancer-subgraph-queries.graphql | 3 +++ .../20230907135550_gyro_missing_fields/migration.sql | 4 ++++ prisma/schema.prisma | 3 +++ 7 files changed, 28 insertions(+) create mode 100644 prisma/migrations/20230907135550_gyro_missing_fields/migration.sql diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index 9005b13ca..51941591f 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -249,6 +249,9 @@ export class PoolCreatorService { id: pool.id, alpha: pool.alpha || '', beta: pool.beta || '', + sqrtAlpha: pool.sqrtAlpha || '', + sqrtBeta: pool.sqrtBeta || '', + root3Alpha: pool.root3Alpha || '', c: pool.c || '', s: pool.s || '', lambda: pool.lambda || '', diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index 268a1d6ee..9a00ec6be 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -386,6 +386,9 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', c: pool.gyroData?.c || '', s: pool.gyroData?.s || '', lambda: pool.gyroData?.lambda || '', @@ -406,6 +409,9 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', c: pool.gyroData?.c || '', s: pool.gyroData?.s || '', lambda: pool.gyroData?.lambda || '', @@ -426,6 +432,9 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', c: pool.gyroData?.c || '', s: pool.gyroData?.s || '', lambda: pool.gyroData?.lambda || '', diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index 33e176a24..0b2d39658 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -237,6 +237,9 @@ type GqlPoolGyro implements GqlPoolBase { alpha: String! beta: String! + sqrtAlpha: String! + sqrtBeta: String! + root3Alpha: String! c: String! s: String! lambda: String! diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index faca9af33..8561f31d2 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -95,6 +95,9 @@ model PrismaPoolGyroData{ alpha String beta String + sqrtAlpha String? + sqrtBeta String? + root3Alpha String? c String? s String? lambda String? diff --git a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql index f0f3012a2..f154888d1 100644 --- a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql +++ b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql @@ -182,6 +182,9 @@ fragment BalancerPool on Pool { amp alpha beta + sqrtAlpha + sqrtBeta + root3Alpha c s lambda diff --git a/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql b/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql new file mode 100644 index 000000000..c3f5623b1 --- /dev/null +++ b/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "PrismaPoolGyroData" ADD COLUMN "root3Alpha" TEXT, +ADD COLUMN "sqrtAlpha" TEXT, +ADD COLUMN "sqrtBeta" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b725f08d3..a7268cff3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -141,6 +141,9 @@ model PrismaPoolGyroData{ alpha String beta String + sqrtAlpha String? + sqrtBeta String? + root3Alpha String? c String? s String? lambda String? From abbc61ee355c411ee9406c3356b04803d678e7e7 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 7 Sep 2023 15:27:14 +0100 Subject: [PATCH 57/78] feat: Add Gyro pools for sorV2 service. --- modules/pool/pool.service.ts | 8 +++-- modules/sor/sorV2/sorV2.service.ts | 54 ++++++++++++++++++++++++++++-- prisma/prisma-types.ts | 1 + 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index eb1766bd0..05308a2e8 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -444,6 +444,10 @@ export class PoolService { where: { chain: networkContext.chain, poolId: poolId }, }); + await prisma.prismaPoolGyroData.deleteMany({ + where: { chain: networkContext.chain, poolId: poolId }, + }); + await prisma.prismaPoolExpandedTokens.deleteMany({ where: { chain: networkContext.chain, poolId: poolId }, }); @@ -482,9 +486,9 @@ export class PoolService { }, }); - if(gauge && gauge.votingGauge) + if (gauge && gauge.votingGauge) await prisma.prismaVotingGauge.deleteMany({ - where: { chain: networkContext.chain, id: gauge.votingGauge.id } + where: { chain: networkContext.chain, id: gauge.votingGauge.id }, }); await prisma.prismaPoolStakingGauge.deleteMany({ diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 52ed8baaa..8e1463fb1 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -12,6 +12,9 @@ import { Swap as SwapSdk, RawPool, TokenAmount, + RawGyro2Pool, + RawGyro3Pool, + RawGyroEPool, } from '@balancer/sdk'; import cloneDeep from 'lodash/cloneDeep'; import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; @@ -340,9 +343,12 @@ export class SorV2Service implements SwapService { }, NOT: { id: { - in: [...networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, ...poolsToIgnore], + in: [ + ...networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, + ...poolsToIgnore, + ], }, - type: 'LINEAR' // Linear pools are sunset so ignore to avoid issues related to lack of support + type: 'LINEAR', // Linear pools are sunset so ignore to avoid issues related to lack of support }, }, include: prismaPoolWithDynamic.include, @@ -381,7 +387,7 @@ export class SorV2Service implements SwapService { }), isPaused: !!prismaPool.dynamicData?.isPaused, inRecoveryMode: !!prismaPool.dynamicData?.isInRecoveryMode, - name: 'n/a' + name: 'n/a', }; if (['Weighted', 'Investment', 'LiquidityBootstrapping'].includes(rawPool.poolType)) { rawPool = { @@ -406,6 +412,41 @@ export class SorV2Service implements SwapService { }), } as RawMetaStablePool; } + if (rawPool.poolType === 'Gyro2') { + rawPool = { + ...rawPool, + alpha: prismaPool.gyroData?.alpha, + beta: prismaPool.gyroData?.beta, + sqrtAlpha: prismaPool.gyroData?.sqrtAlpha, + sqrtBeta: prismaPool.gyroData?.sqrtBeta, + } as RawGyro2Pool; + } + if (rawPool.poolType === 'Gyro3') { + rawPool = { + ...rawPool, + root3Alpha: prismaPool.gyroData?.root3Alpha, + } as RawGyro3Pool; + } + if (rawPool.poolType === 'GyroE') { + rawPool = { + ...rawPool, + alpha: prismaPool.gyroData?.alpha, + beta: prismaPool.gyroData?.beta, + c: prismaPool.gyroData?.c, + s: prismaPool.gyroData?.s, + lambda: prismaPool.gyroData?.lambda, + tauAlphaX: prismaPool.gyroData?.tauAlphaX, + tauAlphaY: prismaPool.gyroData?.tauAlphaY, + tauBetaX: prismaPool.gyroData?.tauBetaX, + tauBetaY: prismaPool.gyroData?.tauBetaY, + u: prismaPool.gyroData?.u, + v: prismaPool.gyroData?.v, + w: prismaPool.gyroData?.w, + z: prismaPool.gyroData?.z, + dSq: prismaPool.gyroData?.dSq, + tokenRates: prismaPool.tokens.map((t) => t.dynamicData?.priceRate), + } as RawGyroEPool; + } return rawPool; }); } @@ -443,6 +484,13 @@ export class SorV2Service implements SwapService { case PrismaPoolType.PHANTOM_STABLE: // Composablestables are PHANTOM_STABLE in Prisma. b-sdk treats Phantoms as ComposableStable. return 'ComposableStable'; + case PrismaPoolType.GYRO: + return 'Gyro2'; + // TODO - Needs a package update, see: https://github.com/balancer/b-sdk/pull/92 + // case PrismaPoolType.GYRO3: + // return 'Gyro3'; + case PrismaPoolType.GYROE: + return 'GyroE'; default: return type; } diff --git a/prisma/prisma-types.ts b/prisma/prisma-types.ts index a48347d04..7d70788b5 100644 --- a/prisma/prisma-types.ts +++ b/prisma/prisma-types.ts @@ -288,6 +288,7 @@ export const prismaPoolWithDynamic = Prisma.validator()({ dynamicData: true, linearDynamicData: true, linearData: true, + gyroData: true, tokens: { orderBy: { index: 'asc' }, include: { From a3e4a10430f8be3425e816ebe4857ccf5b49cb45 Mon Sep 17 00:00:00 2001 From: franz Date: Fri, 15 Sep 2023 17:15:19 +0200 Subject: [PATCH 58/78] bump typescript version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 49a54f31e..5c3987ebd 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "testcontainers": "^8.0.0", "ts-jest": "^28.0.7", "ts-node": "^10.4.0", - "typescript": "^4.5.3", + "typescript": "^5.2.2", "vitest": "^0.32.4", "vitest-mock-extended": "^1.1.3" } diff --git a/yarn.lock b/yarn.lock index 709c198cb..ac90f3381 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11071,10 +11071,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.3.tgz#afaa858e68c7103317d89eb90c5d8906268d353c" - integrity sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ== +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== ua-parser-js@^0.7.30: version "0.7.31" From c7d44b731e31341c97f1db6e237f5ddf1211fe64 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 18 Sep 2023 16:35:47 +0200 Subject: [PATCH 59/78] formatting and output --- modules/sor/sor.service.ts | 54 ++++++++++++++++++++++--------- modules/sor/sorV2/beetsHelpers.ts | 5 +-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index fb5ff1ebd..d28f8309e 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -95,15 +95,22 @@ export class SorService { if (!v1.isValid || !v2.isValid) { isV1 = v1.isValid ? true : false; } else if (swapType === 'EXACT_IN') { - if (v2.outputAmount < v1.outputAmount) isV1 = true; + if (v2.outputAmount < v1.outputAmount) { + isV1 = true; + } } else { - if (v2.inputAmount > v1.inputAmount) isV1 = true; + if (v2.inputAmount > v1.inputAmount) { + isV1 = true; + } } if (isV1 === true) { this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); return v1; - } else return v2; + } else { + this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut); + } + return v2; } private logResult( @@ -115,18 +122,35 @@ export class SorService { assetOut: string, ) { // console.log() will log to cloudwatch - console.log( - 'SOR Service', - networkContext.chain, - logType, - swapType, - assetIn, - assetOut, - v1.inputAmount, - v1.outputAmount, - v2.inputAmount, - v2.outputAmount, - ); + if (swapType === 'EXACT_IN') { + console.log( + 'SOR Service', + networkContext.chain, + logType, + swapType, + assetIn, + assetOut, + v1.inputAmount, + v1.outputAmount, + v2.inputAmount, + v2.outputAmount, + v1.outputAmount - v2.outputAmount, + ); + } else { + console.log( + 'SOR Service', + networkContext.chain, + logType, + swapType, + assetIn, + assetOut, + v1.inputAmount, + v1.outputAmount, + v2.inputAmount, + v2.outputAmount, + v1.inputAmount - v2.inputAmount, + ); + } } } diff --git a/modules/sor/sorV2/beetsHelpers.ts b/modules/sor/sorV2/beetsHelpers.ts index 5dc457367..135276c23 100644 --- a/modules/sor/sorV2/beetsHelpers.ts +++ b/modules/sor/sorV2/beetsHelpers.ts @@ -74,8 +74,9 @@ export function splitPaths( kind: SwapKind, ): BatchSwapStep[][] { const swapsCopy = [...swaps]; - if (kind === SwapKind.GivenOut) swapsCopy.reverse(); - console.log(swapsCopy); + if (kind === SwapKind.GivenOut) { + swapsCopy.reverse(); + } const assetInIndex = BigInt(assets.indexOf(assetIn)); const assetOutIndex = BigInt(assets.indexOf(assetOut)); let path: BatchSwapStep[]; From ca4865d8f252fb165a92a870e84b94535b860f92 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 18 Sep 2023 17:14:50 +0200 Subject: [PATCH 60/78] adding cloudwatch metrics --- modules/metrics/sor.metric.ts | 11 +++++++++ modules/sor/sor.service.ts | 42 +++++++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 modules/metrics/sor.metric.ts diff --git a/modules/metrics/sor.metric.ts b/modules/metrics/sor.metric.ts new file mode 100644 index 000000000..a066293a4 --- /dev/null +++ b/modules/metrics/sor.metric.ts @@ -0,0 +1,11 @@ +import { CloudwatchMetricsPublisher } from './metrics.client'; + +const publishers: Record = {}; + +export function getSorMetricsPublisher(chainId: string): CloudwatchMetricsPublisher { + if (!publishers[`${chainId}`]) { + console.log(`Creating new SOR publisher for ${chainId}`); + publishers[`${chainId}`] = new CloudwatchMetricsPublisher(`Backend-${chainId}/Sor`); + } + return publishers[`${chainId}`]; +} diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index d28f8309e..b6673474b 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -5,6 +5,8 @@ import { sorV1BeetsService } from './sorV1Beets/sorV1Beets.service'; import { sorV2Service } from './sorV2/sorV2.service'; import { GetSwapsInput, SwapResult } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; +import { getSorMetricsPublisher } from '../metrics/sor.metric'; +import moment from 'moment'; export class SorService { public async getCowSwaps({ @@ -32,7 +34,7 @@ export class SorService { if (!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); - const bestSwap = this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut); + const bestSwap = await this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut); try { // Updates with latest onchain data before returning @@ -48,20 +50,33 @@ export class SorService { input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, ): Promise { console.time(`sorV1-${networkContext.chain}`); + const v1Start = moment().unix(); const swapV1 = await sorV1BeetsService.getSwapResult(input); + const v1End = moment().unix(); console.timeEnd(`sorV1-${networkContext.chain}`); + console.time(`sorV2-${networkContext.chain}`); + const v2Start = moment().unix(); const swapV2 = await sorV2Service.getSwapResult(input); + const v2End = moment().unix(); console.timeEnd(`sorV2-${networkContext.chain}`); + const sorMetricsPublisher = await getSorMetricsPublisher(networkContext.chain); + + sorMetricsPublisher.publish(`SOR_TIME_V1_${input.tokenIn}_${input.tokenOut}`, v1End - v1Start); + sorMetricsPublisher.publish(`SOR_TIME_V2_${input.tokenIn}_${input.tokenOut}`, v2End - v2Start); + + sorMetricsPublisher.publish(`SOR_VALID_V1_${input.tokenIn}_${input.tokenOut}`, swapV1.isValid ? 1 : 0); + sorMetricsPublisher.publish(`SOR_VALID_V2_${input.tokenIn}_${input.tokenOut}`, swapV2.isValid ? 1 : 0); + if (!swapV1.isValid && !swapV2.isValid) return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); - const bestSwap = this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut); + const bestSwap = await this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut); try { // Updates with latest onchain data before returning - return await bestSwap.getBeetsSwapResponse(true); + return bestSwap.getBeetsSwapResponse(true); } catch (err) { console.log(`Error Retrieving QuerySwap`); console.log(err); @@ -76,14 +91,14 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap( + private async getBestSwap( v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string, debugOut = false, - ): SwapResult { + ): Promise { // Useful for comparing if (debugOut) { console.log(`------ DEBUG`); @@ -105,15 +120,15 @@ export class SorService { } if (isV1 === true) { - this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); + await this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); return v1; } else { - this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut); + await this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut); } return v2; } - private logResult( + private async logResult( logType: string, v1: SwapResult, v2: SwapResult, @@ -122,8 +137,13 @@ export class SorService { assetOut: string, ) { // console.log() will log to cloudwatch + const sorMetricsPublisher = await getSorMetricsPublisher(networkContext.chain); if (swapType === 'EXACT_IN') { - console.log( + sorMetricsPublisher.publish( + `SOR_EXACT_IN_${assetIn}_${assetOut}`, + Number(v1.outputAmount - v2.outputAmount), + ); + await console.log( 'SOR Service', networkContext.chain, logType, @@ -137,6 +157,10 @@ export class SorService { v1.outputAmount - v2.outputAmount, ); } else { + sorMetricsPublisher.publish( + `SOR_EXACT_OUT_${assetIn}_${assetOut}`, + Number(v1.inputAmount - v2.inputAmount), + ); console.log( 'SOR Service', networkContext.chain, From 0aca52083725557f9b3a2b446c37b54324509fd3 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 18 Sep 2023 20:05:26 +0200 Subject: [PATCH 61/78] logging --- modules/sor/sor.service.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index b6673474b..69577f718 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -63,11 +63,22 @@ export class SorService { const sorMetricsPublisher = await getSorMetricsPublisher(networkContext.chain); - sorMetricsPublisher.publish(`SOR_TIME_V1_${input.tokenIn}_${input.tokenOut}`, v1End - v1Start); - sorMetricsPublisher.publish(`SOR_TIME_V2_${input.tokenIn}_${input.tokenOut}`, v2End - v2Start); + sorMetricsPublisher.publish(`SOR_TIME_V1`, v1End - v1Start); + sorMetricsPublisher.publish(`SOR_TIME_V2`, v2End - v2Start); - sorMetricsPublisher.publish(`SOR_VALID_V1_${input.tokenIn}_${input.tokenOut}`, swapV1.isValid ? 1 : 0); - sorMetricsPublisher.publish(`SOR_VALID_V2_${input.tokenIn}_${input.tokenOut}`, swapV2.isValid ? 1 : 0); + sorMetricsPublisher.publish(`SOR_VALID_V1`, swapV1.isValid ? 10 : 1); + sorMetricsPublisher.publish(`SOR_VALID_V2`, swapV2.isValid ? 10 : 1); + + const swapDiff = + input.swapType === 'EXACT_IN' + ? Number(swapV1.outputAmount - swapV2.outputAmount) + : Number(swapV1.inputAmount - swapV2.inputAmount); + + console.log( + `${input.tokenIn},${input.tokenOut},${input.swapType},${input.swapAmount},${swapV1.isValid},${ + swapV2.isValid + },${v1End - v1Start},${v2End - v2Start},${swapDiff},${swapDiff > 0}`, + ); if (!swapV1.isValid && !swapV2.isValid) return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); @@ -137,12 +148,7 @@ export class SorService { assetOut: string, ) { // console.log() will log to cloudwatch - const sorMetricsPublisher = await getSorMetricsPublisher(networkContext.chain); if (swapType === 'EXACT_IN') { - sorMetricsPublisher.publish( - `SOR_EXACT_IN_${assetIn}_${assetOut}`, - Number(v1.outputAmount - v2.outputAmount), - ); await console.log( 'SOR Service', networkContext.chain, @@ -157,10 +163,6 @@ export class SorService { v1.outputAmount - v2.outputAmount, ); } else { - sorMetricsPublisher.publish( - `SOR_EXACT_OUT_${assetIn}_${assetOut}`, - Number(v1.inputAmount - v2.inputAmount), - ); console.log( 'SOR Service', networkContext.chain, From caca5e39696ed65a59f6421c72fd17847f4404f7 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 13:45:53 +0100 Subject: [PATCH 62/78] chore: Bump b-sdk. --- package.json | 2 +- yarn.lock | 70 +++++++++++++++++++++++----------------------------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 5c3987ebd..a90815f77 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", - "@balancer/sdk": "^0.1.1", + "@balancer/sdk": "^0.2.0", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", diff --git a/yarn.lock b/yarn.lock index ac90f3381..547110cdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2456,10 +2456,10 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" -"@balancer/sdk@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.1.1.tgz#5449f76757812f11ffee06b2d3f6019c791f0c45" - integrity sha512-qCibAQJ3hiZcQYL3Y/qhedn8Ro1BszIDL0i/SroALLN9RlhtWni2lNHu9vCNSgneJbvlFfLxQ+NNJ6dyOv67HQ== +"@balancer/sdk@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.2.0.tgz#bda93d18324d2a75f3a474ef546b281e056b4b2e" + integrity sha512-97VF4G15VdonKySOgVWOHcsuizqfOrhs0b8i5fbB7AmJSlaLNuW+Q06crBUqexkEFEw+/h2LnwqJeqRKIbsYbQ== dependencies: async-retry "^1.3.3" decimal.js-light "^2.5.1" @@ -4812,13 +4812,6 @@ dependencies: "@types/node" "*" -"@types/ws@^8.5.5": - version "8.5.5" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" - integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -8083,10 +8076,10 @@ isomorphic-ws@4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isomorphic-ws@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" - integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isows@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" + integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" @@ -9418,9 +9411,9 @@ obliterator@^2.0.2: integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== on-exit-leak-free@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" - integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== on-finished@~2.3.0: version "2.3.0" @@ -9683,10 +9676,10 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pino-abstract-transport@v1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" - integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== +pino-abstract-transport@v1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" + integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== dependencies: readable-stream "^4.0.0" split2 "^4.0.0" @@ -9697,20 +9690,20 @@ pino-std-serializers@^6.0.0: integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== pino@^8.11.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.15.0.tgz#67c61d5e397bf297e5a0433976a7f7b8aa6f876b" - integrity sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ== + version "8.16.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.16.0.tgz#2465012a1d11fa2e7a0545032f636e203990ae26" + integrity sha512-UUmvQ/7KTZt/vHjhRrnyS7h+J7qPBQnpG80V56xmIC+o9IqYmQOw/UIny9S9zYDfRBR0ClouCr464EkBMIT7Fw== dependencies: atomic-sleep "^1.0.0" fast-redact "^3.1.1" on-exit-leak-free "^2.1.0" - pino-abstract-transport v1.0.0 + pino-abstract-transport v1.1.0 pino-std-serializers "^6.0.0" process-warning "^2.0.0" quick-format-unescaped "^4.0.3" real-require "^0.2.0" safe-stable-stringify "^2.3.1" - sonic-boom "^3.1.0" + sonic-boom "^3.7.0" thread-stream "^2.0.0" pirates@^4.0.4: @@ -10447,10 +10440,10 @@ sonic-boom@^2.1.0: dependencies: atomic-sleep "^1.0.0" -sonic-boom@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c" - integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g== +sonic-boom@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.7.0.tgz#b4b7b8049a912986f4a92c51d4660b721b11f2f2" + integrity sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg== dependencies: atomic-sleep "^1.0.0" @@ -10858,9 +10851,9 @@ testcontainers@^8.0.0: tar-fs "^2.1.1" thread-stream@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.0.tgz#5def29598d1d4171ba3bace7e023a71d87d99c07" - integrity sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw== + version "2.4.1" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.1.tgz#6d588b14f0546e59d3f306614f044bc01ce43351" + integrity sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg== dependencies: real-require "^0.2.0" @@ -11249,18 +11242,17 @@ vary@^1, vary@~1.1.2: integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= viem@^1.4.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.10.1.tgz#b91a2e5e583e94d61c210391322de01692f19a07" - integrity sha512-rN2GhbEElz47z0aOXly4A+XLGNNQJkCUR03pL9EoRVbMpAPf7mK3Pk0m7crRvBesb0xiS8rgIg0Ip50fq9vWIw== + version "1.16.4" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.4.tgz#4cb90dd27d13356ea00d21f6fd363bdfe0628c4e" + integrity sha512-T9ziN3EERXz0BtQSS2VJM+P1EJ2W7K7PviobFrmvWCEYmNQ/vJDhfFqGjvq0ZL9LVz9HvevCbenEy8oIdMEZ+w== dependencies: "@adraffy/ens-normalize" "1.9.4" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@scure/bip32" "1.3.2" "@scure/bip39" "1.2.1" - "@types/ws" "^8.5.5" abitype "0.9.8" - isomorphic-ws "5.0.0" + isows "1.0.3" ws "8.13.0" vite-node@0.32.4: From 026bf8564c2efbc8f4bd7b67adee8589a97e13ca Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 13 Oct 2023 09:48:45 +0100 Subject: [PATCH 63/78] chore: Add Gyro3 to mapRawPoolType. --- modules/sor/sorV2/sorV2.service.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 8e1463fb1..64bb7ac15 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -486,9 +486,8 @@ export class SorV2Service implements SwapService { return 'ComposableStable'; case PrismaPoolType.GYRO: return 'Gyro2'; - // TODO - Needs a package update, see: https://github.com/balancer/b-sdk/pull/92 - // case PrismaPoolType.GYRO3: - // return 'Gyro3'; + case PrismaPoolType.GYRO3: + return 'Gyro3'; case PrismaPoolType.GYROE: return 'GyroE'; default: From 33e39d5d3d26ba8435ba1e8fac65ff137661b23b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 13 Oct 2023 14:59:32 +0100 Subject: [PATCH 64/78] fix: Handle amount scaling correctly across various services by using TokenAmounts. --- modules/balancer/balancer.resolvers.ts | 7 ++++++- modules/beethoven/balancer-sdk.resolvers.ts | 7 ++++++- modules/sor/constants.ts | 5 +++-- modules/sor/sor.service.ts | 3 +++ modules/sor/sorV1Balancer/sorV1Balancer.service.ts | 5 +++-- modules/sor/sorV1Beets/sorV1Beets.service.ts | 11 +++++++---- modules/sor/sorV2/sorV2.service.ts | 4 ++++ modules/sor/types.ts | 3 ++- modules/sor/utils.ts | 12 ++++++++++++ 9 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 modules/sor/utils.ts diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 13d38e39a..6d21e028b 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,10 +1,15 @@ import { Resolvers } from '../../schema'; import { sorService } from '../sor/sor.service'; +import { getTokenAmount } from '../sor/utils'; const balancerResolvers: Resolvers = { Query: { sorGetCowSwaps: async (parent, args, context) => { - const swaps = await sorService.getCowSwaps({ ...args }); + const amountToken = args.swapType === "EXACT_IN" ? args.tokenIn : args.tokenOut; + // Use TokenAmount to help follow scaling requirements in later logic + // args.swapAmount is HumanScale + const amount = await getTokenAmount(amountToken, args.swapAmount); + const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, }, diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index e2fa4e974..4cfd99023 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -2,11 +2,16 @@ import { Resolvers } from '../../schema'; import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; import { sorService } from '../sor/sor.service'; +import { getTokenAmount } from '../sor/utils'; const balancerSdkResolvers: Resolvers = { Query: { sorGetSwaps: async (parent, args, context) => { - const swaps = await sorService.getBeetsSwaps({ ...args }); + const amountToken = args.swapType === 'EXACT_IN' ? args.tokenIn : args.tokenOut; + // Use TokenAmount to help follow scaling requirements in later logic + // args.swapAmount is HumanScale + const amount = await getTokenAmount(amountToken, args.swapAmount); + const swaps = await sorService.getBeetsSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts index ed1f5b2f2..0fa69e5be 100644 --- a/modules/sor/constants.ts +++ b/modules/sor/constants.ts @@ -1,12 +1,13 @@ +import { TokenAmount } from '@balancer/sdk'; import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; -export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: string): GqlCowSwapApiResponse => { +export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: TokenAmount): GqlCowSwapApiResponse => { return { marketSp: '0', returnAmount: '0', returnAmountConsideringFees: '0', returnAmountFromSwaps: '0', - swapAmount: amount, + swapAmount: amount.amount.toString(), swapAmountForSwaps: '0', swaps: [], tokenAddresses: [], diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 69577f718..5ed45fce7 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -80,6 +80,9 @@ export class SorService { },${v1End - v1Start},${v2End - v2Start},${swapDiff},${swapDiff > 0}`, ); + console.log(`!!!!!!!!! input`, swapV1.inputAmount.toString(), swapV2.inputAmount.toString()); + console.log(`!!!!!!!!! output`, swapV1.outputAmount.toString(), swapV2.outputAmount.toString()); + if (!swapV1.isValid && !swapV2.isValid) return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts index 2c0d2ee8c..49e9d510a 100644 --- a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -10,6 +10,7 @@ import { DeploymentEnv } from '../../network/network-config-types'; import VaultAbi from '../../pool/abi/Vault.json'; import { BigNumber } from 'ethers'; +import { TokenAmount } from '@balancer/sdk'; type CowSwapSwapType = 'buy' | 'sell'; @@ -91,7 +92,7 @@ export class SorV1BalancerService implements SwapService { swapType: GqlSorSwapType, tokenIn: string, tokenOut: string, - swapAmountScaled: string, + swapAmount: TokenAmount, ): Promise { const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); @@ -99,7 +100,7 @@ export class SorV1BalancerService implements SwapService { orderKind: this.mapSwapType(swapType), sellToken: tokenIn, buyToken: tokenOut, - amount: swapAmountScaled, + amount: swapAmount.amount.toString(), gasPrice, }; diff --git a/modules/sor/sorV1Beets/sorV1Beets.service.ts b/modules/sor/sorV1Beets/sorV1Beets.service.ts index b54914ae5..163cdf42a 100644 --- a/modules/sor/sorV1Beets/sorV1Beets.service.ts +++ b/modules/sor/sorV1Beets/sorV1Beets.service.ts @@ -1,7 +1,9 @@ +import { formatEther } from 'viem'; import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../../schema'; import { GetSwapsInput, SwapService, SwapResult } from '../types'; import { BalancerSorService } from '../../beethoven/balancer-sor.service'; import { tokenService } from '../../token/token.service'; +import { TokenAmount } from '@balancer/sdk'; class SwapResultV1 implements SwapResult { public inputAmount: bigint = BigInt(0); @@ -13,7 +15,8 @@ class SwapResultV1 implements SwapResult { this.isValid = false; this.swap = null; } else { - this.inputAmount = swapType === 'EXACT_IN' ? BigInt(swap.swapAmountScaled) : BigInt(swap.returnAmountScaled); + this.inputAmount = + swapType === 'EXACT_IN' ? BigInt(swap.swapAmountScaled) : BigInt(swap.returnAmountScaled); this.outputAmount = swapType === 'EXACT_IN' ? BigInt(swap.returnAmountScaled) : BigInt(swap.swapAmountScaled); this.isValid = swap.swaps.length === 0 ? false : true; @@ -51,16 +54,16 @@ export class SorV1BeetsService implements SwapService { swapType: GqlSorSwapType, tokenIn: string, tokenOut: string, - swapAmount: string, + swapAmount: TokenAmount, ): GqlSorGetSwapsResponse { - return this.sorService.zeroResponse(swapType, tokenIn, tokenOut, swapAmount); + return this.sorService.zeroResponse(swapType, tokenIn, tokenOut, formatEther(swapAmount.scale18)); } private async querySorBeets( input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, ): Promise { const tokens = await tokenService.getTokens(); - return await this.sorService.getSwaps({ ...input, tokens }); + return await this.sorService.getSwaps({ ...input, tokens, swapAmount: formatEther(input.swapAmount.scale18) }); } } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 64bb7ac15..f71c112a5 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -276,6 +276,10 @@ export class SorV2Service implements SwapService { const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); const swapKind = this.mapSwapType(swapType); + console.log('!!!!mmmmhhh'); + console.log(tIn.chainId); + console.log(tOut.chainId); + console.log(swapAmount.token.chainId); // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, diff --git a/modules/sor/types.ts b/modules/sor/types.ts index 56c034410..f4410cffd 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -1,9 +1,10 @@ import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse } from '../../schema'; +import { TokenAmount } from '@balancer/sdk'; export interface GetSwapsInput { tokenIn: string; tokenOut: string; swapType: GqlSorSwapType; - swapAmount: string; + swapAmount: TokenAmount; } export interface SwapResult { diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts new file mode 100644 index 000000000..c3c9b87c4 --- /dev/null +++ b/modules/sor/utils.ts @@ -0,0 +1,12 @@ +import { TokenAmount, Token, Address, ChainId } from '@balancer/sdk'; +import { tokenService } from '../token/token.service'; +import { networkContext } from '../network/network-context.service'; + + +export async function getTokenAmount(tokenAddr: string, humanAmount: string): Promise { + const prismaToken = await tokenService.getToken(tokenAddr); + if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); + const chainId = networkContext.chainId as unknown as ChainId; + const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); + return TokenAmount.fromHumanAmount(token, humanAmount as `${number}`); +} From d5f7af5d7b20689db69051911bcc9948855c6811 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 13 Oct 2023 15:02:54 +0100 Subject: [PATCH 65/78] chore: Remove debug statements. --- modules/sor/sor.service.ts | 3 --- modules/sor/sorV2/sorV2.service.ts | 4 ---- 2 files changed, 7 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 5ed45fce7..69577f718 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -80,9 +80,6 @@ export class SorService { },${v1End - v1Start},${v2End - v2Start},${swapDiff},${swapDiff > 0}`, ); - console.log(`!!!!!!!!! input`, swapV1.inputAmount.toString(), swapV2.inputAmount.toString()); - console.log(`!!!!!!!!! output`, swapV1.outputAmount.toString(), swapV2.outputAmount.toString()); - if (!swapV1.isValid && !swapV2.isValid) return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index f71c112a5..64bb7ac15 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -276,10 +276,6 @@ export class SorV2Service implements SwapService { const tIn = await this.getToken(tokenIn as Address, chainId); const tOut = await this.getToken(tokenOut as Address, chainId); const swapKind = this.mapSwapType(swapType); - console.log('!!!!mmmmhhh'); - console.log(tIn.chainId); - console.log(tOut.chainId); - console.log(swapAmount.token.chainId); // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, From 54d90d2e1abc4a074a657d58c779ca91424a742d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 17 Oct 2023 10:05:32 +0100 Subject: [PATCH 66/78] fix: For Balancer resolver use raw scale for swapAmount. --- modules/balancer/balancer.resolvers.ts | 6 +++--- modules/beethoven/balancer-sdk.resolvers.ts | 4 ++-- modules/sor/utils.ts | 10 +++++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 6d21e028b..6ff9ca1cc 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,14 +1,14 @@ import { Resolvers } from '../../schema'; import { sorService } from '../sor/sor.service'; -import { getTokenAmount } from '../sor/utils'; +import { getTokenAmountRaw } from '../sor/utils'; const balancerResolvers: Resolvers = { Query: { sorGetCowSwaps: async (parent, args, context) => { const amountToken = args.swapType === "EXACT_IN" ? args.tokenIn : args.tokenOut; // Use TokenAmount to help follow scaling requirements in later logic - // args.swapAmount is HumanScale - const amount = await getTokenAmount(amountToken, args.swapAmount); + // args.swapAmount is RawScale, e.g. 1USDC should be passed as 1000000 + const amount = await getTokenAmountRaw(amountToken, args.swapAmount); const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index 4cfd99023..eaea53466 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -2,7 +2,7 @@ import { Resolvers } from '../../schema'; import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; import { sorService } from '../sor/sor.service'; -import { getTokenAmount } from '../sor/utils'; +import { getTokenAmountHuman } from '../sor/utils'; const balancerSdkResolvers: Resolvers = { Query: { @@ -10,7 +10,7 @@ const balancerSdkResolvers: Resolvers = { const amountToken = args.swapType === 'EXACT_IN' ? args.tokenIn : args.tokenOut; // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is HumanScale - const amount = await getTokenAmount(amountToken, args.swapAmount); + const amount = await getTokenAmountHuman(amountToken, args.swapAmount); const swaps = await sorService.getBeetsSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts index c3c9b87c4..fbb483a5f 100644 --- a/modules/sor/utils.ts +++ b/modules/sor/utils.ts @@ -3,10 +3,18 @@ import { tokenService } from '../token/token.service'; import { networkContext } from '../network/network-context.service'; -export async function getTokenAmount(tokenAddr: string, humanAmount: string): Promise { +export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string): Promise { const prismaToken = await tokenService.getToken(tokenAddr); if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); const chainId = networkContext.chainId as unknown as ChainId; const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); return TokenAmount.fromHumanAmount(token, humanAmount as `${number}`); } + +export async function getTokenAmountRaw(tokenAddr: string, rawAmount: string): Promise { + const prismaToken = await tokenService.getToken(tokenAddr); + if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); + const chainId = networkContext.chainId as unknown as ChainId; + const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); + return TokenAmount.fromRawAmount(token, rawAmount); +} From d99f22f4dcb1d9b887c581d83785dfeaaa6f0208 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:25:35 +0100 Subject: [PATCH 67/78] chain aware SOR queries --- modules/balancer/balancer.gql | 1 + modules/balancer/balancer.resolvers.ts | 3 +- modules/beethoven/balancer-sdk.gql | 1 + modules/beethoven/balancer-sdk.resolvers.ts | 1 + modules/network/network-config.ts | 6 +- modules/pool/pool.service.ts | 4 -- modules/sor/sor.service.ts | 40 ++++++----- .../sorV1Balancer/sorV1Balancer.service.ts | 23 ++++--- modules/sor/sorV1Beets/sorV1Beets.service.ts | 2 +- modules/sor/sorV2/sorV2.service.ts | 69 +++++++++---------- modules/sor/types.ts | 4 +- modules/sor/utils.ts | 8 ++- modules/token/token.service.ts | 12 ++-- 13 files changed, 94 insertions(+), 80 deletions(-) diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index fa952c7a7..228429a2f 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -4,6 +4,7 @@ extend type Mutation { extend type Query { sorGetCowSwaps( + chain: GqlChain! tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 897e707ff..a21e0b548 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,4 +1,3 @@ -import moment from 'moment'; import { Resolvers } from '../../schema'; import { sorService } from '../sor/sor.service'; import { getTokenAmountRaw } from '../sor/utils'; @@ -9,7 +8,7 @@ const balancerResolvers: Resolvers = { const amountToken = args.swapType === "EXACT_IN" ? args.tokenIn : args.tokenOut; // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is RawScale, e.g. 1USDC should be passed as 1000000 - const amount = await getTokenAmountRaw(amountToken, args.swapAmount); + const amount = await getTokenAmountRaw(amountToken, args.swapAmount, args.chain); const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index f099bccf2..d26122746 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -1,5 +1,6 @@ extend type Query { sorGetSwaps( + chain: GqlChain! tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index eaea53466..e764fb411 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -3,6 +3,7 @@ import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; import { sorService } from '../sor/sor.service'; import { getTokenAmountHuman } from '../sor/utils'; +import { chainToIdMap } from '../network/network-config'; const balancerSdkResolvers: Resolvers = { Query: { diff --git a/modules/network/network-config.ts b/modules/network/network-config.ts index 7cc0786da..74ec636e3 100644 --- a/modules/network/network-config.ts +++ b/modules/network/network-config.ts @@ -9,7 +9,6 @@ import { zkevmNetworkConfig } from './zkevm'; import { avalancheNetworkConfig } from './avalanche'; import { baseNetworkConfig } from './base'; import { Chain } from '@prisma/client'; -import { keyBy, pickBy } from 'lodash'; export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = { '250': fantomNetworkConfig, @@ -37,3 +36,8 @@ export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig } export const BalancerChainIds = ['1', '137', '42161', '100', '1101', '43114', '8453']; export const BeethovenChainIds = ['250', '10']; + +export const chainToIdMap = Object.values(AllNetworkConfigs).reduce((acc, config) => { + acc[config.data.chain.gqlId] = String(config.data.chain.id); + return acc; +}, {} as { [chain in Chain]: string }); diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index 927d3d8eb..77e9a1d28 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -455,10 +455,6 @@ export class PoolService { where: { chain: this.chain, poolId: poolId }, }); - await prisma.prismaPoolGyroData.deleteMany({ - where: { chain: networkContext.chain, poolId: poolId }, - }); - await prisma.prismaPoolExpandedTokens.deleteMany({ where: { chain: this.chain, poolId: poolId }, }); diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 69577f718..f52e5797d 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -7,38 +7,42 @@ import { GetSwapsInput, SwapResult } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; import { getSorMetricsPublisher } from '../metrics/sor.metric'; import moment from 'moment'; +import { Chain } from '@prisma/client'; export class SorService { public async getCowSwaps({ + chain, tokenIn, tokenOut, swapType, swapAmount, }: GetSwapsInput): Promise { - console.time(`sorV1-${networkContext.chain}`); + console.time(`sorV1-${chain}`); const swapV1 = await sorV1BalancerService.getSwapResult({ + chain, tokenIn, tokenOut, swapType, swapAmount, }); - console.timeEnd(`sorV1-${networkContext.chain}`); - console.time(`sorV2-${networkContext.chain}`); + console.timeEnd(`sorV1-${chain}`); + console.time(`sorV2-${chain}`); const swapV2 = await sorV2Service.getSwapResult({ + chain, tokenIn, tokenOut, swapType, swapAmount, }); - console.timeEnd(`sorV2-${networkContext.chain}`); + console.timeEnd(`sorV2-${chain}`); if (!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); - const bestSwap = await this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut); + const bestSwap = await this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut, chain); try { // Updates with latest onchain data before returning - return await bestSwap.getCowSwapResponse(true); + return await bestSwap.getCowSwapResponse(chain, true); } catch (err) { console.log(`Error Retrieving QuerySwap`); console.log(err); @@ -49,19 +53,19 @@ export class SorService { public async getBeetsSwaps( input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, ): Promise { - console.time(`sorV1-${networkContext.chain}`); + console.time(`sorV1-${input.chain}`); const v1Start = moment().unix(); const swapV1 = await sorV1BeetsService.getSwapResult(input); const v1End = moment().unix(); - console.timeEnd(`sorV1-${networkContext.chain}`); + console.timeEnd(`sorV1-${input.chain}`); - console.time(`sorV2-${networkContext.chain}`); + console.time(`sorV2-${input.chain}`); const v2Start = moment().unix(); const swapV2 = await sorV2Service.getSwapResult(input); const v2End = moment().unix(); - console.timeEnd(`sorV2-${networkContext.chain}`); + console.timeEnd(`sorV2-${input.chain}`); - const sorMetricsPublisher = await getSorMetricsPublisher(networkContext.chain); + const sorMetricsPublisher = getSorMetricsPublisher(input.chain); sorMetricsPublisher.publish(`SOR_TIME_V1`, v1End - v1Start); sorMetricsPublisher.publish(`SOR_TIME_V2`, v2End - v2Start); @@ -83,7 +87,7 @@ export class SorService { if (!swapV1.isValid && !swapV2.isValid) return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); - const bestSwap = await this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut); + const bestSwap = await this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut, input.chain); try { // Updates with latest onchain data before returning @@ -108,6 +112,7 @@ export class SorService { swapType: GqlSorSwapType, assetIn: string, assetOut: string, + chain: Chain, debugOut = false, ): Promise { // Useful for comparing @@ -131,10 +136,10 @@ export class SorService { } if (isV1 === true) { - await this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut); + await this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut, chain); return v1; } else { - await this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut); + await this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut, chain); } return v2; } @@ -146,12 +151,13 @@ export class SorService { swapType: GqlSorSwapType, assetIn: string, assetOut: string, + chain: Chain ) { // console.log() will log to cloudwatch if (swapType === 'EXACT_IN') { - await console.log( + console.log( 'SOR Service', - networkContext.chain, + chain, logType, swapType, assetIn, @@ -165,7 +171,7 @@ export class SorService { } else { console.log( 'SOR Service', - networkContext.chain, + chain, logType, swapType, assetIn, diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts index 49e9d510a..e73fecae3 100644 --- a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -6,11 +6,13 @@ import { GetSwapsInput, SwapService, SwapResult } from '../types'; import { FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; import { env } from '../../../app/env'; import { networkContext } from '../../network/network-context.service'; +import { AllNetworkConfigs, AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; import { DeploymentEnv } from '../../network/network-config-types'; import VaultAbi from '../../pool/abi/Vault.json'; import { BigNumber } from 'ethers'; import { TokenAmount } from '@balancer/sdk'; +import { Chain } from '@prisma/client'; type CowSwapSwapType = 'buy' | 'sell'; @@ -30,12 +32,12 @@ class SwapResultV1 implements SwapResult { } } - async getCowSwapResponse(queryFirst = false): Promise { + async getCowSwapResponse(chain = networkContext.chain, queryFirst = false): Promise { if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); if (queryFirst) { const swapType = this.mapSwapType(this.swapType); - const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses); + const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses, chain); const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenIn)].toString(); const tokenOutAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenOut)].abs().toString(); // console.log(`UPDATE:`, this.inputAmount, this.outputAmount, tokenInAmount, tokenOutAmount, deltas.toString()); @@ -52,8 +54,11 @@ class SwapResultV1 implements SwapResult { throw new Error('Use Beets service.'); } - private queryBatchSwap(swapType: SwapTypes, swaps: SwapV2[], assets: string[]): Promise { - const vaultContract = new Contract(networkContext.data.balancer.vault, VaultAbi, networkContext.provider); + private queryBatchSwap(swapType: SwapTypes, swaps: SwapV2[], assets: string[], chain: Chain): Promise { + const vault = AllNetworkConfigsKeyedOnChain[chain].data.balancer.vault; + const provider = AllNetworkConfigsKeyedOnChain[chain].provider; + + const vaultContract = new Contract(vault, VaultAbi, provider); const funds: FundManagement = { sender: AddressZero, recipient: AddressZero, @@ -69,9 +74,9 @@ class SwapResultV1 implements SwapResult { } } export class SorV1BalancerService implements SwapService { - public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + public async getSwapResult({ chain, tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { try { - const swap = await this.querySorBalancer(swapType, tokenIn, tokenOut, swapAmount); + const swap = await this.querySorBalancer(chain, swapType, tokenIn, tokenOut, swapAmount); return new SwapResultV1(swap, swapType); } catch (err) { console.log(`sorV1 Service Error`, err); @@ -89,13 +94,15 @@ export class SorV1BalancerService implements SwapService { * @returns */ private async querySorBalancer( + chain: Chain, swapType: GqlSorSwapType, tokenIn: string, tokenOut: string, swapAmount: TokenAmount, ): Promise { - const endPoint = `https://api.balancer.fi/sor/${networkContext.chainId}`; - const gasPrice = networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); + const chainId = chainToIdMap[chain]; + const endPoint = `https://api.balancer.fi/sor/${chainId}`; + const gasPrice = AllNetworkConfigs[chainId].data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); const swapData = { orderKind: this.mapSwapType(swapType), sellToken: tokenIn, diff --git a/modules/sor/sorV1Beets/sorV1Beets.service.ts b/modules/sor/sorV1Beets/sorV1Beets.service.ts index 163cdf42a..067e5f87c 100644 --- a/modules/sor/sorV1Beets/sorV1Beets.service.ts +++ b/modules/sor/sorV1Beets/sorV1Beets.service.ts @@ -23,7 +23,7 @@ class SwapResultV1 implements SwapResult { } } - async getCowSwapResponse(queryFirst = false): Promise { + async getCowSwapResponse(chain = 'MAINNET', queryFirst = false): Promise { throw new Error('Use Balancer Service'); } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 64bb7ac15..95d20f4a3 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -1,6 +1,5 @@ import { BasePool, - ChainId, sorGetSwapsWithPools, Token, Address, @@ -16,9 +15,8 @@ import { RawGyro3Pool, RawGyroEPool, } from '@balancer/sdk'; -import cloneDeep from 'lodash/cloneDeep'; import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; -import { PrismaPoolType, PrismaToken } from '@prisma/client'; +import { Chain, PrismaPoolType } from '@prisma/client'; import { GetSwapsInput, SwapResult, SwapService } from '../types'; import { tokenService } from '../../token/token.service'; import { networkContext } from '../../network/network-context.service'; @@ -38,6 +36,7 @@ import { BigNumber } from 'ethers'; import { oldBnumScale } from '../../big-number/old-big-number'; import { mapRoutes } from './beetsHelpers'; import { poolsToIgnore } from '../constants'; +import { AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -59,13 +58,14 @@ class SwapResultV2 implements SwapResult { } } - async getCowSwapResponse(queryFirst = false): Promise { + async getCowSwapResponse(chain = networkContext.chain, queryFirst = false): Promise { if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); if (!queryFirst) return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); else { + const rpcUrl = AllNetworkConfigsKeyedOnChain[chain].data.rpcUrl; // Needs node >= 18 (https://github.com/wagmi-dev/viem/discussions/147) - const updatedResult = await this.swap.query(networkContext.data.rpcUrl); + const updatedResult = await this.swap.query(rpcUrl); // console.log(`UPDATE:`, this.swap.quote.amount.toString(), updatedResult.amount.toString()); const ip = this.swap.swapKind === SwapKind.GivenIn ? this.swap.inputAmount : updatedResult; @@ -269,20 +269,19 @@ export class SorV2Service implements SwapService { this.cache = new Cache(); } - public async getSwapResult({ tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + public async getSwapResult({ chain, tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { try { - const poolsFromDb = await this.getBasePools(); - const chainId = networkContext.chainId as unknown as ChainId; - const tIn = await this.getToken(tokenIn as Address, chainId); - const tOut = await this.getToken(tokenOut as Address, chainId); + const poolsFromDb = await this.getBasePools(chain); + console.log(`Got ${poolsFromDb.length} pools from DB`); + const tIn = await this.getToken(tokenIn as Address, chain); + const tOut = await this.getToken(tokenOut as Address, chain); const swapKind = this.mapSwapType(swapType); - // Constructing a Swap mutates the pools so I used cloneDeep const swap = await sorGetSwapsWithPools( tIn, tOut, swapKind, swapAmount, - cloneDeep(poolsFromDb), + poolsFromDb, // swapOptions, // I don't think we need specific swapOptions for this? ); return new SwapResultV2(swap); @@ -294,35 +293,28 @@ export class SorV2Service implements SwapService { /** * Gets a b-sdk Token based off tokenAddr. - * @param tokenAddr - * @param chainId + * @param address + * @param chain * @returns */ - private async getToken(tokenAddr: Address, chainId: ChainId): Promise { - const tokens = await tokenService.getTokens(); - const prismaToken = this.getPrismaToken(tokenAddr, tokens); - return new Token(chainId, tokenAddr, prismaToken.decimals, prismaToken.symbol); - } - - private getPrismaToken(tokenAddress: string, tokens: PrismaToken[]): PrismaToken { - tokenAddress = tokenAddress.toLowerCase(); - const match = tokens.find((token) => token.address === tokenAddress); - - if (!match) { - throw new Error('Unknown token: ' + tokenAddress); + private async getToken(address: Address, chain: Chain): Promise { + const token = await tokenService.getToken(address, chain); + if (!token) { + throw new Error('Unknown token: ' + address); } - return match; + const chainId = Number(chainToIdMap[chain]); + return new Token(chainId, address, token.decimals, token.symbol); } private mapSwapType(swapType: GqlSorSwapType): SwapKind { return swapType === 'EXACT_IN' ? SwapKind.GivenIn : SwapKind.GivenOut; } - private async getBasePools(): Promise { - let basePools: BasePool[] | null = this.cache.get(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`); + private async getBasePools(chain: Chain): Promise { + let basePools: BasePool[] | null = this.cache.get(`${ALL_BASEPOOLS_CACHE_KEY}:${chain}`); if (!basePools) { - basePools = await this.getBasePoolsFromDb(); - this.cache.put(`${ALL_BASEPOOLS_CACHE_KEY}:${networkContext.chainId}`, basePools, 5 * 60 * 1000); + basePools = await this.getBasePoolsFromDb(chain); + this.cache.put(`${ALL_BASEPOOLS_CACHE_KEY}:${chain}`, basePools, 5 * 60 * 1000); } return basePools; } @@ -331,10 +323,12 @@ export class SorV2Service implements SwapService { * Fetch pools from Prisma and map to b-sdk BasePool. * @returns */ - private async getBasePoolsFromDb(): Promise { + private async getBasePoolsFromDb(chain: Chain): Promise { + console.log(`Fetching pools from DB`, chain) + const { poolIdsToExclude } = AllNetworkConfigsKeyedOnChain[chain].data.sor[env.DEPLOYMENT_ENV as DeploymentEnv]; const pools = await prisma.prismaPool.findMany({ where: { - chain: networkContext.chain, + chain, dynamicData: { totalSharesNum: { gt: 0.000000000001, @@ -344,7 +338,7 @@ export class SorV2Service implements SwapService { NOT: { id: { in: [ - ...networkContext.data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].poolIdsToExclude, + ...poolIdsToExclude, ...poolsToIgnore, ], }, @@ -353,8 +347,9 @@ export class SorV2Service implements SwapService { }, include: prismaPoolWithDynamic.include, }); + console.log(`Got ${pools.length} pools from DB`) const rawPools = this.mapToRawPools(pools); - return this.mapToBasePools(rawPools); + return this.mapToBasePools(rawPools, chain); } /** @@ -456,8 +451,8 @@ export class SorV2Service implements SwapService { * @param pools * @returns */ - private mapToBasePools(pools: RawPool[]): BasePool[] { - const chainId = networkContext.chainId as unknown as ChainId; + private mapToBasePools(pools: RawPool[], chain: Chain): BasePool[] { + const chainId = Number(chainToIdMap[chain]); return sorParseRawPools(chainId, pools); } diff --git a/modules/sor/types.ts b/modules/sor/types.ts index f4410cffd..a5ae51370 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -1,6 +1,8 @@ +import { Chain } from '@prisma/client'; import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse } from '../../schema'; import { TokenAmount } from '@balancer/sdk'; export interface GetSwapsInput { + chain: Chain; tokenIn: string; tokenOut: string; swapType: GqlSorSwapType; @@ -8,7 +10,7 @@ export interface GetSwapsInput { } export interface SwapResult { - getCowSwapResponse(queryFirst: boolean): Promise; + getCowSwapResponse(chain: Chain, queryFirst: boolean): Promise; getBeetsSwapResponse(queryFirst: boolean): Promise; isValid: boolean; outputAmount: bigint; diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts index fbb483a5f..612c21f97 100644 --- a/modules/sor/utils.ts +++ b/modules/sor/utils.ts @@ -1,6 +1,8 @@ import { TokenAmount, Token, Address, ChainId } from '@balancer/sdk'; import { tokenService } from '../token/token.service'; import { networkContext } from '../network/network-context.service'; +import { Chain } from '@prisma/client'; +import { chainToIdMap } from '../network/network-config'; export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string): Promise { @@ -11,10 +13,10 @@ export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string return TokenAmount.fromHumanAmount(token, humanAmount as `${number}`); } -export async function getTokenAmountRaw(tokenAddr: string, rawAmount: string): Promise { - const prismaToken = await tokenService.getToken(tokenAddr); +export async function getTokenAmountRaw(tokenAddr: string, rawAmount: string, chain: Chain): Promise { + const chainId = Number(chainToIdMap[chain]); + const prismaToken = await tokenService.getToken(tokenAddr, chain); if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); - const chainId = networkContext.chainId as unknown as ChainId; const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); return TokenAmount.fromRawAmount(token, rawAmount); } diff --git a/modules/token/token.service.ts b/modules/token/token.service.ts index 4e1eb374c..21aff4d98 100644 --- a/modules/token/token.service.ts +++ b/modules/token/token.service.ts @@ -27,22 +27,22 @@ export class TokenService { await networkContext.config.contentService.syncTokenContentData(); } - public async getToken(address: string): Promise { + public async getToken(address: string, chain = networkContext.chain): Promise { return prisma.prismaToken.findUnique({ where: { address_chain: { address: address.toLowerCase(), - chain: networkContext.chain, + chain, }, }, }); } - public async getTokens(addresses?: string[]): Promise { - let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`); + public async getTokens(addresses?: string[], chain = networkContext.chain): Promise { + let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${chain}`); if (!tokens) { - tokens = await prisma.prismaToken.findMany({ where: { chain: networkContext.chain } }); - this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`, tokens, 5 * 60 * 1000); + tokens = await prisma.prismaToken.findMany({ where: { chain: chain } }); + this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${chain}`, tokens, 5 * 60 * 1000); } if (addresses) { return tokens.filter((token) => addresses.includes(token.address)); From 23aad1b39f3c05118736ee253c2ad51e2ef3cebf Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:25:44 +0100 Subject: [PATCH 68/78] gyro pool data updates --- modules/pool/lib/pool-creator.service.ts | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index a8994889a..21cf32f19 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -32,6 +32,39 @@ export class PoolCreatorService { await this.createPoolRecord(subgraphPool, blockNumber); poolIds.push(subgraphPool.id); + } else if (subgraphPool.poolType?.includes('Gyro')) { + await prisma.prismaPool.update({ + data: { + gyroData: { + update: { + id: subgraphPool.id, + alpha: subgraphPool.alpha || '', + beta: subgraphPool.beta || '', + sqrtAlpha: subgraphPool.sqrtAlpha || '', + sqrtBeta: subgraphPool.sqrtBeta || '', + root3Alpha: subgraphPool.root3Alpha || '', + c: subgraphPool.c || '', + s: subgraphPool.s || '', + lambda: subgraphPool.lambda || '', + tauAlphaX: subgraphPool.tauAlphaX || '', + tauAlphaY: subgraphPool.tauAlphaY || '', + tauBetaX: subgraphPool.tauBetaX || '', + tauBetaY: subgraphPool.tauBetaY || '', + u: subgraphPool.u || '', + v: subgraphPool.v || '', + w: subgraphPool.w || '', + z: subgraphPool.z || '', + dSq: subgraphPool.dSq || '', + }, + } + }, + where: { + id_chain: { + id: subgraphPool.id, + chain: networkContext.chain + } + } + }) } } From 056c17802729a45fb0543ff702fccc2e833c625a Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:54:43 +0100 Subject: [PATCH 69/78] excluding unsupported pools --- modules/sor/sorV2/sorV2.service.ts | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 95d20f4a3..cb9260808 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -272,7 +272,6 @@ export class SorV2Service implements SwapService { public async getSwapResult({ chain, tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { try { const poolsFromDb = await this.getBasePools(chain); - console.log(`Got ${poolsFromDb.length} pools from DB`); const tIn = await this.getToken(tokenIn as Address, chain); const tOut = await this.getToken(tokenOut as Address, chain); const swapKind = this.mapSwapType(swapType); @@ -324,7 +323,6 @@ export class SorV2Service implements SwapService { * @returns */ private async getBasePoolsFromDb(chain: Chain): Promise { - console.log(`Fetching pools from DB`, chain) const { poolIdsToExclude } = AllNetworkConfigsKeyedOnChain[chain].data.sor[env.DEPLOYMENT_ENV as DeploymentEnv]; const pools = await prisma.prismaPool.findMany({ where: { @@ -335,19 +333,32 @@ export class SorV2Service implements SwapService { }, swapEnabled: true, }, - NOT: { - id: { - in: [ - ...poolIdsToExclude, - ...poolsToIgnore, - ], - }, - type: 'LINEAR', // Linear pools are sunset so ignore to avoid issues related to lack of support + id: { + notIn: [ + ...poolIdsToExclude, + ...poolsToIgnore, + ], + }, + type: { + notIn: [ + 'LINEAR', // Linear pools are sunset so ignore to avoid issues related to lack of support + 'LIQUIDITY_BOOTSTRAPPING', // not supported by b-sdk + 'ELEMENT', // not supported by b-sdk + 'UNKNOWN', // not supported by b-sdk + 'INVESTMENT', // not supported by b-sdk + ] }, + AND: { + NOT: { // not supported by b-sdk + type: 'STABLE', + version: { + in: [1,2] + } + }, + } }, include: prismaPoolWithDynamic.include, }); - console.log(`Got ${pools.length} pools from DB`) const rawPools = this.mapToRawPools(pools); return this.mapToBasePools(rawPools, chain); } From 14f2393269ec01271c59a94a8b69ce164d7b1719 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:03:43 +0100 Subject: [PATCH 70/78] sor metrics --- modules/balancer/balancer.resolvers.ts | 2 +- modules/beethoven/balancer-sdk.resolvers.ts | 3 +- modules/metrics/sor.metric.ts | 11 +- modules/sor/sor.service.ts | 208 +++++++++----------- modules/sor/types.ts | 3 +- modules/sor/utils.ts | 4 +- 6 files changed, 107 insertions(+), 124 deletions(-) diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index a21e0b548..a7f0b1843 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -9,7 +9,7 @@ const balancerResolvers: Resolvers = { // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is RawScale, e.g. 1USDC should be passed as 1000000 const amount = await getTokenAmountRaw(amountToken, args.swapAmount, args.chain); - const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount }); + const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount, swapOptions: {} }); return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, }, diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index e764fb411..aa830250f 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -3,7 +3,6 @@ import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; import { sorService } from '../sor/sor.service'; import { getTokenAmountHuman } from '../sor/utils'; -import { chainToIdMap } from '../network/network-config'; const balancerSdkResolvers: Resolvers = { Query: { @@ -11,7 +10,7 @@ const balancerSdkResolvers: Resolvers = { const amountToken = args.swapType === 'EXACT_IN' ? args.tokenIn : args.tokenOut; // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is HumanScale - const amount = await getTokenAmountHuman(amountToken, args.swapAmount); + const amount = await getTokenAmountHuman(amountToken, args.swapAmount, args.chain); const swaps = await sorService.getBeetsSwaps({ ...args, swapAmount: amount }); return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, diff --git a/modules/metrics/sor.metric.ts b/modules/metrics/sor.metric.ts index a066293a4..bac38295e 100644 --- a/modules/metrics/sor.metric.ts +++ b/modules/metrics/sor.metric.ts @@ -1,11 +1,12 @@ +import { Chain } from '@prisma/client'; import { CloudwatchMetricsPublisher } from './metrics.client'; const publishers: Record = {}; -export function getSorMetricsPublisher(chainId: string): CloudwatchMetricsPublisher { - if (!publishers[`${chainId}`]) { - console.log(`Creating new SOR publisher for ${chainId}`); - publishers[`${chainId}`] = new CloudwatchMetricsPublisher(`Backend-${chainId}/Sor`); +export function getSorMetricsPublisher(chain: Chain): CloudwatchMetricsPublisher { + if (!publishers[chain]) { + console.log(`Creating new SOR publisher for ${chain}`); + publishers[chain] = new CloudwatchMetricsPublisher(`Backend-${chain}/Sor`); } - return publishers[`${chainId}`]; + return publishers[chain]; } diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index f52e5797d..1dd37a89c 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -1,104 +1,65 @@ -import { networkContext } from '../network/network-context.service'; import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../schema'; import { sorV1BalancerService } from './sorV1Balancer/sorV1Balancer.service'; import { sorV1BeetsService } from './sorV1Beets/sorV1Beets.service'; import { sorV2Service } from './sorV2/sorV2.service'; -import { GetSwapsInput, SwapResult } from './types'; +import { GetSwapsInput, SwapResult, SwapService } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; import { getSorMetricsPublisher } from '../metrics/sor.metric'; -import moment from 'moment'; import { Chain } from '@prisma/client'; +import { parseUnits } from '@ethersproject/units'; +import { tokenService } from '../token/token.service'; export class SorService { - public async getCowSwaps({ - chain, - tokenIn, - tokenOut, - swapType, - swapAmount, - }: GetSwapsInput): Promise { - console.time(`sorV1-${chain}`); - const swapV1 = await sorV1BalancerService.getSwapResult({ - chain, - tokenIn, - tokenOut, - swapType, - swapAmount, - }); - console.timeEnd(`sorV1-${chain}`); - console.time(`sorV2-${chain}`); - const swapV2 = await sorV2Service.getSwapResult({ - chain, - tokenIn, - tokenOut, - swapType, - swapAmount, - }); - console.timeEnd(`sorV2-${chain}`); - - if (!swapV1.isValid && !swapV2.isValid) return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); + async getCowSwaps(input: GetSwapsInput): Promise { + const swap = await this.getSwap({ ...input, swapOptions: {} }); + const emptyResponse = EMPTY_COWSWAP_RESPONSE(input.tokenIn, input.tokenOut, input.swapAmount); - const bestSwap = await this.getBestSwap(swapV1, swapV2, swapType, tokenIn, tokenOut, chain); + if (!swap) return emptyResponse; try { // Updates with latest onchain data before returning - return await bestSwap.getCowSwapResponse(chain, true); + return await swap.getCowSwapResponse(input.chain, true); } catch (err) { - console.log(`Error Retrieving QuerySwap`); - console.log(err); - return EMPTY_COWSWAP_RESPONSE(tokenIn, tokenOut, swapAmount); + console.log(`Error Retrieving QuerySwap`, err); + return emptyResponse; } } - public async getBeetsSwaps( - input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, - ): Promise { - console.time(`sorV1-${input.chain}`); - const v1Start = moment().unix(); - const swapV1 = await sorV1BeetsService.getSwapResult(input); - const v1End = moment().unix(); - console.timeEnd(`sorV1-${input.chain}`); - - console.time(`sorV2-${input.chain}`); - const v2Start = moment().unix(); - const swapV2 = await sorV2Service.getSwapResult(input); - const v2End = moment().unix(); - console.timeEnd(`sorV2-${input.chain}`); + async getBeetsSwaps(input: GetSwapsInput): Promise { + const swap = await this.getSwap(input, sorV1BeetsService); + const emptyResponse = sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); - const sorMetricsPublisher = getSorMetricsPublisher(input.chain); + if (!swap) return emptyResponse; - sorMetricsPublisher.publish(`SOR_TIME_V1`, v1End - v1Start); - sorMetricsPublisher.publish(`SOR_TIME_V2`, v2End - v2Start); + try { + // Updates with latest onchain data before returning + return swap.getBeetsSwapResponse(true); + } catch (err) { + console.log(`Error Retrieving QuerySwap`, err); + return emptyResponse; + } + } - sorMetricsPublisher.publish(`SOR_VALID_V1`, swapV1.isValid ? 10 : 1); - sorMetricsPublisher.publish(`SOR_VALID_V2`, swapV2.isValid ? 10 : 1); + private async getSwap(input: GetSwapsInput, v1Service: SwapService = sorV1BalancerService) { + const v1Start = +(new Date()) + const swapV1 = await v1Service.getSwapResult(input); + const v1Time = +(new Date()) - v1Start; - const swapDiff = - input.swapType === 'EXACT_IN' - ? Number(swapV1.outputAmount - swapV2.outputAmount) - : Number(swapV1.inputAmount - swapV2.inputAmount); + const v2Start = +(new Date()); + const swapV2 = await sorV2Service.getSwapResult(input); + const v2Time = +(new Date()) - v2Start; - console.log( - `${input.tokenIn},${input.tokenOut},${input.swapType},${input.swapAmount},${swapV1.isValid},${ - swapV2.isValid - },${v1End - v1Start},${v2End - v2Start},${swapDiff},${swapDiff > 0}`, - ); + const version = this.getBestSwap(swapV1, swapV2, input.swapType); - if (!swapV1.isValid && !swapV2.isValid) - return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); + await this.logResult(version, swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut, input.chain, v1Time, v2Time); - const bestSwap = await this.getBestSwap(swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut, input.chain); + if (!version) + return null; - try { - // Updates with latest onchain data before returning - return bestSwap.getBeetsSwapResponse(true); - } catch (err) { - console.log(`Error Retrieving QuerySwap`); - console.log(err); - return sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); - } + return version === 'V1' ? swapV1 : swapV2; } + /** * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. * @param v1 @@ -106,15 +67,12 @@ export class SorService { * @param swapType * @returns */ - private async getBestSwap( + private getBestSwap( v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, - assetIn: string, - assetOut: string, - chain: Chain, debugOut = false, - ): Promise { + ) { // Useful for comparing if (debugOut) { console.log(`------ DEBUG`); @@ -122,6 +80,9 @@ export class SorService { console.log(v2); } + if (!v1.isValid && !v2.isValid) + return null; + let isV1 = false; if (!v1.isValid || !v2.isValid) { isV1 = v1.isValid ? true : false; @@ -136,53 +97,74 @@ export class SorService { } if (isV1 === true) { - await this.logResult(`V1`, v1, v2, swapType, assetIn, assetOut, chain); - return v1; - } else { - await this.logResult(`V2`, v1, v2, swapType, assetIn, assetOut, chain); + return 'V1'; } - return v2; + + return 'V2'; } private async logResult( - logType: string, + version: string | null, v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, assetIn: string, assetOut: string, - chain: Chain + chain: Chain, + v1Time: number, + v2Time: number, ) { + const sorMetricsPublisher = getSorMetricsPublisher(chain); + + await sorMetricsPublisher.publish(`SOR_VALID_V1`, v1.isValid ? 10 : 1); + await sorMetricsPublisher.publish(`SOR_VALID_V2`, v2.isValid ? 10 : 1); + + if (!version) + return; + // console.log() will log to cloudwatch + let diff = v1.inputAmount - v2.inputAmount; + let tradeAmount = v1.outputAmount; + let v1ResultAmount = v1.inputAmount; + let v2ResultAmount = v2.inputAmount; + let bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + let userToken = assetOut; + let resultToken = assetIn; if (swapType === 'EXACT_IN') { - console.log( - 'SOR Service', - chain, - logType, - swapType, - assetIn, - assetOut, - v1.inputAmount, - v1.outputAmount, - v2.inputAmount, - v2.outputAmount, - v1.outputAmount - v2.outputAmount, - ); - } else { - console.log( - 'SOR Service', - chain, - logType, - swapType, - assetIn, - assetOut, - v1.inputAmount, - v1.outputAmount, - v2.inputAmount, - v2.outputAmount, - v1.inputAmount - v2.inputAmount, - ); + diff = v1.outputAmount - v2.outputAmount; + tradeAmount = v1.inputAmount; + v1ResultAmount = v1.outputAmount; + v2ResultAmount = v2.outputAmount; + bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + userToken = assetIn; + resultToken = assetOut; } + + const fp = (a: bigint, d: number) => Number(parseUnits(String(a), d)); + const prismaToken = await tokenService.getToken(resultToken, chain); + const decimals = prismaToken!.decimals; + const v2Perf = version === 'V1' + ? 1 - (fp(v1ResultAmount, decimals) / fp(v2ResultAmount, decimals)) // negative perf means V1 is better + : (fp(v2ResultAmount, decimals) / fp(v1ResultAmount, decimals)) - 1; // positive perf means V2 is better + + await sorMetricsPublisher.publish(`SOR_TIME_V1`, v1Time); + await sorMetricsPublisher.publish(`SOR_TIME_V2`, v2Time); + await sorMetricsPublisher.publish(`SOR_V2_PERFORMACE`, v2Perf); + + console.log( + 'SOR_RESULT', + v1Time, + v2Time, + chain, + version, + swapType, + userToken, + resultToken, + tradeAmount, + bestResultAmount, + diff, + v2Perf + ); } } diff --git a/modules/sor/types.ts b/modules/sor/types.ts index a5ae51370..a46f395c6 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -1,5 +1,5 @@ import { Chain } from '@prisma/client'; -import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse } from '../../schema'; +import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../schema'; import { TokenAmount } from '@balancer/sdk'; export interface GetSwapsInput { chain: Chain; @@ -7,6 +7,7 @@ export interface GetSwapsInput { tokenOut: string; swapType: GqlSorSwapType; swapAmount: TokenAmount; + swapOptions: GqlSorSwapOptionsInput; } export interface SwapResult { diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts index 612c21f97..8fe17576a 100644 --- a/modules/sor/utils.ts +++ b/modules/sor/utils.ts @@ -5,8 +5,8 @@ import { Chain } from '@prisma/client'; import { chainToIdMap } from '../network/network-config'; -export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string): Promise { - const prismaToken = await tokenService.getToken(tokenAddr); +export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string, chain: Chain): Promise { + const prismaToken = await tokenService.getToken(tokenAddr, chain); if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); const chainId = networkContext.chainId as unknown as ChainId; const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); From 920600c70033702e14e7927f3eb93c252a414585 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:20:13 +0100 Subject: [PATCH 71/78] sor bench logging --- modules/sor/sor.service.ts | 37 ++++++++++--------- .../sorV1Balancer/sorV1Balancer.service.ts | 15 +++++++- modules/sor/sorV2/sorV2.service.ts | 15 +++++++- modules/sor/utils.ts | 2 +- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 1dd37a89c..ba071ad33 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -6,7 +6,7 @@ import { GetSwapsInput, SwapResult, SwapService } from './types'; import { EMPTY_COWSWAP_RESPONSE } from './constants'; import { getSorMetricsPublisher } from '../metrics/sor.metric'; import { Chain } from '@prisma/client'; -import { parseUnits } from '@ethersproject/units'; +import { parseUnits, formatUnits } from '@ethersproject/units'; import { tokenService } from '../token/token.service'; export class SorService { @@ -122,36 +122,37 @@ export class SorService { if (!version) return; - // console.log() will log to cloudwatch - let diff = v1.inputAmount - v2.inputAmount; - let tradeAmount = v1.outputAmount; let v1ResultAmount = v1.inputAmount; - let v2ResultAmount = v2.inputAmount; - let bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + let v2ResultAmount = v2.inputAmount < 0 ? -v2.inputAmount : v2.inputAmount; + let tradeAmount = v1.outputAmount; let userToken = assetOut; let resultToken = assetIn; if (swapType === 'EXACT_IN') { - diff = v1.outputAmount - v2.outputAmount; - tradeAmount = v1.inputAmount; v1ResultAmount = v1.outputAmount; - v2ResultAmount = v2.outputAmount; - bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + v2ResultAmount = v2.outputAmount < 0 ? -v2.outputAmount : v2.outputAmount; + tradeAmount = v1.inputAmount; userToken = assetIn; resultToken = assetOut; } - const fp = (a: bigint, d: number) => Number(parseUnits(String(a), d)); + const fp = (a: bigint, d: number) => Number(formatUnits(String(a), d)); + const bn = (a: string, d: number) => BigInt(String(parseUnits(a, d))); const prismaToken = await tokenService.getToken(resultToken, chain); const decimals = prismaToken!.decimals; - const v2Perf = version === 'V1' + let v2Perf = version === 'V1' ? 1 - (fp(v1ResultAmount, decimals) / fp(v2ResultAmount, decimals)) // negative perf means V1 is better : (fp(v2ResultAmount, decimals) / fp(v1ResultAmount, decimals)) - 1; // positive perf means V2 is better + v2Perf = Math.max(-1, Math.min(1, v2Perf)); + let diffN = fp(v2ResultAmount, decimals) - fp(v1ResultAmount, decimals) + let diff = bn(diffN.toFixed(decimals), decimals) + let bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + await sorMetricsPublisher.publish(`SOR_TIME_V1`, v1Time); await sorMetricsPublisher.publish(`SOR_TIME_V2`, v2Time); await sorMetricsPublisher.publish(`SOR_V2_PERFORMACE`, v2Perf); - console.log( + console.log([ 'SOR_RESULT', v1Time, v2Time, @@ -160,11 +161,11 @@ export class SorService { swapType, userToken, resultToken, - tradeAmount, - bestResultAmount, - diff, - v2Perf - ); + String(tradeAmount), + String(bestResultAmount), + String(diff), + v2Perf.toFixed(8), + ].join(',')); } } diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts index e73fecae3..d133ef87c 100644 --- a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import * as Sentry from '@sentry/node' import { AddressZero } from '@ethersproject/constants'; import { Contract } from '@ethersproject/contracts'; import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse } from '../../../schema'; @@ -78,8 +79,18 @@ export class SorV1BalancerService implements SwapService { try { const swap = await this.querySorBalancer(chain, swapType, tokenIn, tokenOut, swapAmount); return new SwapResultV1(swap, swapType); - } catch (err) { - console.log(`sorV1 Service Error`, err); + } catch (err: any) { + console.error(`SOR_V1_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`); + Sentry.captureException(err.message, { + tags: { + service: 'sorV1', + tokenIn, + tokenOut, + swapAmount: swapAmount.amount, + swapType, + chain, + } + }) return new SwapResultV1(null, swapType); } } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index cb9260808..a90af8fdd 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -37,6 +37,7 @@ import { oldBnumScale } from '../../big-number/old-big-number'; import { mapRoutes } from './beetsHelpers'; import { poolsToIgnore } from '../constants'; import { AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; +import * as Sentry from '@sentry/node' const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -284,8 +285,18 @@ export class SorV2Service implements SwapService { // swapOptions, // I don't think we need specific swapOptions for this? ); return new SwapResultV2(swap); - } catch (err) { - console.log(`sorV2 Service Error`, err); + } catch (err: any) { + console.error(`SOR_V2_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`); + Sentry.captureException(err.message, { + tags: { + service: 'sorV2', + tokenIn, + tokenOut, + swapAmount: swapAmount.amount, + swapType, + chain, + } + }) return new SwapResultV2(null); } } diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts index 8fe17576a..9e88f04c3 100644 --- a/modules/sor/utils.ts +++ b/modules/sor/utils.ts @@ -6,9 +6,9 @@ import { chainToIdMap } from '../network/network-config'; export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string, chain: Chain): Promise { + const chainId = Number(chainToIdMap[chain]); const prismaToken = await tokenService.getToken(tokenAddr, chain); if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); - const chainId = networkContext.chainId as unknown as ChainId; const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); return TokenAmount.fromHumanAmount(token, humanAmount as `${number}`); } From d12e89245e703ee6eddc4011f301747affe7397d Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:13:13 +0100 Subject: [PATCH 72/78] last review and styling --- modules/beethoven/balancer-sor.service.ts | 57 +++++++------ modules/sor/sor.service.ts | 85 ++++++++++--------- .../sorV1Balancer/sorV1Balancer.service.ts | 10 ++- modules/sor/sorV2/sorV2.service.ts | 26 +++--- 4 files changed, 98 insertions(+), 80 deletions(-) diff --git a/modules/beethoven/balancer-sor.service.ts b/modules/beethoven/balancer-sor.service.ts index e541b199e..37106b79a 100644 --- a/modules/beethoven/balancer-sor.service.ts +++ b/modules/beethoven/balancer-sor.service.ts @@ -218,28 +218,33 @@ export class BalancerSorService { }; } - zeroResponse(swapType: GqlSorSwapType, tokenIn: string, tokenOut: string, swapAmount: string): GqlSorGetSwapsResponse { - return { - marketSp: '0', - tokenAddresses: [], - swaps: [], - tokenIn: replaceZeroAddressWithEth(tokenIn), - tokenOut: replaceZeroAddressWithEth(tokenOut), - swapType, - tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : '0', - tokenOutAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, - swapAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, - swapAmountScaled: '0', - swapAmountForSwaps: '0', - returnAmount: '0', - returnAmountScaled: '0', - returnAmountConsideringFees: '0', - returnAmountFromSwaps: '0', - routes: [], - effectivePrice:'0', - effectivePriceReversed: '0', - priceImpact: '0', - }; + zeroResponse( + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmount: string, + ): GqlSorGetSwapsResponse { + return { + marketSp: '0', + tokenAddresses: [], + swaps: [], + tokenIn: replaceZeroAddressWithEth(tokenIn), + tokenOut: replaceZeroAddressWithEth(tokenOut), + swapType, + tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : '0', + tokenOutAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmountScaled: '0', + swapAmountForSwaps: '0', + returnAmount: '0', + returnAmountScaled: '0', + returnAmountConsideringFees: '0', + returnAmountFromSwaps: '0', + routes: [], + effectivePrice: '0', + effectivePriceReversed: '0', + priceImpact: '0', + }; } private async querySor( @@ -331,11 +336,13 @@ export class BalancerSorService { tokenAddress = tokenAddress.toLowerCase(); const match = tokens.find((token) => token.address === tokenAddress); - if (!match) { - throw new Error('Unknown token: ' + tokenAddress); + let decimals = match?.decimals; + if (!decimals) { + console.error(`Unknown token: ${tokenAddress}`); + decimals = 18; } - return match.decimals; + return decimals; } private batchSwaps(assetArray: string[][], swaps: SwapV2[][]): { swaps: SwapV2[]; assets: string[] } { diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index ba071ad33..789fb0550 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -27,7 +27,12 @@ export class SorService { async getBeetsSwaps(input: GetSwapsInput): Promise { const swap = await this.getSwap(input, sorV1BeetsService); - const emptyResponse = sorV1BeetsService.zeroResponse(input.swapType, input.tokenIn, input.tokenOut, input.swapAmount); + const emptyResponse = sorV1BeetsService.zeroResponse( + input.swapType, + input.tokenIn, + input.tokenOut, + input.swapAmount, + ); if (!swap) return emptyResponse; @@ -41,25 +46,33 @@ export class SorService { } private async getSwap(input: GetSwapsInput, v1Service: SwapService = sorV1BalancerService) { - const v1Start = +(new Date()) + const v1Start = +new Date(); const swapV1 = await v1Service.getSwapResult(input); - const v1Time = +(new Date()) - v1Start; + const v1Time = +new Date() - v1Start; - const v2Start = +(new Date()); + const v2Start = +new Date(); const swapV2 = await sorV2Service.getSwapResult(input); - const v2Time = +(new Date()) - v2Start; + const v2Time = +new Date() - v2Start; const version = this.getBestSwap(swapV1, swapV2, input.swapType); - await this.logResult(version, swapV1, swapV2, input.swapType, input.tokenIn, input.tokenOut, input.chain, v1Time, v2Time); + await this.logResult( + version, + swapV1, + swapV2, + input.swapType, + input.tokenIn, + input.tokenOut, + input.chain, + v1Time, + v2Time, + ); - if (!version) - return null; + if (!version) return null; return version === 'V1' ? swapV1 : swapV2; } - /** * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. * @param v1 @@ -67,12 +80,7 @@ export class SorService { * @param swapType * @returns */ - private getBestSwap( - v1: SwapResult, - v2: SwapResult, - swapType: GqlSorSwapType, - debugOut = false, - ) { + private getBestSwap(v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, debugOut = false) { // Useful for comparing if (debugOut) { console.log(`------ DEBUG`); @@ -80,8 +88,7 @@ export class SorService { console.log(v2); } - if (!v1.isValid && !v2.isValid) - return null; + if (!v1.isValid && !v2.isValid) return null; let isV1 = false; if (!v1.isValid || !v2.isValid) { @@ -119,8 +126,7 @@ export class SorService { await sorMetricsPublisher.publish(`SOR_VALID_V1`, v1.isValid ? 10 : 1); await sorMetricsPublisher.publish(`SOR_VALID_V2`, v2.isValid ? 10 : 1); - if (!version) - return; + if (!version) return; let v1ResultAmount = v1.inputAmount; let v2ResultAmount = v2.inputAmount < 0 ? -v2.inputAmount : v2.inputAmount; @@ -139,33 +145,36 @@ export class SorService { const bn = (a: string, d: number) => BigInt(String(parseUnits(a, d))); const prismaToken = await tokenService.getToken(resultToken, chain); const decimals = prismaToken!.decimals; - let v2Perf = version === 'V1' - ? 1 - (fp(v1ResultAmount, decimals) / fp(v2ResultAmount, decimals)) // negative perf means V1 is better - : (fp(v2ResultAmount, decimals) / fp(v1ResultAmount, decimals)) - 1; // positive perf means V2 is better + let v2Perf = + version === 'V1' + ? 1 - fp(v1ResultAmount, decimals) / fp(v2ResultAmount, decimals) // negative perf means V1 is better + : fp(v2ResultAmount, decimals) / fp(v1ResultAmount, decimals) - 1; // positive perf means V2 is better v2Perf = Math.max(-1, Math.min(1, v2Perf)); - let diffN = fp(v2ResultAmount, decimals) - fp(v1ResultAmount, decimals) - let diff = bn(diffN.toFixed(decimals), decimals) + let diffN = fp(v2ResultAmount, decimals) - fp(v1ResultAmount, decimals); + let diff = bn(diffN.toFixed(decimals), decimals); let bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; await sorMetricsPublisher.publish(`SOR_TIME_V1`, v1Time); await sorMetricsPublisher.publish(`SOR_TIME_V2`, v2Time); await sorMetricsPublisher.publish(`SOR_V2_PERFORMACE`, v2Perf); - console.log([ - 'SOR_RESULT', - v1Time, - v2Time, - chain, - version, - swapType, - userToken, - resultToken, - String(tradeAmount), - String(bestResultAmount), - String(diff), - v2Perf.toFixed(8), - ].join(',')); + console.log( + [ + 'SOR_RESULT', + v1Time, + v2Time, + chain, + version, + swapType, + userToken, + resultToken, + String(tradeAmount), + String(bestResultAmount), + String(diff), + v2Perf.toFixed(8), + ].join(','), + ); } } diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts index d133ef87c..73ad3b5ab 100644 --- a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import * as Sentry from '@sentry/node' +import * as Sentry from '@sentry/node'; import { AddressZero } from '@ethersproject/constants'; import { Contract } from '@ethersproject/contracts'; import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse } from '../../../schema'; @@ -80,7 +80,9 @@ export class SorV1BalancerService implements SwapService { const swap = await this.querySorBalancer(chain, swapType, tokenIn, tokenOut, swapAmount); return new SwapResultV1(swap, swapType); } catch (err: any) { - console.error(`SOR_V1_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`); + console.error( + `SOR_V1_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`, + ); Sentry.captureException(err.message, { tags: { service: 'sorV1', @@ -89,8 +91,8 @@ export class SorV1BalancerService implements SwapService { swapAmount: swapAmount.amount, swapType, chain, - } - }) + }, + }); return new SwapResultV1(null, swapType); } } diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index a90af8fdd..70be4c3ae 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -37,7 +37,7 @@ import { oldBnumScale } from '../../big-number/old-big-number'; import { mapRoutes } from './beetsHelpers'; import { poolsToIgnore } from '../constants'; import { AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; -import * as Sentry from '@sentry/node' +import * as Sentry from '@sentry/node'; const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; @@ -286,7 +286,9 @@ export class SorV2Service implements SwapService { ); return new SwapResultV2(swap); } catch (err: any) { - console.error(`SOR_V2_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`); + console.error( + `SOR_V2_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`, + ); Sentry.captureException(err.message, { tags: { service: 'sorV2', @@ -295,8 +297,8 @@ export class SorV2Service implements SwapService { swapAmount: swapAmount.amount, swapType, chain, - } - }) + }, + }); return new SwapResultV2(null); } } @@ -345,10 +347,7 @@ export class SorV2Service implements SwapService { swapEnabled: true, }, id: { - notIn: [ - ...poolIdsToExclude, - ...poolsToIgnore, - ], + notIn: [...poolIdsToExclude, ...poolsToIgnore], }, type: { notIn: [ @@ -357,16 +356,17 @@ export class SorV2Service implements SwapService { 'ELEMENT', // not supported by b-sdk 'UNKNOWN', // not supported by b-sdk 'INVESTMENT', // not supported by b-sdk - ] + ], }, AND: { - NOT: { // not supported by b-sdk + NOT: { + // not supported by b-sdk type: 'STABLE', version: { - in: [1,2] - } + in: [1, 2], + }, }, - } + }, }, include: prismaPoolWithDynamic.include, }); From f2565924f7ce6e543f21af7dbd0bfbf9d2e5e759 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:18:02 +0100 Subject: [PATCH 73/78] updating b-sdk --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a90815f77..e041d16cf 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", - "@balancer/sdk": "^0.2.0", + "@balancer/sdk": "^0.3.0", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", diff --git a/yarn.lock b/yarn.lock index 547110cdf..b8c20666d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" - integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== "@ampproject/remapping@^2.1.0": version "2.2.0" @@ -2456,15 +2456,15 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" -"@balancer/sdk@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.2.0.tgz#bda93d18324d2a75f3a474ef546b281e056b4b2e" - integrity sha512-97VF4G15VdonKySOgVWOHcsuizqfOrhs0b8i5fbB7AmJSlaLNuW+Q06crBUqexkEFEw+/h2LnwqJeqRKIbsYbQ== +"@balancer/sdk@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.3.1.tgz#ca70a0b0082a809d2b0889277dff37c1676d46ef" + integrity sha512-eyhfgjPlVR5nZ6+Az1bDVnmycjkVa2wx7uO5bs/ntb/HYvheeJt+mcoHUOTojMRYvWUSal0WrenfbxWy0pn9Pg== dependencies: async-retry "^1.3.3" decimal.js-light "^2.5.1" pino "^8.11.0" - viem "^1.4.1" + viem "^1.9.3" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -11241,12 +11241,12 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -viem@^1.4.1: - version "1.16.4" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.4.tgz#4cb90dd27d13356ea00d21f6fd363bdfe0628c4e" - integrity sha512-T9ziN3EERXz0BtQSS2VJM+P1EJ2W7K7PviobFrmvWCEYmNQ/vJDhfFqGjvq0ZL9LVz9HvevCbenEy8oIdMEZ+w== +viem@^1.9.3: + version "1.19.6" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.19.6.tgz#daf1ebf4774a5bb9e87822ab25fcc67420471847" + integrity sha512-WSBHBMurWIWQk2yisOD8hqSA5S56cZu6onty3hzauVjiHMildtVWujF7YT0xjoU40GpFODvJASRR2RFdzgvUUg== dependencies: - "@adraffy/ens-normalize" "1.9.4" + "@adraffy/ens-normalize" "1.10.0" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@scure/bip32" "1.3.2" From 3792147f523666577654cc75562bea5e41f734ae Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:17:33 +0100 Subject: [PATCH 74/78] prettier --- modules/pool/lib/pool-creator.service.ts | 12 ++++----- modules/sor/utils.ts | 4 +-- tsconfig.json | 32 +++++++++++------------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index 1350b6815..9235162e1 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -16,7 +16,7 @@ export class PoolCreatorService { } private get chain() { - return networkContext.chain + return networkContext.chain; } public async syncAllPoolsFromSubgraph(blockNumber: number): Promise { @@ -60,15 +60,15 @@ export class PoolCreatorService { z: subgraphPool.z || '', dSq: subgraphPool.dSq || '', }, - } + }, }, where: { id_chain: { id: subgraphPool.id, - chain: networkContext.chain - } - } - }) + chain: this.chain, + }, + }, + }); } } diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts index 9e88f04c3..1be0ad8ba 100644 --- a/modules/sor/utils.ts +++ b/modules/sor/utils.ts @@ -1,10 +1,8 @@ -import { TokenAmount, Token, Address, ChainId } from '@balancer/sdk'; +import { TokenAmount, Token, Address } from '@balancer/sdk'; import { tokenService } from '../token/token.service'; -import { networkContext } from '../network/network-context.service'; import { Chain } from '@prisma/client'; import { chainToIdMap } from '../network/network-config'; - export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string, chain: Chain): Promise { const chainId = Number(chainToIdMap[chain]); const prismaToken = await tokenService.getToken(tokenAddr, chain); diff --git a/tsconfig.json b/tsconfig.json index 817776afd..a9c4bc4b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,16 @@ { - "compilerOptions": { - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es2018"], - "module": "commonjs", - "strict": true, - "target": "ES2020", - "resolveJsonModule": true, - "outDir": "./dist", - "skipLibCheck": true - }, - "exclude": [ - "node_modules", - "debug", - "**/*.spec.ts", - "**/*.test.ts" - ] + "compilerOptions": { + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2018"], + "module": "commonjs", + "strict": true, + "target": "ES2020", + "resolveJsonModule": true, + "outDir": "./dist", + "skipLibCheck": true, + "sourceMap": true + }, + "exclude": ["node_modules", "debug", "**/*.spec.ts", "**/*.test.ts"] } From c62793eedebfa3db40175eb50ebc095f837d1e69 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:18:06 +0100 Subject: [PATCH 75/78] V2 options --- modules/beethoven/balancer-sdk.gql | 9 +++++++++ modules/beethoven/balancer-sdk.resolvers.ts | 9 ++++++++- modules/sor/sor.service.ts | 5 ++--- modules/sor/sorV2/sorV2.service.ts | 20 +++++++++++--------- modules/sor/types.ts | 8 ++++++++ 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index d26122746..a2d9e8d26 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -6,6 +6,7 @@ extend type Query { swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in human readable form swapOptions: GqlSorSwapOptionsInput! + graphTraversalConfig: GqlGraphTraversalConfigInput ): GqlSorGetSwapsResponse! sorGetBatchSwapForTokensIn( tokensIn: [GqlTokenAmountHumanReadable!]! @@ -25,6 +26,14 @@ input GqlSorSwapOptionsInput { forceRefresh: Boolean #don't use any cached responses } +input GqlGraphTraversalConfigInput { + maxDepth: Int # default 6 + maxNonBoostedPathDepth: Int # default 3 + maxNonBoostedHopTokensInBoostedPath: Int # default 2 + approxPathsToReturn: Int # default 5 + poolIdsToInclude: [String] +} + type GqlSorGetSwapsResponse { tokenIn: String! tokenOut: String! diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index aa830250f..51498e230 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -3,6 +3,7 @@ import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; import { sorService } from '../sor/sor.service'; import { getTokenAmountHuman } from '../sor/utils'; +import { GraphTraversalConfig } from '../sor/types'; const balancerSdkResolvers: Resolvers = { Query: { @@ -11,7 +12,13 @@ const balancerSdkResolvers: Resolvers = { // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is HumanScale const amount = await getTokenAmountHuman(amountToken, args.swapAmount, args.chain); - const swaps = await sorService.getBeetsSwaps({ ...args, swapAmount: amount }); + const { graphTraversalConfig, ...cleanArgs } = args; + + const swaps = await sorService.getBeetsSwaps({ + ...cleanArgs, + graphTraversalConfig: graphTraversalConfig as GraphTraversalConfig, + swapAmount: amount, + }); return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts index 789fb0550..1602e1d34 100644 --- a/modules/sor/sor.service.ts +++ b/modules/sor/sor.service.ts @@ -122,9 +122,8 @@ export class SorService { v2Time: number, ) { const sorMetricsPublisher = getSorMetricsPublisher(chain); - - await sorMetricsPublisher.publish(`SOR_VALID_V1`, v1.isValid ? 10 : 1); - await sorMetricsPublisher.publish(`SOR_VALID_V2`, v2.isValid ? 10 : 1); + await sorMetricsPublisher.publish(`SOR_VALID_V1`, v1.isValid ? 1 : 0); + await sorMetricsPublisher.publish(`SOR_VALID_V2`, v2.isValid ? 1 : 0); if (!version) return; diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts index 70be4c3ae..04b7892b0 100644 --- a/modules/sor/sorV2/sorV2.service.ts +++ b/modules/sor/sorV2/sorV2.service.ts @@ -270,20 +270,22 @@ export class SorV2Service implements SwapService { this.cache = new Cache(); } - public async getSwapResult({ chain, tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + public async getSwapResult({ + chain, + tokenIn, + tokenOut, + swapType, + swapAmount, + graphTraversalConfig, + }: GetSwapsInput): Promise { try { const poolsFromDb = await this.getBasePools(chain); const tIn = await this.getToken(tokenIn as Address, chain); const tOut = await this.getToken(tokenOut as Address, chain); const swapKind = this.mapSwapType(swapType); - const swap = await sorGetSwapsWithPools( - tIn, - tOut, - swapKind, - swapAmount, - poolsFromDb, - // swapOptions, // I don't think we need specific swapOptions for this? - ); + const swap = await sorGetSwapsWithPools(tIn, tOut, swapKind, swapAmount, poolsFromDb, { + graphTraversalConfig, + }); return new SwapResultV2(swap); } catch (err: any) { console.error( diff --git a/modules/sor/types.ts b/modules/sor/types.ts index a46f395c6..564647c84 100644 --- a/modules/sor/types.ts +++ b/modules/sor/types.ts @@ -8,6 +8,14 @@ export interface GetSwapsInput { swapType: GqlSorSwapType; swapAmount: TokenAmount; swapOptions: GqlSorSwapOptionsInput; + graphTraversalConfig?: GraphTraversalConfig; +} + +export interface GraphTraversalConfig { + approxPathsToReturn?: number; + maxDepth?: number; + maxNonBoostedHopTokensInBoostedPath?: number; + maxNonBoostedPathDepth?: number; } export interface SwapResult { From 61cec2b8b37bea8250b7c2381af0b09de23a62da Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:50:03 +0100 Subject: [PATCH 76/78] node18 in github action --- .github/workflows/checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5d111eefa..321219553 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -20,9 +20,9 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: '16.x' + node-version: '18.x' - name: Install deps - run: yarn + run: yarn - name: Generate Schema run: yarn generate - name: Prisma Generate From b50d476bbe9c29146d45dd2d0c903d34392a25af Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:08:05 +0100 Subject: [PATCH 77/78] sor args cleaup --- modules/beethoven/balancer-sdk.resolvers.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index 51498e230..ccedce89b 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -8,17 +8,22 @@ import { GraphTraversalConfig } from '../sor/types'; const balancerSdkResolvers: Resolvers = { Query: { sorGetSwaps: async (parent, args, context) => { - const amountToken = args.swapType === 'EXACT_IN' ? args.tokenIn : args.tokenOut; + const tokenIn = args.tokenIn.toLowerCase(); + const tokenOut = args.tokenOut.toLowerCase(); + const amountToken = args.swapType === 'EXACT_IN' ? tokenIn : tokenOut; // Use TokenAmount to help follow scaling requirements in later logic // args.swapAmount is HumanScale const amount = await getTokenAmountHuman(amountToken, args.swapAmount, args.chain); - const { graphTraversalConfig, ...cleanArgs } = args; + const graphTraversalConfig = args.graphTraversalConfig as GraphTraversalConfig; const swaps = await sorService.getBeetsSwaps({ - ...cleanArgs, - graphTraversalConfig: graphTraversalConfig as GraphTraversalConfig, + ...args, + tokenIn, + tokenOut, + graphTraversalConfig, swapAmount: amount, }); + return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { From 0aa8c08bbd2778f05aa6f3a5149c3385212fecec Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:32:20 +0100 Subject: [PATCH 78/78] less profiling --- worker/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/worker.ts b/worker/worker.ts index 45cd2e0ed..c5da70337 100644 --- a/worker/worker.ts +++ b/worker/worker.ts @@ -17,7 +17,7 @@ export async function startWorker() { new ProfilingIntegration(), ], tracesSampleRate: 0.2, - profilesSampleRate: 1.0, + profilesSampleRate: 0.1, }); app.use(Sentry.Handlers.requestHandler());