From 38f4653245b69738f0a41bedd02ec22c94107bc5 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:38:12 +0100 Subject: [PATCH 01/14] v3 subgraph --- .github/workflows/checks.yml | 1 + buildspec.yml | 1 + codegen.yml | 14 +++++++ env.local | 1 + modules/subgraphs/balancer-v3-vault/index.ts | 17 ++++++++ .../balancer-v3-vault/join-exits.graphql | 38 +++++++++++++++++ .../balancer-v3-vault/pool-snapshots.graphql | 32 ++++++++++++++ .../subgraphs/balancer-v3-vault/pools.graphql | 40 ++++++++++++++++++ .../subgraphs/balancer-v3-vault/swaps.graphql | 35 ++++++++++++++++ .../subgraphs/balancer-v3-vault/users.graphql | 42 +++++++++++++++++++ 10 files changed, 221 insertions(+) create mode 100644 modules/subgraphs/balancer-v3-vault/index.ts create mode 100644 modules/subgraphs/balancer-v3-vault/join-exits.graphql create mode 100644 modules/subgraphs/balancer-v3-vault/pool-snapshots.graphql create mode 100644 modules/subgraphs/balancer-v3-vault/pools.graphql create mode 100644 modules/subgraphs/balancer-v3-vault/swaps.graphql create mode 100644 modules/subgraphs/balancer-v3-vault/users.graphql diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c0ffaedaf..7d5b21a8c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -6,6 +6,7 @@ on: jobs: Build: env: + V3_SUBGRAPH: https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest BALANCER_SUBGRAPH: https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx MASTERCHEF_SUBGRAPH: https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2 BLOCKS_SUBGRAPH: https://api.thegraph.com/subgraphs/name/danielmkm/optimism-blocks diff --git a/buildspec.yml b/buildspec.yml index 3b9a1e090..5cdeecf14 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -1,6 +1,7 @@ version: 0.2 env: variables: + V3_SUBGRAPH: 'https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest' BALANCER_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx-v2-optimism' MASTERCHEF_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2' RELIQUARY_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/reliquary' diff --git a/codegen.yml b/codegen.yml index b6d7a4a77..aa64b9dd4 100644 --- a/codegen.yml +++ b/codegen.yml @@ -3,6 +3,20 @@ hooks: afterAllFileWrite: - prettier --write generates: + modules/subgraphs/balancer-v3-vault/generated/types.ts: + schema: ${V3_SUBGRAPH} + documents: 'modules/subgraphs/balancer-v3-vault/*.graphql' + plugins: + - typescript + - typescript-operations + - typescript-graphql-request + config: + skipTypename: true + scalars: + BigInt: string + Bytes: string + BigDecimal: string + modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts: schema: ${BALANCER_SUBGRAPH} documents: 'modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql' diff --git a/env.local b/env.local index bf4e9fc42..a9effff2f 100644 --- a/env.local +++ b/env.local @@ -20,6 +20,7 @@ WORKER_QUEUE_URL=https://# DEFAULT_CHAIN_ID=250 # Subgraph type config +V3_SUBGRAPH=https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest BALANCER_SUBGRAPH=https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx MASTERCHEF_SUBGRAPH=https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2 BLOCKS_SUBGRAPH=https://api.thegraph.com/subgraphs/name/danielmkm/optimism-blocks diff --git a/modules/subgraphs/balancer-v3-vault/index.ts b/modules/subgraphs/balancer-v3-vault/index.ts new file mode 100644 index 000000000..1a8b7bdfc --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/index.ts @@ -0,0 +1,17 @@ +import { GraphQLClient } from 'graphql-request'; +import { getSdk } from './generated/types'; + +/** + * Builds a client based on subgraph URL. + * + * @param subgraphUrl - url of the subgraph + * @returns sdk - generated sdk for the subgraph + */ +export const getClient = (subgraphUrl: string) => { + const client = new GraphQLClient(subgraphUrl); + const sdk = getSdk(client); + + return sdk; +}; + +export type V3SubgraphClient = ReturnType; diff --git a/modules/subgraphs/balancer-v3-vault/join-exits.graphql b/modules/subgraphs/balancer-v3-vault/join-exits.graphql new file mode 100644 index 000000000..25c3c07f3 --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/join-exits.graphql @@ -0,0 +1,38 @@ +fragment JoinExit on JoinExit { + id + type + sender + amounts + pool { + id + tokens { + address + } + } + user { + id + } + blockNumber + blockTimestamp + transactionHash +} + +query JoinExits( + $skip: Int + $first: Int + $orderBy: JoinExit_orderBy + $orderDirection: OrderDirection + $where: JoinExit_filter + $block: Block_height +) { + joinExits( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...JoinExit + } +} diff --git a/modules/subgraphs/balancer-v3-vault/pool-snapshots.graphql b/modules/subgraphs/balancer-v3-vault/pool-snapshots.graphql new file mode 100644 index 000000000..cadf3ac3f --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/pool-snapshots.graphql @@ -0,0 +1,32 @@ +fragment PoolSnapshot on PoolSnapshot { + id + pool { + id + tokens { + address + } + } + timestamp + balances + totalShares +} + +query PoolSnapshots( + $skip: Int + $first: Int + $orderBy: PoolSnapshot_orderBy + $orderDirection: OrderDirection + $where: PoolSnapshot_filter + $block: Block_height +) { + poolSnapshots( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...PoolSnapshot + } +} diff --git a/modules/subgraphs/balancer-v3-vault/pools.graphql b/modules/subgraphs/balancer-v3-vault/pools.graphql new file mode 100644 index 000000000..4ddb690cf --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/pools.graphql @@ -0,0 +1,40 @@ +fragment Pool on Pool { + id + factory + totalShares + pauseWindowEndTime + pauseManager + blockNumber + blockTimestamp + transactionHash + tokens { + address + index + balance + totalProtocolSwapFee + totalProtocolYieldFee + } + rateProviders { + address + } +} + +query Pools( + $skip: Int + $first: Int + $orderBy: Pool_orderBy + $orderDirection: OrderDirection + $where: Pool_filter + $block: Block_height +) { + pools( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...Pool + } +} diff --git a/modules/subgraphs/balancer-v3-vault/swaps.graphql b/modules/subgraphs/balancer-v3-vault/swaps.graphql new file mode 100644 index 000000000..89f72ef63 --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/swaps.graphql @@ -0,0 +1,35 @@ +fragment Swap on Swap { + id + pool + tokenIn + tokenOut + tokenAmountIn + tokenAmountOut + swapFeeAmount + user { + id + } + blockNumber + blockTimestamp + transactionHash +} + +query Swaps( + $skip: Int + $first: Int + $orderBy: Swap_orderBy + $orderDirection: OrderDirection + $where: Swap_filter + $block: Block_height +) { + swaps( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...Swap + } +} diff --git a/modules/subgraphs/balancer-v3-vault/users.graphql b/modules/subgraphs/balancer-v3-vault/users.graphql new file mode 100644 index 000000000..15fd08d5b --- /dev/null +++ b/modules/subgraphs/balancer-v3-vault/users.graphql @@ -0,0 +1,42 @@ +fragment User on User { + id + swaps(first: 1000) { + id + pool + tokenIn + tokenOut + tokenAmountIn + tokenAmountOut + swapFeeAmount + blockNumber + blockTimestamp + transactionHash + } + shares(first: 1000) { + id + pool { + id + } + balance + } +} + +query Users( + $skip: Int + $first: Int + $orderBy: User_orderBy + $orderDirection: OrderDirection + $where: User_filter + $block: Block_height +) { + users( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...User + } +} From f607354b52f86e5b1b842df63f93ec2eee346441 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:38:44 +0100 Subject: [PATCH 02/14] v3 general docs --- app-architecture.excalidraw | 1832 +++++++++++++++++++++++++++++++++++ modules/v3/README.md | 34 + 2 files changed, 1866 insertions(+) create mode 100644 app-architecture.excalidraw create mode 100644 modules/v3/README.md diff --git a/app-architecture.excalidraw b/app-architecture.excalidraw new file mode 100644 index 000000000..16cc41d47 --- /dev/null +++ b/app-architecture.excalidraw @@ -0,0 +1,1832 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 865, + "versionNonce": 618847347, + "isDeleted": false, + "id": "0rCUkN1a2gY5msly4lPoJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 626.0769073589, + "y": 1234.8874532379957, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 805.0668571070347, + "height": 339.28462450621436, + "seed": 485198810, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "9Dx_1COVEMPVHccffiLwH" + }, + { + "id": "VboAKrgz851OORtW9No3h", + "type": "arrow" + }, + { + "id": "y_uM2fFfpaEi9hwrpZBqP", + "type": "arrow" + }, + { + "id": "614vaW_3XHnGgPUnoYSFE", + "type": "arrow" + } + ], + "updated": 1707940476825, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 574, + "versionNonce": 412435485, + "isDeleted": false, + "id": "9Dx_1COVEMPVHccffiLwH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 631.0769073589, + "y": 1239.8874532379957, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 286.9158935546875, + "height": 70, + "seed": 1498115654, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707940454699, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "\n Configured context", + "textAlign": "left", + "verticalAlign": "top", + "containerId": "0rCUkN1a2gY5msly4lPoJ", + "originalText": "\n Configured context", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 168, + "versionNonce": 1333760326, + "isDeleted": false, + "id": "l9lNn3ZlV1VxxGrVFqyBr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 512.3144110597245, + "y": 84.15771359161883, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 504.25177001953125, + "height": 45, + "seed": 1343185222, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707925076170, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "API application architecture", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "API application architecture", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 374, + "versionNonce": 1338218266, + "isDeleted": false, + "id": "rtyvvejqKNEkFQQW0oda0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 629.7265182237195, + "y": 261.2164066662183, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 219.95703124999997, + "height": 103.03515625, + "seed": 700003270, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "95gidIyH9fC5A3lmczIdh" + }, + { + "id": "vg6-l8Xznua-BCWboSEs4", + "type": "arrow" + }, + { + "id": "j-VKbIqAdLxV6NjHu9da1", + "type": "arrow" + } + ], + "updated": 1707925098341, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 472, + "versionNonce": 2083890138, + "isDeleted": false, + "id": "95gidIyH9fC5A3lmczIdh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 680.255090306239, + "y": 300.2339847912183, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 118.89988708496094, + "height": 25, + "seed": 728430426, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707925098341, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Express app", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rtyvvejqKNEkFQQW0oda0", + "originalText": "Express app", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 498, + "versionNonce": 226062170, + "isDeleted": false, + "id": "bvxjwWJEryYTp1hXvIEk5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 358.6028636353002, + "y": 478.09765624999994, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 547445830, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "0lVehAIjpyAIvWJF1OD0o" + }, + { + "id": "vg6-l8Xznua-BCWboSEs4", + "type": "arrow" + }, + { + "id": "uj7GcQ_fyBRff70WrSx4w", + "type": "arrow" + } + ], + "updated": 1707925337870, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 643, + "versionNonce": 2126256518, + "isDeleted": false, + "id": "0lVehAIjpyAIvWJF1OD0o", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 389.77093919682363, + "y": 513.0859375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 245.99978637695312, + "height": 50, + "seed": 905569158, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707925104984, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "POST /jobs\nruns the background jobs", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bvxjwWJEryYTp1hXvIEk5", + "originalText": "POST /jobs\nruns the background jobs", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 506, + "versionNonce": 1183149210, + "isDeleted": false, + "id": "BI0s-hG2xUwjQmFAhFfn0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 838.9249916537059, + "y": 480.9868795587057, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 427759302, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "vmTgASKFvWEof8L2w_UmV" + }, + { + "id": "j-VKbIqAdLxV6NjHu9da1", + "type": "arrow" + }, + { + "id": "4x-jVg3VAfW0rFQzP402V", + "type": "arrow" + } + ], + "updated": 1707926250651, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 689, + "versionNonce": 887754886, + "isDeleted": false, + "id": "vmTgASKFvWEof8L2w_UmV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 921.1630135042918, + "y": 515.9751608087057, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 143.85989379882812, + "height": 50, + "seed": 1883000326, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926247787, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "POST /graphql\nApollo server", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BI0s-hG2xUwjQmFAhFfn0", + "originalText": "POST /graphql\nApollo server", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 670, + "versionNonce": 1661281715, + "isDeleted": false, + "id": "b5Ml9fkgIPa8v2fp9Kt9d", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1811.227829284344, + "y": 267.40536771482607, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 2112226522, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "wN-cyeOs1s97VjXdqlN1_" + } + ], + "updated": 1707940527608, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 687, + "versionNonce": 224130899, + "isDeleted": false, + "id": "wN-cyeOs1s97VjXdqlN1_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1920.115845031414, + "y": 314.89364896482607, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 90.55990600585938, + "height": 25, + "seed": 524573082, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707940527608, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Scheduler", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "b5Ml9fkgIPa8v2fp9Kt9d", + "originalText": "Scheduler", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 110, + "versionNonce": 725348358, + "isDeleted": false, + "id": "vg6-l8Xznua-BCWboSEs4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 731.8248229331246, + "y": 367.0552833346115, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 212.6538719242776, + "height": 109.48671833378177, + "seed": 37291354, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707925104984, + "link": null, + "locked": false, + "startBinding": { + "elementId": "rtyvvejqKNEkFQQW0oda0", + "focus": -0.4644458004203634, + "gap": 2.8037204183931976 + }, + "endBinding": { + "elementId": "bvxjwWJEryYTp1hXvIEk5", + "focus": -0.4179643889919988, + "gap": 1.555654581606774 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -212.6538719242776, + 109.48671833378177 + ] + ] + }, + { + "type": "arrow", + "version": 87, + "versionNonce": 1234708230, + "isDeleted": false, + "id": "j-VKbIqAdLxV6NjHu9da1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 740.7665820910862, + "y": 367.1177833346115, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 251.76652049132224, + "height": 104.89156664248742, + "seed": 370382342, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707926247787, + "link": null, + "locked": false, + "startBinding": { + "elementId": "rtyvvejqKNEkFQQW0oda0", + "focus": 0.5530999063539602, + "gap": 2.8662204183931976 + }, + "endBinding": { + "elementId": "BI0s-hG2xUwjQmFAhFfn0", + "focus": 0.5533215980667758, + "gap": 8.977529581606802 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 251.76652049132224, + 104.89156664248742 + ] + ] + }, + { + "type": "rectangle", + "version": 563, + "versionNonce": 1271079898, + "isDeleted": false, + "id": "PQ8zJtBlZuvNesoG3T0A0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 358.7075006737633, + "y": 777.2549019291434, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 474475738, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "yhshF7mYuuI1NENOq7m0w" + }, + { + "id": "p04FjRC7bFzLdr2STyCnI", + "type": "arrow" + }, + { + "id": "gAkm6uT_7wSXCu95CFn8U", + "type": "arrow" + }, + { + "id": "uj7GcQ_fyBRff70WrSx4w", + "type": "arrow" + }, + { + "id": "RHVw0U5boLMcrEmQQbDqj", + "type": "arrow" + } + ], + "updated": 1707925770470, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 803, + "versionNonce": 1709529242, + "isDeleted": false, + "id": "yhshF7mYuuI1NENOq7m0w", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 419.78553412102895, + "y": 812.2431831791434, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 186.17987060546875, + "height": 50, + "seed": 1764311450, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707925572928, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Handler\nknows what to call", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PQ8zJtBlZuvNesoG3T0A0", + "originalText": "Handler\nknows what to call", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 705, + "versionNonce": 1945243354, + "isDeleted": false, + "id": "vRh35_WImTNMpdlYPHt7E", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -145.3083445419249, + "y": 771.9810153460681, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 2077206278, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Yq8uvKvWpUrW3aM02foeq" + }, + { + "id": "p04FjRC7bFzLdr2STyCnI", + "type": "arrow" + } + ], + "updated": 1707925776969, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 965, + "versionNonce": 950901574, + "isDeleted": false, + "id": "Yq8uvKvWpUrW3aM02foeq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -136.04026287688583, + "y": 794.4692965960681, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 289.7997741699219, + "height": 75, + "seed": 311481926, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926212942, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Execution logic\n(concurrency, tracing, alerts, \nmonitoring)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "vRh35_WImTNMpdlYPHt7E", + "originalText": "Execution logic\n(concurrency, tracing, alerts, monitoring)", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "arrow", + "version": 242, + "versionNonce": 420593478, + "isDeleted": false, + "id": "p04FjRC7bFzLdr2STyCnI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 170.329589698084, + "y": 834.1066579326716, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 183.46155436416043, + "height": 2.9490252402465558, + "seed": 2046748442, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707925779844, + "link": null, + "locked": false, + "startBinding": { + "elementId": "vRh35_WImTNMpdlYPHt7E", + "focus": -0.007334553906602865, + "gap": 7.301996740008889 + }, + "endBinding": { + "elementId": "PQ8zJtBlZuvNesoG3T0A0", + "focus": -0.037935209026344924, + "gap": 4.916356611518893 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 183.46155436416043, + 2.9490252402465558 + ] + ] + }, + { + "type": "rectangle", + "version": 795, + "versionNonce": 1475902086, + "isDeleted": false, + "id": "mGeAjrKYThqTz6OROStTH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -133.90378372517483, + "y": 1009.1715495169069, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 2079939782, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UNrJfhn0S00DQ2x_Tglun" + }, + { + "id": "gAkm6uT_7wSXCu95CFn8U", + "type": "arrow" + } + ], + "updated": 1707925805653, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1043, + "versionNonce": 693320134, + "isDeleted": false, + "id": "UNrJfhn0S00DQ2x_Tglun", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -43.14574997273343, + "y": 1056.659830766907, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 126.81986999511719, + "height": 25, + "seed": 1467864070, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707925805653, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Configuration", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "mGeAjrKYThqTz6OROStTH", + "originalText": "Configuration", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 441, + "versionNonce": 1436774598, + "isDeleted": false, + "id": "gAkm6uT_7wSXCu95CFn8U", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 181.09991116802735, + "y": 1079.4842931952699, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 163.93625108134245, + "height": 2.4055138321657523, + "seed": 757321606, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707926019123, + "link": null, + "locked": false, + "startBinding": { + "elementId": "mGeAjrKYThqTz6OROStTH", + "focus": 0.20699809207901113, + "gap": 6.6677573932022085 + }, + "endBinding": { + "elementId": "nplU0SHojzsk4FXPETiYF", + "focus": -0.01742985684773468, + "gap": 6.50532555727159 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 163.93625108134245, + -2.4055138321657523 + ] + ] + }, + { + "type": "rectangle", + "version": 799, + "versionNonce": 546811709, + "isDeleted": false, + "id": "nplU0SHojzsk4FXPETiYF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 351.5414878066414, + "y": 1013.6478465678853, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 1382588294, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "OQu_Hxf7mq-UU4hUK9caz" + }, + { + "id": "gAkm6uT_7wSXCu95CFn8U", + "type": "arrow" + }, + { + "id": "RHVw0U5boLMcrEmQQbDqj", + "type": "arrow" + }, + { + "id": "614vaW_3XHnGgPUnoYSFE", + "type": "arrow" + }, + { + "id": "zwnghJfQIHaTc7m3J2hIg", + "type": "arrow" + } + ], + "updated": 1707940418860, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1026, + "versionNonce": 1668187078, + "isDeleted": false, + "id": "OQu_Hxf7mq-UU4hUK9caz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 395.399550550782, + "y": 1036.1361278178854, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 220.61981201171875, + "height": 75, + "seed": 209108678, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926019123, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Controllers\nknows how to execute,\ncreates context", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "nplU0SHojzsk4FXPETiYF", + "originalText": "Controllers\nknows how to execute,\ncreates context", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "arrow", + "version": 97, + "versionNonce": 1740671834, + "isDeleted": false, + "id": "uj7GcQ_fyBRff70WrSx4w", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 513.779012861997, + "y": 600.8473679154308, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.2527582283347556, + "height": 173.37700981947967, + "seed": 472969158, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707925514888, + "link": null, + "locked": false, + "startBinding": { + "elementId": "bvxjwWJEryYTp1hXvIEk5", + "focus": -0.01362000814081663, + "gap": 2.7731491654307945 + }, + "endBinding": { + "elementId": "PQ8zJtBlZuvNesoG3T0A0", + "focus": -0.022740965350795648, + "gap": 3.030524194232953 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.2527582283347556, + 173.37700981947967 + ] + ] + }, + { + "type": "rectangle", + "version": 974, + "versionNonce": 488162301, + "isDeleted": false, + "id": "z1P6dAwrnt95XQLJrTDlz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 349.3956006344352, + "y": 1678.7191717521068, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 1145436550, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "jqXGGNGzRN3ai7m8gvJpq" + }, + { + "id": "Pp3cnjDdYnCjvNGio5jzA", + "type": "arrow" + }, + { + "id": "ObWGMI7bb4fMOA7infU3T", + "type": "arrow" + }, + { + "id": "y_uM2fFfpaEi9hwrpZBqP", + "type": "arrow" + }, + { + "id": "zwnghJfQIHaTc7m3J2hIg", + "type": "arrow" + } + ], + "updated": 1707940418860, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1294, + "versionNonce": 1908571526, + "isDeleted": false, + "id": "jqXGGNGzRN3ai7m8gvJpq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 358.7736829098258, + "y": 1713.7074530021068, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 289.57977294921875, + "height": 50, + "seed": 1301862598, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926052057, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Actions\nknows what needs to be done", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z1P6dAwrnt95XQLJrTDlz", + "originalText": "Actions\nknows what needs to be done", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 1494, + "versionNonce": 859913693, + "isDeleted": false, + "id": "SF-Ct3jvdbXrlXmM5qhTu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1068.067252614944, + "y": 1378.9330618856036, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 1492098714, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "2759MoHnFvyuXdinx-RFE" + }, + { + "id": "VboAKrgz851OORtW9No3h", + "type": "arrow" + } + ], + "updated": 1707940458158, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1887, + "versionNonce": 1227366621, + "isDeleted": false, + "id": "2759MoHnFvyuXdinx-RFE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1171.0752634792018, + "y": 1413.9213431356036, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 102.31991577148438, + "height": 50, + "seed": 561298266, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707940509638, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Stores\nDB models", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "SF-Ct3jvdbXrlXmM5qhTu", + "originalText": "Stores\nDB models", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 1544, + "versionNonce": 386015357, + "isDeleted": false, + "id": "4IzITRueridnIqiLKsGi1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 678.2142338689831, + "y": 1379.9813732445514, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 1345582426, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "O3yNoHaKP0LBUhiIm84ii" + } + ], + "updated": 1707940408758, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1973, + "versionNonce": 37947613, + "isDeleted": false, + "id": "O3yNoHaKP0LBUhiIm84ii", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 714.0723042425183, + "y": 1414.9696544945514, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 236.6197967529297, + "height": 50, + "seed": 1132476954, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707940408758, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data sources\nsubgraph, contract, logs", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4IzITRueridnIqiLKsGi1", + "originalText": "Data sources\nsubgraph, contract, logs", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 1256, + "versionNonce": 686864070, + "isDeleted": false, + "id": "tO4GEy8oBl6piCMtE5BYt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 342.27168157891697, + "y": 1957.431911454487, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 2022345670, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "M-6U6BUI39ixWdxUNP_YS" + }, + { + "id": "ObWGMI7bb4fMOA7infU3T", + "type": "arrow" + } + ], + "updated": 1707926030524, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1756, + "versionNonce": 1618603526, + "isDeleted": false, + "id": "M-6U6BUI39ixWdxUNP_YS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 364.42976263360447, + "y": 1992.420192704487, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 264.019775390625, + "height": 50, + "seed": 672211718, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926030524, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Transformers\nprepares data for the DB", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tO4GEy8oBl6piCMtE5BYt", + "originalText": "Transformers\nprepares data for the DB", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "arrow", + "version": 933, + "versionNonce": 541887110, + "isDeleted": false, + "id": "ObWGMI7bb4fMOA7infU3T", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 503.1177388531386, + "y": 1951.683528736298, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.20933401772674642, + "height": 146.81350481066056, + "seed": 1088990726, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707926052057, + "link": null, + "locked": false, + "startBinding": { + "elementId": "tO4GEy8oBl6piCMtE5BYt", + "focus": 0.0438986464455738, + "gap": 5.74838271818885 + }, + "endBinding": { + "elementId": "z1P6dAwrnt95XQLJrTDlz", + "focus": 0.004858900627053375, + "gap": 6.174289673530666 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.20933401772674642, + -146.81350481066056 + ] + ] + }, + { + "type": "arrow", + "version": 38, + "versionNonce": 791050054, + "isDeleted": false, + "id": "RHVw0U5boLMcrEmQQbDqj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 508.9348937747624, + "y": 901.1234252897526, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.693399649831008, + "height": 108.88017912269822, + "seed": 1922867462, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707926019124, + "link": null, + "locked": false, + "startBinding": { + "elementId": "PQ8zJtBlZuvNesoG3T0A0", + "focus": 0.03183082197404965, + "gap": 3.8919608606090605 + }, + "endBinding": { + "elementId": "nplU0SHojzsk4FXPETiYF", + "focus": 0.03809457720533805, + "gap": 3.644242155434256 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.693399649831008, + 108.88017912269822 + ] + ] + }, + { + "type": "text", + "version": 144, + "versionNonce": 615599130, + "isDeleted": false, + "id": "MCju5oeR-thaF0t-6Co6C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 167.47715758645728, + "y": 302.51959011572853, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 140.47592163085938, + "height": 35, + "seed": 2043388314, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926152525, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "DB writing", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "DB writing", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 209, + "versionNonce": 371194694, + "isDeleted": false, + "id": "q375xvuA_Vs7J6Gglhqrz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1223.9749075834748, + "y": 304.1284151646575, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 151.39593505859375, + "height": 35, + "seed": 1599926554, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926164810, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "DB reading", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "DB reading", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 542, + "versionNonce": 709293715, + "isDeleted": false, + "id": "6uYVJb4hhqsXKen0kvDn9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 849.9003831051311, + "y": 781.7174259605832, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.3359375, + "height": 119.9765625, + "seed": 201315334, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "3I468xPZ0ZRZtcCurgeWT" + }, + { + "id": "4x-jVg3VAfW0rFQzP402V", + "type": "arrow" + }, + { + "id": "VboAKrgz851OORtW9No3h", + "type": "arrow" + } + ], + "updated": 1707940286790, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 743, + "versionNonce": 83141318, + "isDeleted": false, + "id": "3I468xPZ0ZRZtcCurgeWT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 968.3383866451702, + "y": 829.2057072105832, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 71.45993041992188, + "height": 25, + "seed": 1714643782, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707926244620, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "loaders", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "6uYVJb4hhqsXKen0kvDn9", + "originalText": "loaders", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 28, + "versionNonce": 2133703386, + "isDeleted": false, + "id": "4x-jVg3VAfW0rFQzP402V", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 991.4503747299802, + "y": 611.2276276530748, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.781409869044637, + "height": 164.65368709275606, + "seed": 1167236486, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707926252727, + "link": null, + "locked": false, + "startBinding": { + "elementId": "BI0s-hG2xUwjQmFAhFfn0", + "focus": 0.028958350055107673, + "gap": 10.26418559436911 + }, + "endBinding": { + "elementId": "6uYVJb4hhqsXKen0kvDn9", + "focus": -0.01995376479451029, + "gap": 5.83611121475235 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 6.781409869044637, + 164.65368709275606 + ] + ] + }, + { + "id": "VboAKrgz851OORtW9No3h", + "type": "arrow", + "x": 999.3729793068529, + "y": 1210.1039915799047, + "width": 3.842047300777608, + "height": 282.1120384916244, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1530990877, + "version": 1580, + "versionNonce": 160865203, + "isDeleted": false, + "boundElements": null, + "updated": 1707940476825, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -3.842047300777608, + -282.1120384916244 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "0rCUkN1a2gY5msly4lPoJ", + "gap": 24.7834616580908, + "focus": -0.06567990475367153 + }, + "endBinding": { + "elementId": "6uYVJb4hhqsXKen0kvDn9", + "gap": 26.297964627696956, + "focus": 0.06266764348604946 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "y_uM2fFfpaEi9hwrpZBqP", + "type": "arrow", + "x": 708.0096168316634, + "y": 1590.571541415734, + "width": 103.06612005250531, + "height": 77.30992844466846, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 420217171, + "version": 954, + "versionNonce": 290687731, + "isDeleted": false, + "boundElements": null, + "updated": 1707940476825, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -103.06612005250531, + 77.30992844466846 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "0rCUkN1a2gY5msly4lPoJ", + "gap": 16.39946367152379, + "focus": 0.11499207460118326 + }, + "endBinding": { + "elementId": "z1P6dAwrnt95XQLJrTDlz", + "gap": 10.837701891704455, + "focus": 0.02971667668683233 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "614vaW_3XHnGgPUnoYSFE", + "type": "arrow", + "x": 710.4007569205448, + "y": 1220.377265850082, + "width": 95.30409985136987, + "height": 69.33891056634707, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 336140285, + "version": 983, + "versionNonce": 260942387, + "isDeleted": false, + "boundElements": null, + "updated": 1707940476825, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -95.30409985136987, + -69.33891056634707 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "0rCUkN1a2gY5msly4lPoJ", + "gap": 14.510187387913447, + "focus": -0.10312430370153185 + }, + "endBinding": { + "elementId": "nplU0SHojzsk4FXPETiYF", + "gap": 17.41394621584965, + "focus": -0.012679959910820856 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "zwnghJfQIHaTc7m3J2hIg", + "type": "arrow", + "x": 499.119442701254, + "y": 1150.4231809452544, + "width": 4.174397296830875, + "height": 507.8264164155132, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1578221843, + "version": 49, + "versionNonce": 1203290013, + "isDeleted": false, + "boundElements": null, + "updated": 1707940418860, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -4.174397296830875, + 507.8264164155132 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "nplU0SHojzsk4FXPETiYF", + "focus": 0.038528210058637914, + "gap": 16.798771877369347 + }, + "endBinding": { + "elementId": "z1P6dAwrnt95XQLJrTDlz", + "focus": -0.06000150598742052, + "gap": 20.469574391339165 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/modules/v3/README.md b/modules/v3/README.md new file mode 100644 index 000000000..75bd94b86 --- /dev/null +++ b/modules/v3/README.md @@ -0,0 +1,34 @@ +### Architecture + +The API is a monolith designed to cover 2 main functions: + +1. Serve graphql API with cached onchain state of pool and user data designed around frontend needs. It has 3 components: + - Jobs queue scheduler – is pushing jobs to the queue on cron schedule + - Jobs handler – executes ETL actions + - Server – serves graphql schema for reading +2. Provide SOR for liquidity on Balancer. It's route finding is based on BSF on the pools directed graph where nodes are the tokens and edges are triads: [pool.id, tokenIn, tokenOut]. + +#### Structural Layers + +Excalidraw diagram: app-architecture.excalidraw + +**/modules/controllers** +Controllers are resposible for creating context from configuration, eg: setting up external data sources or passing required configs to the actions. It's the entry point for executing logic based on the users / cron requests. + +**/modules/actions** +This directory contains the code that orchestrates the ETL process, calling the appropriate functions from the sources, transformers, and stores directories. This is the main logic of the ETL process. + +**/modules/sources** +Sources is the external data access layer, it's responsible for extracting data from 3 main sources: + +1. Subgraph – responsible for static data with low frequency updates +2. Contracts – used for quering real time data +3. Logs – extracting events for direct indexing skipping subgraph + +The extracted data is then passed on to the transform functions for DB format mapping. + +**/modules/sources/transformers** +Transformers are responsible for converting the extracted data format into DB expected one. This could involve cleaning the data, filtering it, aggregating it, or applying business rules. The transformed data is then passed on to the Stores for loading into the DB. + +**Application data access layer** +Prisma ORM to abstract the DB. From 56d9267e74bd621cc4f0d83dcb4d08a996850c82 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:39:41 +0100 Subject: [PATCH 03/14] clean config --- config/index.ts | 16 +++++++ config/sepolia.ts | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 config/index.ts create mode 100644 config/sepolia.ts diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 000000000..f661c4d58 --- /dev/null +++ b/config/index.ts @@ -0,0 +1,16 @@ +import { Chain } from '@prisma/client'; +import { NetworkData } from '@modules/network/network-config-types'; +import sepolia from './sepolia'; + +export default { + [Chain.ARBITRUM]: {} as NetworkData, + [Chain.AVALANCHE]: {} as NetworkData, + [Chain.BASE]: {} as NetworkData, + [Chain.FANTOM]: {} as NetworkData, + [Chain.GNOSIS]: {} as NetworkData, + [Chain.MAINNET]: {} as NetworkData, + [Chain.OPTIMISM]: {} as NetworkData, + [Chain.POLYGON]: {} as NetworkData, + [Chain.SEPOLIA]: sepolia, + [Chain.ZKEVM]: {} as NetworkData, +}; diff --git a/config/sepolia.ts b/config/sepolia.ts new file mode 100644 index 000000000..691d2f43c --- /dev/null +++ b/config/sepolia.ts @@ -0,0 +1,112 @@ +import { env } from '@app/env'; +import { NetworkData } from '@modules/network/network-config-types'; +import { BigNumber } from 'ethers'; + +export default { + chain: { + slug: 'sepolia', + id: 11155111, + nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + wrappedNativeAssetAddress: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + prismaId: 'SEPOLIA', + gqlId: 'SEPOLIA', + }, + subgraphs: { + startDate: '2023-05-03', + balancer: 'https://api.studio.thegraph.com/query/24660/balancer-sepolia-v2/version/latest', + balancerV3: 'https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest', + beetsBar: 'https://', + blocks: 'https://api.studio.thegraph.com/query/48427/bleu-sepolia-blocks/version/latest', + gauge: 'https://api.studio.thegraph.com/proxy/24660/balancer-gauges-sepolia/version/latest', + // veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + userBalances: 'https://', + }, + eth: { + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + symbol: 'ETH', + name: 'Ether', + }, + weth: { + address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + addressFormatted: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + }, + coingecko: { + nativeAssetId: 'ethereum', + platformId: 'ethereum', + excludedTokenAddresses: [], + }, + rpcUrl: env.GROVE_CITY + ? `https://sepolia.rpc.grove.city/v1/${env.GROVE_CITY}` + : env.INFURA_API_KEY + ? `https://sepolia.infura.io/v3/${env.INFURA_API_KEY}` + : 'https://gateway.tenderly.co/public/sepolia', + rpcMaxBlockRange: 700, + protocolToken: 'bal', + bal: { + address: '0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75', + }, + // veBal: { + // address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', + // delegationProxy: '0x81cfae226343b24ba12ec6521db2c79e7aeeb310', + // }, + balancer: { + v2: { + vaultAddress: '0xba12222222228d8ba445958a75a0704d566bf2c8', + defaultSwapFeePercentage: '0.5', + defaultYieldFeePercentage: '0.5', + balancerQueriesAddress: '0xe39b5e3b6d74016b2f6a9673d7d7493b6df549d5', + }, + v3: { + vaultAddress: '0x816e90DC85bF016455017a76Bc09CC0451Eeb308', + defaultSwapFeePercentage: '0.5', + defaultYieldFeePercentage: '0.5', + }, + }, + multicall: '0x80c7dd17b01855a6d2347444a0fcc36136a314de', + multicall3: '0xca11bde05977b3631167028862be2a173976ca11', + avgBlockSpeed: 1, + sor: { + main: { + url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], + }, + canary: { + url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', + maxPools: 8, + forceRefresh: false, + gasPrice: BigNumber.from(10), + swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], + }, + }, + ybAprConfig: {}, + datastudio: { + main: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + canary: { + user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', + sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', + databaseTabName: 'Database v2', + compositionTabName: 'Pool Composition v2', + emissionDataTabName: 'EmissionData', + }, + }, + monitoring: { + main: { + alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', + }, + canary: { + alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', + }, + }, +}; From c0a8f3e5bd9ca42873b57e5efd54c5e04024fb4a Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:41:14 +0100 Subject: [PATCH 04/14] viem chain mapping --- modules/content/github-content.service.ts | 2 +- modules/context/header-chain.ts | 2 +- modules/network/chain-id-to-chain.ts | 14 ++++++++++++++ modules/network/network-config.ts | 13 ------------- 4 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 modules/network/chain-id-to-chain.ts diff --git a/modules/content/github-content.service.ts b/modules/content/github-content.service.ts index b8eecb11d..079adc6cc 100644 --- a/modules/content/github-content.service.ts +++ b/modules/content/github-content.service.ts @@ -4,7 +4,7 @@ import axios from 'axios'; import { prisma } from '../../prisma/prisma-client'; import { networkContext } from '../network/network-context.service'; import { ContentService, FeaturedPool, HomeScreenFeaturedPoolGroup, HomeScreenNewsItem } from './content-types'; -import { chainIdToChain } from '../network/network-config'; +import { chainIdToChain } from '../network/chain-id-to-chain'; import { LinearData } from '../pool/subgraph-mapper'; const POOLS_METADATA_URL = 'https://raw.githubusercontent.com/balancer/metadata/main/pools/featured.json'; diff --git a/modules/context/header-chain.ts b/modules/context/header-chain.ts index 799f9d097..55224f32b 100644 --- a/modules/context/header-chain.ts +++ b/modules/context/header-chain.ts @@ -1,6 +1,6 @@ import { getRequestScopeContextValue } from '../context/request-scoped-context'; import { Chain } from '@prisma/client'; -import { chainIdToChain } from '../network/network-config'; +import { chainIdToChain } from '../network/chain-id-to-chain'; /** * Setup to transition out from the old header-based chainIDs to the new required chain query filters. diff --git a/modules/network/chain-id-to-chain.ts b/modules/network/chain-id-to-chain.ts new file mode 100644 index 000000000..49cdba0ab --- /dev/null +++ b/modules/network/chain-id-to-chain.ts @@ -0,0 +1,14 @@ +import { Chain } from '@prisma/client'; + +export const chainIdToChain: { [id: string]: Chain } = { + '1': Chain.MAINNET, + '10': Chain.OPTIMISM, + '100': Chain.GNOSIS, + '137': Chain.POLYGON, + '250': Chain.FANTOM, + '1101': Chain.ZKEVM, + '8453': Chain.BASE, + '42161': Chain.ARBITRUM, + '43114': Chain.AVALANCHE, + '11155111': Chain.SEPOLIA, +}; diff --git a/modules/network/network-config.ts b/modules/network/network-config.ts index fd555b33e..1cb0c6033 100644 --- a/modules/network/network-config.ts +++ b/modules/network/network-config.ts @@ -37,19 +37,6 @@ export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig } SEPOLIA: sepoliaNetworkConfig, }; -export const chainIdToChain: { [id: string]: Chain } = { - '1': Chain.MAINNET, - '10': Chain.OPTIMISM, - '100': Chain.GNOSIS, - '137': Chain.POLYGON, - '250': Chain.FANTOM, - '1101': Chain.ZKEVM, - '8453': Chain.BASE, - '42161': Chain.ARBITRUM, - '43114': Chain.AVALANCHE, - '11155111': Chain.SEPOLIA, -}; - export const BalancerChainIds = ['1', '137', '42161', '100', '1101', '43114', '8453', '11155111']; export const BeethovenChainIds = ['250', '10']; From 30e07718df0f1d83007313b0e87e151e1453e253 Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:41:43 +0100 Subject: [PATCH 05/14] v3 pools --- jest.config.js | 5 + modules/actions/jobs-actions/index.ts | 1 + .../actions/jobs-actions/sync-pools.test.ts | 57 +++++++ modules/actions/jobs-actions/sync-pools.ts | 148 ++++++++++++++++++ modules/controllers/jobs-controller.test.ts | 21 +++ modules/controllers/jobs-controller.ts | 41 +++++ modules/network/network-config-types.ts | 1 + modules/network/sepolia.ts | 112 +------------ modules/pool/abi/VaultV3.json | 4 +- .../sources/contracts/abis/weighted_pool.abi | 0 .../sources/contracts/fetch-erc20-headers.ts | 54 +++++++ .../sources/contracts/fetch-pool-tokens.ts | 51 ++++++ .../contracts/fetch-weighted-pools-data.ts | 41 +++++ modules/sources/contracts/index.ts | 3 + modules/sources/logs/get-new-pools.ts | 142 +++++++++++++++++ modules/sources/logs/get-swaps.ts | 58 +++++++ modules/sources/logs/get-transfers.ts | 48 ++++++ modules/sources/logs/index.ts | 3 + modules/sources/transformers/index.ts | 2 + .../transformers/pool-tokens-transformer.ts | 13 ++ .../sources/transformers/pool-transformer.ts | 24 +++ modules/sources/types.ts | 1 + modules/sources/viem-client.ts | 37 +++++ package.json | 9 +- tasks/index.ts | 18 +++ tsconfig.build.json | 4 + tsconfig.json | 10 +- worker/job-handlers.ts | 5 +- yarn.lock | 26 ++- 29 files changed, 822 insertions(+), 117 deletions(-) create mode 100644 modules/actions/jobs-actions/index.ts create mode 100644 modules/actions/jobs-actions/sync-pools.test.ts create mode 100644 modules/actions/jobs-actions/sync-pools.ts create mode 100644 modules/controllers/jobs-controller.test.ts create mode 100644 modules/controllers/jobs-controller.ts create mode 100644 modules/sources/contracts/abis/weighted_pool.abi create mode 100644 modules/sources/contracts/fetch-erc20-headers.ts create mode 100644 modules/sources/contracts/fetch-pool-tokens.ts create mode 100644 modules/sources/contracts/fetch-weighted-pools-data.ts create mode 100644 modules/sources/contracts/index.ts create mode 100644 modules/sources/logs/get-new-pools.ts create mode 100644 modules/sources/logs/get-swaps.ts create mode 100644 modules/sources/logs/get-transfers.ts create mode 100644 modules/sources/logs/index.ts create mode 100644 modules/sources/transformers/index.ts create mode 100644 modules/sources/transformers/pool-tokens-transformer.ts create mode 100644 modules/sources/transformers/pool-transformer.ts create mode 100644 modules/sources/types.ts create mode 100644 modules/sources/viem-client.ts create mode 100644 tasks/index.ts create mode 100644 tsconfig.build.json diff --git a/jest.config.js b/jest.config.js index ab69250fa..cda9284cf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,4 +10,9 @@ module.exports = { isolatedModules: true, }, }, + moduleNameMapper: { + '^@app/(.*)$': '/app/$1', + '^@config/(.*)$': '/config/$1', + '^@modules/(.*)$': '/modules/$1', + }, }; diff --git a/modules/actions/jobs-actions/index.ts b/modules/actions/jobs-actions/index.ts new file mode 100644 index 000000000..0d1fa40bf --- /dev/null +++ b/modules/actions/jobs-actions/index.ts @@ -0,0 +1 @@ +export * from './sync-pools'; diff --git a/modules/actions/jobs-actions/sync-pools.test.ts b/modules/actions/jobs-actions/sync-pools.test.ts new file mode 100644 index 000000000..c0aa81975 --- /dev/null +++ b/modules/actions/jobs-actions/sync-pools.test.ts @@ -0,0 +1,57 @@ +import { syncPools } from './sync-pools'; +import { prisma } from '../../../prisma/prisma-client'; +import { PrismaPool } from '@prisma/client'; +import { PoolFragment } from '@modules/subgraphs/balancer-v3-vault/generated/types'; +import { fetchErc20Headers } from '@modules/sources/contracts'; +import { getViemClient } from '@modules/sources/viem-client'; + +// Mock the module dependencies +jest.mock('@modules/sources/contracts', () => ({ + ...jest.requireActual('@modules/sources/contracts'), + fetchErc20Headers: jest.fn().mockResolvedValue({ '2': { name: 'name', symbol: 'symbol' } }), + fetchWeightedPoolData: jest.fn().mockResolvedValue({}), + fetchPoolTokens: jest.fn().mockResolvedValue({}), +})); + +jest.mock('../../../prisma/prisma-client', () => ({ + prisma: { + prismaPool: { + findMany: jest.fn().mockResolvedValue([{ id: '1' }] as PrismaPool[]), + create: jest.fn(), + }, + prismaToken: { + findMany: jest.fn(), + createMany: jest.fn(), + }, + prismaPoolExpandedTokens: { + createMany: jest.fn(), + }, + prismaPoolTokenDynamicData: { + createMany: jest.fn(), + }, + }, +})); + +describe('syncPools', () => { + const subgraphClient = { + Pools: jest.fn().mockResolvedValue({ pools: [{ id: '1' }, { id: '2' }] as PoolFragment[] }), + }; + const viemClient = jest.mocked(getViemClient('SEPOLIA')); + + beforeEach(() => { + jest.clearAllMocks(); + return syncPools(subgraphClient, viemClient, 'vaultAddress', 'SEPOLIA'); + }); + + it('should fetch pools from subgraph', async () => { + expect(subgraphClient.Pools).toHaveBeenCalled(); + }); + + it('should fetch additional data from contracts for missing pools', async () => { + expect(fetchErc20Headers).toHaveBeenCalledWith(['2'], expect.anything()); + }); + + it('should store missing pools in the database', async () => { + expect(prisma.prismaPool.create).toHaveBeenCalledWith({ data: expect.objectContaining({ id: '2' }) }); + }); +}); diff --git a/modules/actions/jobs-actions/sync-pools.ts b/modules/actions/jobs-actions/sync-pools.ts new file mode 100644 index 000000000..c14538c5c --- /dev/null +++ b/modules/actions/jobs-actions/sync-pools.ts @@ -0,0 +1,148 @@ +import { Chain } from '@prisma/client'; +import { prisma } from '../../../prisma/prisma-client'; +import { V3SubgraphClient } from '@modules/subgraphs/balancer-v3-vault'; +import { poolTransformer, poolTokensTransformer } from '@modules/sources/transformers'; +import { fetchErc20Headers, fetchWeightedPoolData, fetchPoolTokens } from '@modules/sources/contracts'; +import type { ViemClient } from 'modules/sources/types'; + +/** + * Makes sure that all pools are synced in the database + * + * @param subgraphClient + * @param chain + * @returns syncedPools - the pools that were synced + */ +export async function syncPools( + subgraphClient: Pick, + viemClient: ViemClient, + vaultAddress: string, + chain = 'SEPOLIA' as Chain, +) { + // Fetch pools from subgraph + const { pools: subgraphPools } = await subgraphClient.Pools(); + + // Find pools missing from the database + const dbPools = await prisma.prismaPool.findMany({ where: { chain, vaultVersion: 3 } }); + const dbPoolIds = new Set(dbPools.map((pool) => pool.id.toLowerCase())); + const subgraphPoolIds = subgraphPools.map((pool) => pool.id.toLowerCase()); + const missingPools = subgraphPoolIds.filter((id) => !dbPoolIds.has(id)); + + if (missingPools.length === 0) { + return true; + } + + // Fetch additional data from contracts required in the DB for missing pools + const contractData = await fetchErc20Headers(missingPools as `0x${string}`[], viemClient); + const poolTokens = subgraphPools.filter((pool) => !dbPoolIds.has(pool.id)).flatMap((pool) => pool.tokens ?? []); + + // Fetch pool type specific data + // TODO: this will be covert by the subgraph, but in the meantime we need to fetch it from the contracts + const weightedPoolData = await fetchWeightedPoolData(missingPools, viemClient); + + // TODO: this fails for now, there is something wrong with the ABI, or vault contract + const poolTokenInfo = await fetchPoolTokens(vaultAddress, missingPools, viemClient); + + // Check if we need to get token information from the contracts as well + const tokenAddresses = poolTokens.map((token) => token.address); + const dbTokens = await prisma.prismaToken.findMany({ where: { address: { in: tokenAddresses }, chain } }); + const missingTokens = tokenAddresses.filter((address) => !dbTokens.some((token) => token.address === address)); + const tokenData = await fetchErc20Headers(missingTokens as `0x${string}`[], viemClient); + + // console.log(poolTokenInfo, weightedPoolData, contractData, tokenData); + // Store pool tokens and BPT in the tokens table before creating the pools + try { + const allTokens = Object.entries({ ...tokenData, ...contractData }).map(([address, token]) => ({ + ...token, + address, + })); + + await prisma.prismaToken.createMany({ + data: allTokens.map((data) => ({ ...data, chain })), + skipDuplicates: true, + }); + } catch (e) { + console.error('Error creating tokens', e); + } + + // Transform pool data for the database + const dbPoolEntries = missingPools + .map((id) => { + const subgraphPool = subgraphPools.find((pool) => pool.id === id); + const data = contractData[id]; + if (!subgraphPool || !data) { + // That won't happen, but TS doesn't know that + return null; + } + return { + ...poolTransformer(subgraphPool, data, chain), + typeData: JSON.stringify({}), + tokens: { + createMany: { + // TODO: Will be great to create all the token data here, including dynamic data + // but for now we can only store static data, because prisma doesn't support nested createMany + // to create dynamic data tabels as well. One solution is to move "dynamicData" to the tokens table + data: poolTokensTransformer(subgraphPool), + }, + }, + dynamicData: { + create: { + id: subgraphPool.id, + swapFee: '0', + blockNumber: Number(subgraphPool.blockNumber), + swapEnabled: true, + totalLiquidity: 1, + totalShares: '1', // TODO: update once we get the value from SG: subgraphPool.totalShares, + totalSharesNum: 1, // TODO: update once we get the value from SG: subgraphPool.totalShares, + }, + }, + }; + }) + .filter((entry): entry is NonNullable => entry !== null); + + // Store missing pools in the database + let allOk = true; + for (const data of dbPoolEntries) { + try { + console.log('Storing', data.id); + const weightedData = weightedPoolData[data.id]; + // poolTokenInfo is failing for now, there is something wrong with the ABI, or vault contract + // Once that is fixed, get the token balances in place + const poolData = poolTokenInfo[data.id]; + await prisma.prismaPool.create({ data }); + // Create pool tokens dynamic data + await prisma.prismaPoolTokenDynamicData.createMany({ + data: data.tokens.createMany.data.map((token, i) => { + return { + id: token.id, + poolTokenId: token.id, + chain, + blockNumber: data.dynamicData.create.blockNumber, + balance: '0', + balanceUSD: 0, + priceRate: '0', + weight: weightedData.weights[token.index] ?? '0', + latestFxPrice: null, + }; + }), + }); + } catch (e) { + // TODO: handle errors + console.error(`Error creating pool ${data.id}`, e); + allOk = false; + } + + // Create pool tokens – something to talk about if we want to do it here on write, or on read + // Pool queries fail without excplicitly creating the nested tokens relation + await prisma.prismaPoolExpandedTokens.createMany({ + skipDuplicates: true, + data: data.tokens.createMany.data.map((token) => ({ + poolId: data.id, + chain: chain, + tokenAddress: token.address, + nestedPoolId: token.nestedPoolId || null, + })), + }); + } + + return allOk; +} diff --git a/modules/controllers/jobs-controller.test.ts b/modules/controllers/jobs-controller.test.ts new file mode 100644 index 000000000..9138c6bdc --- /dev/null +++ b/modules/controllers/jobs-controller.test.ts @@ -0,0 +1,21 @@ +import { JobsController } from './jobs-controller'; +import * as actions from '@modules/actions/jobs-actions'; + +// Mock the actions +jest.mock('@modules/actions/jobs_actions', () => ({ + syncPools: jest.fn(), +})); + +describe('jobsController', () => { + const jobsController = JobsController(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call getClient with correct chain', () => { + jobsController.syncPools('11155111'); + + expect(actions.syncPools).toHaveBeenCalled(); + }); +}); diff --git a/modules/controllers/jobs-controller.ts b/modules/controllers/jobs-controller.ts new file mode 100644 index 000000000..764b76880 --- /dev/null +++ b/modules/controllers/jobs-controller.ts @@ -0,0 +1,41 @@ +import * as actions from '@modules/actions/jobs-actions'; +import * as subgraphV3Vault from '@modules/subgraphs/balancer-v3-vault'; +import config from '@config/index'; +import { chainIdToChain } from '@modules/network/chain-id-to-chain'; +import { getViemClient } from '@modules/sources/viem-client'; + +/** + * Controller responsible for matching job requests to configured job handlers + * + * @param name - the name of the job + * @param chain - the chain to run the job on + * @returns a controller with configured job handlers + */ +export function JobsController(tracer?: any) { + // Setup tracing + // ... + return { + syncPools(chainId: string) { + const chain = chainIdToChain[chainId]; + const { + subgraphs: { balancerV3 }, + balancer: { + v3: { vaultAddress }, + }, + } = config[chain]; + + // Guard against unconfigured chains + if (!balancerV3) { + throw new Error(`Chain not configured: ${chain}`); + } + + const subgraphClient = subgraphV3Vault.getClient(balancerV3); + const viemClient = getViemClient(chain); + + // TODO: add syncing v2 pools as well by splitting the poolService into separate + // actions with extracted configuration + + return actions.syncPools(subgraphClient, viemClient, vaultAddress, chain); + }, + }; +} diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index 345b484ca..4b0228e5a 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -65,6 +65,7 @@ export interface NetworkData { subgraphs: { startDate: string; balancer: string; + balancerV3?: string; blocks: string; masterchef?: string; reliquary?: string; diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index c25dbb054..22821bb90 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -1,5 +1,5 @@ -import { BigNumber, ethers } from 'ethers'; -import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; +import { ethers } from 'ethers'; +import { NetworkConfig } from './network-config-types'; import { tokenService } from '../token/token.service'; import { PhantomStableAprService } from '../pool/lib/apr-data-sources/phantom-stable-apr.service'; import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/boosted-pool-apr.service'; @@ -16,114 +16,10 @@ import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph import { CoingeckoPriceHandlerService } from '../token/lib/token-price-handlers/coingecko-price-handler.service'; import { coingeckoService } from '../coingecko/coingecko.service'; import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; -import { env } from '../../app/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; -export const sepoliaNetworkData: NetworkData = { - chain: { - slug: 'sepolia', - id: 11155111, - nativeAssetAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - wrappedNativeAssetAddress: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', - prismaId: 'SEPOLIA', - gqlId: 'SEPOLIA', - }, - subgraphs: { - startDate: '2023-05-03', - balancer: 'https://api.studio.thegraph.com/query/24660/balancer-sepolia-v2/version/latest', - beetsBar: 'https://', - blocks: 'https://api.studio.thegraph.com/query/48427/bleu-sepolia-blocks/version/latest', - gauge: 'https://api.studio.thegraph.com/proxy/24660/balancer-gauges-sepolia/version/latest', - // veBalLocks: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', - userBalances: 'https://', - }, - eth: { - address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - addressFormatted: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'ETH', - name: 'Ether', - }, - weth: { - address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', - addressFormatted: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', - }, - coingecko: { - nativeAssetId: 'ethereum', - platformId: 'ethereum', - excludedTokenAddresses: [], - }, - rpcUrl: env.INFURA_API_KEY - ? `https://sepolia.infura.io/v3/${env.INFURA_API_KEY}` - : 'https://gateway.tenderly.co/public/sepolia', - rpcMaxBlockRange: 700, - protocolToken: 'bal', - bal: { - address: '0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75', - }, - // veBal: { - // address: '0xc128a9954e6c874ea3d62ce62b468ba073093f25', - // delegationProxy: '0x81cfae226343b24ba12ec6521db2c79e7aeeb310', - // }, - balancer: { - v2: { - vaultAddress: '0xba12222222228d8ba445958a75a0704d566bf2c8', - defaultSwapFeePercentage: '0.5', - defaultYieldFeePercentage: '0.5', - balancerQueriesAddress: '0xe39b5e3b6d74016b2f6a9673d7d7493b6df549d5', - }, - v3: { - vaultAddress: '0x816e90DC85bF016455017a76Bc09CC0451Eeb308', - defaultSwapFeePercentage: '0.5', - defaultYieldFeePercentage: '0.5', - }, - }, - multicall: '0x80c7dd17b01855a6d2347444a0fcc36136a314de', - multicall3: '0xca11bde05977b3631167028862be2a173976ca11', - avgBlockSpeed: 1, - sor: { - main: { - url: 'https://uu6cfghhd5lqa7py3nojxkivd40zuugb.lambda-url.ca-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - poolIdsToExclude: [], - }, - canary: { - url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', - maxPools: 8, - forceRefresh: false, - gasPrice: BigNumber.from(10), - swapGas: BigNumber.from('1000000'), - poolIdsToExclude: [], - }, - }, - ybAprConfig: {}, - datastudio: { - main: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '11anHUEb9snGwvB-errb5HvO8TvoLTRJhkDdD80Gxw1Q', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - canary: { - user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', - sheetId: '1HnJOuRQXGy06tNgqjYMzQNIsaCSCC01Yxe_lZhXBDpY', - databaseTabName: 'Database v2', - compositionTabName: 'Pool Composition v2', - emissionDataTabName: 'EmissionData', - }, - }, - monitoring: { - main: { - alarmTopicArn: 'arn:aws:sns:ca-central-1:118697801881:api_alarms', - }, - canary: { - alarmTopicArn: 'arn:aws:sns:eu-central-1:118697801881:api_alarms', - }, - }, -}; +import sepoliaNetworkData from '@config/sepolia'; +export { sepoliaNetworkData }; export const sepoliaNetworkConfig: NetworkConfig = { data: sepoliaNetworkData, diff --git a/modules/pool/abi/VaultV3.json b/modules/pool/abi/VaultV3.json index 2e0ae17c0..e4ba48715 100644 --- a/modules/pool/abi/VaultV3.json +++ b/modules/pool/abi/VaultV3.json @@ -1314,7 +1314,7 @@ "stateMutability": "payable", "type": "receive" }, - + { "inputs": [ { "internalType": "contract IVault", @@ -3398,4 +3398,4 @@ "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] diff --git a/modules/sources/contracts/abis/weighted_pool.abi b/modules/sources/contracts/abis/weighted_pool.abi new file mode 100644 index 000000000..e69de29bb diff --git a/modules/sources/contracts/fetch-erc20-headers.ts b/modules/sources/contracts/fetch-erc20-headers.ts new file mode 100644 index 000000000..8ea18a97b --- /dev/null +++ b/modules/sources/contracts/fetch-erc20-headers.ts @@ -0,0 +1,54 @@ +import { parseAbi } from 'viem'; +import type { ViemClient } from '../types'; + +const poolAbi = [ + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', +]; + +const abi = parseAbi(poolAbi); + +export async function fetchErc20Headers(addresses: `0x${string}`[], client: ViemClient) { + const contracts = addresses + .map((address) => [ + { + address, + abi, + functionName: 'name', + }, + { + address, + abi, + functionName: 'symbol', + }, + { + address, + abi, + functionName: 'decimals', + }, + ]) + .flat(); + + const results = await client.multicall({ contracts }); + + // Parse the results + const parsedResults = results.map((result) => { + if (result.status === 'success' && result.result !== undefined) { + return result.result as string; + } + // Handle the error + return undefined; + }); + + return Object.fromEntries( + addresses.map((address, i) => [ + address, + { + name: String(parsedResults[i * 3]), + symbol: String(parsedResults[i * 3 + 1]), + decimals: Number(parsedResults[i * 3 + 2]), + }, + ]), + ); +} diff --git a/modules/sources/contracts/fetch-pool-tokens.ts b/modules/sources/contracts/fetch-pool-tokens.ts new file mode 100644 index 000000000..e6325f4b8 --- /dev/null +++ b/modules/sources/contracts/fetch-pool-tokens.ts @@ -0,0 +1,51 @@ +import { parseAbi } from 'viem'; +import { ViemClient } from '../types'; + +type PoolTokenInfo = { + tokens: `0x${string}`[]; + tokenTypes: number[]; + balancesRaw: bigint[]; + decimalScalingFactors: bigint[]; + rateProviders: `0x${string}`[]; +}; + +const abi = parseAbi([ + 'function getPoolTokenInfo(address pool) view returns (address[] tokens, uint8[] tokenTypes, uint[] balancesRaw, uint[] decimalScalingFactors, address[] rateProviders)', +]); + +export async function fetchPoolTokens(vault: string, pools: string[], client: ViemClient) { + const contracts = pools + .map((pool) => [ + { + address: vault as `0x${string}`, + abi, + args: [pool as `0x${string}`], + functionName: 'getPoolTokenInfo', + }, + ]) + .flat(); + + const results = await client.multicall({ contracts }); + + // Parse the results + const parsedResults = results + .map((result, i) => { + if (result.status === 'success' && result.result !== undefined) { + // parse the result here using the abi + const poolTokens = { + tokens: result.result[0], + tokenTypes: result.result[1], + balancesRaw: result.result[2], + decimalScalingFactors: result.result[3], + rateProviders: result.result[4], + } as PoolTokenInfo; + + return [pools[i], poolTokens]; + } + // Handle the error + return undefined; + }) + .filter((result): result is NonNullable => result !== undefined); + + return Object.fromEntries(parsedResults) as Record; +} diff --git a/modules/sources/contracts/fetch-weighted-pools-data.ts b/modules/sources/contracts/fetch-weighted-pools-data.ts new file mode 100644 index 000000000..d79c7868e --- /dev/null +++ b/modules/sources/contracts/fetch-weighted-pools-data.ts @@ -0,0 +1,41 @@ +import { parseAbi, formatEther } from 'viem'; +import { ViemClient } from '../types'; + +const abi = parseAbi(['function getNormalizedWeights() view returns (uint[] weights)']); + +interface WeightedPoolData { + weights: string[]; +} + +export async function fetchWeightedPoolData(pools: string[], client: ViemClient) { + const contracts = pools + .map((pool) => [ + { + address: pool as `0x${string}`, + abi, + args: [], + functionName: 'getNormalizedWeights', + }, + ]) + .flat(); + + const results = await client.multicall({ contracts }); + + // Parse the results + const parsedResults = results + .map((result, i) => { + if (result.status === 'success' && result.result !== undefined) { + return [ + pools[i], + { + weights: result.result.map((weight) => formatEther(weight)), + }, + ]; + } + // Handle the error + return undefined; + }) + .filter((result): result is NonNullable => result !== undefined); + + return Object.fromEntries(parsedResults) as Record; +} diff --git a/modules/sources/contracts/index.ts b/modules/sources/contracts/index.ts new file mode 100644 index 000000000..1347526f8 --- /dev/null +++ b/modules/sources/contracts/index.ts @@ -0,0 +1,3 @@ +export * from './fetch-erc20-headers'; +export * from './fetch-pool-tokens'; +export * from './fetch-weighted-pools-data'; diff --git a/modules/sources/logs/get-new-pools.ts b/modules/sources/logs/get-new-pools.ts new file mode 100644 index 000000000..5ef6dc038 --- /dev/null +++ b/modules/sources/logs/get-new-pools.ts @@ -0,0 +1,142 @@ +import { ViemClient } from '../types'; + +const event = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'factory', + type: 'address', + }, + { + components: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'yieldFeeExempt', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct TokenConfig[]', + name: 'tokenConfig', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'uint256', + name: 'pauseWindowEndTime', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'pauseManager', + type: 'address', + }, + { + components: [ + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct PoolCallbacks', + name: 'callbacks', + type: 'tuple', + }, + { + components: [ + { + internalType: 'bool', + name: 'supportsAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'supportsRemoveLiquidityCustom', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct LiquidityManagement', + name: 'liquidityManagement', + type: 'tuple', + }, + ], + name: 'PoolRegistered', + type: 'event', +} as const; + +/** + * Extract balances from the contract + */ +export const getNewPools = async (vaultAddress: string, client: ViemClient, fromBlock: bigint) => { + // Get Transfer logs from the vault + const logs = await client.getLogs({ + address: vaultAddress as `0x${string}`, + event, + fromBlock, + }); + + // Parse the logs + return logs; +}; diff --git a/modules/sources/logs/get-swaps.ts b/modules/sources/logs/get-swaps.ts new file mode 100644 index 000000000..1f042e1c6 --- /dev/null +++ b/modules/sources/logs/get-swaps.ts @@ -0,0 +1,58 @@ +import { ViemClient } from '../types'; + +const event = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'tokenIn', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'tokenOut', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'swapFeeAmount', + type: 'uint256', + }, + ], + name: 'Swap', + type: 'event', +} as const; + +/** + * Extract balances from the contract + */ +export const getSwaps = async (vaultAddress: string, client: ViemClient, fromBlock: bigint) => { + const logs = await client.getLogs({ + address: vaultAddress as `0x${string}`, + event, + fromBlock, + }); + + return logs; +}; diff --git a/modules/sources/logs/get-transfers.ts b/modules/sources/logs/get-transfers.ts new file mode 100644 index 000000000..7396b4f38 --- /dev/null +++ b/modules/sources/logs/get-transfers.ts @@ -0,0 +1,48 @@ +import { ViemClient } from '../types'; + +const event = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', +} as const; + +/** + * Extract balances from the contract + */ +export const getTransfers = async (vaultAddress: string, client: ViemClient, fromBlock: bigint) => { + // Get Transfer logs from the vault + const logs = await client.getLogs({ + address: vaultAddress as `0x${string}`, + event, + fromBlock, + }); + + // Parse the logs + return logs; +}; diff --git a/modules/sources/logs/index.ts b/modules/sources/logs/index.ts new file mode 100644 index 000000000..7bdec8277 --- /dev/null +++ b/modules/sources/logs/index.ts @@ -0,0 +1,3 @@ +export * from './get-swaps'; +export * from './get-transfers'; +export * from './get-new-pools'; diff --git a/modules/sources/transformers/index.ts b/modules/sources/transformers/index.ts new file mode 100644 index 000000000..ca74df49e --- /dev/null +++ b/modules/sources/transformers/index.ts @@ -0,0 +1,2 @@ +export * from './pool-transformer'; +export * from './pool-tokens-transformer'; diff --git a/modules/sources/transformers/pool-tokens-transformer.ts b/modules/sources/transformers/pool-tokens-transformer.ts new file mode 100644 index 000000000..d7f708370 --- /dev/null +++ b/modules/sources/transformers/pool-tokens-transformer.ts @@ -0,0 +1,13 @@ +import { PoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; + +export const poolTokensTransformer = (subgraphPool: PoolFragment) => { + const tokens = subgraphPool.tokens ?? []; + return tokens.map((token, i) => ({ + id: `${subgraphPool.id}-${token.address}`.toLowerCase(), + address: token.address.toLowerCase(), + index: token.index, + nestedPoolId: null, + priceRateProvider: subgraphPool.rateProviders![i].address.toLowerCase(), + exemptFromProtocolYieldFee: token.totalProtocolYieldFee === '0' ? true : false, + })); +}; diff --git a/modules/sources/transformers/pool-transformer.ts b/modules/sources/transformers/pool-transformer.ts new file mode 100644 index 000000000..674b08c7e --- /dev/null +++ b/modules/sources/transformers/pool-transformer.ts @@ -0,0 +1,24 @@ +import { Chain, PrismaPool, PrismaPoolType } from '@prisma/client'; +import { PoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; + +export const poolTransformer = ( + subgraphPool: PoolFragment, + contractData: { name?: string; symbol?: string }, + chain: Chain, +): PrismaPool => { + return { + id: subgraphPool.id.toLowerCase(), + chain: chain, + vaultVersion: 3, + address: subgraphPool.id.toLowerCase(), + decimals: 18, + symbol: contractData.symbol || '', + name: contractData.name || '', + owner: '', + factory: (subgraphPool.factory && subgraphPool.factory.toLowerCase()) || '', + type: PrismaPoolType.WEIGHTED, + typeData: {}, + version: 1, + createTime: Number(subgraphPool.blockTimestamp), + }; +}; diff --git a/modules/sources/types.ts b/modules/sources/types.ts new file mode 100644 index 000000000..489287794 --- /dev/null +++ b/modules/sources/types.ts @@ -0,0 +1 @@ +export type { ViemClient } from './viem-client'; diff --git a/modules/sources/viem-client.ts b/modules/sources/viem-client.ts new file mode 100644 index 000000000..447def93f --- /dev/null +++ b/modules/sources/viem-client.ts @@ -0,0 +1,37 @@ +import { createPublicClient, http } from 'viem'; +import { + arbitrum, + avalanche, + base, + fantom, + gnosis, + mainnet, + optimism, + polygon, + polygonZkEvm, + sepolia, +} from 'viem/chains'; +import { Chain } from '@prisma/client'; +import config from '@config/index'; + +export type ViemClient = ReturnType; + +const chain2ViemChain = { + [Chain.MAINNET]: mainnet, + [Chain.SEPOLIA]: sepolia, + [Chain.ARBITRUM]: arbitrum, + [Chain.AVALANCHE]: avalanche, + [Chain.BASE]: base, + [Chain.FANTOM]: fantom, + [Chain.GNOSIS]: gnosis, + [Chain.OPTIMISM]: optimism, + [Chain.POLYGON]: polygon, + [Chain.ZKEVM]: polygonZkEvm, +}; + +export const getViemClient = (chain: Chain) => { + return createPublicClient({ + chain: chain2ViemChain[chain], + transport: http(config[chain]?.rpcUrl), + }); +}; diff --git a/package.json b/package.json index cab372b3d..98b667b9e 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,14 @@ "private": true, "scripts": { "start": "node dist/app.js", - "start:local": "ts-node -r dotenv/config app", + "start:local": "ts-node -r dotenv/config -r tsconfig-paths/register app", "watch": "concurrently \"nodemon app\" \"yarn generate --watch\"", - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "generate": "graphql-codegen --config codegen.yml -r dotenv/config", "prisma-merge": "ts-node prisma/prisma-merge.ts", "test": "jest", - "vitest": "vitest" + "vitest": "vitest", + "task": "ts-node -r tsconfig-paths/register -r dotenv/config tasks/index.ts" }, "dependencies": { "@aws-sdk/client-cloudwatch": "^3.388.0", @@ -33,6 +34,7 @@ "@sentry/node": "^7.0.0", "@sentry/profiling-node": "^1.2.6", "@sentry/tracing": "^7.56.0", + "abitype": "^1.0.0", "apollo-server-core": "^3.5.0", "apollo-server-express": "^3.5.0", "axios": "^0.24.0", @@ -92,6 +94,7 @@ "testcontainers": "^8.0.0", "ts-jest": "^28.0.7", "ts-node": "^10.4.0", + "tsconfig-paths": "^4.2.0", "typescript": "^5.3.3", "vitest": "^0.32.4", "vitest-mock-extended": "^1.1.3" diff --git a/tasks/index.ts b/tasks/index.ts new file mode 100644 index 000000000..bb0cdf104 --- /dev/null +++ b/tasks/index.ts @@ -0,0 +1,18 @@ +import { JobsController } from '@modules/controllers/jobs-controller'; + +const jobsController = JobsController(); + +async function run(job: string = process.argv[2], chain: string = process.argv[3]) { + console.log('Running job', job, chain); + + if (job === 'sync-changed-pools-v3') { + return jobsController.syncPools(chain); + } + + return Promise.reject(new Error(`Unknown job: ${job}`)); +} + +run() + .then((r) => console.log) + .then(() => process.exit(0)) + .catch((e) => console.error); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 000000000..d92252c64 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "debug", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index a9c4bc4b4..0a0c1eec5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,13 @@ "resolveJsonModule": true, "outDir": "./dist", "skipLibCheck": true, - "sourceMap": true + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@app/*": ["app/*"], + "@config/*": ["config/*"], + "@modules/*": ["modules/*"] + } }, - "exclude": ["node_modules", "debug", "**/*.spec.ts", "**/*.test.ts"] + "exclude": ["node_modules"] } diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 06a54a168..6378cafc3 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -17,9 +17,12 @@ import { cronsDurationMetricPublisher } from '../modules/metrics/cron-duration-m import { syncLatestFXPrices } from '../modules/token/latest-fx-price'; import { AllNetworkConfigs } from '../modules/network/network-config'; import { sftmxService } from '../modules/sftmx/sftmx.service'; +import { JobsController } from '@modules/controllers/jobs-controller'; const runningJobs: Set = new Set(); +const jobsController = JobsController(); + async function runIfNotAlreadyRunning( id: string, chainId: string, @@ -105,7 +108,7 @@ export function configureWorkerRoutes(app: Express) { break; case 'sync-changed-pools-v3': - await runIfNotAlreadyRunning(job.name, chainId, () => poolService.syncChangedPoolsV3(), res, next); + await runIfNotAlreadyRunning(job.name, chainId, () => jobsController.syncPools(chainId), res, next); break; case 'user-sync-wallet-balances-for-all-pools': await runIfNotAlreadyRunning( diff --git a/yarn.lock b/yarn.lock index 5b741e11f..5ae87b4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5084,7 +5084,7 @@ abitype@0.9.8: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== -abitype@1.0.0: +abitype@1.0.0, abitype@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== @@ -9037,6 +9037,11 @@ json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -9575,6 +9580,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -11283,6 +11293,11 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -11611,6 +11626,15 @@ ts-node@^9: source-map-support "^0.5.17" yn "3.1.1" +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.11.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" From c07e18c73b009feb93c03300ab3442efb6d1114a Mon Sep 17 00:00:00 2001 From: franz Date: Tue, 20 Feb 2024 21:41:26 +0100 Subject: [PATCH 06/14] add pool --- buildspec.yml | 3 +- codegen.yml | 22 +- config/index.ts | 4 +- config/sepolia.ts | 5 +- env.local | 1 + modules/actions/jobs-actions/index.ts | 1 - .../actions/jobs-actions/sync-pools.test.ts | 28 +- modules/actions/jobs-actions/sync-pools.ts | 112 +- modules/controllers/jobs-controller.test.ts | 7 +- modules/controllers/jobs-controller.ts | 18 +- modules/network/network-config-types.ts | 1 + modules/network/sepolia.ts | 4 +- modules/pool/abi/VaultV3.json | 5821 +++++++++-------- .../transformers/pool-tokens-transformer.ts | 31 +- .../sources/transformers/pool-transformer.ts | 43 +- modules/subgraphs/balancer-v3-pools/index.ts | 17 + .../subgraphs/balancer-v3-pools/pools.graphql | 42 + modules/subgraphs/balancer-v3-vault/index.ts | 4 +- .../subgraphs/balancer-v3-vault/pools.graphql | 10 + tasks/index.ts | 2 +- worker/job-handlers.ts | 8 +- 21 files changed, 3269 insertions(+), 2915 deletions(-) delete mode 100644 modules/actions/jobs-actions/index.ts create mode 100644 modules/subgraphs/balancer-v3-pools/index.ts create mode 100644 modules/subgraphs/balancer-v3-pools/pools.graphql diff --git a/buildspec.yml b/buildspec.yml index 5cdeecf14..2672c08a7 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -1,7 +1,8 @@ version: 0.2 env: variables: - V3_SUBGRAPH: 'https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest' + V3_SUBGRAPH: 'https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest/graphql' + V3_POOLS_SUBGRAPH: 'https://api.studio.thegraph.com/proxy/31386/balancer-pools-v3-sepolia/version/latest/graphql' BALANCER_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx-v2-optimism' MASTERCHEF_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2' RELIQUARY_SUBGRAPH: 'https://api.thegraph.com/subgraphs/name/beethovenxfi/reliquary' diff --git a/codegen.yml b/codegen.yml index aa64b9dd4..e87c909af 100644 --- a/codegen.yml +++ b/codegen.yml @@ -16,7 +16,27 @@ generates: BigInt: string Bytes: string BigDecimal: string - + modules/subgraphs/balancer-v3-vault/generated/balancer-v3-schema.graphql: + schema: ${V3_SUBGRAPH} + plugins: + - schema-ast + modules/subgraphs/balancer-v3-pools/generated/types.ts: + schema: ${V3_POOLS_SUBGRAPH} + documents: 'modules/subgraphs/balancer-v3-pools/*.graphql' + plugins: + - typescript + - typescript-operations + - typescript-graphql-request + config: + skipTypename: true + scalars: + BigInt: string + Bytes: string + BigDecimal: string + modules/subgraphs/balancer-v3-pools/generated/balancer-v3-pools-schema.graphql: + schema: ${V3_POOLS_SUBGRAPH} + plugins: + - schema-ast modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts: schema: ${BALANCER_SUBGRAPH} documents: 'modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql' diff --git a/config/index.ts b/config/index.ts index f661c4d58..bd2d384f7 100644 --- a/config/index.ts +++ b/config/index.ts @@ -1,6 +1,6 @@ import { Chain } from '@prisma/client'; import { NetworkData } from '@modules/network/network-config-types'; -import sepolia from './sepolia'; +import { sepoliaConfig } from './sepolia'; export default { [Chain.ARBITRUM]: {} as NetworkData, @@ -11,6 +11,6 @@ export default { [Chain.MAINNET]: {} as NetworkData, [Chain.OPTIMISM]: {} as NetworkData, [Chain.POLYGON]: {} as NetworkData, - [Chain.SEPOLIA]: sepolia, + [Chain.SEPOLIA]: sepoliaConfig, [Chain.ZKEVM]: {} as NetworkData, }; diff --git a/config/sepolia.ts b/config/sepolia.ts index 691d2f43c..da9e44c51 100644 --- a/config/sepolia.ts +++ b/config/sepolia.ts @@ -2,7 +2,7 @@ import { env } from '@app/env'; import { NetworkData } from '@modules/network/network-config-types'; import { BigNumber } from 'ethers'; -export default { +export const sepoliaConfig: NetworkData = { chain: { slug: 'sepolia', id: 11155111, @@ -15,6 +15,7 @@ export default { startDate: '2023-05-03', balancer: 'https://api.studio.thegraph.com/query/24660/balancer-sepolia-v2/version/latest', balancerV3: 'https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest', + balancerPoolsV3: 'https://api.studio.thegraph.com/proxy/31386/balancer-pools-v3-sepolia/version/latest', beetsBar: 'https://', blocks: 'https://api.studio.thegraph.com/query/48427/bleu-sepolia-blocks/version/latest', gauge: 'https://api.studio.thegraph.com/proxy/24660/balancer-gauges-sepolia/version/latest', @@ -58,7 +59,7 @@ export default { balancerQueriesAddress: '0xe39b5e3b6d74016b2f6a9673d7d7493b6df549d5', }, v3: { - vaultAddress: '0x816e90DC85bF016455017a76Bc09CC0451Eeb308', + vaultAddress: '0xdaa273aeec06e9ccb7428a77e2abb1e4659b16d2', defaultSwapFeePercentage: '0.5', defaultYieldFeePercentage: '0.5', }, diff --git a/env.local b/env.local index a9effff2f..31d2dae37 100644 --- a/env.local +++ b/env.local @@ -21,6 +21,7 @@ DEFAULT_CHAIN_ID=250 # Subgraph type config V3_SUBGRAPH=https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest +V3_POOLS_SUBGRAPH=https://api.studio.thegraph.com/proxy/31386/balancer-pools-v3-sepolia/version/latest BALANCER_SUBGRAPH=https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx MASTERCHEF_SUBGRAPH=https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2 BLOCKS_SUBGRAPH=https://api.thegraph.com/subgraphs/name/danielmkm/optimism-blocks diff --git a/modules/actions/jobs-actions/index.ts b/modules/actions/jobs-actions/index.ts deleted file mode 100644 index 0d1fa40bf..000000000 --- a/modules/actions/jobs-actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './sync-pools'; diff --git a/modules/actions/jobs-actions/sync-pools.test.ts b/modules/actions/jobs-actions/sync-pools.test.ts index c0aa81975..40bfd4512 100644 --- a/modules/actions/jobs-actions/sync-pools.test.ts +++ b/modules/actions/jobs-actions/sync-pools.test.ts @@ -1,9 +1,10 @@ -import { syncPools } from './sync-pools'; +import { syncMissingPools } from './sync-pools'; import { prisma } from '../../../prisma/prisma-client'; import { PrismaPool } from '@prisma/client'; -import { PoolFragment } from '@modules/subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as VaultSubgraphPoolFragment } from '@modules/subgraphs/balancer-v3-vault/generated/types'; import { fetchErc20Headers } from '@modules/sources/contracts'; import { getViemClient } from '@modules/sources/viem-client'; +import { PoolFragment as PoolSubgraphPoolFragment } from '@modules/subgraphs/balancer-v3-pools/generated/types'; // Mock the module dependencies jest.mock('@modules/sources/contracts', () => ({ @@ -33,22 +34,29 @@ jest.mock('../../../prisma/prisma-client', () => ({ })); describe('syncPools', () => { - const subgraphClient = { - Pools: jest.fn().mockResolvedValue({ pools: [{ id: '1' }, { id: '2' }] as PoolFragment[] }), + const vaultSubgraphClient = { + Pools: jest.fn().mockResolvedValue({ pools: [{ id: '1' }, { id: '2' }] as VaultSubgraphPoolFragment[] }), + }; + const poolSubgraphClient = { + Pools: jest.fn().mockResolvedValue({ + pools: [ + { id: '1', factory: { id: '1' } }, + { id: '2', factory: { id: '1' } }, + ] as PoolSubgraphPoolFragment[], + }), }; - const viemClient = jest.mocked(getViemClient('SEPOLIA')); beforeEach(() => { jest.clearAllMocks(); - return syncPools(subgraphClient, viemClient, 'vaultAddress', 'SEPOLIA'); + return syncMissingPools(vaultSubgraphClient, poolSubgraphClient, 'SEPOLIA'); }); - it('should fetch pools from subgraph', async () => { - expect(subgraphClient.Pools).toHaveBeenCalled(); + it('should fetch pools from vault subgraph', async () => { + expect(vaultSubgraphClient.Pools).toHaveBeenCalled(); }); - it('should fetch additional data from contracts for missing pools', async () => { - expect(fetchErc20Headers).toHaveBeenCalledWith(['2'], expect.anything()); + it('should fetch pools from pools subgraph', async () => { + expect(poolSubgraphClient.Pools).toHaveBeenCalled(); }); it('should store missing pools in the database', async () => { diff --git a/modules/actions/jobs-actions/sync-pools.ts b/modules/actions/jobs-actions/sync-pools.ts index c14538c5c..ebef7d346 100644 --- a/modules/actions/jobs-actions/sync-pools.ts +++ b/modules/actions/jobs-actions/sync-pools.ts @@ -1,63 +1,71 @@ import { Chain } from '@prisma/client'; import { prisma } from '../../../prisma/prisma-client'; import { V3SubgraphClient } from '@modules/subgraphs/balancer-v3-vault'; -import { poolTransformer, poolTokensTransformer } from '@modules/sources/transformers'; +import { + poolTransformer, + poolTokensTransformer, + poolTokensDynamicDataTransformer, +} from '@modules/sources/transformers'; import { fetchErc20Headers, fetchWeightedPoolData, fetchPoolTokens } from '@modules/sources/contracts'; import type { ViemClient } from 'modules/sources/types'; +import { V3PoolsSubgraphClient } from '@modules/subgraphs/balancer-v3-pools'; +import { decimal } from '@modules/big-number/big-number'; /** * Makes sure that all pools are synced in the database * - * @param subgraphClient + * @param vaultSubgraphClient + * @param poolSubgraphClient * @param chain * @returns syncedPools - the pools that were synced */ -export async function syncPools( - subgraphClient: Pick, - viemClient: ViemClient, - vaultAddress: string, +export async function syncMissingPools( + vaultSubgraphClient: Pick, + poolSubgraphClient: V3PoolsSubgraphClient, + // viemClient: ViemClient, + // vaultAddress: string, chain = 'SEPOLIA' as Chain, ) { // Fetch pools from subgraph - const { pools: subgraphPools } = await subgraphClient.Pools(); + // TODO this needs paging + const { pools: vaultSubgraphPools } = await vaultSubgraphClient.Pools(); + const { pools: poolSubgraphPools } = await poolSubgraphClient.Pools(); // Find pools missing from the database const dbPools = await prisma.prismaPool.findMany({ where: { chain, vaultVersion: 3 } }); const dbPoolIds = new Set(dbPools.map((pool) => pool.id.toLowerCase())); - const subgraphPoolIds = subgraphPools.map((pool) => pool.id.toLowerCase()); - const missingPools = subgraphPoolIds.filter((id) => !dbPoolIds.has(id)); + const missingPools = vaultSubgraphPools.filter((pool) => !dbPoolIds.has(pool.id)); if (missingPools.length === 0) { return true; } - // Fetch additional data from contracts required in the DB for missing pools - const contractData = await fetchErc20Headers(missingPools as `0x${string}`[], viemClient); - const poolTokens = subgraphPools.filter((pool) => !dbPoolIds.has(pool.id)).flatMap((pool) => pool.tokens ?? []); - - // Fetch pool type specific data - // TODO: this will be covert by the subgraph, but in the meantime we need to fetch it from the contracts - const weightedPoolData = await fetchWeightedPoolData(missingPools, viemClient); - - // TODO: this fails for now, there is something wrong with the ABI, or vault contract - const poolTokenInfo = await fetchPoolTokens(vaultAddress, missingPools, viemClient); - - // Check if we need to get token information from the contracts as well - const tokenAddresses = poolTokens.map((token) => token.address); - const dbTokens = await prisma.prismaToken.findMany({ where: { address: { in: tokenAddresses }, chain } }); - const missingTokens = tokenAddresses.filter((address) => !dbTokens.some((token) => token.address === address)); - const tokenData = await fetchErc20Headers(missingTokens as `0x${string}`[], viemClient); - - // console.log(poolTokenInfo, weightedPoolData, contractData, tokenData); // Store pool tokens and BPT in the tokens table before creating the pools try { - const allTokens = Object.entries({ ...tokenData, ...contractData }).map(([address, token]) => ({ - ...token, - address, - })); + const allTokens: { address: string; name: string; decimals: number; symbol: string; chain: Chain }[] = []; + missingPools.forEach((pool) => { + allTokens.push({ + address: pool.address, + decimals: 18, + name: pool.name, + symbol: pool.symbol, + chain: chain, + }); + if (pool.tokens) { + for (const poolToken of pool.tokens) { + allTokens.push({ + address: poolToken.address, + decimals: poolToken.decimals, + name: poolToken.name, + symbol: poolToken.symbol, + chain: chain, + }); + } + } + }); await prisma.prismaToken.createMany({ - data: allTokens.map((data) => ({ ...data, chain })), + data: allTokens, skipDuplicates: true, }); } catch (e) { @@ -66,35 +74,36 @@ export async function syncPools( // Transform pool data for the database const dbPoolEntries = missingPools - .map((id) => { - const subgraphPool = subgraphPools.find((pool) => pool.id === id); - const data = contractData[id]; - if (!subgraphPool || !data) { + .map((missingPool) => { + const vaultSubgraphPool = vaultSubgraphPools.find((pool) => pool.id === missingPool.id); + const poolSubgraphPool = poolSubgraphPools.find((pool) => pool.id === missingPool.id); + if (!vaultSubgraphPool || !poolSubgraphPool) { // That won't happen, but TS doesn't know that return null; } return { - ...poolTransformer(subgraphPool, data, chain), + ...poolTransformer(vaultSubgraphPool, poolSubgraphPool, chain), typeData: JSON.stringify({}), tokens: { createMany: { // TODO: Will be great to create all the token data here, including dynamic data // but for now we can only store static data, because prisma doesn't support nested createMany // to create dynamic data tabels as well. One solution is to move "dynamicData" to the tokens table - data: poolTokensTransformer(subgraphPool), + data: poolTokensTransformer(vaultSubgraphPool), }, }, dynamicData: { create: { - id: subgraphPool.id, + id: vaultSubgraphPool.id, swapFee: '0', - blockNumber: Number(subgraphPool.blockNumber), + blockNumber: Number(vaultSubgraphPool.blockNumber), swapEnabled: true, totalLiquidity: 1, - totalShares: '1', // TODO: update once we get the value from SG: subgraphPool.totalShares, - totalSharesNum: 1, // TODO: update once we get the value from SG: subgraphPool.totalShares, + totalShares: vaultSubgraphPool.totalShares, + totalSharesNum: parseFloat(vaultSubgraphPool.totalShares), }, }, + poolTokenDynamicData: poolTokensDynamicDataTransformer(vaultSubgraphPool, poolSubgraphPool, chain), }; }) .filter((entry): entry is NonNullable => entry !== null); @@ -103,27 +112,10 @@ export async function syncPools( let allOk = true; for (const data of dbPoolEntries) { try { - console.log('Storing', data.id); - const weightedData = weightedPoolData[data.id]; - // poolTokenInfo is failing for now, there is something wrong with the ABI, or vault contract - // Once that is fixed, get the token balances in place - const poolData = poolTokenInfo[data.id]; await prisma.prismaPool.create({ data }); // Create pool tokens dynamic data await prisma.prismaPoolTokenDynamicData.createMany({ - data: data.tokens.createMany.data.map((token, i) => { - return { - id: token.id, - poolTokenId: token.id, - chain, - blockNumber: data.dynamicData.create.blockNumber, - balance: '0', - balanceUSD: 0, - priceRate: '0', - weight: weightedData.weights[token.index] ?? '0', - latestFxPrice: null, - }; - }), + data: data.poolTokenDynamicData, }); } catch (e) { // TODO: handle errors diff --git a/modules/controllers/jobs-controller.test.ts b/modules/controllers/jobs-controller.test.ts index 9138c6bdc..7853598be 100644 --- a/modules/controllers/jobs-controller.test.ts +++ b/modules/controllers/jobs-controller.test.ts @@ -1,6 +1,5 @@ +import { syncMissingPools } from '@modules/actions/jobs-actions/sync-pools'; import { JobsController } from './jobs-controller'; -import * as actions from '@modules/actions/jobs-actions'; - // Mock the actions jest.mock('@modules/actions/jobs_actions', () => ({ syncPools: jest.fn(), @@ -14,8 +13,8 @@ describe('jobsController', () => { }); it('should call getClient with correct chain', () => { - jobsController.syncPools('11155111'); + jobsController.addMissingPools('11155111'); - expect(actions.syncPools).toHaveBeenCalled(); + expect(syncMissingPools).toHaveBeenCalled(); }); }); diff --git a/modules/controllers/jobs-controller.ts b/modules/controllers/jobs-controller.ts index 764b76880..2ae7ec484 100644 --- a/modules/controllers/jobs-controller.ts +++ b/modules/controllers/jobs-controller.ts @@ -1,8 +1,9 @@ -import * as actions from '@modules/actions/jobs-actions'; -import * as subgraphV3Vault from '@modules/subgraphs/balancer-v3-vault'; import config from '@config/index'; import { chainIdToChain } from '@modules/network/chain-id-to-chain'; import { getViemClient } from '@modules/sources/viem-client'; +import { syncMissingPools } from '@modules/actions/jobs-actions/sync-pools'; +import { getVaultSubgraphClient } from '@modules/subgraphs/balancer-v3-vault'; +import { getPoolsSubgraphClient } from '@modules/subgraphs/balancer-v3-pools'; /** * Controller responsible for matching job requests to configured job handlers @@ -15,13 +16,10 @@ export function JobsController(tracer?: any) { // Setup tracing // ... return { - syncPools(chainId: string) { + addMissingPools(chainId: string) { const chain = chainIdToChain[chainId]; const { - subgraphs: { balancerV3 }, - balancer: { - v3: { vaultAddress }, - }, + subgraphs: { balancerV3, balancerPoolsV3 }, } = config[chain]; // Guard against unconfigured chains @@ -29,13 +27,13 @@ export function JobsController(tracer?: any) { throw new Error(`Chain not configured: ${chain}`); } - const subgraphClient = subgraphV3Vault.getClient(balancerV3); - const viemClient = getViemClient(chain); + const vaultSubgraphClient = getVaultSubgraphClient(balancerV3); + const poolSubgraphClient = getPoolsSubgraphClient(balancerPoolsV3!); // TODO: add syncing v2 pools as well by splitting the poolService into separate // actions with extracted configuration - return actions.syncPools(subgraphClient, viemClient, vaultAddress, chain); + return syncMissingPools(vaultSubgraphClient, poolSubgraphClient, chain); }, }; } diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index 4b0228e5a..595ba5269 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -66,6 +66,7 @@ export interface NetworkData { startDate: string; balancer: string; balancerV3?: string; + balancerPoolsV3?: string; blocks: string; masterchef?: string; reliquary?: string; diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index 22821bb90..9b21275f6 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -13,12 +13,10 @@ import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance import { every } from '../../worker/intervals'; import { GithubContentService } from '../content/github-content.service'; import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph.service'; -import { CoingeckoPriceHandlerService } from '../token/lib/token-price-handlers/coingecko-price-handler.service'; -import { coingeckoService } from '../coingecko/coingecko.service'; import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; +import { sepoliaConfig as sepoliaNetworkData } from '@config/sepolia'; -import sepoliaNetworkData from '@config/sepolia'; export { sepoliaNetworkData }; export const sepoliaNetworkConfig: NetworkConfig = { diff --git a/modules/pool/abi/VaultV3.json b/modules/pool/abi/VaultV3.json index e4ba48715..cee6a2f2e 100644 --- a/modules/pool/abi/VaultV3.json +++ b/modules/pool/abi/VaultV3.json @@ -1,3401 +1,3622 @@ [ - { - "inputs": [ - { - "internalType": "contract IVaultExtension", - "name": "vaultExtension", - "type": "address" - }, - { - "internalType": "contract IAuthorizer", - "name": "authorizer", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddressInsufficientBalance", - "type": "error" - }, - { - "inputs": [], - "name": "AllZeroInputs", - "type": "error" - }, - { - "inputs": [], - "name": "AmountGivenZero", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "AmountInAboveMax", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "AmountOutBelowMin", - "type": "error" - }, - { - "inputs": [], - "name": "BalanceNotSettled", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "BptAmountInAboveMax", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "BptAmountOutBelowMin", - "type": "error" - }, - { - "inputs": [], - "name": "CallbackFailed", - "type": "error" - }, - { - "inputs": [], - "name": "CannotReceiveEth", - "type": "error" - }, - { - "inputs": [], - "name": "CannotSwapSameToken", - "type": "error" - }, - { - "inputs": [], - "name": "DoesNotSupportAddLiquidityCustom", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "allowance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientAllowance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientBalance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "approver", - "type": "address" - } - ], - "name": "ERC20InvalidApprover", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "ERC20InvalidReceiver", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "ERC20InvalidSender", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "ERC20InvalidSpender", - "type": "error" - }, - { - "inputs": [], - "name": "FailedInnerCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "name": "HandlerOutOfBounds", - "type": "error" - }, - { - "inputs": [], - "name": "InputLengthMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidAddLiquidityKind", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidRemoveLiquidityKind", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidToken", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidTokenConfiguration", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidTokenType", - "type": "error" - }, - { - "inputs": [], - "name": "MaxTokens", - "type": "error" - }, - { - "inputs": [], - "name": "MinTokens", - "type": "error" - }, - { - "inputs": [], - "name": "MultipleNonZeroInputs", - "type": "error" - }, - { - "inputs": [], - "name": "NoHandler", - "type": "error" - }, - { - "inputs": [], - "name": "NotStaticCall", - "type": "error" - }, - { - "inputs": [], - "name": "NotVaultDelegateCall", - "type": "error" - }, - { - "inputs": [], - "name": "PauseBufferPeriodDurationTooLarge", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolAlreadyInitialized", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolAlreadyRegistered", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolInRecoveryMode", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolNotInRecoveryMode", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolNotInitialized", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolNotPaused", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolNotRegistered", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolPauseWindowExpired", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolPaused", - "type": "error" - }, - { - "inputs": [], - "name": "ProtocolSwapFeePercentageTooHigh", - "type": "error" - }, - { - "inputs": [], - "name": "QueriesDisabled", - "type": "error" - }, - { - "inputs": [], - "name": "ReentrancyGuardReentrantCall", - "type": "error" - }, - { - "inputs": [], - "name": "RouterNotTrusted", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "bits", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "SafeCastOverflowedUintDowncast", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "SafeCastOverflowedUintToInt", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "SenderIsNotPauseManager", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "SenderIsNotVault", - "type": "error" - }, - { - "inputs": [], - "name": "SwapFeePercentageTooHigh", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } - ], - "name": "TokenAlreadyRegistered", - "type": "error" - }, - { - "inputs": [], - "name": "TokenNotRegistered", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "expectedToken", - "type": "address" - }, - { - "internalType": "address", - "name": "actualToken", - "type": "address" - } - ], - "name": "TokensMismatch", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "TotalSupplyTooLow", - "type": "error" - }, - { - "inputs": [], - "name": "UserDataNotSupported", - "type": "error" - }, - { - "inputs": [], - "name": "VaultNotPaused", - "type": "error" - }, - { - "inputs": [], - "name": "VaultPauseWindowDurationTooLarge", - "type": "error" - }, - { - "inputs": [], - "name": "VaultPauseWindowExpired", - "type": "error" - }, - { - "inputs": [], - "name": "VaultPaused", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "handler", - "type": "address" - }, - { - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "WrongHandler", - "type": "error" - }, - { - "inputs": [], - "name": "WrongVaultExtensionDeployment", - "type": "error" - }, - { - "inputs": [], - "name": "ZeroDivision", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract IAuthorizer", - "name": "newAuthorizer", - "type": "address" - } - ], - "name": "AuthorizerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "liquidityProvider", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract IERC20[]", - "name": "tokens", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "int256[]", - "name": "deltas", - "type": "int256[]" - } - ], - "name": "PoolBalanceChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "PoolInitialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "paused", - "type": "bool" - } - ], - "name": "PoolPausedStateChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "recoveryMode", - "type": "bool" - } - ], - "name": "PoolRecoveryModeStateChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "factory", - "type": "address" - }, - { - "components": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "enum TokenType", - "name": "tokenType", - "type": "uint8" - }, - { - "internalType": "contract IRateProvider", - "name": "rateProvider", - "type": "address" - }, - { - "internalType": "bool", - "name": "yieldFeeExempt", - "type": "bool" - } - ], - "indexed": false, - "internalType": "struct TokenConfig[]", - "name": "tokenConfig", - "type": "tuple[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "pauseWindowEndTime", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "pauseManager", - "type": "address" - }, - { - "components": [ - { - "internalType": "bool", - "name": "shouldCallBeforeInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeRemoveLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterRemoveLiquidity", - "type": "bool" - } - ], - "indexed": false, - "internalType": "struct PoolCallbacks", - "name": "callbacks", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "bool", - "name": "supportsAddLiquidityCustom", - "type": "bool" - }, - { - "internalType": "bool", - "name": "supportsRemoveLiquidityCustom", - "type": "bool" - } - ], - "indexed": false, - "internalType": "struct LiquidityManagement", - "name": "liquidityManagement", - "type": "tuple" - } - ], - "name": "PoolRegistered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "ProtocolFeeCollected", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "swapFeePercentage", - "type": "uint256" - } - ], - "name": "ProtocolSwapFeePercentageChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract IERC20", - "name": "tokenIn", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract IERC20", - "name": "tokenOut", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "swapFeeAmount", - "type": "uint256" - } - ], - "name": "Swap", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "paused", - "type": "bool" - } - ], - "name": "VaultPausedStateChanged", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" - }, - { - "inputs": [], - "name": "MAX_BUFFER_PERIOD_DURATION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_PAUSE_WINDOW_DURATION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "maxAmountsIn", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "minBptAmountOut", - "type": "uint256" - }, - { - "internalType": "enum AddLiquidityKind", - "name": "kind", - "type": "uint8" - }, - { - "internalType": "bytes", - "name": "userData", - "type": "bytes" - } - ], - "internalType": "struct AddLiquidityParams", - "name": "params", - "type": "tuple" - } - ], - "name": "addLiquidity", - "outputs": [ - { - "internalType": "uint256[]", - "name": "amountsIn", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "bptAmountOut", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "getVaultExtension", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "invoke", - "outputs": [ - { - "internalType": "bytes", - "name": "result", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "maxBptAmountIn", - "type": "uint256" - }, - { - "internalType": "uint256[]", - "name": "minAmountsOut", - "type": "uint256[]" - }, - { - "internalType": "enum RemoveLiquidityKind", - "name": "kind", - "type": "uint8" - }, - { - "internalType": "bytes", - "name": "userData", - "type": "bytes" - } - ], - "internalType": "struct RemoveLiquidityParams", - "name": "params", - "type": "tuple" - } - ], - "name": "removeLiquidity", - "outputs": [ - { - "internalType": "uint256", - "name": "bptAmountIn", - "type": "uint256" - }, - { - "internalType": "uint256[]", - "name": "amountsOut", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "exactBptAmountIn", - "type": "uint256" - } - ], - "name": "removeLiquidityRecovery", - "outputs": [ - { - "internalType": "uint256[]", - "name": "amountsOutRaw", - "type": "uint256[]" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "retrieve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } - ], - "name": "settle", - "outputs": [ - { - "internalType": "uint256", - "name": "paid", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "enum SwapKind", - "name": "kind", - "type": "uint8" - }, - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "contract IERC20", - "name": "tokenIn", - "type": "address" - }, - { - "internalType": "contract IERC20", - "name": "tokenOut", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amountGivenRaw", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "userData", - "type": "bytes" - } - ], - "internalType": "struct SwapParams", - "name": "params", - "type": "tuple" - } - ], - "name": "swap", - "outputs": [ - { - "internalType": "uint256", - "name": "amountCalculated", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "wire", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - }, - { - "inputs": [ - { - "internalType": "contract IVault", - "name": "mainVault", - "type": "address" - }, - { - "internalType": "uint256", - "name": "pauseWindowDuration", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "bufferPeriodDuration", - "type": "uint256" - } + { + "inputs": [ + { + "internalType": "contract IVaultExtension", + "name": "vaultExtension", + "type": "address" + }, + { + "internalType": "contract IAuthorizer", + "name": "authorizer", + "type": "address" + } ], "stateMutability": "nonpayable", "type": "constructor" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } + { + "internalType": "address", + "name": "target", + "type": "address" + } ], "name": "AddressEmptyCode", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "AddressInsufficientBalance", "type": "error" - }, - { + }, + { + "inputs": [], + "name": "AllZeroInputs", + "type": "error" + }, + { "inputs": [], "name": "AmountGivenZero", "type": "error" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } ], "name": "AmountInAboveMax", "type": "error" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } ], "name": "AmountOutBelowMin", "type": "error" - }, - { + }, + { "inputs": [], "name": "BalanceNotSettled", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } ], "name": "BptAmountInAboveMax", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } ], "name": "BptAmountOutBelowMin", "type": "error" - }, - { - "inputs": [], - "name": "CallbackFailed", - "type": "error" - }, - { + }, + { "inputs": [], "name": "CannotReceiveEth", "type": "error" - }, - { + }, + { "inputs": [], "name": "CannotSwapSameToken", "type": "error" - }, - { + }, + { "inputs": [], - "name": "CodecOverflow", + "name": "DoesNotSupportAddLiquidityCustom", "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "allowance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } ], "name": "ERC20InsufficientAllowance", "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } ], "name": "ERC20InsufficientBalance", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "approver", - "type": "address" - } + { + "internalType": "address", + "name": "approver", + "type": "address" + } ], "name": "ERC20InvalidApprover", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - } + { + "internalType": "address", + "name": "receiver", + "type": "address" + } ], "name": "ERC20InvalidReceiver", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } + { + "internalType": "address", + "name": "sender", + "type": "address" + } ], "name": "ERC20InvalidSender", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - } + { + "internalType": "address", + "name": "spender", + "type": "address" + } ], "name": "ERC20InvalidSpender", "type": "error" - }, - { + }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } ], "name": "HandlerOutOfBounds", "type": "error" - }, - { + }, + { + "inputs": [], + "name": "HookFailed", + "type": "error" + }, + { "inputs": [], "name": "InputLengthMismatch", "type": "error" - }, - { + }, + { "inputs": [], "name": "InvalidAddLiquidityKind", "type": "error" - }, - { + }, + { "inputs": [], "name": "InvalidRemoveLiquidityKind", "type": "error" - }, - { + }, + { "inputs": [], "name": "InvalidToken", "type": "error" - }, - { + }, + { "inputs": [], "name": "InvalidTokenConfiguration", "type": "error" - }, - { + }, + { "inputs": [], "name": "InvalidTokenType", "type": "error" - }, - { + }, + { "inputs": [], "name": "MaxTokens", "type": "error" - }, - { + }, + { "inputs": [], "name": "MinTokens", "type": "error" - }, - { + }, + { + "inputs": [], + "name": "MultipleNonZeroInputs", + "type": "error" + }, + { "inputs": [], "name": "NoHandler", "type": "error" - }, - { + }, + { "inputs": [], "name": "NotStaticCall", "type": "error" - }, - { + }, + { "inputs": [], "name": "NotVaultDelegateCall", "type": "error" - }, - { - "inputs": [], - "name": "OutOfBounds", - "type": "error" - }, - { + }, + { "inputs": [], "name": "PauseBufferPeriodDurationTooLarge", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolAlreadyInitialized", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolAlreadyRegistered", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolInRecoveryMode", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolNotInRecoveryMode", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolNotInitialized", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolNotPaused", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolNotRegistered", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolPauseWindowExpired", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolPaused", "type": "error" - }, - { + }, + { "inputs": [], "name": "ProtocolSwapFeePercentageTooHigh", "type": "error" - }, - { + }, + { + "inputs": [], + "name": "ProtocolYieldFeePercentageTooHigh", + "type": "error" + }, + { "inputs": [], "name": "QueriesDisabled", "type": "error" - }, - { + }, + { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" - }, - { + }, + { "inputs": [], "name": "RouterNotTrusted", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint8", - "name": "bits", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } ], "name": "SafeCastOverflowedUintToInt", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } + { + "internalType": "address", + "name": "token", + "type": "address" + } ], "name": "SafeERC20FailedOperation", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "SenderIsNotPauseManager", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } + { + "internalType": "address", + "name": "sender", + "type": "address" + } ], "name": "SenderIsNotVault", "type": "error" - }, - { - "inputs": [], - "name": "SenderNotAllowed", - "type": "error" - }, - { + }, + { "inputs": [], "name": "SwapFeePercentageTooHigh", "type": "error" - }, - { + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "SwapLimit", + "type": "error" + }, + { "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } ], "name": "TokenAlreadyRegistered", "type": "error" - }, - { + }, + { "inputs": [], "name": "TokenNotRegistered", "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "expectedToken", - "type": "address" - }, - { - "internalType": "address", - "name": "actualToken", - "type": "address" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "expectedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "actualToken", + "type": "address" + } ], "name": "TokensMismatch", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } ], "name": "TotalSupplyTooLow", "type": "error" - }, - { + }, + { "inputs": [], "name": "UserDataNotSupported", "type": "error" - }, - { + }, + { "inputs": [], "name": "VaultNotPaused", "type": "error" - }, - { + }, + { "inputs": [], "name": "VaultPauseWindowDurationTooLarge", "type": "error" - }, - { + }, + { "inputs": [], "name": "VaultPauseWindowExpired", "type": "error" - }, - { + }, + { "inputs": [], "name": "VaultPaused", "type": "error" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "handler", - "type": "address" - }, - { - "internalType": "address", - "name": "caller", - "type": "address" - } + { + "internalType": "address", + "name": "handler", + "type": "address" + }, + { + "internalType": "address", + "name": "caller", + "type": "address" + } ], "name": "WrongHandler", "type": "error" - }, - { + }, + { "inputs": [], "name": "WrongVaultExtensionDeployment", "type": "error" - }, - { + }, + { + "inputs": [], + "name": "ZeroDivision", + "type": "error" + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } ], "name": "Approval", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "contract IAuthorizer", - "name": "newAuthorizer", - "type": "address" - } + { + "indexed": true, + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } ], "name": "AuthorizerChanged", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "liquidityProvider", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract IERC20[]", - "name": "tokens", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "int256[]", - "name": "deltas", - "type": "int256[]" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "int256[]", + "name": "deltas", + "type": "int256[]" + } ], "name": "PoolBalanceChanged", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "PoolInitialized", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "paused", - "type": "bool" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } ], "name": "PoolPausedStateChanged", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "recoveryMode", - "type": "bool" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "recoveryMode", + "type": "bool" + } ], "name": "PoolRecoveryModeStateChanged", "type": "event" - }, - { + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "yieldFeeExempt", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct TokenConfig[]", + "name": "tokenConfig", + "type": "tuple[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct PoolHooks", + "name": "hooks", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "supportsAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "supportsRemoveLiquidityCustom", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } + ], + "name": "PoolRegistered", + "type": "event" + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "factory", - "type": "address" - }, - { - "components": [ - { + { + "indexed": true, "internalType": "contract IERC20", "name": "token", "type": "address" - }, - { - "internalType": "enum TokenType", - "name": "tokenType", - "type": "uint8" - }, - { - "internalType": "contract IRateProvider", - "name": "rateProvider", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ProtocolFeeCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", "type": "address" - }, - { - "internalType": "bool", - "name": "yieldFeeExempt", - "type": "bool" - } - ], - "indexed": false, - "internalType": "struct TokenConfig[]", - "name": "tokenConfig", - "type": "tuple[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "pauseWindowEndTime", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "pauseManager", - "type": "address" - }, - { - "components": [ - { - "internalType": "bool", - "name": "shouldCallBeforeInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterAddLiquidity", - "type": "bool" - }, - { + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ProtocolSwapFeeCharged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "ProtocolSwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ProtocolYieldFeeCharged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "yieldFeePercentage", + "type": "uint256" + } + ], + "name": "ProtocolYieldFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeeAmount", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, "internalType": "bool", - "name": "shouldCallBeforeRemoveLiquidity", + "name": "paused", "type": "bool" - }, - { + } + ], + "name": "VaultPausedStateChanged", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "MAX_BUFFER_PERIOD_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_PAUSE_WINDOW_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "enum AddLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct AddLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "addLiquidity", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getPoolTokenCountAndIndexOfToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVaultExtension", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "invoke", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "reentrancyGuardEntered", + "outputs": [ + { "internalType": "bool", - "name": "shouldCallAfterRemoveLiquidity", + "name": "", "type": "bool" - } - ], - "indexed": false, - "internalType": "struct PoolCallbacks", - "name": "callbacks", - "type": "tuple" - }, - { - "components": [ - { + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "enum RemoveLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct RemoveLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "removeLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + } + ], + "name": "removeLiquidityRecovery", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOutRaw", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "retrieve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "settle", + "outputs": [ + { + "internalType": "uint256", + "name": "paid", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountGivenRaw", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limitRaw", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct SwapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCalculated", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "wire", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "mainVault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "AmountGivenZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "AmountInAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "AmountOutBelowMin", + "type": "error" + }, + { + "inputs": [], + "name": "BalanceNotSettled", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "BptAmountInAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "BptAmountOutBelowMin", + "type": "error" + }, + { + "inputs": [], + "name": "CannotReceiveEth", + "type": "error" + }, + { + "inputs": [], + "name": "CannotSwapSameToken", + "type": "error" + }, + { + "inputs": [], + "name": "CodecOverflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "HandlerOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "HookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddLiquidityKind", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRemoveLiquidityKind", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidToken", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenConfiguration", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenType", + "type": "error" + }, + { + "inputs": [], + "name": "MaxTokens", + "type": "error" + }, + { + "inputs": [], + "name": "MinTokens", + "type": "error" + }, + { + "inputs": [], + "name": "NoHandler", + "type": "error" + }, + { + "inputs": [], + "name": "NotStaticCall", + "type": "error" + }, + { + "inputs": [], + "name": "NotVaultDelegateCall", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "PauseBufferPeriodDurationTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolAlreadyRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolInRecoveryMode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotInRecoveryMode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolPauseWindowExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolPaused", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolSwapFeePercentageTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolYieldFeePercentageTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "QueriesDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [], + "name": "RouterNotTrusted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintToInt", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "SenderIsNotPauseManager", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderIsNotVault", + "type": "error" + }, + { + "inputs": [], + "name": "SenderNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFeePercentageTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "SwapLimit", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "TokenAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TokenNotRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "expectedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "actualToken", + "type": "address" + } + ], + "name": "TokensMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "TotalSupplyTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "UserDataNotSupported", + "type": "error" + }, + { + "inputs": [], + "name": "VaultNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPauseWindowDurationTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPauseWindowExpired", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "handler", + "type": "address" + }, + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "WrongHandler", + "type": "error" + }, + { + "inputs": [], + "name": "WrongVaultExtensionDeployment", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "AuthorizerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "int256[]", + "name": "deltas", + "type": "int256[]" + } + ], + "name": "PoolBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, "internalType": "bool", - "name": "supportsAddLiquidityCustom", + "name": "paused", "type": "bool" - }, - { + } + ], + "name": "PoolPausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, "internalType": "bool", - "name": "supportsRemoveLiquidityCustom", + "name": "recoveryMode", "type": "bool" - } - ], - "indexed": false, - "internalType": "struct LiquidityManagement", - "name": "liquidityManagement", - "type": "tuple" - } + } + ], + "name": "PoolRecoveryModeStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "yieldFeeExempt", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct TokenConfig[]", + "name": "tokenConfig", + "type": "tuple[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct PoolHooks", + "name": "hooks", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "supportsAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "supportsRemoveLiquidityCustom", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } ], "name": "PoolRegistered", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } ], "name": "ProtocolFeeCollected", "type": "event" - }, - { + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ProtocolSwapFeeCharged", + "type": "event" + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "swapFeePercentage", - "type": "uint256" - } + { + "indexed": true, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } ], "name": "ProtocolSwapFeePercentageChanged", "type": "event" - }, - { + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ProtocolYieldFeeCharged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "yieldFeePercentage", + "type": "uint256" + } + ], + "name": "ProtocolYieldFeePercentageChanged", + "type": "event" + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "swapFeePercentage", - "type": "uint256" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } ], "name": "SwapFeePercentageChanged", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } ], "name": "Transfer", "type": "event" - }, - { + }, + { "anonymous": false, "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "paused", - "type": "bool" - } + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } ], "name": "VaultPausedStateChanged", "type": "event" - }, - { + }, + { "inputs": [], "name": "MAX_BUFFER_PERIOD_DURATION", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "MAX_PAUSE_WINDOW_DURATION", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } ], "name": "allowance", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } ], "name": "approve", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "balanceOf", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "contract IERC20[]", - "name": "tokens", - "type": "address[]" - } + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } ], "name": "collectProtocolFees", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [], "name": "disableQuery", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "disableRecoveryMode", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "enableRecoveryMode", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "bytes4", - "name": "selector", - "type": "bytes4" - } + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } ], "name": "getActionId", "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getAuthorizer", "outputs": [ - { - "internalType": "contract IAuthorizer", - "name": "", - "type": "address" - } + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getBufferPeriodDuration", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getBufferPeriodEndTime", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } ], "name": "getHandler", "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } + { + "internalType": "address", + "name": "", + "type": "address" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getHandlersCount", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getMaximumPoolTokens", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "pure", "type": "function" - }, - { + }, + { "inputs": [], "name": "getMinimumPoolTokens", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "pure", "type": "function" - }, - { + }, + { "inputs": [], "name": "getNonzeroDeltaCount", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getPauseWindowEndTime", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "getPoolConfig", "outputs": [ - { - "components": [ - { - "internalType": "bool", - "name": "isPoolRegistered", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isPoolInitialized", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isPoolPaused", - "type": "bool" - }, - { - "internalType": "bool", - "name": "isPoolInRecoveryMode", - "type": "bool" - }, - { - "internalType": "bool", - "name": "hasDynamicSwapFee", - "type": "bool" - }, - { - "internalType": "uint64", - "name": "staticSwapFeePercentage", - "type": "uint64" - }, - { - "internalType": "uint24", - "name": "tokenDecimalDiffs", - "type": "uint24" - }, - { - "internalType": "uint32", - "name": "pauseWindowEndTime", - "type": "uint32" - }, - { - "components": [ - { - "internalType": "bool", - "name": "shouldCallBeforeInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeRemoveLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterRemoveLiquidity", - "type": "bool" - } - ], - "internalType": "struct PoolCallbacks", - "name": "callbacks", - "type": "tuple" - }, - { + { "components": [ - { - "internalType": "bool", - "name": "supportsAddLiquidityCustom", - "type": "bool" - }, - { - "internalType": "bool", - "name": "supportsRemoveLiquidityCustom", - "type": "bool" - } + { + "internalType": "bool", + "name": "isPoolRegistered", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolInitialized", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolInRecoveryMode", + "type": "bool" + }, + { + "internalType": "bool", + "name": "hasDynamicSwapFee", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "staticSwapFeePercentage", + "type": "uint64" + }, + { + "internalType": "uint24", + "name": "tokenDecimalDiffs", + "type": "uint24" + }, + { + "internalType": "uint32", + "name": "pauseWindowEndTime", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + } + ], + "internalType": "struct PoolHooks", + "name": "hooks", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "supportsAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "supportsRemoveLiquidityCustom", + "type": "bool" + } + ], + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } ], - "internalType": "struct LiquidityManagement", - "name": "liquidityManagement", + "internalType": "struct PoolConfig", + "name": "", "type": "tuple" - } - ], - "internalType": "struct PoolConfig", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "getPoolPausedState", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], - "name": "getPoolTokenCountAndIndexOfToken", + "name": "getPoolPausedState", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "getPoolTokenInfo", "outputs": [ - { - "internalType": "contract IERC20[]", - "name": "tokens", - "type": "address[]" - }, - { - "internalType": "enum TokenType[]", - "name": "tokenTypes", - "type": "uint8[]" - }, - { - "internalType": "uint256[]", - "name": "balancesRaw", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "decimalScalingFactors", - "type": "uint256[]" - }, - { - "internalType": "contract IRateProvider[]", - "name": "rateProviders", - "type": "address[]" - } + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "enum TokenType[]", + "name": "tokenTypes", + "type": "uint8[]" + }, + { + "internalType": "uint256[]", + "name": "balancesRaw", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "decimalScalingFactors", + "type": "uint256[]" + }, + { + "internalType": "contract IRateProvider[]", + "name": "rateProviders", + "type": "address[]" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "getPoolTokenRates", "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "getPoolTokens", "outputs": [ - { - "internalType": "contract IERC20[]", - "name": "", - "type": "address[]" - } + { + "internalType": "contract IERC20[]", + "name": "", + "type": "address[]" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } + { + "internalType": "address", + "name": "token", + "type": "address" + } ], - "name": "getProtocolSwapFee", + "name": "getProtocolFees", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getProtocolSwapFeePercentage", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolYieldFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "getStaticSwapFeePercentage", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } ], "name": "getTokenDelta", "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } + { + "internalType": "int256", + "name": "", + "type": "int256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } ], "name": "getTokenReserve", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "getVaultPausedState", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "contract IERC20[]", - "name": "tokens", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "exactAmountsIn", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "minBptAmountOut", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "userData", - "type": "bytes" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } ], "name": "initialize", "outputs": [ - { - "internalType": "uint256", - "name": "bptAmountOut", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } ], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "isPoolInRecoveryMode", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "isPoolInitialized", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "isPoolPaused", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "isPoolRegistered", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "isQueryDisabled", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [], "name": "isVaultPaused", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "view", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "pausePool", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [], "name": "pauseVault", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } ], "name": "quote", "outputs": [ - { - "internalType": "bytes", - "name": "result", - "type": "bytes" - } + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } ], "stateMutability": "payable", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "components": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "enum TokenType", - "name": "tokenType", - "type": "uint8" - }, - { - "internalType": "contract IRateProvider", - "name": "rateProvider", - "type": "address" - }, - { - "internalType": "bool", - "name": "yieldFeeExempt", - "type": "bool" - } - ], - "internalType": "struct TokenConfig[]", - "name": "tokenConfig", - "type": "tuple[]" - }, - { - "internalType": "uint256", - "name": "pauseWindowEndTime", - "type": "uint256" - }, - { - "internalType": "address", - "name": "pauseManager", - "type": "address" - }, - { - "components": [ - { - "internalType": "bool", - "name": "shouldCallBeforeInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterInitialize", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterSwap", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterAddLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallBeforeRemoveLiquidity", - "type": "bool" - }, - { - "internalType": "bool", - "name": "shouldCallAfterRemoveLiquidity", - "type": "bool" - } - ], - "internalType": "struct PoolCallbacks", - "name": "poolCallbacks", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "bool", - "name": "supportsAddLiquidityCustom", - "type": "bool" - }, - { + }, + { + "inputs": [], + "name": "reentrancyGuardEntered", + "outputs": [ + { "internalType": "bool", - "name": "supportsRemoveLiquidityCustom", + "name": "", "type": "bool" - } - ], - "internalType": "struct LiquidityManagement", - "name": "liquidityManagement", - "type": "tuple" - } + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "yieldFeeExempt", + "type": "bool" + } + ], + "internalType": "struct TokenConfig[]", + "name": "tokenConfig", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + } + ], + "internalType": "struct PoolHooks", + "name": "poolHooks", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "supportsAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "supportsRemoveLiquidityCustom", + "type": "bool" + } + ], + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } ], "name": "registerPool", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "contract IAuthorizer", - "name": "newAuthorizer", - "type": "address" - } + { + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } ], "name": "setAuthorizer", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "uint256", - "name": "newProtocolSwapFeePercentage", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "newProtocolSwapFeePercentage", + "type": "uint256" + } ], "name": "setProtocolSwapFeePercentage", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newProtocolYieldFeePercentage", + "type": "uint256" + } + ], + "name": "setProtocolYieldFeePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "uint256", - "name": "swapFeePercentage", - "type": "uint256" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } ], "name": "setStaticSwapFeePercentage", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } + { + "internalType": "address", + "name": "token", + "type": "address" + } ], "name": "totalSupply", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } ], "name": "transfer", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "nonpayable", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } ], "name": "transferFrom", "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } + { + "internalType": "bool", + "name": "", + "type": "bool" + } ], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } + { + "internalType": "address", + "name": "pool", + "type": "address" + } ], "name": "unpausePool", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [], "name": "unpauseVault", "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { + }, + { "inputs": [], "name": "vault", "outputs": [ - { - "internalType": "contract IVault", - "name": "", - "type": "address" - } + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } ], "stateMutability": "view", "type": "function" - } -] + } +] \ No newline at end of file diff --git a/modules/sources/transformers/pool-tokens-transformer.ts b/modules/sources/transformers/pool-tokens-transformer.ts index d7f708370..ed9b912f7 100644 --- a/modules/sources/transformers/pool-tokens-transformer.ts +++ b/modules/sources/transformers/pool-tokens-transformer.ts @@ -1,13 +1,34 @@ -import { PoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; +import { Chain } from '@prisma/client'; -export const poolTokensTransformer = (subgraphPool: PoolFragment) => { - const tokens = subgraphPool.tokens ?? []; +export const poolTokensTransformer = (vaultSubgraphPool: VaultSubgraphPoolFragment) => { + const tokens = vaultSubgraphPool.tokens ?? []; return tokens.map((token, i) => ({ - id: `${subgraphPool.id}-${token.address}`.toLowerCase(), + id: `${vaultSubgraphPool.id}-${token.address}`.toLowerCase(), address: token.address.toLowerCase(), index: token.index, nestedPoolId: null, - priceRateProvider: subgraphPool.rateProviders![i].address.toLowerCase(), + priceRateProvider: vaultSubgraphPool.rateProviders![i].address.toLowerCase(), exemptFromProtocolYieldFee: token.totalProtocolYieldFee === '0' ? true : false, })); }; + +export const poolTokensDynamicDataTransformer = ( + vaultSubgraphPool: VaultSubgraphPoolFragment, + poolSubgraphPool: PoolSubgraphPoolFragment, + chain: Chain, +) => { + const tokens = vaultSubgraphPool.tokens ?? []; + return tokens.map((token, i) => ({ + id: `${vaultSubgraphPool.id}-${token.address}`.toLowerCase(), + poolTokenId: `${vaultSubgraphPool.id}-${token.address}`.toLowerCase(), + chain, + blockNumber: parseFloat(vaultSubgraphPool.blockNumber), + balance: token.balance, + balanceUSD: 0, + priceRate: '0', + weight: poolSubgraphPool.weights[token.index] ?? null, + // latestFxPrice: poolSubgraphPool.latestFxPrice, + })); +}; diff --git a/modules/sources/transformers/pool-transformer.ts b/modules/sources/transformers/pool-transformer.ts index 674b08c7e..5faf2e306 100644 --- a/modules/sources/transformers/pool-transformer.ts +++ b/modules/sources/transformers/pool-transformer.ts @@ -1,24 +1,43 @@ import { Chain, PrismaPool, PrismaPoolType } from '@prisma/client'; -import { PoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as PoolSubgraphPoolFragment, PoolType } from '../../subgraphs/balancer-v3-pools/generated/types'; +import { StableData } from '@modules/pool/subgraph-mapper'; export const poolTransformer = ( - subgraphPool: PoolFragment, - contractData: { name?: string; symbol?: string }, + vaultSubgraphPool: VaultSubgraphPoolFragment, + poolSubgraphPool: PoolSubgraphPoolFragment, chain: Chain, ): PrismaPool => { + let type: PrismaPoolType; + let typeData = {}; + + switch (poolSubgraphPool.factory.type) { + case PoolType.Weighted: + type = PrismaPoolType.WEIGHTED; + break; + case PoolType.Stable: + type = PrismaPoolType.STABLE; + typeData = { + amp: '10', // TODO just a place holder + } as StableData; + break; + default: + type = PrismaPoolType.UNKNOWN; + } + return { - id: subgraphPool.id.toLowerCase(), + id: vaultSubgraphPool.id.toLowerCase(), chain: chain, vaultVersion: 3, - address: subgraphPool.id.toLowerCase(), + address: vaultSubgraphPool.id.toLowerCase(), decimals: 18, - symbol: contractData.symbol || '', - name: contractData.name || '', + symbol: vaultSubgraphPool.symbol, + name: vaultSubgraphPool.name, owner: '', - factory: (subgraphPool.factory && subgraphPool.factory.toLowerCase()) || '', - type: PrismaPoolType.WEIGHTED, - typeData: {}, - version: 1, - createTime: Number(subgraphPool.blockTimestamp), + factory: poolSubgraphPool.factory.id.toLowerCase(), + type: type, + typeData: typeData, + version: poolSubgraphPool.factory.version, + createTime: Number(vaultSubgraphPool.blockTimestamp), }; }; diff --git a/modules/subgraphs/balancer-v3-pools/index.ts b/modules/subgraphs/balancer-v3-pools/index.ts new file mode 100644 index 000000000..7d0e78052 --- /dev/null +++ b/modules/subgraphs/balancer-v3-pools/index.ts @@ -0,0 +1,17 @@ +import { GraphQLClient } from 'graphql-request'; +import { getSdk } from './generated/types'; + +/** + * Builds a client based on subgraph URL. + * + * @param subgraphUrl - url of the subgraph + * @returns sdk - generated sdk for the subgraph + */ +export const getPoolsSubgraphClient = (subgraphUrl: string) => { + const client = new GraphQLClient(subgraphUrl); + const sdk = getSdk(client); + + return sdk; +}; + +export type V3PoolsSubgraphClient = ReturnType; diff --git a/modules/subgraphs/balancer-v3-pools/pools.graphql b/modules/subgraphs/balancer-v3-pools/pools.graphql new file mode 100644 index 000000000..31541dce5 --- /dev/null +++ b/modules/subgraphs/balancer-v3-pools/pools.graphql @@ -0,0 +1,42 @@ +fragment Factory on Factory { + id + type + version + pools { + id + address + weights + } +} + +fragment Pool on Pool { + id + address + factory { + id + # address + type + version + } + weights +} + +query Pools( + $skip: Int + $first: Int + $orderBy: Pool_orderBy + $orderDirection: OrderDirection + $where: Pool_filter + $block: Block_height +) { + pools( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...Pool + } +} diff --git a/modules/subgraphs/balancer-v3-vault/index.ts b/modules/subgraphs/balancer-v3-vault/index.ts index 1a8b7bdfc..c3f2b1c96 100644 --- a/modules/subgraphs/balancer-v3-vault/index.ts +++ b/modules/subgraphs/balancer-v3-vault/index.ts @@ -7,11 +7,11 @@ import { getSdk } from './generated/types'; * @param subgraphUrl - url of the subgraph * @returns sdk - generated sdk for the subgraph */ -export const getClient = (subgraphUrl: string) => { +export const getVaultSubgraphClient = (subgraphUrl: string) => { const client = new GraphQLClient(subgraphUrl); const sdk = getSdk(client); return sdk; }; -export type V3SubgraphClient = ReturnType; +export type V3SubgraphClient = ReturnType; diff --git a/modules/subgraphs/balancer-v3-vault/pools.graphql b/modules/subgraphs/balancer-v3-vault/pools.graphql index 4ddb690cf..786e1ffcf 100644 --- a/modules/subgraphs/balancer-v3-vault/pools.graphql +++ b/modules/subgraphs/balancer-v3-vault/pools.graphql @@ -1,6 +1,9 @@ fragment Pool on Pool { id factory + address + name + symbol totalShares pauseWindowEndTime pauseManager @@ -8,14 +11,21 @@ fragment Pool on Pool { blockTimestamp transactionHash tokens { + id address index + name + symbol + decimals balance totalProtocolSwapFee totalProtocolYieldFee } rateProviders { address + token { + address + } } } diff --git a/tasks/index.ts b/tasks/index.ts index bb0cdf104..f27f6e4c8 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -6,7 +6,7 @@ async function run(job: string = process.argv[2], chain: string = process.argv[3 console.log('Running job', job, chain); if (job === 'sync-changed-pools-v3') { - return jobsController.syncPools(chain); + return jobsController.addMissingPools(chain); } return Promise.reject(new Error(`Unknown job: ${job}`)); diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 6378cafc3..78fcb7f23 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -108,7 +108,13 @@ export function configureWorkerRoutes(app: Express) { break; case 'sync-changed-pools-v3': - await runIfNotAlreadyRunning(job.name, chainId, () => jobsController.syncPools(chainId), res, next); + await runIfNotAlreadyRunning( + job.name, + chainId, + () => jobsController.addMissingPools(chainId), + res, + next, + ); break; case 'user-sync-wallet-balances-for-all-pools': await runIfNotAlreadyRunning( From fd91ad9378eacdabca3aabb2003ec4f24bb14eec Mon Sep 17 00:00:00 2001 From: franz Date: Wed, 21 Feb 2024 12:45:36 +0100 Subject: [PATCH 07/14] wip --- modules/actions/jobs-actions/sync-pools.test.ts | 10 ++++------ modules/actions/jobs-actions/sync-pools.ts | 16 ++++------------ modules/controllers/jobs-controller.test.ts | 6 +++--- modules/controllers/jobs-controller.ts | 15 +++++++-------- modules/sources/transformers/pool-transformer.ts | 2 +- tasks/index.ts | 2 +- tsconfig.json | 10 ++-------- worker/job-handlers.ts | 2 +- 8 files changed, 23 insertions(+), 40 deletions(-) diff --git a/modules/actions/jobs-actions/sync-pools.test.ts b/modules/actions/jobs-actions/sync-pools.test.ts index 40bfd4512..6abb107f8 100644 --- a/modules/actions/jobs-actions/sync-pools.test.ts +++ b/modules/actions/jobs-actions/sync-pools.test.ts @@ -1,10 +1,8 @@ -import { syncMissingPools } from './sync-pools'; +import { addMissingPoolsFromSubgraph } from './sync-pools'; import { prisma } from '../../../prisma/prisma-client'; import { PrismaPool } from '@prisma/client'; -import { PoolFragment as VaultSubgraphPoolFragment } from '@modules/subgraphs/balancer-v3-vault/generated/types'; -import { fetchErc20Headers } from '@modules/sources/contracts'; -import { getViemClient } from '@modules/sources/viem-client'; -import { PoolFragment as PoolSubgraphPoolFragment } from '@modules/subgraphs/balancer-v3-pools/generated/types'; +import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; // Mock the module dependencies jest.mock('@modules/sources/contracts', () => ({ @@ -48,7 +46,7 @@ describe('syncPools', () => { beforeEach(() => { jest.clearAllMocks(); - return syncMissingPools(vaultSubgraphClient, poolSubgraphClient, 'SEPOLIA'); + return addMissingPoolsFromSubgraph(vaultSubgraphClient, poolSubgraphClient, 'SEPOLIA'); }); it('should fetch pools from vault subgraph', async () => { diff --git a/modules/actions/jobs-actions/sync-pools.ts b/modules/actions/jobs-actions/sync-pools.ts index ebef7d346..4f02cb56e 100644 --- a/modules/actions/jobs-actions/sync-pools.ts +++ b/modules/actions/jobs-actions/sync-pools.ts @@ -1,16 +1,8 @@ import { Chain } from '@prisma/client'; import { prisma } from '../../../prisma/prisma-client'; -import { V3SubgraphClient } from '@modules/subgraphs/balancer-v3-vault'; -import { - poolTransformer, - poolTokensTransformer, - poolTokensDynamicDataTransformer, -} from '@modules/sources/transformers'; -import { fetchErc20Headers, fetchWeightedPoolData, fetchPoolTokens } from '@modules/sources/contracts'; -import type { ViemClient } from 'modules/sources/types'; -import { V3PoolsSubgraphClient } from '@modules/subgraphs/balancer-v3-pools'; -import { decimal } from '@modules/big-number/big-number'; - +import { poolTransformer, poolTokensTransformer, poolTokensDynamicDataTransformer } from '../../sources/transformers'; +import { V3PoolsSubgraphClient } from '../../subgraphs/balancer-v3-pools'; +import { V3SubgraphClient } from '../../subgraphs/balancer-v3-vault'; /** * Makes sure that all pools are synced in the database * @@ -19,7 +11,7 @@ import { decimal } from '@modules/big-number/big-number'; * @param chain * @returns syncedPools - the pools that were synced */ -export async function syncMissingPools( +export async function addMissingPoolsFromSubgraph( vaultSubgraphClient: Pick, poolSubgraphClient: V3PoolsSubgraphClient, // viemClient: ViemClient, diff --git a/modules/controllers/jobs-controller.test.ts b/modules/controllers/jobs-controller.test.ts index 7853598be..ee9676189 100644 --- a/modules/controllers/jobs-controller.test.ts +++ b/modules/controllers/jobs-controller.test.ts @@ -1,4 +1,4 @@ -import { syncMissingPools } from '@modules/actions/jobs-actions/sync-pools'; +import { addMissingPoolsFromSubgraph } from '../actions/jobs-actions/sync-pools'; import { JobsController } from './jobs-controller'; // Mock the actions jest.mock('@modules/actions/jobs_actions', () => ({ @@ -13,8 +13,8 @@ describe('jobsController', () => { }); it('should call getClient with correct chain', () => { - jobsController.addMissingPools('11155111'); + jobsController.addMissingPoolsFromSubgraph('11155111'); - expect(syncMissingPools).toHaveBeenCalled(); + expect(addMissingPoolsFromSubgraph).toHaveBeenCalled(); }); }); diff --git a/modules/controllers/jobs-controller.ts b/modules/controllers/jobs-controller.ts index 2ae7ec484..039de4ebe 100644 --- a/modules/controllers/jobs-controller.ts +++ b/modules/controllers/jobs-controller.ts @@ -1,9 +1,8 @@ -import config from '@config/index'; -import { chainIdToChain } from '@modules/network/chain-id-to-chain'; -import { getViemClient } from '@modules/sources/viem-client'; -import { syncMissingPools } from '@modules/actions/jobs-actions/sync-pools'; -import { getVaultSubgraphClient } from '@modules/subgraphs/balancer-v3-vault'; -import { getPoolsSubgraphClient } from '@modules/subgraphs/balancer-v3-pools'; +import config from '../../config'; +import { addMissingPoolsFromSubgraph } from '../actions/jobs-actions/sync-pools'; +import { chainIdToChain } from '../network/chain-id-to-chain'; +import { getPoolsSubgraphClient } from '../subgraphs/balancer-v3-pools'; +import { getVaultSubgraphClient } from '../subgraphs/balancer-v3-vault'; /** * Controller responsible for matching job requests to configured job handlers @@ -16,7 +15,7 @@ export function JobsController(tracer?: any) { // Setup tracing // ... return { - addMissingPools(chainId: string) { + addMissingPoolsFromSubgraph(chainId: string) { const chain = chainIdToChain[chainId]; const { subgraphs: { balancerV3, balancerPoolsV3 }, @@ -33,7 +32,7 @@ export function JobsController(tracer?: any) { // TODO: add syncing v2 pools as well by splitting the poolService into separate // actions with extracted configuration - return syncMissingPools(vaultSubgraphClient, poolSubgraphClient, chain); + return addMissingPoolsFromSubgraph(vaultSubgraphClient, poolSubgraphClient, chain); }, }; } diff --git a/modules/sources/transformers/pool-transformer.ts b/modules/sources/transformers/pool-transformer.ts index 5faf2e306..b5dcc1d94 100644 --- a/modules/sources/transformers/pool-transformer.ts +++ b/modules/sources/transformers/pool-transformer.ts @@ -1,7 +1,7 @@ import { Chain, PrismaPool, PrismaPoolType } from '@prisma/client'; import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; import { PoolFragment as PoolSubgraphPoolFragment, PoolType } from '../../subgraphs/balancer-v3-pools/generated/types'; -import { StableData } from '@modules/pool/subgraph-mapper'; +import { StableData } from '../../pool/subgraph-mapper'; export const poolTransformer = ( vaultSubgraphPool: VaultSubgraphPoolFragment, diff --git a/tasks/index.ts b/tasks/index.ts index f27f6e4c8..5e1082727 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -6,7 +6,7 @@ async function run(job: string = process.argv[2], chain: string = process.argv[3 console.log('Running job', job, chain); if (job === 'sync-changed-pools-v3') { - return jobsController.addMissingPools(chain); + return jobsController.addMissingPoolsFromSubgraph(chain); } return Promise.reject(new Error(`Unknown job: ${job}`)); diff --git a/tsconfig.json b/tsconfig.json index 0a0c1eec5..a9c4bc4b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,13 +10,7 @@ "resolveJsonModule": true, "outDir": "./dist", "skipLibCheck": true, - "sourceMap": true, - "baseUrl": ".", - "paths": { - "@app/*": ["app/*"], - "@config/*": ["config/*"], - "@modules/*": ["modules/*"] - } + "sourceMap": true }, - "exclude": ["node_modules"] + "exclude": ["node_modules", "debug", "**/*.spec.ts", "**/*.test.ts"] } diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 78fcb7f23..f43cc0c9c 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -111,7 +111,7 @@ export function configureWorkerRoutes(app: Express) { await runIfNotAlreadyRunning( job.name, chainId, - () => jobsController.addMissingPools(chainId), + () => jobsController.addMissingPoolsFromSubgraph(chainId), res, next, ); From 17be6d97db52d50ade9a0b5230a86f14fbac9102 Mon Sep 17 00:00:00 2001 From: franz Date: Wed, 21 Feb 2024 15:52:11 +0100 Subject: [PATCH 08/14] write pools --- config/index.ts | 2 +- config/sepolia.ts | 4 +- .../actions/jobs-actions/sync-pools.test.ts | 2 +- modules/actions/jobs-actions/sync-pools.ts | 82 +++++++++++-------- modules/network/sepolia.ts | 2 +- .../transformers/pool-tokens-transformer.ts | 30 +++++-- .../sources/transformers/pool-transformer.ts | 5 +- modules/sources/viem-client.ts | 2 +- .../subgraphs/balancer-v3-pools/pools.graphql | 4 +- tasks/index.ts | 3 +- worker/job-handlers.ts | 2 +- 11 files changed, 84 insertions(+), 54 deletions(-) diff --git a/config/index.ts b/config/index.ts index bd2d384f7..384c7b540 100644 --- a/config/index.ts +++ b/config/index.ts @@ -1,6 +1,6 @@ import { Chain } from '@prisma/client'; -import { NetworkData } from '@modules/network/network-config-types'; import { sepoliaConfig } from './sepolia'; +import { NetworkData } from '../modules/network/network-config-types'; export default { [Chain.ARBITRUM]: {} as NetworkData, diff --git a/config/sepolia.ts b/config/sepolia.ts index da9e44c51..954990207 100644 --- a/config/sepolia.ts +++ b/config/sepolia.ts @@ -1,6 +1,6 @@ -import { env } from '@app/env'; -import { NetworkData } from '@modules/network/network-config-types'; import { BigNumber } from 'ethers'; +import { env } from '../app/env'; +import { NetworkData } from '../modules/network/network-config-types'; export const sepoliaConfig: NetworkData = { chain: { diff --git a/modules/actions/jobs-actions/sync-pools.test.ts b/modules/actions/jobs-actions/sync-pools.test.ts index 6abb107f8..0426e63c1 100644 --- a/modules/actions/jobs-actions/sync-pools.test.ts +++ b/modules/actions/jobs-actions/sync-pools.test.ts @@ -2,7 +2,7 @@ import { addMissingPoolsFromSubgraph } from './sync-pools'; import { prisma } from '../../../prisma/prisma-client'; import { PrismaPool } from '@prisma/client'; import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; -import { PoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; +import { TypePoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; // Mock the module dependencies jest.mock('@modules/sources/contracts', () => ({ diff --git a/modules/actions/jobs-actions/sync-pools.ts b/modules/actions/jobs-actions/sync-pools.ts index 4f02cb56e..3fc1fdbf6 100644 --- a/modules/actions/jobs-actions/sync-pools.ts +++ b/modules/actions/jobs-actions/sync-pools.ts @@ -1,8 +1,22 @@ -import { Chain } from '@prisma/client'; +import { Chain, Prisma, PrismaPoolType } from '@prisma/client'; import { prisma } from '../../../prisma/prisma-client'; -import { poolTransformer, poolTokensTransformer, poolTokensDynamicDataTransformer } from '../../sources/transformers'; +import { + poolTransformer, + poolTokensTransformer, + poolTokensDynamicDataTransformer, + poolExpandedTokensTransformer, +} from '../../sources/transformers'; import { V3PoolsSubgraphClient } from '../../subgraphs/balancer-v3-pools'; import { V3SubgraphClient } from '../../subgraphs/balancer-v3-vault'; +import { PoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; +import { PoolType, TypePoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; +import _ from 'lodash'; + +type PoolDbEntry = { + pool: Prisma.PrismaPoolCreateInput; + poolTokenDynamicData: Prisma.PrismaPoolTokenDynamicDataCreateManyInput[]; + poolExpandedTokens: Prisma.PrismaPoolExpandedTokensCreateManyInput[]; +}; /** * Makes sure that all pools are synced in the database * @@ -28,10 +42,6 @@ export async function addMissingPoolsFromSubgraph( const dbPoolIds = new Set(dbPools.map((pool) => pool.id.toLowerCase())); const missingPools = vaultSubgraphPools.filter((pool) => !dbPoolIds.has(pool.id)); - if (missingPools.length === 0) { - return true; - } - // Store pool tokens and BPT in the tokens table before creating the pools try { const allTokens: { address: string; name: string; decimals: number; symbol: string; chain: Chain }[] = []; @@ -65,15 +75,17 @@ export async function addMissingPoolsFromSubgraph( } // Transform pool data for the database - const dbPoolEntries = missingPools - .map((missingPool) => { - const vaultSubgraphPool = vaultSubgraphPools.find((pool) => pool.id === missingPool.id); - const poolSubgraphPool = poolSubgraphPools.find((pool) => pool.id === missingPool.id); - if (!vaultSubgraphPool || !poolSubgraphPool) { - // That won't happen, but TS doesn't know that - return null; - } - return { + const dbEntries: PoolDbEntry[] = []; + + missingPools.forEach((missingPool) => { + const vaultSubgraphPool = vaultSubgraphPools.find((pool) => pool.id === missingPool.id); + const poolSubgraphPool = poolSubgraphPools.find((pool) => pool.id === missingPool.id); + if (!vaultSubgraphPool || !poolSubgraphPool) { + // That won't happen, but TS doesn't know that + return null; + } + const dbEntry: PoolDbEntry = { + pool: { ...poolTransformer(vaultSubgraphPool, poolSubgraphPool, chain), typeData: JSON.stringify({}), tokens: { @@ -84,6 +96,7 @@ export async function addMissingPoolsFromSubgraph( data: poolTokensTransformer(vaultSubgraphPool), }, }, + // placeholder data, will be updated with onchain values dynamicData: { create: { id: vaultSubgraphPool.id, @@ -95,37 +108,34 @@ export async function addMissingPoolsFromSubgraph( totalSharesNum: parseFloat(vaultSubgraphPool.totalShares), }, }, - poolTokenDynamicData: poolTokensDynamicDataTransformer(vaultSubgraphPool, poolSubgraphPool, chain), - }; - }) - .filter((entry): entry is NonNullable => entry !== null); + }, + poolTokenDynamicData: poolTokensDynamicDataTransformer(vaultSubgraphPool, poolSubgraphPool, chain), + poolExpandedTokens: poolExpandedTokensTransformer(vaultSubgraphPool, chain), + }; + dbEntries.push(dbEntry); + }); // Store missing pools in the database let allOk = true; - for (const data of dbPoolEntries) { + for (const entry of dbEntries) { try { - await prisma.prismaPool.create({ data }); - // Create pool tokens dynamic data + await prisma.prismaPool.create({ data: entry.pool }); + await prisma.prismaPoolTokenDynamicData.createMany({ - data: data.poolTokenDynamicData, + skipDuplicates: true, + data: entry.poolTokenDynamicData, + }); + + // TODO deal with nested pools + await prisma.prismaPoolExpandedTokens.createMany({ + skipDuplicates: true, + data: entry.poolExpandedTokens, }); } catch (e) { // TODO: handle errors - console.error(`Error creating pool ${data.id}`, e); + console.error(`Error creating pool ${entry.pool.id}`, e); allOk = false; } - - // Create pool tokens – something to talk about if we want to do it here on write, or on read - // Pool queries fail without excplicitly creating the nested tokens relation - await prisma.prismaPoolExpandedTokens.createMany({ - skipDuplicates: true, - data: data.tokens.createMany.data.map((token) => ({ - poolId: data.id, - chain: chain, - tokenAddress: token.address, - nestedPoolId: token.nestedPoolId || null, - })), - }); } return allOk; diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index 9b21275f6..f4188fa22 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -15,7 +15,7 @@ import { GithubContentService } from '../content/github-content.service'; import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph.service'; import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; -import { sepoliaConfig as sepoliaNetworkData } from '@config/sepolia'; +import { sepoliaConfig as sepoliaNetworkData } from '../../config/sepolia'; export { sepoliaNetworkData }; diff --git a/modules/sources/transformers/pool-tokens-transformer.ts b/modules/sources/transformers/pool-tokens-transformer.ts index ed9b912f7..30a2ee535 100644 --- a/modules/sources/transformers/pool-tokens-transformer.ts +++ b/modules/sources/transformers/pool-tokens-transformer.ts @@ -1,8 +1,10 @@ import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; -import { PoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; -import { Chain } from '@prisma/client'; +import { TypePoolFragment as PoolSubgraphPoolFragment } from '../../subgraphs/balancer-v3-pools/generated/types'; +import { Chain, Prisma } from '@prisma/client'; -export const poolTokensTransformer = (vaultSubgraphPool: VaultSubgraphPoolFragment) => { +export function poolTokensTransformer( + vaultSubgraphPool: VaultSubgraphPoolFragment, +): Prisma.PrismaPoolTokenCreateManyPoolInput[] { const tokens = vaultSubgraphPool.tokens ?? []; return tokens.map((token, i) => ({ id: `${vaultSubgraphPool.id}-${token.address}`.toLowerCase(), @@ -12,13 +14,13 @@ export const poolTokensTransformer = (vaultSubgraphPool: VaultSubgraphPoolFragme priceRateProvider: vaultSubgraphPool.rateProviders![i].address.toLowerCase(), exemptFromProtocolYieldFee: token.totalProtocolYieldFee === '0' ? true : false, })); -}; +} -export const poolTokensDynamicDataTransformer = ( +export function poolTokensDynamicDataTransformer( vaultSubgraphPool: VaultSubgraphPoolFragment, poolSubgraphPool: PoolSubgraphPoolFragment, chain: Chain, -) => { +): Prisma.PrismaPoolTokenDynamicDataCreateManyInput[] { const tokens = vaultSubgraphPool.tokens ?? []; return tokens.map((token, i) => ({ id: `${vaultSubgraphPool.id}-${token.address}`.toLowerCase(), @@ -31,4 +33,18 @@ export const poolTokensDynamicDataTransformer = ( weight: poolSubgraphPool.weights[token.index] ?? null, // latestFxPrice: poolSubgraphPool.latestFxPrice, })); -}; +} + +// TODO deal with nested pools +export function poolExpandedTokensTransformer( + vaultSubgraphPool: VaultSubgraphPoolFragment, + chain: Chain, +): Prisma.PrismaPoolExpandedTokensCreateManyInput[] { + const tokens = vaultSubgraphPool.tokens ?? []; + return tokens.map((token, i) => ({ + poolId: vaultSubgraphPool.id, + chain: chain, + tokenAddress: token.address, + nestedPoolId: null, + })); +} diff --git a/modules/sources/transformers/pool-transformer.ts b/modules/sources/transformers/pool-transformer.ts index b5dcc1d94..69830e328 100644 --- a/modules/sources/transformers/pool-transformer.ts +++ b/modules/sources/transformers/pool-transformer.ts @@ -1,6 +1,9 @@ import { Chain, PrismaPool, PrismaPoolType } from '@prisma/client'; import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; -import { PoolFragment as PoolSubgraphPoolFragment, PoolType } from '../../subgraphs/balancer-v3-pools/generated/types'; +import { + TypePoolFragment as PoolSubgraphPoolFragment, + PoolType, +} from '../../subgraphs/balancer-v3-pools/generated/types'; import { StableData } from '../../pool/subgraph-mapper'; export const poolTransformer = ( diff --git a/modules/sources/viem-client.ts b/modules/sources/viem-client.ts index 447def93f..5d268d9e7 100644 --- a/modules/sources/viem-client.ts +++ b/modules/sources/viem-client.ts @@ -12,7 +12,7 @@ import { sepolia, } from 'viem/chains'; import { Chain } from '@prisma/client'; -import config from '@config/index'; +import config from '../../config'; export type ViemClient = ReturnType; diff --git a/modules/subgraphs/balancer-v3-pools/pools.graphql b/modules/subgraphs/balancer-v3-pools/pools.graphql index 31541dce5..c9417d1c0 100644 --- a/modules/subgraphs/balancer-v3-pools/pools.graphql +++ b/modules/subgraphs/balancer-v3-pools/pools.graphql @@ -9,7 +9,7 @@ fragment Factory on Factory { } } -fragment Pool on Pool { +fragment TypePool on Pool { id address factory { @@ -37,6 +37,6 @@ query Pools( where: $where block: $block ) { - ...Pool + ...TypePool } } diff --git a/tasks/index.ts b/tasks/index.ts index 5e1082727..49ace8c83 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -1,5 +1,6 @@ -import { JobsController } from '@modules/controllers/jobs-controller'; +import { JobsController } from '../modules/controllers/jobs-controller'; +// TODO needed? const jobsController = JobsController(); async function run(job: string = process.argv[2], chain: string = process.argv[3]) { diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index f43cc0c9c..89a6b082e 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -17,7 +17,7 @@ import { cronsDurationMetricPublisher } from '../modules/metrics/cron-duration-m import { syncLatestFXPrices } from '../modules/token/latest-fx-price'; import { AllNetworkConfigs } from '../modules/network/network-config'; import { sftmxService } from '../modules/sftmx/sftmx.service'; -import { JobsController } from '@modules/controllers/jobs-controller'; +import { JobsController } from '../modules/controllers/jobs-controller'; const runningJobs: Set = new Set(); From 5130fe533a8b21d90c6a0dc782a7476bf2f377b2 Mon Sep 17 00:00:00 2001 From: franz Date: Wed, 21 Feb 2024 17:25:45 +0100 Subject: [PATCH 09/14] wip --- modules/sources/contracts/abis/VaultV3.ts | 3622 +++++++++++++++++ .../contracts/abis/VaultV3JSON.json} | 0 .../sources/contracts/fetch-pool-tokens.ts | 12 +- 3 files changed, 3632 insertions(+), 2 deletions(-) create mode 100644 modules/sources/contracts/abis/VaultV3.ts rename modules/{pool/abi/VaultV3.json => sources/contracts/abis/VaultV3JSON.json} (100%) diff --git a/modules/sources/contracts/abis/VaultV3.ts b/modules/sources/contracts/abis/VaultV3.ts new file mode 100644 index 000000000..dcf38d114 --- /dev/null +++ b/modules/sources/contracts/abis/VaultV3.ts @@ -0,0 +1,3622 @@ +export const vaultV3Abi = [ + { + inputs: [ + { + internalType: 'contract IVaultExtension', + name: 'vaultExtension', + type: 'address', + }, + { + internalType: 'contract IAuthorizer', + name: 'authorizer', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + ], + name: 'AddressEmptyCode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'AddressInsufficientBalance', + type: 'error', + }, + { + inputs: [], + name: 'AllZeroInputs', + type: 'error', + }, + { + inputs: [], + name: 'AmountGivenZero', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'AmountInAboveMax', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'AmountOutBelowMin', + type: 'error', + }, + { + inputs: [], + name: 'BalanceNotSettled', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'BptAmountInAboveMax', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'BptAmountOutBelowMin', + type: 'error', + }, + { + inputs: [], + name: 'CannotReceiveEth', + type: 'error', + }, + { + inputs: [], + name: 'CannotSwapSameToken', + type: 'error', + }, + { + inputs: [], + name: 'DoesNotSupportAddLiquidityCustom', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'allowance', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'needed', + type: 'uint256', + }, + ], + name: 'ERC20InsufficientAllowance', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'balance', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'needed', + type: 'uint256', + }, + ], + name: 'ERC20InsufficientBalance', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'approver', + type: 'address', + }, + ], + name: 'ERC20InvalidApprover', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + ], + name: 'ERC20InvalidReceiver', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'ERC20InvalidSender', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'ERC20InvalidSpender', + type: 'error', + }, + { + inputs: [], + name: 'FailedInnerCall', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'HandlerOutOfBounds', + type: 'error', + }, + { + inputs: [], + name: 'HookFailed', + type: 'error', + }, + { + inputs: [], + name: 'InputLengthMismatch', + type: 'error', + }, + { + inputs: [], + name: 'InvalidAddLiquidityKind', + type: 'error', + }, + { + inputs: [], + name: 'InvalidRemoveLiquidityKind', + type: 'error', + }, + { + inputs: [], + name: 'InvalidToken', + type: 'error', + }, + { + inputs: [], + name: 'InvalidTokenConfiguration', + type: 'error', + }, + { + inputs: [], + name: 'InvalidTokenType', + type: 'error', + }, + { + inputs: [], + name: 'MaxTokens', + type: 'error', + }, + { + inputs: [], + name: 'MinTokens', + type: 'error', + }, + { + inputs: [], + name: 'MultipleNonZeroInputs', + type: 'error', + }, + { + inputs: [], + name: 'NoHandler', + type: 'error', + }, + { + inputs: [], + name: 'NotStaticCall', + type: 'error', + }, + { + inputs: [], + name: 'NotVaultDelegateCall', + type: 'error', + }, + { + inputs: [], + name: 'PauseBufferPeriodDurationTooLarge', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolAlreadyInitialized', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolAlreadyRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolInRecoveryMode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotInRecoveryMode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotInitialized', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotPaused', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolPauseWindowExpired', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolPaused', + type: 'error', + }, + { + inputs: [], + name: 'ProtocolSwapFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [], + name: 'ProtocolYieldFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [], + name: 'QueriesDisabled', + type: 'error', + }, + { + inputs: [], + name: 'ReentrancyGuardReentrantCall', + type: 'error', + }, + { + inputs: [], + name: 'RouterNotTrusted', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'bits', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'SafeCastOverflowedUintDowncast', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'SafeCastOverflowedUintToInt', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'SafeERC20FailedOperation', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'SenderIsNotPauseManager', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'SenderIsNotVault', + type: 'error', + }, + { + inputs: [], + name: 'SwapFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'SwapLimit', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'TokenAlreadyRegistered', + type: 'error', + }, + { + inputs: [], + name: 'TokenNotRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'expectedToken', + type: 'address', + }, + { + internalType: 'address', + name: 'actualToken', + type: 'address', + }, + ], + name: 'TokensMismatch', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'TotalSupplyTooLow', + type: 'error', + }, + { + inputs: [], + name: 'UserDataNotSupported', + type: 'error', + }, + { + inputs: [], + name: 'VaultNotPaused', + type: 'error', + }, + { + inputs: [], + name: 'VaultPauseWindowDurationTooLarge', + type: 'error', + }, + { + inputs: [], + name: 'VaultPauseWindowExpired', + type: 'error', + }, + { + inputs: [], + name: 'VaultPaused', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'handler', + type: 'address', + }, + { + internalType: 'address', + name: 'caller', + type: 'address', + }, + ], + name: 'WrongHandler', + type: 'error', + }, + { + inputs: [], + name: 'WrongVaultExtensionDeployment', + type: 'error', + }, + { + inputs: [], + name: 'ZeroDivision', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IAuthorizer', + name: 'newAuthorizer', + type: 'address', + }, + ], + name: 'AuthorizerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'liquidityProvider', + type: 'address', + }, + { + indexed: false, + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + indexed: false, + internalType: 'int256[]', + name: 'deltas', + type: 'int256[]', + }, + ], + name: 'PoolBalanceChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolInitialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'paused', + type: 'bool', + }, + ], + name: 'PoolPausedStateChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'recoveryMode', + type: 'bool', + }, + ], + name: 'PoolRecoveryModeStateChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'factory', + type: 'address', + }, + { + components: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'yieldFeeExempt', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct TokenConfig[]', + name: 'tokenConfig', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'uint256', + name: 'pauseWindowEndTime', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'pauseManager', + type: 'address', + }, + { + components: [ + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct PoolHooks', + name: 'hooks', + type: 'tuple', + }, + { + components: [ + { + internalType: 'bool', + name: 'supportsAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'supportsRemoveLiquidityCustom', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct LiquidityManagement', + name: 'liquidityManagement', + type: 'tuple', + }, + ], + name: 'PoolRegistered', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolFeeCollected', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolSwapFeeCharged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'swapFeePercentage', + type: 'uint256', + }, + ], + name: 'ProtocolSwapFeePercentageChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolYieldFeeCharged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'yieldFeePercentage', + type: 'uint256', + }, + ], + name: 'ProtocolYieldFeePercentageChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'tokenIn', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'tokenOut', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'swapFeeAmount', + type: 'uint256', + }, + ], + name: 'Swap', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'paused', + type: 'bool', + }, + ], + name: 'VaultPausedStateChanged', + type: 'event', + }, + { + stateMutability: 'payable', + type: 'fallback', + }, + { + inputs: [], + name: 'MAX_BUFFER_PERIOD_DURATION', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MAX_PAUSE_WINDOW_DURATION', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'maxAmountsIn', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'minBptAmountOut', + type: 'uint256', + }, + { + internalType: 'enum AddLiquidityKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct AddLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'addLiquidity', + outputs: [ + { + internalType: 'uint256[]', + name: 'amountsIn', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'bptAmountOut', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'getPoolTokenCountAndIndexOfToken', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVaultExtension', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'invoke', + outputs: [ + { + internalType: 'bytes', + name: 'result', + type: 'bytes', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'reentrancyGuardEntered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'uint256', + name: 'maxBptAmountIn', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'minAmountsOut', + type: 'uint256[]', + }, + { + internalType: 'enum RemoveLiquidityKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct RemoveLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'removeLiquidity', + outputs: [ + { + internalType: 'uint256', + name: 'bptAmountIn', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'amountsOut', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'uint256', + name: 'exactBptAmountIn', + type: 'uint256', + }, + ], + name: 'removeLiquidityRecovery', + outputs: [ + { + internalType: 'uint256[]', + name: 'amountsOutRaw', + type: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'retrieve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'settle', + outputs: [ + { + internalType: 'uint256', + name: 'paid', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum SwapKind', + name: 'kind', + type: 'uint8', + }, + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountGivenRaw', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limitRaw', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + internalType: 'struct SwapParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'swap', + outputs: [ + { + internalType: 'uint256', + name: 'amountCalculated', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'wire', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IVault', + name: 'mainVault', + type: 'address', + }, + { + internalType: 'uint256', + name: 'pauseWindowDuration', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'bufferPeriodDuration', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + ], + name: 'AddressEmptyCode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'AddressInsufficientBalance', + type: 'error', + }, + { + inputs: [], + name: 'AmountGivenZero', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'AmountInAboveMax', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'AmountOutBelowMin', + type: 'error', + }, + { + inputs: [], + name: 'BalanceNotSettled', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'BptAmountInAboveMax', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'BptAmountOutBelowMin', + type: 'error', + }, + { + inputs: [], + name: 'CannotReceiveEth', + type: 'error', + }, + { + inputs: [], + name: 'CannotSwapSameToken', + type: 'error', + }, + { + inputs: [], + name: 'CodecOverflow', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'allowance', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'needed', + type: 'uint256', + }, + ], + name: 'ERC20InsufficientAllowance', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'balance', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'needed', + type: 'uint256', + }, + ], + name: 'ERC20InsufficientBalance', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'approver', + type: 'address', + }, + ], + name: 'ERC20InvalidApprover', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + ], + name: 'ERC20InvalidReceiver', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'ERC20InvalidSender', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'ERC20InvalidSpender', + type: 'error', + }, + { + inputs: [], + name: 'FailedInnerCall', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'HandlerOutOfBounds', + type: 'error', + }, + { + inputs: [], + name: 'HookFailed', + type: 'error', + }, + { + inputs: [], + name: 'InputLengthMismatch', + type: 'error', + }, + { + inputs: [], + name: 'InvalidAddLiquidityKind', + type: 'error', + }, + { + inputs: [], + name: 'InvalidRemoveLiquidityKind', + type: 'error', + }, + { + inputs: [], + name: 'InvalidToken', + type: 'error', + }, + { + inputs: [], + name: 'InvalidTokenConfiguration', + type: 'error', + }, + { + inputs: [], + name: 'InvalidTokenType', + type: 'error', + }, + { + inputs: [], + name: 'MaxTokens', + type: 'error', + }, + { + inputs: [], + name: 'MinTokens', + type: 'error', + }, + { + inputs: [], + name: 'NoHandler', + type: 'error', + }, + { + inputs: [], + name: 'NotStaticCall', + type: 'error', + }, + { + inputs: [], + name: 'NotVaultDelegateCall', + type: 'error', + }, + { + inputs: [], + name: 'OutOfBounds', + type: 'error', + }, + { + inputs: [], + name: 'PauseBufferPeriodDurationTooLarge', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolAlreadyInitialized', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolAlreadyRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolInRecoveryMode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotInRecoveryMode', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotInitialized', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotPaused', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolNotRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolPauseWindowExpired', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolPaused', + type: 'error', + }, + { + inputs: [], + name: 'ProtocolSwapFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [], + name: 'ProtocolYieldFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [], + name: 'QueriesDisabled', + type: 'error', + }, + { + inputs: [], + name: 'ReentrancyGuardReentrantCall', + type: 'error', + }, + { + inputs: [], + name: 'RouterNotTrusted', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'bits', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'SafeCastOverflowedUintDowncast', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'SafeCastOverflowedUintToInt', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'SafeERC20FailedOperation', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'SenderIsNotPauseManager', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'SenderIsNotVault', + type: 'error', + }, + { + inputs: [], + name: 'SenderNotAllowed', + type: 'error', + }, + { + inputs: [], + name: 'SwapFeePercentageTooHigh', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'SwapLimit', + type: 'error', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'TokenAlreadyRegistered', + type: 'error', + }, + { + inputs: [], + name: 'TokenNotRegistered', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'expectedToken', + type: 'address', + }, + { + internalType: 'address', + name: 'actualToken', + type: 'address', + }, + ], + name: 'TokensMismatch', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'limit', + type: 'uint256', + }, + ], + name: 'TotalSupplyTooLow', + type: 'error', + }, + { + inputs: [], + name: 'UserDataNotSupported', + type: 'error', + }, + { + inputs: [], + name: 'VaultNotPaused', + type: 'error', + }, + { + inputs: [], + name: 'VaultPauseWindowDurationTooLarge', + type: 'error', + }, + { + inputs: [], + name: 'VaultPauseWindowExpired', + type: 'error', + }, + { + inputs: [], + name: 'VaultPaused', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'handler', + type: 'address', + }, + { + internalType: 'address', + name: 'caller', + type: 'address', + }, + ], + name: 'WrongHandler', + type: 'error', + }, + { + inputs: [], + name: 'WrongVaultExtensionDeployment', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IAuthorizer', + name: 'newAuthorizer', + type: 'address', + }, + ], + name: 'AuthorizerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'liquidityProvider', + type: 'address', + }, + { + indexed: false, + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + indexed: false, + internalType: 'int256[]', + name: 'deltas', + type: 'int256[]', + }, + ], + name: 'PoolBalanceChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'PoolInitialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'paused', + type: 'bool', + }, + ], + name: 'PoolPausedStateChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'recoveryMode', + type: 'bool', + }, + ], + name: 'PoolRecoveryModeStateChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'factory', + type: 'address', + }, + { + components: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'yieldFeeExempt', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct TokenConfig[]', + name: 'tokenConfig', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'uint256', + name: 'pauseWindowEndTime', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'pauseManager', + type: 'address', + }, + { + components: [ + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct PoolHooks', + name: 'hooks', + type: 'tuple', + }, + { + components: [ + { + internalType: 'bool', + name: 'supportsAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'supportsRemoveLiquidityCustom', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct LiquidityManagement', + name: 'liquidityManagement', + type: 'tuple', + }, + ], + name: 'PoolRegistered', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolFeeCollected', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolSwapFeeCharged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'swapFeePercentage', + type: 'uint256', + }, + ], + name: 'ProtocolSwapFeePercentageChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ProtocolYieldFeeCharged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'yieldFeePercentage', + type: 'uint256', + }, + ], + name: 'ProtocolYieldFeePercentageChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'swapFeePercentage', + type: 'uint256', + }, + ], + name: 'SwapFeePercentageChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'paused', + type: 'bool', + }, + ], + name: 'VaultPausedStateChanged', + type: 'event', + }, + { + inputs: [], + name: 'MAX_BUFFER_PERIOD_DURATION', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MAX_PAUSE_WINDOW_DURATION', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + ], + name: 'collectProtocolFees', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'disableQuery', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'disableRecoveryMode', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'enableRecoveryMode', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'selector', + type: 'bytes4', + }, + ], + name: 'getActionId', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAuthorizer', + outputs: [ + { + internalType: 'contract IAuthorizer', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBufferPeriodDuration', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBufferPeriodEndTime', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'getHandler', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getHandlersCount', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getMaximumPoolTokens', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getMinimumPoolTokens', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'getNonzeroDeltaCount', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPauseWindowEndTime', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getPoolConfig', + outputs: [ + { + components: [ + { + internalType: 'bool', + name: 'isPoolRegistered', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isPoolInitialized', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isPoolPaused', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isPoolInRecoveryMode', + type: 'bool', + }, + { + internalType: 'bool', + name: 'hasDynamicSwapFee', + type: 'bool', + }, + { + internalType: 'uint64', + name: 'staticSwapFeePercentage', + type: 'uint64', + }, + { + internalType: 'uint24', + name: 'tokenDecimalDiffs', + type: 'uint24', + }, + { + internalType: 'uint32', + name: 'pauseWindowEndTime', + type: 'uint32', + }, + { + components: [ + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + internalType: 'struct PoolHooks', + name: 'hooks', + type: 'tuple', + }, + { + components: [ + { + internalType: 'bool', + name: 'supportsAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'supportsRemoveLiquidityCustom', + type: 'bool', + }, + ], + internalType: 'struct LiquidityManagement', + name: 'liquidityManagement', + type: 'tuple', + }, + ], + internalType: 'struct PoolConfig', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getPoolPausedState', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getPoolTokenInfo', + outputs: [ + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + internalType: 'enum TokenType[]', + name: 'tokenTypes', + type: 'uint8[]', + }, + { + internalType: 'uint256[]', + name: 'balancesRaw', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'decimalScalingFactors', + type: 'uint256[]', + }, + { + internalType: 'contract IRateProvider[]', + name: 'rateProviders', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getPoolTokenRates', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getPoolTokens', + outputs: [ + { + internalType: 'contract IERC20[]', + name: '', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'getProtocolFees', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolSwapFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolYieldFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'getStaticSwapFeePercentage', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'user', + type: 'address', + }, + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'getTokenDelta', + outputs: [ + { + internalType: 'int256', + name: '', + type: 'int256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + name: 'getTokenReserve', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVaultPausedState', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'exactAmountsIn', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'minBptAmountOut', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'userData', + type: 'bytes', + }, + ], + name: 'initialize', + outputs: [ + { + internalType: 'uint256', + name: 'bptAmountOut', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'isPoolInRecoveryMode', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'isPoolInitialized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'isPoolPaused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'isPoolRegistered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isQueryDisabled', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isVaultPaused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'pausePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'pauseVault', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'quote', + outputs: [ + { + internalType: 'bytes', + name: 'result', + type: 'bytes', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'reentrancyGuardEntered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + components: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'enum TokenType', + name: 'tokenType', + type: 'uint8', + }, + { + internalType: 'contract IRateProvider', + name: 'rateProvider', + type: 'address', + }, + { + internalType: 'bool', + name: 'yieldFeeExempt', + type: 'bool', + }, + ], + internalType: 'struct TokenConfig[]', + name: 'tokenConfig', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'pauseWindowEndTime', + type: 'uint256', + }, + { + internalType: 'address', + name: 'pauseManager', + type: 'address', + }, + { + components: [ + { + internalType: 'bool', + name: 'shouldCallBeforeInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterInitialize', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterSwap', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterAddLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallBeforeRemoveLiquidity', + type: 'bool', + }, + { + internalType: 'bool', + name: 'shouldCallAfterRemoveLiquidity', + type: 'bool', + }, + ], + internalType: 'struct PoolHooks', + name: 'poolHooks', + type: 'tuple', + }, + { + components: [ + { + internalType: 'bool', + name: 'supportsAddLiquidityCustom', + type: 'bool', + }, + { + internalType: 'bool', + name: 'supportsRemoveLiquidityCustom', + type: 'bool', + }, + ], + internalType: 'struct LiquidityManagement', + name: 'liquidityManagement', + type: 'tuple', + }, + ], + name: 'registerPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IAuthorizer', + name: 'newAuthorizer', + type: 'address', + }, + ], + name: 'setAuthorizer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'newProtocolSwapFeePercentage', + type: 'uint256', + }, + ], + name: 'setProtocolSwapFeePercentage', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'newProtocolYieldFeePercentage', + type: 'uint256', + }, + ], + name: 'setProtocolYieldFeePercentage', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'swapFeePercentage', + type: 'uint256', + }, + ], + name: 'setStaticSwapFeePercentage', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool', + type: 'address', + }, + ], + name: 'unpausePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unpauseVault', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'vault', + outputs: [ + { + internalType: 'contract IVault', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/modules/pool/abi/VaultV3.json b/modules/sources/contracts/abis/VaultV3JSON.json similarity index 100% rename from modules/pool/abi/VaultV3.json rename to modules/sources/contracts/abis/VaultV3JSON.json diff --git a/modules/sources/contracts/fetch-pool-tokens.ts b/modules/sources/contracts/fetch-pool-tokens.ts index e6325f4b8..29432a35d 100644 --- a/modules/sources/contracts/fetch-pool-tokens.ts +++ b/modules/sources/contracts/fetch-pool-tokens.ts @@ -1,5 +1,6 @@ import { parseAbi } from 'viem'; import { ViemClient } from '../types'; +import { vaultV3Abi } from './abis/VaultV3'; type PoolTokenInfo = { tokens: `0x${string}`[]; @@ -18,15 +19,22 @@ export async function fetchPoolTokens(vault: string, pools: string[], client: Vi .map((pool) => [ { address: vault as `0x${string}`, - abi, - args: [pool as `0x${string}`], + abi: vaultV3Abi, functionName: 'getPoolTokenInfo', + args: [pool as `0x${string}`], }, ]) .flat(); const results = await client.multicall({ contracts }); + const data = await client.readContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: vaultV3Abi, + functionName: 'getPoolTokenInfo' as 'getPoolTokenInfo', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }); + // Parse the results const parsedResults = results .map((result, i) => { From 31c734b4a6b852b669233182a8dc13e4bace8d2a Mon Sep 17 00:00:00 2001 From: franz Date: Wed, 21 Feb 2024 17:54:28 +0100 Subject: [PATCH 10/14] wip --- .../sources/contracts/fetch-pool-tokens.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/modules/sources/contracts/fetch-pool-tokens.ts b/modules/sources/contracts/fetch-pool-tokens.ts index 29432a35d..99a4d0d8e 100644 --- a/modules/sources/contracts/fetch-pool-tokens.ts +++ b/modules/sources/contracts/fetch-pool-tokens.ts @@ -1,4 +1,3 @@ -import { parseAbi } from 'viem'; import { ViemClient } from '../types'; import { vaultV3Abi } from './abis/VaultV3'; @@ -8,13 +7,10 @@ type PoolTokenInfo = { balancesRaw: bigint[]; decimalScalingFactors: bigint[]; rateProviders: `0x${string}`[]; + tokenRates: bigint[]; }; -const abi = parseAbi([ - 'function getPoolTokenInfo(address pool) view returns (address[] tokens, uint8[] tokenTypes, uint[] balancesRaw, uint[] decimalScalingFactors, address[] rateProviders)', -]); - -export async function fetchPoolTokens(vault: string, pools: string[], client: ViemClient) { +export async function fetchPoolTokenInfo(vault: string, pools: string[], client: ViemClient) { const contracts = pools .map((pool) => [ { @@ -22,19 +18,18 @@ export async function fetchPoolTokens(vault: string, pools: string[], client: Vi abi: vaultV3Abi, functionName: 'getPoolTokenInfo', args: [pool as `0x${string}`], - }, + } as const, + { + address: vault as `0x${string}`, + abi: vaultV3Abi, + functionName: 'getPoolTokenRates', + args: [pool as `0x${string}`], + } as const, ]) .flat(); const results = await client.multicall({ contracts }); - const data = await client.readContract({ - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi: vaultV3Abi, - functionName: 'getPoolTokenInfo' as 'getPoolTokenInfo', - args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], - }); - // Parse the results const parsedResults = results .map((result, i) => { @@ -46,6 +41,7 @@ export async function fetchPoolTokens(vault: string, pools: string[], client: Vi balancesRaw: result.result[2], decimalScalingFactors: result.result[3], rateProviders: result.result[4], + tokenRates: result.result[5], } as PoolTokenInfo; return [pools[i], poolTokens]; From 9adafcd354abeca6b7650d106457295e5b8780e6 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 22 Feb 2024 12:40:46 +0100 Subject: [PATCH 11/14] sync on chain data --- .../add-pools-from-subgraph.test.ts} | 2 +- .../add-pools-from-subgraph.ts} | 9 +- modules/actions/pool/update-on-chain-data.ts | 219 ++++++++++++++++++ modules/controllers/jobs-controller.test.ts | 2 +- modules/controllers/jobs-controller.ts | 18 +- modules/controllers/pools-controller.ts | 38 +++ modules/sources/contracts/fetch-pool-data.ts | 95 ++++++++ .../sources/contracts/fetch-pool-tokens.ts | 53 ++++- 8 files changed, 418 insertions(+), 18 deletions(-) rename modules/actions/{jobs-actions/sync-pools.test.ts => pool/add-pools-from-subgraph.test.ts} (96%) rename modules/actions/{jobs-actions/sync-pools.ts => pool/add-pools-from-subgraph.ts} (98%) create mode 100644 modules/actions/pool/update-on-chain-data.ts create mode 100644 modules/controllers/pools-controller.ts create mode 100644 modules/sources/contracts/fetch-pool-data.ts diff --git a/modules/actions/jobs-actions/sync-pools.test.ts b/modules/actions/pool/add-pools-from-subgraph.test.ts similarity index 96% rename from modules/actions/jobs-actions/sync-pools.test.ts rename to modules/actions/pool/add-pools-from-subgraph.test.ts index 0426e63c1..cf0165764 100644 --- a/modules/actions/jobs-actions/sync-pools.test.ts +++ b/modules/actions/pool/add-pools-from-subgraph.test.ts @@ -1,4 +1,4 @@ -import { addMissingPoolsFromSubgraph } from './sync-pools'; +import { addMissingPoolsFromSubgraph } from './add-pools-from-subgraph'; import { prisma } from '../../../prisma/prisma-client'; import { PrismaPool } from '@prisma/client'; import { PoolFragment as VaultSubgraphPoolFragment } from '../../subgraphs/balancer-v3-vault/generated/types'; diff --git a/modules/actions/jobs-actions/sync-pools.ts b/modules/actions/pool/add-pools-from-subgraph.ts similarity index 98% rename from modules/actions/jobs-actions/sync-pools.ts rename to modules/actions/pool/add-pools-from-subgraph.ts index 3fc1fdbf6..7af60a10f 100644 --- a/modules/actions/jobs-actions/sync-pools.ts +++ b/modules/actions/pool/add-pools-from-subgraph.ts @@ -31,7 +31,7 @@ export async function addMissingPoolsFromSubgraph( // viemClient: ViemClient, // vaultAddress: string, chain = 'SEPOLIA' as Chain, -) { +): Promise { // Fetch pools from subgraph // TODO this needs paging const { pools: vaultSubgraphPools } = await vaultSubgraphClient.Pools(); @@ -116,7 +116,7 @@ export async function addMissingPoolsFromSubgraph( }); // Store missing pools in the database - let allOk = true; + const added: string[] = []; for (const entry of dbEntries) { try { await prisma.prismaPool.create({ data: entry.pool }); @@ -131,12 +131,13 @@ export async function addMissingPoolsFromSubgraph( skipDuplicates: true, data: entry.poolExpandedTokens, }); + + added.push(entry.pool.id); } catch (e) { // TODO: handle errors console.error(`Error creating pool ${entry.pool.id}`, e); - allOk = false; } } - return allOk; + return added; } diff --git a/modules/actions/pool/update-on-chain-data.ts b/modules/actions/pool/update-on-chain-data.ts new file mode 100644 index 000000000..6188cf902 --- /dev/null +++ b/modules/actions/pool/update-on-chain-data.ts @@ -0,0 +1,219 @@ +import { Chain, Prisma, PrismaPoolType } from '@prisma/client'; +import { prisma } from '../../../prisma/prisma-client'; +import { tokenService } from '../../token/token.service'; +import { fetchPoolTokenInfo, fetchPoolTokenRates } from '../../sources/contracts'; +import { ViemClient } from '../../sources/viem-client'; +import { fetchPoolData } from '../../sources/contracts/fetch-pool-data'; +import { formatEther, formatUnits, parseUnits } from 'viem'; +import { isSameAddress } from '@balancer-labs/sdk'; + +const SUPPORTED_POOL_TYPES: PrismaPoolType[] = ['WEIGHTED', 'STABLE']; + +export async function updateOnchainDataForAllPools( + vaultAddress: string, + viemClient: ViemClient, + chain = 'SEPOLIA' as Chain, +): Promise { + const allPools = await prisma.prismaPool.findMany({ + where: { chain: chain, vaultVersion: 3 }, + select: { + id: true, + }, + }); + + return updateOnChainDataForPools( + vaultAddress, + '123', + allPools.map((pool) => pool.id), + viemClient, + chain, + ); +} + +/** + * Makes sure that all pools are synced in the database + * + * @param vaultSubgraphClient + * @param poolSubgraphClient + * @param chain + * @returns syncedPools - the pools that were synced + */ +export async function updateOnChainDataForPools( + vaultAddress: string, + balancerQueriesAddress: string, + poolIds: string[], + viemClient: ViemClient, + chain = 'SEPOLIA' as Chain, +): Promise { + if (poolIds.length === 0) { + return []; + } + const updated: string[] = []; + + const filteredPools = await prisma.prismaPool.findMany({ + where: { + id: { in: poolIds }, + chain: chain, + type: { in: SUPPORTED_POOL_TYPES }, + }, + include: { + tokens: { orderBy: { index: 'asc' }, include: { dynamicData: true, token: true } }, + dynamicData: true, + }, + }); + + const filteredPoolIds = filteredPools.map((pool) => pool.id); + const filteredPoolInputs = filteredPools.map((pool) => ({ + id: pool.id, + address: pool.address, + type: pool.type, + version: pool.version, + })); + + const blockNumber = await viemClient.getBlockNumber(); + + const tokenPricesForCurrentChain = await tokenService.getTokenPrices(chain); + const poolTokenData = await fetchPoolTokenInfo(vaultAddress, filteredPoolIds, viemClient, blockNumber); + const poolTokenRatesData = await fetchPoolTokenRates(vaultAddress, filteredPoolIds, viemClient, blockNumber); + const poolConfigData = await fetchPoolData(vaultAddress, filteredPoolInputs, viemClient, blockNumber); + + // TODO also need to add tokenPairs for SOR and calc normalized liquidity + // const tokenPairData = await fetchTokenPairData( + // filteredPools, + // balancerQueriesAddress, + // chain === 'ZKEVM' ? 190 : 1024, + // ); + + const operations = []; + for (const pool of filteredPools) { + const poolTokens = poolTokenData[pool.id]; + const poolTokenRates = poolTokenRatesData[pool.id]; + const poolConfig = poolConfigData[pool.id]; + + try { + // if (isStablePool(pool.type)) { + // if (!amp) { + // console.error(`Stable Pool Missing Amp: ${pool.id}`); + // continue; + // } + + // //only update if amp has changed + // if ((pool.typeData as StableData).amp !== amp) { + // operations.push( + // prisma.prismaPool.update({ + // where: { id_chain: { id: pool.id, chain: this.options.chain } }, + // data: { + // typeData: { + // ...(pool.typeData as StableData), + // amp, + // }, + // }, + // }), + // ); + // } + // } + + const swapFee = poolConfig.swapFee.toString(); + const totalSupply = formatEther(poolConfig.totalSupply); + const swapEnabled = !poolConfig.isPoolPaused; + const isPaused = poolConfig.isPoolPaused; + const isInRecoveryMode = poolConfig.isPoolInRecoveryMode; + + const yieldProtocolFeePercentage = '0'; // TODO + const protocolSwapFeePercentage = poolConfig.protocolSwapFeePercentage.toString(); + + if ( + pool.dynamicData && + (pool.dynamicData.swapFee !== swapFee || + pool.dynamicData.totalShares !== totalSupply || + pool.dynamicData.swapEnabled !== swapEnabled || + pool.dynamicData.protocolYieldFee !== yieldProtocolFeePercentage || + pool.dynamicData.protocolSwapFee !== protocolSwapFeePercentage || + pool.dynamicData.isInRecoveryMode !== isInRecoveryMode || + pool.dynamicData.isPaused !== isPaused) + ) { + operations.push( + prisma.prismaPoolDynamicData.update({ + where: { id_chain: { id: pool.id, chain: chain } }, + data: { + swapFee, + totalShares: totalSupply, + totalSharesNum: parseFloat(totalSupply), + swapEnabled: typeof swapEnabled !== 'undefined' ? swapEnabled : true, + isInRecoveryMode: isInRecoveryMode, + isPaused: isPaused, + protocolYieldFee: yieldProtocolFeePercentage, + protocolSwapFee: protocolSwapFeePercentage, + blockNumber: parseFloat(blockNumber.toString()), + }, + }), + ); + } + + // always update tokenPair data + // if (pool.dynamicData) { + // operations.push( + // prisma.prismaPoolDynamicData.update({ + // where: { id_chain: { id: pool.id, chain: this.options.chain } }, + // data: { + // tokenPairsData: tokenPairs, + // }, + // }), + // ); + // } + + for (let i = 0; i < poolTokens.tokens.length; i++) { + const tokenAddress = poolTokens.tokens[i]; + const poolToken = pool.tokens.find((token) => isSameAddress(token.address, tokenAddress)); + + if (!poolToken) { + throw `Pool Missing Expected Token: ${pool.id} ${tokenAddress}`; + } + + if (poolToken.index !== i) { + throw `Pooltoken index mismatch! "poolToken.index": ${poolToken.index} vs "i": ${i} on pool ${pool.id}`; + } + + const balance = formatUnits(poolTokens.balancesRaw[i], poolToken.token.decimals); + + // set token price rate for various rate types + + // top level token rates, e.g. LSTs in pools + let priceRate = formatEther(poolTokenRates[i]); + + // // bpt price rate + // if (onchainData.rate && isSameAddress(poolToken.address, pool.address)) { + // priceRate = onchainData.rate; + // } + + // TODO v3 does not contain the BPT as pool token, do we need to add it nevertheless? + + if ( + !poolToken.dynamicData || + poolToken.dynamicData.balance !== balance || + poolToken.dynamicData.priceRate !== priceRate + ) { + operations.push( + prisma.prismaPoolTokenDynamicData.update({ + where: { id_chain: { id: poolToken.id, chain: chain } }, + data: { + blockNumber: parseFloat(blockNumber.toString()), + priceRate, + balance, + balanceUSD: + poolToken.address === pool.address + ? 0 + : tokenService.getPriceForToken(tokenPricesForCurrentChain, poolToken.address) * + parseFloat(balance), + }, + }), + ); + } + } + } catch (e) { + console.log('error syncing on chain data', e); + } + } + + return updated; +} diff --git a/modules/controllers/jobs-controller.test.ts b/modules/controllers/jobs-controller.test.ts index ee9676189..537d322f2 100644 --- a/modules/controllers/jobs-controller.test.ts +++ b/modules/controllers/jobs-controller.test.ts @@ -1,4 +1,4 @@ -import { addMissingPoolsFromSubgraph } from '../actions/jobs-actions/sync-pools'; +import { addMissingPoolsFromSubgraph } from '../actions/pool/add-pools-from-subgraph'; import { JobsController } from './jobs-controller'; // Mock the actions jest.mock('@modules/actions/jobs_actions', () => ({ diff --git a/modules/controllers/jobs-controller.ts b/modules/controllers/jobs-controller.ts index 039de4ebe..a6f03728c 100644 --- a/modules/controllers/jobs-controller.ts +++ b/modules/controllers/jobs-controller.ts @@ -1,6 +1,8 @@ import config from '../../config'; -import { addMissingPoolsFromSubgraph } from '../actions/jobs-actions/sync-pools'; +import { addMissingPoolsFromSubgraph } from '../actions/pool/add-pools-from-subgraph'; +import { updateOnChainDataForPools } from '../actions/pool/update-on-chain-data'; import { chainIdToChain } from '../network/chain-id-to-chain'; +import { getViemClient } from '../sources/viem-client'; import { getPoolsSubgraphClient } from '../subgraphs/balancer-v3-pools'; import { getVaultSubgraphClient } from '../subgraphs/balancer-v3-vault'; @@ -15,10 +17,13 @@ export function JobsController(tracer?: any) { // Setup tracing // ... return { - addMissingPoolsFromSubgraph(chainId: string) { + async addMissingPoolsFromSubgraph(chainId: string) { const chain = chainIdToChain[chainId]; const { subgraphs: { balancerV3, balancerPoolsV3 }, + balancer: { + v3: { vaultAddress }, + }, } = config[chain]; // Guard against unconfigured chains @@ -28,11 +33,18 @@ export function JobsController(tracer?: any) { const vaultSubgraphClient = getVaultSubgraphClient(balancerV3); const poolSubgraphClient = getPoolsSubgraphClient(balancerPoolsV3!); + const viemClient = getViemClient(chain); // TODO: add syncing v2 pools as well by splitting the poolService into separate // actions with extracted configuration - return addMissingPoolsFromSubgraph(vaultSubgraphClient, poolSubgraphClient, chain); + const added = await addMissingPoolsFromSubgraph(vaultSubgraphClient, poolSubgraphClient, chain); + + //required steps: + // update onchain data and status + const updated = updateOnChainDataForPools(vaultAddress, '123', added, viemClient); + // sync swaps + // updateVolumeAndFeeValues }, }; } diff --git a/modules/controllers/pools-controller.ts b/modules/controllers/pools-controller.ts new file mode 100644 index 000000000..2834e3e38 --- /dev/null +++ b/modules/controllers/pools-controller.ts @@ -0,0 +1,38 @@ +import config from '../../config'; +import { addMissingPoolsFromSubgraph } from '../actions/pool/add-pools-from-subgraph'; +import { updateOnChainDataForPools, updateOnchainDataForAllPools } from '../actions/pool/update-on-chain-data'; +import { chainIdToChain } from '../network/chain-id-to-chain'; +import { getViemClient } from '../sources/viem-client'; +import { getPoolsSubgraphClient } from '../subgraphs/balancer-v3-pools'; +import { getVaultSubgraphClient } from '../subgraphs/balancer-v3-vault'; + +/** + * Controller responsible for matching job requests to configured job handlers + * + * @param name - the name of the job + * @param chain - the chain to run the job on + * @returns a controller with configured job handlers + */ +export function PoolsController(tracer?: any) { + // Setup tracing + // ... + return { + async updateOnChainDataForPools(chainId: string) { + const chain = chainIdToChain[chainId]; + const { + balancer: { + v3: { vaultAddress }, + }, + } = config[chain]; + + // Guard against unconfigured chains + if (!vaultAddress) { + throw new Error(`Chain not configured: ${chain}`); + } + const viemClient = getViemClient(chain); + + const updated = updateOnchainDataForAllPools(vaultAddress, viemClient, chain); + return updated; + }, + }; +} diff --git a/modules/sources/contracts/fetch-pool-data.ts b/modules/sources/contracts/fetch-pool-data.ts new file mode 100644 index 000000000..bafe1627e --- /dev/null +++ b/modules/sources/contracts/fetch-pool-data.ts @@ -0,0 +1,95 @@ +import { PrismaPoolType } from '@prisma/client'; +import { ViemClient } from '../types'; +import { vaultV3Abi } from './abis/VaultV3'; +import { fetchPoolTokenInfo } from './fetch-pool-tokens'; + +interface PoolInput { + id: string; + address: string; + type: PrismaPoolType; + version: number; +} + +interface PoolData { + totalSupply: bigint; + swapFee: bigint; + protocolSwapFeePercentage: bigint; + // protocolYieldFeePercentage: bigint; + // rate?: bigint; + // amp?: [bigint, boolean, bigint]; + isPoolPaused: boolean; + isPoolInRecoveryMode: boolean; +} + +export async function fetchPoolData( + vault: string, + pools: PoolInput[], + client: ViemClient, + blockNumber: bigint, +): Promise> { + const totalSupplyContracts = pools + .map((pool) => [ + { + address: vault as `0x${string}`, + abi: vaultV3Abi, + functionName: 'totalSupply', + args: [pool.address as `0x${string}`], + } as const, + ]) + .flat(); + + const configContracts = pools + .map((pool) => [ + { + address: vault as `0x${string}`, + abi: vaultV3Abi, + functionName: 'getPoolConfig', + args: [pool.address as `0x${string}`], + } as const, + ]) + .flat(); + + const protocolSwapFeeContracts = pools + .map((pool) => [ + { + address: vault as `0x${string}`, + abi: vaultV3Abi, + functionName: 'getProtocolSwapFeePercentage', + } as const, + ]) + .flat(); + + // TODO combine into one call + const totalSupplyResult = await client.multicall({ contracts: totalSupplyContracts, blockNumber: blockNumber }); + const configResult = await client.multicall({ contracts: configContracts, blockNumber: blockNumber }); + const protocolSwapFeeResult = await client.multicall({ + contracts: protocolSwapFeeContracts, + blockNumber: blockNumber, + }); + + // Parse the results + const parsedResults: Record = {}; + pools.forEach((result, i) => { + if ( + totalSupplyResult[i].status === 'success' && + totalSupplyResult[i].result !== undefined && + configResult[i].status === 'success' && + configResult[i].result !== undefined && + protocolSwapFeeResult[i].status === 'success' && + protocolSwapFeeResult[i].result !== undefined + ) { + // parse the result here using the abi + const poolData = { + totalSupply: totalSupplyResult[i].result!, + swapFee: configResult[i].result!.staticSwapFeePercentage, + protocolSwapFeePercentage: 0n, // TODO can this be added to config? + isPoolPaused: configResult[i].result!.isPoolPaused, + isPoolInRecoveryMode: configResult[i].result!.isPoolInRecoveryMode, + } as PoolData; + + parsedResults[result.id] = poolData; + } + // Handle the error + }); + return parsedResults; +} diff --git a/modules/sources/contracts/fetch-pool-tokens.ts b/modules/sources/contracts/fetch-pool-tokens.ts index 99a4d0d8e..ad5cf87ee 100644 --- a/modules/sources/contracts/fetch-pool-tokens.ts +++ b/modules/sources/contracts/fetch-pool-tokens.ts @@ -7,10 +7,18 @@ type PoolTokenInfo = { balancesRaw: bigint[]; decimalScalingFactors: bigint[]; rateProviders: `0x${string}`[]; +}; + +type PoolTokenRates = { tokenRates: bigint[]; }; -export async function fetchPoolTokenInfo(vault: string, pools: string[], client: ViemClient) { +export async function fetchPoolTokenInfo( + vault: string, + pools: string[], + client: ViemClient, + blockNumber: bigint, +): Promise> { const contracts = pools .map((pool) => [ { @@ -19,16 +27,10 @@ export async function fetchPoolTokenInfo(vault: string, pools: string[], client: functionName: 'getPoolTokenInfo', args: [pool as `0x${string}`], } as const, - { - address: vault as `0x${string}`, - abi: vaultV3Abi, - functionName: 'getPoolTokenRates', - args: [pool as `0x${string}`], - } as const, ]) .flat(); - const results = await client.multicall({ contracts }); + const results = await client.multicall({ contracts, blockNumber: blockNumber }); // Parse the results const parsedResults = results @@ -41,7 +43,6 @@ export async function fetchPoolTokenInfo(vault: string, pools: string[], client: balancesRaw: result.result[2], decimalScalingFactors: result.result[3], rateProviders: result.result[4], - tokenRates: result.result[5], } as PoolTokenInfo; return [pools[i], poolTokens]; @@ -53,3 +54,37 @@ export async function fetchPoolTokenInfo(vault: string, pools: string[], client: return Object.fromEntries(parsedResults) as Record; } + +export async function fetchPoolTokenRates( + vault: string, + pools: string[], + client: ViemClient, + blockNumber: bigint, +): Promise> { + const contracts = pools + .map((pool) => [ + { + address: vault as `0x${string}`, + abi: vaultV3Abi, + functionName: 'getPoolTokenRates', + args: [pool as `0x${string}`], + } as const, + ]) + .flat(); + + const results = await client.multicall({ contracts, blockNumber: blockNumber }); + + // Parse the results + const parsedResults = results + .map((result, i) => { + if (result.status === 'success' && result.result !== undefined) { + // parse the result here using the abi + return [pools[i], result.result]; + } + // Handle the error + return undefined; + }) + .filter((result): result is NonNullable => result !== undefined); + + return Object.fromEntries(parsedResults) as Record; +} From be2eba22e09c1346048a6c8455b3b4a04103e550 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 22 Feb 2024 12:47:57 +0100 Subject: [PATCH 12/14] write to prisma --- modules/actions/pool/update-on-chain-data.ts | 2 ++ modules/controllers/pools-controller.ts | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/actions/pool/update-on-chain-data.ts b/modules/actions/pool/update-on-chain-data.ts index 6188cf902..fae0ba93b 100644 --- a/modules/actions/pool/update-on-chain-data.ts +++ b/modules/actions/pool/update-on-chain-data.ts @@ -6,6 +6,7 @@ import { ViemClient } from '../../sources/viem-client'; import { fetchPoolData } from '../../sources/contracts/fetch-pool-data'; import { formatEther, formatUnits, parseUnits } from 'viem'; import { isSameAddress } from '@balancer-labs/sdk'; +import { prismaBulkExecuteOperations } from '../../../prisma/prisma-util'; const SUPPORTED_POOL_TYPES: PrismaPoolType[] = ['WEIGHTED', 'STABLE']; @@ -214,6 +215,7 @@ export async function updateOnChainDataForPools( console.log('error syncing on chain data', e); } } + await prismaBulkExecuteOperations(operations, false); return updated; } diff --git a/modules/controllers/pools-controller.ts b/modules/controllers/pools-controller.ts index 2834e3e38..b452db0a0 100644 --- a/modules/controllers/pools-controller.ts +++ b/modules/controllers/pools-controller.ts @@ -1,10 +1,7 @@ import config from '../../config'; -import { addMissingPoolsFromSubgraph } from '../actions/pool/add-pools-from-subgraph'; -import { updateOnChainDataForPools, updateOnchainDataForAllPools } from '../actions/pool/update-on-chain-data'; +import { updateOnchainDataForAllPools } from '../actions/pool/update-on-chain-data'; import { chainIdToChain } from '../network/chain-id-to-chain'; import { getViemClient } from '../sources/viem-client'; -import { getPoolsSubgraphClient } from '../subgraphs/balancer-v3-pools'; -import { getVaultSubgraphClient } from '../subgraphs/balancer-v3-vault'; /** * Controller responsible for matching job requests to configured job handlers @@ -17,7 +14,7 @@ export function PoolsController(tracer?: any) { // Setup tracing // ... return { - async updateOnChainDataForPools(chainId: string) { + async updateOnChainDataForAllPools(chainId: string) { const chain = chainIdToChain[chainId]; const { balancer: { From 038a2fbe9847a755de53a737a1aeb50a80f7e2a0 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 22 Feb 2024 13:22:09 +0100 Subject: [PATCH 13/14] enable jobs --- modules/actions/pool/get-changed-pools.ts | 52 +++++++++++++++++++ modules/actions/pool/update-on-chain-data.ts | 5 +- modules/controllers/jobs-controller.ts | 52 +++++++++++++++++-- modules/controllers/pools-controller.ts | 3 +- modules/network/sepolia.ts | 24 +++------ .../sources/logs/get-pool-balance-changed.ts | 49 +++++++++++++++++ modules/sources/logs/get-swaps.ts | 8 ++- worker/job-handlers.ts | 4 +- 8 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 modules/actions/pool/get-changed-pools.ts create mode 100644 modules/sources/logs/get-pool-balance-changed.ts diff --git a/modules/actions/pool/get-changed-pools.ts b/modules/actions/pool/get-changed-pools.ts new file mode 100644 index 000000000..b19423b1d --- /dev/null +++ b/modules/actions/pool/get-changed-pools.ts @@ -0,0 +1,52 @@ +import { Chain, Prisma, PrismaLastBlockSyncedCategory, PrismaPoolType } from '@prisma/client'; +import { prisma } from '../../../prisma/prisma-client'; +import { tokenService } from '../../token/token.service'; +import { fetchPoolTokenInfo, fetchPoolTokenRates } from '../../sources/contracts'; +import { ViemClient } from '../../sources/viem-client'; +import { fetchPoolData } from '../../sources/contracts/fetch-pool-data'; +import { formatEther, formatUnits, parseUnits } from 'viem'; +import { isSameAddress } from '@balancer-labs/sdk'; +import { prismaBulkExecuteOperations } from '../../../prisma/prisma-util'; +import { getPoolBalanceChanged } from '../../sources/logs/get-pool-balance-changed'; +import { start } from 'repl'; +import { getSwaps } from '../../sources/logs'; +import _ from 'lodash'; + +/** + * Get all pool IDs of pools that have emitted a poolBalanceChanged event + * + * @param vaultAddress + * @param viemClient + * @param chain + * @returns list of changed pool IDs + */ +export async function getChangedPools( + vaultAddress: string, + viemClient: ViemClient, + blockNumber: bigint, + chain = 'SEPOLIA' as Chain, +): Promise { + let lastSync = await prisma.prismaLastBlockSynced.findUnique({ + where: { category_chain: { category: PrismaLastBlockSyncedCategory.POOLS, chain: chain } }, + }); + const lastSyncBlock = lastSync?.blockNumber ? BigInt(lastSync.blockNumber) : 0n; + const latestBlock = blockNumber; + + const startBlock = lastSyncBlock + 1n; + const endBlock = latestBlock; + + // no new blocks have been minted, needed for slow networks + if (startBlock > endBlock) { + return []; + } + + const poolBalanceChangedEvents = await getPoolBalanceChanged(vaultAddress, viemClient, startBlock, endBlock); + const poolIdsFromBalanceChangedEvents = poolBalanceChangedEvents.map((event) => event.args.pool!); + + const swapEvents = await getSwaps(vaultAddress, viemClient, startBlock, endBlock); + const poolIdsFromSwapEvents = swapEvents.map((event) => event.args.pool!); + + const changedPoolIds = _.uniq(poolIdsFromBalanceChangedEvents.concat(poolIdsFromSwapEvents)); + + return changedPoolIds; +} diff --git a/modules/actions/pool/update-on-chain-data.ts b/modules/actions/pool/update-on-chain-data.ts index fae0ba93b..4183d2074 100644 --- a/modules/actions/pool/update-on-chain-data.ts +++ b/modules/actions/pool/update-on-chain-data.ts @@ -13,6 +13,7 @@ const SUPPORTED_POOL_TYPES: PrismaPoolType[] = ['WEIGHTED', 'STABLE']; export async function updateOnchainDataForAllPools( vaultAddress: string, viemClient: ViemClient, + blockNumber: bigint, chain = 'SEPOLIA' as Chain, ): Promise { const allPools = await prisma.prismaPool.findMany({ @@ -27,6 +28,7 @@ export async function updateOnchainDataForAllPools( '123', allPools.map((pool) => pool.id), viemClient, + blockNumber, chain, ); } @@ -44,6 +46,7 @@ export async function updateOnChainDataForPools( balancerQueriesAddress: string, poolIds: string[], viemClient: ViemClient, + blockNumber: bigint, chain = 'SEPOLIA' as Chain, ): Promise { if (poolIds.length === 0) { @@ -71,8 +74,6 @@ export async function updateOnChainDataForPools( version: pool.version, })); - const blockNumber = await viemClient.getBlockNumber(); - const tokenPricesForCurrentChain = await tokenService.getTokenPrices(chain); const poolTokenData = await fetchPoolTokenInfo(vaultAddress, filteredPoolIds, viemClient, blockNumber); const poolTokenRatesData = await fetchPoolTokenRates(vaultAddress, filteredPoolIds, viemClient, blockNumber); diff --git a/modules/controllers/jobs-controller.ts b/modules/controllers/jobs-controller.ts index a6f03728c..79fc608ed 100644 --- a/modules/controllers/jobs-controller.ts +++ b/modules/controllers/jobs-controller.ts @@ -1,5 +1,7 @@ +import { update } from 'lodash'; import config from '../../config'; import { addMissingPoolsFromSubgraph } from '../actions/pool/add-pools-from-subgraph'; +import { getChangedPools } from '../actions/pool/get-changed-pools'; import { updateOnChainDataForPools } from '../actions/pool/update-on-chain-data'; import { chainIdToChain } from '../network/chain-id-to-chain'; import { getViemClient } from '../sources/viem-client'; @@ -34,17 +36,57 @@ export function JobsController(tracer?: any) { const vaultSubgraphClient = getVaultSubgraphClient(balancerV3); const poolSubgraphClient = getPoolsSubgraphClient(balancerPoolsV3!); const viemClient = getViemClient(chain); + const latestBlock = await viemClient.getBlockNumber(); // TODO: add syncing v2 pools as well by splitting the poolService into separate // actions with extracted configuration + // find all missing pools and add them to the DB const added = await addMissingPoolsFromSubgraph(vaultSubgraphClient, poolSubgraphClient, chain); - //required steps: - // update onchain data and status - const updated = updateOnChainDataForPools(vaultAddress, '123', added, viemClient); - // sync swaps - // updateVolumeAndFeeValues + // update with latest on-chain data + const updated = await updateOnChainDataForPools(vaultAddress, '123', added, viemClient, latestBlock); + + // also sync swaps and volumeAndFee values + // const poolsWithNewSwaps = await poolService.syncSwapsForLast48Hours(); + // await poolService.updateVolumeAndFeeValuesForPools(poolsWithNewSwaps); + + return updated; + }, + async updateOnChainDataChangedPools(chainId: string) { + const chain = chainIdToChain[chainId]; + const { + balancer: { + v3: { vaultAddress }, + }, + } = config[chain]; + + // Guard against unconfigured chains + if (!vaultAddress) { + throw new Error(`Chain not configured: ${chain}`); + } + const viemClient = getViemClient(chain); + + const blockNumber = await viemClient.getBlockNumber(); + + const changedPools = await getChangedPools(vaultAddress, viemClient, blockNumber, chain); + if (changedPools.length === 0) { + return []; + } + + const updated = updateOnChainDataForPools( + vaultAddress, + '123', + changedPools, + viemClient, + blockNumber, + chain, + ); + // also sync swaps and volumeAndFee values + // const poolsWithNewSwaps = await poolService.syncSwapsForLast48Hours(); + // await poolService.updateVolumeAndFeeValuesForPools(poolsWithNewSwaps); + + return updated; }, }; } diff --git a/modules/controllers/pools-controller.ts b/modules/controllers/pools-controller.ts index b452db0a0..400d1c5e9 100644 --- a/modules/controllers/pools-controller.ts +++ b/modules/controllers/pools-controller.ts @@ -27,8 +27,9 @@ export function PoolsController(tracer?: any) { throw new Error(`Chain not configured: ${chain}`); } const viemClient = getViemClient(chain); + const latestBlockNumber = await viemClient.getBlockNumber(); - const updated = updateOnchainDataForAllPools(vaultAddress, viemClient, chain); + const updated = updateOnchainDataForAllPools(vaultAddress, viemClient, latestBlockNumber, chain); return updated; }, }; diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index f4188fa22..b3ae77b27 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -114,25 +114,17 @@ export const sepoliaNetworkConfig: NetworkConfig = { name: 'user-sync-staked-balances', interval: every(5, 'minutes'), }, - // { - // name: 'sync-coingecko-coinids', - // interval: every(2, 'hours'), - // }, { name: 'update-fee-volume-yield-all-pools', interval: every(1, 'hours'), }, - // { - // name: 'sync-vebal-balances', - // interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(9, 'minutes') : every(3, 'minutes'), - // }, - // { - // name: 'sync-vebal-totalSupply', - // interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(10, 'minutes') : every(5, 'minutes'), - // }, - // { - // name: 'feed-data-to-datastudio', - // interval: (env.DEPLOYMENT_ENV as DeploymentEnv) === 'canary' ? every(5, 'minutes') : every(5, 'minutes'), - // }, + { + name: 'sync-changed-pools-v3', + interval: every(15, 'minutes'), + }, + { + name: 'sync-new-pools-from-subgraph-v3', + interval: every(20, 'minutes'), + }, ], }; diff --git a/modules/sources/logs/get-pool-balance-changed.ts b/modules/sources/logs/get-pool-balance-changed.ts new file mode 100644 index 000000000..5140d1d41 --- /dev/null +++ b/modules/sources/logs/get-pool-balance-changed.ts @@ -0,0 +1,49 @@ +import { ViemClient } from '../types'; + +const event = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'pool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'liquidityProvider', + type: 'address', + }, + { + indexed: false, + internalType: 'contract IERC20[]', + name: 'tokens', + type: 'address[]', + }, + { + indexed: false, + internalType: 'int256[]', + name: 'deltas', + type: 'int256[]', + }, + ], + name: 'PoolBalanceChanged', + type: 'event', +} as const; + +export const getPoolBalanceChanged = async ( + vaultAddress: string, + client: ViemClient, + fromBlock: bigint, + toBlock: bigint | undefined = undefined, +) => { + const logs = await client.getLogs({ + address: vaultAddress as `0x${string}`, + event, + fromBlock, + toBlock, + }); + + return logs; +}; diff --git a/modules/sources/logs/get-swaps.ts b/modules/sources/logs/get-swaps.ts index 1f042e1c6..b63773e33 100644 --- a/modules/sources/logs/get-swaps.ts +++ b/modules/sources/logs/get-swaps.ts @@ -47,11 +47,17 @@ const event = { /** * Extract balances from the contract */ -export const getSwaps = async (vaultAddress: string, client: ViemClient, fromBlock: bigint) => { +export const getSwaps = async ( + vaultAddress: string, + client: ViemClient, + fromBlock: bigint, + toBlock: bigint | undefined = undefined, +) => { const logs = await client.getLogs({ address: vaultAddress as `0x${string}`, event, fromBlock, + toBlock, }); return logs; diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 89a6b082e..1eccf9932 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -111,7 +111,7 @@ export function configureWorkerRoutes(app: Express) { await runIfNotAlreadyRunning( job.name, chainId, - () => jobsController.addMissingPoolsFromSubgraph(chainId), + () => jobsController.updateOnChainDataChangedPools(chainId), res, next, ); @@ -189,7 +189,7 @@ export function configureWorkerRoutes(app: Express) { await runIfNotAlreadyRunning( job.name, chainId, - () => poolService.syncNewPoolsFromSubgraphV3(), + () => jobsController.addMissingPoolsFromSubgraph(chainId), res, next, ); From da58a7cad43280dfc7e83d3b48dbc3141919ce5d Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 22 Feb 2024 13:44:06 +0100 Subject: [PATCH 14/14] add pool subgraph to git workflow --- .github/workflows/checks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7d5b21a8c..62f0c7275 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,6 +7,7 @@ jobs: Build: env: V3_SUBGRAPH: https://api.studio.thegraph.com/proxy/31386/balancer-v3-sepolia/version/latest + V3_POOLS_SUBGRAPH: https://api.studio.thegraph.com/proxy/31386/balancer-pools-v3-sepolia/version/latest BALANCER_SUBGRAPH: https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx MASTERCHEF_SUBGRAPH: https://api.thegraph.com/subgraphs/name/beethovenxfi/masterchefv2 BLOCKS_SUBGRAPH: https://api.thegraph.com/subgraphs/name/danielmkm/optimism-blocks