From 743da61bbe0334a32d13d0ba07757c810fd1fbd1 Mon Sep 17 00:00:00 2001 From: duoxehyon <86877776+duoxehyon@users.noreply.github.com> Date: Wed, 27 Mar 2024 01:18:58 +0530 Subject: [PATCH] Balancer V2 (#511) * wip * wip * wip * wip * wip * wip * wip * wip * ouch * cleanup * cleanup * wip * wip * wip * fix * fmt * wip * ok * wip * painful purge * cleanup * action impl bug fix * cl * ok * add test logging * test * ensure actual protocol * fix balancer tests * no meta * fix: stop cpu timeout * wip --------- Co-authored-by: Will Smith --- config/classifier_config.toml | 3 + .../{ => balancer}/BalancerV1CrpFactory.json | 0 .../{ => balancer}/BalancerV1Factory.json | 0 .../{ => balancer}/BalancerV1Pool.json | 0 .../balancer/BalancerV2Vault.json | 1179 +++++++++++++++++ .../src/classifiers/balancer/balancer_v2.rs | 363 +++++ .../src/classifiers/balancer/discovery.rs | 3 - .../src/classifiers/balancer/mod.rs | 8 +- .../brontes-classifier/src/classifiers/mod.rs | 8 +- crates/brontes-classifier/src/lib.rs | 45 +- .../src/test_utils/tests.rs | 2 +- .../src/tree_builder/mod.rs | 10 + .../src/clickhouse/db_client.rs | 2 +- .../src/libmdbx/initialize.rs | 21 +- .../src/action_classifier/action_impl.rs | 14 +- .../src/normalized_actions/mod.rs | 4 + crates/brontes-types/src/protocol.rs | 2 + 17 files changed, 1633 insertions(+), 31 deletions(-) rename crates/brontes-classifier/classifier-abis/{ => balancer}/BalancerV1CrpFactory.json (100%) rename crates/brontes-classifier/classifier-abis/{ => balancer}/BalancerV1Factory.json (100%) rename crates/brontes-classifier/classifier-abis/{ => balancer}/BalancerV1Pool.json (100%) create mode 100644 crates/brontes-classifier/classifier-abis/balancer/BalancerV2Vault.json create mode 100644 crates/brontes-classifier/src/classifiers/balancer/balancer_v2.rs diff --git a/config/classifier_config.toml b/config/classifier_config.toml index 205a154af..d8d8ad763 100644 --- a/config/classifier_config.toml +++ b/config/classifier_config.toml @@ -49,6 +49,9 @@ symbol = "USDP" [UniswapX."0x6000da47483062a0d734ba3dc7576ce6a0b645c4"] init_block = 17777988 +[BalancerV2."0xBA12222222228d8Ba445958a75a0704d566BF2C8"] +init_block = 12272146 + [BalancerV1."0x92E7Eb99a38C8eB655B15467774C6d56Fb810BC9"] init_block = 10866521 diff --git a/crates/brontes-classifier/classifier-abis/BalancerV1CrpFactory.json b/crates/brontes-classifier/classifier-abis/balancer/BalancerV1CrpFactory.json similarity index 100% rename from crates/brontes-classifier/classifier-abis/BalancerV1CrpFactory.json rename to crates/brontes-classifier/classifier-abis/balancer/BalancerV1CrpFactory.json diff --git a/crates/brontes-classifier/classifier-abis/BalancerV1Factory.json b/crates/brontes-classifier/classifier-abis/balancer/BalancerV1Factory.json similarity index 100% rename from crates/brontes-classifier/classifier-abis/BalancerV1Factory.json rename to crates/brontes-classifier/classifier-abis/balancer/BalancerV1Factory.json diff --git a/crates/brontes-classifier/classifier-abis/BalancerV1Pool.json b/crates/brontes-classifier/classifier-abis/balancer/BalancerV1Pool.json similarity index 100% rename from crates/brontes-classifier/classifier-abis/BalancerV1Pool.json rename to crates/brontes-classifier/classifier-abis/balancer/BalancerV1Pool.json diff --git a/crates/brontes-classifier/classifier-abis/balancer/BalancerV2Vault.json b/crates/brontes-classifier/classifier-abis/balancer/BalancerV2Vault.json new file mode 100644 index 000000000..1d62baa89 --- /dev/null +++ b/crates/brontes-classifier/classifier-abis/balancer/BalancerV2Vault.json @@ -0,0 +1,1179 @@ +[ + { + "inputs": [ + { + "internalType": "contract IAuthorizer", + "name": "authorizer", + "type": "address" + }, + { + "internalType": "contract IWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "AuthorizerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalBalanceTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "name": "FlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "delta", + "type": "int256" + } + ], + "name": "InternalBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "int256[]", + "name": "deltas", + "type": "int256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "protocolFeeAmounts", + "type": "uint256[]" + } + ], + "name": "PoolBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "assetManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "cashDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "managedDelta", + "type": "int256" + } + ], + "name": "PoolBalanceManaged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "poolAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "PoolRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "RelayerApprovalChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "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" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "TokensDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "TokensRegistered", + "type": "event" + }, + { + "inputs": [], + "name": "WETH", + "outputs": [ + { + "internalType": "contract IWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "int256[]", + "name": "limits", + "type": "int256[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "batchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "assetDeltas", + "type": "int256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "deregisterTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.ExitPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "exitPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "flashLoan", + "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": "getDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "getInternalBalance", + "outputs": [ + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getNextNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "enum IVault.PoolSpecialization", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getPoolTokenInfo", + "outputs": [ + { + "internalType": "uint256", + "name": "cash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "managed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "assetManager", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPoolTokens", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolFeesCollector", + "outputs": [ + { + "internalType": "contract ProtocolFeesCollector", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + } + ], + "name": "hasApprovedRelayer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.JoinPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "joinPool", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.PoolBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IVault.PoolBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "managePoolBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.UserBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + } + ], + "internalType": "struct IVault.UserBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "manageUserBalance", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + } + ], + "name": "queryBatchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "", + "type": "int256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "registerPool", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "registerTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "setAuthorizer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setRelayerApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "assetIn", + "type": "address" + }, + { + "internalType": "contract IAsset", + "name": "assetOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.SingleSwap", + "name": "singleSwap", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCalculated", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/crates/brontes-classifier/src/classifiers/balancer/balancer_v2.rs b/crates/brontes-classifier/src/classifiers/balancer/balancer_v2.rs new file mode 100644 index 000000000..beca91a45 --- /dev/null +++ b/crates/brontes-classifier/src/classifiers/balancer/balancer_v2.rs @@ -0,0 +1,363 @@ +use alloy_primitives::{Address, FixedBytes}; +use brontes_database::libmdbx::{DBWriter, LibmdbxReader}; +use brontes_macros::action_impl; +use brontes_pricing::Protocol; +use brontes_types::{ + db::token_info::TokenInfoWithAddress, + normalized_actions::{ + NormalizedBurn, NormalizedMint, NormalizedNewPool, NormalizedPoolConfigUpdate, + NormalizedSwap, + }, + structured_trace::CallInfo, + ToScaledRational, +}; +use eyre::Error; +use malachite::Rational; +use reth_primitives::U256; + +use crate::{ + BalancerV2Vault::PoolBalanceChanged, IGeneralPool::onSwap_0Return, + IMinimalSwapInfoPool::onSwap_1Return, +}; + +action_impl!( + Protocol::BalancerV2, + crate::IGeneralPool::onSwap_0Call, + Swap, + [..], + call_data: true, + return_data: true, + |info: CallInfo, call_data: onSwap_0Call, return_data: onSwap_0Return, db: &DB| { + let pool = pool_id_to_address(call_data.swapRequest.poolId); + let token_in = db.try_fetch_token_info(call_data.swapRequest.tokenIn)?; + let token_out = db.try_fetch_token_info(call_data.swapRequest.tokenOut)?; + let amount_in = call_data.swapRequest.amount.to_scaled_rational(token_in.decimals); + let amount_out = return_data.amount.to_scaled_rational(token_out.decimals); + + Ok(NormalizedSwap { + protocol: Protocol::BalancerV2, + trace_index: info.trace_idx, + from: call_data.swapRequest.from, + recipient: call_data.swapRequest.to, + pool, + token_in, + amount_in, + token_out, + amount_out, + msg_value: U256::ZERO, + }) + } +); + +action_impl!( + Protocol::BalancerV2, + crate::IMinimalSwapInfoPool::onSwap_1Call, + Swap, + [..], + call_data: true, + return_data: true, + |info: CallInfo, call_data: onSwap_1Call, return_data: onSwap_1Return, db: &DB| { + let pool = pool_id_to_address(call_data.swapRequest.poolId); + let token_in = db.try_fetch_token_info(call_data.swapRequest.tokenIn)?; + let token_out = db.try_fetch_token_info(call_data.swapRequest.tokenOut)?; + let amount_in = call_data.swapRequest.amount.to_scaled_rational(token_in.decimals); + let amount_out = return_data.amount.to_scaled_rational(token_out.decimals); + + Ok(NormalizedSwap { + protocol: Protocol::BalancerV2, + trace_index: info.trace_idx, + from: call_data.swapRequest.from, + recipient: call_data.swapRequest.to, + pool, + token_in, + amount_in, + token_out, + amount_out, + msg_value: U256::ZERO, + }) + } +); + +fn process_pool_balance_changes( + logs: &PoolBalanceChanged, + db: &DB, +) -> Result<(Vec, Vec), Error> { + let mut tokens = Vec::new(); + let mut amounts = Vec::new(); + + for (i, &token_address) in logs.tokens.iter().enumerate() { + if logs.deltas[i].is_zero() { + continue + } + + let token = db.try_fetch_token_info(token_address)?; + let amount = logs.deltas[i].abs().to_scaled_rational(token.decimals); + tokens.push(token); + amounts.push(amount); + } + + Ok((tokens, amounts)) +} + +action_impl!( + Protocol::BalancerV2, + crate::BalancerV2Vault::joinPoolCall, + Mint, + [..PoolBalanceChanged], + call_data: true, + logs: true, + |info: CallInfo, call_data: joinPoolCall, log_data: BalancerV2JoinPoolCallLogs, db: &DB| { + let logs = log_data.pool_balance_changed_field?; + let (tokens, amounts) = process_pool_balance_changes(&logs, db)?; + + Ok(NormalizedMint { + protocol: Protocol::BalancerV2, + trace_index: info.trace_idx, + from: call_data.sender, + recipient: call_data.recipient, + pool: pool_id_to_address(call_data.poolId), + token: tokens, + amount: amounts + }) + } +); + +action_impl!( + Protocol::BalancerV2, + crate::BalancerV2Vault::exitPoolCall, + Burn, + [..PoolBalanceChanged], + call_data: true, + logs: true, + |info: CallInfo, call_data: exitPoolCall, log_data: BalancerV2ExitPoolCallLogs, db: &DB| { + let logs = log_data.pool_balance_changed_field?; + let (tokens, amounts) = process_pool_balance_changes(&logs, db)?; + + Ok(NormalizedBurn { + protocol: Protocol::BalancerV2, + trace_index: info.trace_idx, + from: call_data.sender, + recipient: call_data.recipient, + pool: pool_id_to_address(call_data.poolId), + token: tokens, + amount: amounts + }) + } +); + +action_impl!( + Protocol::BalancerV2, + crate::BalancerV2Vault::registerPoolCall, + NewPool, + [..PoolRegistered], + logs: true, + |info: CallInfo, log_data: BalancerV2RegisterPoolCallLogs, _| { + let logs = log_data.pool_registered_field?; + + Ok(NormalizedNewPool { + trace_index: info.trace_idx, + protocol: Protocol::BalancerV2, + pool_address: logs.poolAddress, + tokens: vec![], + }) + } +); + +action_impl!( + Protocol::BalancerV2, + crate::BalancerV2Vault::registerTokensCall, + PoolConfigUpdate, + [..TokensRegistered], + logs: true, + |info: CallInfo, log_data: BalancerV2RegisterTokensCallLogs, _| { + let logs = log_data.tokens_registered_field?; + let pool_address = pool_id_to_address(logs.poolId); + + Ok(NormalizedPoolConfigUpdate{ + trace_index: info.trace_idx, + protocol: Protocol::BalancerV2, + pool_address, + tokens: logs.tokens + }) + } +); + +// ~ https://docs.balancer.fi/reference/contracts/pool-interfacing.html#poolids +// The poolId is a unique identifier, the first portion of which is the pool's +// contract address. For example, the pool with the id +// 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 has a +// contract address of 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56. +fn pool_id_to_address(pool_id: FixedBytes<32>) -> Address { + Address::from_slice(&pool_id[0..20]) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use alloy_primitives::{hex, B256}; + use brontes_classifier::test_utils::ClassifierTestUtils; + use brontes_types::{ + constants::WETH_ADDRESS, db::token_info::TokenInfo, normalized_actions::Actions, + Protocol::BalancerV2, TreeSearchBuilder, + }; + + use super::*; + + #[brontes_macros::test] + async fn test_balancer_v2_swap() { + let classifier_utils = ClassifierTestUtils::new().await; + let swap = + B256::from(hex!("da10a5e3cb8c34c77634cb9a1cfe02ec2b23029f1f288d79b6252b2f8cae20d3")); + + classifier_utils.ensure_token(TokenInfoWithAddress { + address: Address::new(hex!("6C22910c6F75F828B305e57c6a54855D8adeAbf8")), + inner: TokenInfo { decimals: 9, symbol: "SATS".to_string() }, + }); + + classifier_utils.ensure_protocol( + Protocol::BalancerV2, + hex!("358e056c50eea4ca707e891404e81d9b898d0b41").into(), + WETH_ADDRESS, + Some(hex!("6C22910c6F75F828B305e57c6a54855D8adeAbf8").into()), + None, + None, + None, + None, + ); + + // Minimal swap + let eq_action = Actions::Swap(NormalizedSwap { + protocol: BalancerV2, + trace_index: 1, + from: Address::new(hex!("5d2146eAB0C6360B864124A99BD58808a3014b5d")), + recipient: Address::new(hex!("5d2146eAB0C6360B864124A99BD58808a3014b5d")), + pool: Address::new(hex!("358e056c50eea4ca707e891404e81d9b898d0b41")), + token_in: TokenInfoWithAddress::weth(), + amount_in: U256::from_str("10000000000000000") + .unwrap() + .to_scaled_rational(18), + token_out: TokenInfoWithAddress { + address: Address::new(hex!("6C22910c6F75F828B305e57c6a54855D8adeAbf8")), + inner: TokenInfo { decimals: 9, symbol: "SATS".to_string() }, + }, + amount_out: U256::from_str("7727102831493") + .unwrap() + .to_scaled_rational(9), + + msg_value: U256::ZERO, + }); + + classifier_utils + .contains_action( + swap, + 0, + eq_action, + TreeSearchBuilder::default().with_action(Actions::is_swap), + ) + .await + .unwrap(); + } + + #[brontes_macros::test] + async fn test_balancer_v2_join_pool() { + let classifier_utils = ClassifierTestUtils::new().await; + let mint = + B256::from(hex!("ffed34d6f2d9e239b5cd3985840a37f1fa0c558edcd1a2f3d2b8bd7f314ef6a3")); + + classifier_utils.ensure_token(TokenInfoWithAddress { + address: Address::new(hex!("cd5fe23c85820f7b72d0926fc9b05b43e359b7ee")), + inner: TokenInfo { decimals: 18, symbol: "weETH".to_string() }, + }); + + let eq_action = Actions::Mint(NormalizedMint { + protocol: Protocol::BalancerV2, + trace_index: 0, + from: Address::new(hex!("750c31d2290c456fcca1c659b6add80e7a88f881")), + recipient: Address::new(hex!("750c31d2290c456fcca1c659b6add80e7a88f881")), + pool: Address::new(hex!("848a5564158d84b8A8fb68ab5D004Fae11619A54")), + token: vec![TokenInfoWithAddress { + address: Address::new(hex!("cd5fe23c85820f7b72d0926fc9b05b43e359b7ee")), + inner: TokenInfo { decimals: 18, symbol: "weETH".to_string() }, + }], + amount: vec![U256::from_str("1935117712922949743") + .unwrap() + .to_scaled_rational(18)], + }); + + classifier_utils + .contains_action( + mint, + 0, + eq_action, + TreeSearchBuilder::default().with_action(Actions::is_mint), + ) + .await + .unwrap(); + } + + #[brontes_macros::test] + async fn test_balancer_v2_exit_pool() { + let classifier_utils = ClassifierTestUtils::new().await; + let burn = + B256::from(hex!("ad13973ee8e507b36adc5d28dc53b77d58d00d5ac6a09aa677936be8aaf6c8a1")); + + classifier_utils.ensure_token(TokenInfoWithAddress { + address: Address::new(hex!("bf5495efe5db9ce00f80364c8b423567e58d2110")), + inner: TokenInfo { decimals: 18, symbol: "ezETH".to_string() }, + }); + + classifier_utils.ensure_token(TokenInfoWithAddress { + address: Address::new(hex!("cd5fe23c85820f7b72d0926fc9b05b43e359b7ee")), + inner: TokenInfo { decimals: 18, symbol: "weETH".to_string() }, + }); + + classifier_utils.ensure_token(TokenInfoWithAddress { + address: Address::new(hex!("fae103dc9cf190ed75350761e95403b7b8afa6c0")), + inner: TokenInfo { decimals: 18, symbol: "rswETH".to_string() }, + }); + + let eq_action = Actions::Burn(NormalizedBurn { + protocol: Protocol::BalancerV2, + trace_index: 0, + from: Address::new(hex!("f4283d13ba1e17b33bb3310c3149136a2ef79ef7")), + recipient: Address::new(hex!("f4283d13ba1e17b33bb3310c3149136a2ef79ef7")), + pool: Address::new(hex!("848a5564158d84b8A8fb68ab5D004Fae11619A54")), + token: vec![ + TokenInfoWithAddress { + address: Address::new(hex!("bf5495efe5db9ce00f80364c8b423567e58d2110")), + inner: TokenInfo { decimals: 18, symbol: "ezETH".to_string() }, + }, + TokenInfoWithAddress { + address: Address::new(hex!("cd5fe23c85820f7b72d0926fc9b05b43e359b7ee")), + inner: TokenInfo { decimals: 18, symbol: "weETH".to_string() }, + }, + TokenInfoWithAddress { + address: Address::new(hex!("fae103dc9cf190ed75350761e95403b7b8afa6c0")), + inner: TokenInfo { decimals: 18, symbol: "rswETH".to_string() }, + }, + ], + amount: vec![ + U256::from_str("471937215318872937") + .unwrap() + .to_scaled_rational(18), + U256::from_str("757823171697267931") + .unwrap() + .to_scaled_rational(18), + U256::from_str("699970729674926490") + .unwrap() + .to_scaled_rational(18), + ], + }); + + classifier_utils + .contains_action( + burn, + 0, + eq_action, + TreeSearchBuilder::default().with_action(Actions::is_burn), + ) + .await + .unwrap(); + } +} diff --git a/crates/brontes-classifier/src/classifiers/balancer/discovery.rs b/crates/brontes-classifier/src/classifiers/balancer/discovery.rs index c5fb31e34..129001c59 100644 --- a/crates/brontes-classifier/src/classifiers/balancer/discovery.rs +++ b/crates/brontes-classifier/src/classifiers/balancer/discovery.rs @@ -32,9 +32,6 @@ discovery_impl!( } ); -// Smart Pool Factory -// fub4 - #[cfg(test)] mod tests { use alloy_primitives::{hex, Address, B256}; diff --git a/crates/brontes-classifier/src/classifiers/balancer/mod.rs b/crates/brontes-classifier/src/classifiers/balancer/mod.rs index 3f6bd9927..1f7987872 100644 --- a/crates/brontes-classifier/src/classifiers/balancer/mod.rs +++ b/crates/brontes-classifier/src/classifiers/balancer/mod.rs @@ -1,5 +1,11 @@ mod balancer_v1; -mod discovery; pub use balancer_v1::*; + +mod balancer_v2; + +pub use balancer_v2::*; + +mod discovery; + pub use discovery::*; diff --git a/crates/brontes-classifier/src/classifiers/mod.rs b/crates/brontes-classifier/src/classifiers/mod.rs index 665142a89..c3dbc1e1d 100644 --- a/crates/brontes-classifier/src/classifiers/mod.rs +++ b/crates/brontes-classifier/src/classifiers/mod.rs @@ -55,7 +55,8 @@ discovery_dispatch!( CurvecrvUSDMetaDiscovery1, CurveCryptoSwapDiscovery, CurveTriCryptoDiscovery, - BalancerV1CoreDiscovery + BalancerV1CoreDiscovery, + BalancerV1SmartPoolDiscovery ); action_dispatch!( @@ -144,6 +145,11 @@ action_dispatch!( BalancerV1SwapExactAmountInCall, BalancerV1SwapExactAmountOutCall, BalancerV1BindCall, + BalancerV2OnSwap_0Call, + BalancerV2OnSwap_1Call, + BalancerV2JoinPoolCall, + BalancerV2ExitPoolCall, + BalancerV2RegisterTokensCall, CompoundV2LiquidateBorrowCall, CompoundV2Initialize_0Call, CompoundV2Initialize_1Call, diff --git a/crates/brontes-classifier/src/lib.rs b/crates/brontes-classifier/src/lib.rs index 0b4985e92..3257f7fdd 100644 --- a/crates/brontes-classifier/src/lib.rs +++ b/crates/brontes-classifier/src/lib.rs @@ -39,7 +39,8 @@ sol!(CurveV2MetapoolImpl, "./classifier-abis/CurveV2MetapoolImpl.json"); sol!(CurveV2PlainImpl, "./classifier-abis/CurveV2PlainImpl.json"); sol!(CurvecrvUSDPlainImpl, "./classifier-abis/CurvecrvUSDPlainImpl.json"); sol!(CurveCryptoSwap, "./classifier-abis/CurveCryptoSwap.json"); -sol!(BalancerV1, "./classifier-abis/BalancerV1Pool.json"); +sol!(BalancerV1, "./classifier-abis/balancer/BalancerV1Pool.json"); +sol!(BalancerV2Vault, "./classifier-abis/balancer/BalancerV2Vault.json"); sol!(AaveV2, "./classifier-abis/AaveV2Pool.json"); sol!(AaveV3, "./classifier-abis/AaveV3Pool.json"); sol!(UniswapX, "./classifier-abis/UniswapXExclusiveDutchOrderReactor.json"); @@ -62,8 +63,46 @@ sol!(CurveTriCryptoFactory, "./classifier-abis/CurveTriCryptoFactory.json"); sol!(PancakeSwapV3PoolDeployer, "./classifier-abis/PancakeSwapV3PoolDeployer.json"); sol!(CompoundV2Comptroller, "./classifier-abis/CompoundV2Comptroller.json"); sol!(CErc20Delegate, "./classifier-abis/CErc20Delegate.json"); -sol!(BalancerV1CorePoolFactory, "./classifier-abis/BalancerV1Factory.json"); -sol!(BalancerV1SmartPoolFactory, "./classifier-abis/BalancerV1CrpFactory.json"); +sol!(BalancerV1CorePoolFactory, "./classifier-abis/balancer/BalancerV1Factory.json"); +sol!(BalancerV1SmartPoolFactory, "./classifier-abis/balancer/BalancerV1CrpFactory.json"); + +// Balancer Pool Interfaces +sol! { + enum SwapKind { + GIVEN_IN, + GIVEN_OUT + } + + struct SwapRequest { + SwapKind kind; + address tokenIn; + address tokenOut; + uint256 amount; + // Misc data + bytes32 poolId; + uint256 lastChangeBlock; + address from; + address to; + bytes userData; + } + + interface IGeneralPool { + function onSwap( + SwapRequest memory swapRequest, + uint256[] memory balances, + uint256 indexIn, + uint256 indexOut + ) external returns (uint256 amount); + } + + interface IMinimalSwapInfoPool { + function onSwap( + SwapRequest memory swapRequest, + uint256 currentBalanceTokenIn, + uint256 currentBalanceTokenOut + ) external returns (uint256 amount); + } +} sol! { event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/crates/brontes-classifier/src/test_utils/tests.rs b/crates/brontes-classifier/src/test_utils/tests.rs index 728b1f44d..0dd6af6af 100644 --- a/crates/brontes-classifier/src/test_utils/tests.rs +++ b/crates/brontes-classifier/src/test_utils/tests.rs @@ -550,7 +550,7 @@ impl ClassifierTestUtils { let mut trace_addr = found_trace.get_trace_address(); - if trace_addr.len() > 1 { + if !trace_addr.is_empty() { trace_addr.pop().unwrap(); } else { return Err(ClassifierTestUtilsError::ProtocolDiscoveryError(created_pool)) diff --git a/crates/brontes-classifier/src/tree_builder/mod.rs b/crates/brontes-classifier/src/tree_builder/mod.rs index 8483c5643..0f95e28e3 100644 --- a/crates/brontes-classifier/src/tree_builder/mod.rs +++ b/crates/brontes-classifier/src/tree_builder/mod.rs @@ -273,6 +273,16 @@ impl<'db, T: TracingProvider, DB: LibmdbxReader + DBWriter> Classifier<'db, T, D if results.1.is_new_pool() { let Actions::NewPool(p) = &results.1 else { unreachable!() }; self.insert_new_pool(block, p).await; + } else if results.1.is_pool_config_update() { + let Actions::PoolConfigUpdate(p) = &results.1 else { unreachable!() }; + if self + .libmdbx + .insert_pool(block, p.pool_address, p.tokens.as_slice(), None, p.protocol) + .await + .is_err() + { + error!(pool=?p.pool_address,"failed to update pool config"); + } } (vec![results.0], results.1) diff --git a/crates/brontes-database/src/clickhouse/db_client.rs b/crates/brontes-database/src/clickhouse/db_client.rs index b2b6521e5..e635c859b 100644 --- a/crates/brontes-database/src/clickhouse/db_client.rs +++ b/crates/brontes-database/src/clickhouse/db_client.rs @@ -344,7 +344,7 @@ mod tests { use brontes_classifier::test_utils::ClassifierTestUtils; use brontes_core::{get_db_handle, init_trace_parser}; use brontes_types::{ - db::{dex::DexPrices, normalized_actions::TransactionRoot, searcher::SearcherEoaContract}, + db::{dex::DexPrices, searcher::SearcherEoaContract}, init_threadpools, mev::{ AtomicArb, BundleHeader, CexDex, JitLiquidity, JitLiquiditySandwich, Liquidation, diff --git a/crates/brontes-database/src/libmdbx/initialize.rs b/crates/brontes-database/src/libmdbx/initialize.rs index 4019a5de6..e0ce57cbf 100644 --- a/crates/brontes-database/src/libmdbx/initialize.rs +++ b/crates/brontes-database/src/libmdbx/initialize.rs @@ -54,14 +54,18 @@ impl LibmdbxInitializer { clear_tables: bool, block_range: Option<(u64, u64)>, // inclusive of start only ) -> eyre::Result<()> { - join_all( - tables - .iter() - .map(|table| table.initialize_table(self, block_range, clear_tables)), - ) - .await - .into_iter() - .collect::>()?; + futures::stream::iter(tables.to_vec()) + .map(|table| async move { + table + .initialize_table(self, block_range, clear_tables) + .await + }) + .buffered(2) + .collect::>() + .await + .into_iter() + .collect::>()?; + join!( self.load_classifier_config_data(), self.load_searcher_builder_config_data(), @@ -591,6 +595,7 @@ mod tests { TxTraces::test_initialized_data(clickhouse, libmdbx, Some(block_range)) .await .unwrap(); + TxTraces::test_initialized_arbitrary_data(clickhouse, libmdbx, arbitrary_set) .await .unwrap(); diff --git a/crates/brontes-macros/src/action_classifier/action_impl.rs b/crates/brontes-macros/src/action_classifier/action_impl.rs index 15eb3a13c..014273984 100644 --- a/crates/brontes-macros/src/action_classifier/action_impl.rs +++ b/crates/brontes-macros/src/action_classifier/action_impl.rs @@ -58,19 +58,7 @@ impl ActionMacro { let dex_price_return = if action_type.to_string().to_lowercase().as_str() == "poolconfigupdate" { - quote!( - if db_tx.insert_pool(block, - pool.pool_address, - pool.tokens, - pool.protocol).is_err() { - ::tracing::error!( - pool=?pool.pool_address, - block, - "failed to update pool config"); - } - - Ok(::brontes_pricing::types::DexPriceMsg::DiscoveredPool(result)) - ) + quote!(Ok(::brontes_pricing::types::DexPriceMsg::DiscoveredPool(result))) } else { quote!( Ok(::brontes_pricing::types::DexPriceMsg::Update( diff --git a/crates/brontes-types/src/normalized_actions/mod.rs b/crates/brontes-types/src/normalized_actions/mod.rs index 8a6004624..5de85954c 100644 --- a/crates/brontes-types/src/normalized_actions/mod.rs +++ b/crates/brontes-types/src/normalized_actions/mod.rs @@ -383,6 +383,10 @@ impl Actions { matches!(self, Actions::NewPool(_)) } + pub const fn is_pool_config_update(&self) -> bool { + matches!(self, Actions::PoolConfigUpdate(_)) + } + pub const fn is_unclassified(&self) -> bool { matches!(self, Actions::Unclassified(_)) } diff --git a/crates/brontes-types/src/protocol.rs b/crates/brontes-types/src/protocol.rs index aedd55726..0c91acf56 100644 --- a/crates/brontes-types/src/protocol.rs +++ b/crates/brontes-types/src/protocol.rs @@ -73,6 +73,7 @@ utils!( AaveV2, AaveV3, BalancerV1, + BalancerV2, BalancerV1CRP, UniswapX, Cowswap, @@ -114,6 +115,7 @@ impl Protocol { Protocol::AaveV2 => ("Aave", "V2"), Protocol::AaveV3 => ("Aave", "V3"), Protocol::BalancerV1 => ("Balancer", "V1"), + Protocol::BalancerV2 => ("Balancer", "V2"), Protocol::BalancerV1CRP => ("Balancer", "V1SmartPool"), Protocol::UniswapX => ("Uniswap", "X"), Protocol::Cowswap => ("Cowswap", ""),