From db6b3c98b816407c2f37179045f6f952d6830a56 Mon Sep 17 00:00:00 2001 From: Ssimonas <31076019+Ssimonas@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:08:47 +0200 Subject: [PATCH] 30 add earned fees processing (#36) * feat: first try at fees calculation * feat: extended removal message to include earned tokens (tested) * improvement: try to use ABIs instead of splitting data fields by hard coded lengths * improvement: general improvements, renamings * improvement: namings and ABI file embeding --- .../Uniswap_Liquidity_Pool_contract.json | 988 +++++++++++++ .../Uniswap_Liquidity_Position_contract.json | 1221 +++++++++++++++++ .../internal/analytics/ethereum/analytics.go | 22 +- .../internal/analytics/ethereum/convert.go | 59 +- .../analytics/ethereum/ethereum_test.go | 4 +- .../internal/analytics/ethereum/operations.go | 134 +- .../internal/analytics/ethereum/types.go | 4 + .../internal/analytics/ethereum/utils.go | 12 +- publisher/pkg/types/types.go | 24 +- 9 files changed, 2380 insertions(+), 88 deletions(-) create mode 100644 publisher/internal/analytics/ethereum/Uniswap_Liquidity_Pool_contract.json create mode 100644 publisher/internal/analytics/ethereum/Uniswap_Liquidity_Position_contract.json diff --git a/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Pool_contract.json b/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Pool_contract.json new file mode 100644 index 0000000..a9d0f5c --- /dev/null +++ b/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Pool_contract.json @@ -0,0 +1,988 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "feeProtocol0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "feeProtocol1", + "type": "uint8" + } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Position_contract.json b/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Position_contract.json new file mode 100644 index 0000000..ab6e387 --- /dev/null +++ b/publisher/internal/analytics/ethereum/Uniswap_Liquidity_Position_contract.json @@ -0,0 +1,1221 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenDescriptor_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "DecreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "IncreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Max", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Max", + "type": "uint128" + } + ], + "internalType": "struct INonfungiblePositionManager.CollectParams", + "name": "params", + "type": "tuple" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "createAndInitializePoolIfNecessary", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct INonfungiblePositionManager.DecreaseLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "decreaseLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct INonfungiblePositionManager.IncreaseLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "increaseLiquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint256", + "name": "amount0Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Desired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct INonfungiblePositionManager.MintParams", + "name": "params", + "type": "tuple" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint96", + "name": "nonce", + "type": "uint96" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowed", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowedIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Owed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Owed", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "uniswapV3MintCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "unwrapWETH9", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/publisher/internal/analytics/ethereum/analytics.go b/publisher/internal/analytics/ethereum/analytics.go index 6e58583..3d37b3e 100644 --- a/publisher/internal/analytics/ethereum/analytics.go +++ b/publisher/internal/analytics/ethereum/analytics.go @@ -2,10 +2,12 @@ package ethereum import ( "context" + _ "embed" "errors" "github.com/SyntropyNet/swapscope/publisher/pkg/analytics" "github.com/SyntropyNet/swapscope/publisher/pkg/repository" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/patrickmn/go-cache" ) @@ -13,14 +15,28 @@ var ( mintSig string transferSig string burnSig string + collectSig string + + //go:embed Uniswap_Liquidity_Pool_contract.json + uniswapLiqPoolsABIJson string + uniswapLiqPoolsABI abi.ABI + + //go:embed Uniswap_Liquidity_Position_contract.json + uniswapLiqPositionsABIJson string + uniswapLiqPositionsABI abi.ABI ) const ( subSubject = "syntropy.ethereum.log-event" uniswapPositionsOwner = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" - mintEventHeader = "Mint(address,address,int24,int24,uint128,uint256,uint256)" + mintEventHeader = "Mint(address,address,int24,int24,uint128,uint256,uint256)" // Matches function's name and parameters types list + mintEvent = "Mint" // Matches function's name transferEventHeader = "Transfer(address,address,uint256)" + transferEvent = "Transfer" burnEventHeader = "Burn(address,int24,int24,uint128,uint256,uint256)" + burnEvent = "Burn" + collectEventHeader = "Collect(address,address,int24,int24,uint128,uint128)" + collectEvent = "Collect" addressWETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" // https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 addressUSDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7" // https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7 addressUSDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 @@ -43,6 +59,7 @@ func init() { mintSig = convertToEventSignature(mintEventHeader) transferSig = convertToEventSignature(transferEventHeader) burnSig = convertToEventSignature(burnEventHeader) + collectSig = convertToEventSignature(collectEventHeader) } func New(ctx context.Context, db repository.Repository, opts ...Option) (*Analytics, error) { @@ -63,6 +80,9 @@ func New(ctx context.Context, db repository.Repository, opts ...Option) (*Analyt ret.eventLogCache = &EventLogCache{cache.New(ret.Options.eventLogCacheExpirationTime, ret.Options.eventLogCachePurgeTime)} + uniswapLiqPoolsABI = parseJsonToAbi(uniswapLiqPoolsABIJson) + uniswapLiqPositionsABI = parseJsonToAbi(uniswapLiqPositionsABIJson) + return ret, nil } diff --git a/publisher/internal/analytics/ethereum/convert.go b/publisher/internal/analytics/ethereum/convert.go index 22afdb2..5774d62 100644 --- a/publisher/internal/analytics/ethereum/convert.go +++ b/publisher/internal/analytics/ethereum/convert.go @@ -1,6 +1,7 @@ package ethereum import ( + "encoding/hex" "encoding/json" "fmt" "log" @@ -8,6 +9,7 @@ import ( "math/big" "strings" + "github.com/ethereum/go-ethereum/accounts/abi" "golang.org/x/crypto/sha3" ) @@ -63,6 +65,14 @@ func parseEventLogMessage(data []byte) (EventLog, error) { return eLog, nil } +func parseJsonToAbi(jsonABI string) abi.ABI { + abi, err := abi.JSON(strings.NewReader(jsonABI)) + if err != nil { + log.Fatal(err) + } + return abi +} + // convertTransferAmount converts Transfer's hex amount into scaled actual amount of tokens func convertTransferAmount(amountHex string, decimals int) float64 { amount := convertHexToBigInt(amountHex) @@ -72,41 +82,24 @@ func convertTransferAmount(amountHex string, decimals int) float64 { return amountScaled } -func splitBurnDatatoHexStrings(data string) (string, string, string, error) { - const ( - AmountOffset = 2 - AmountToken0Size = 64 - AmountToken1Size = 64 - RequiredDataFieldLength = 194 - ) - - if len(data) != RequiredDataFieldLength { - return "", "", "", fmt.Errorf("the data field length is not of expected size, could not parse amount fields.") +func convertLogDataToHexAmounts(rawData string, eventName string) (string, string, error) { + var abiToUse abi.ABI + switch { + case eventName == collectEvent: + abiToUse = uniswapLiqPositionsABI + default: + abiToUse = uniswapLiqPoolsABI } - amountHex := "0x" + data[AmountOffset:AmountOffset+AmountToken0Size] - amountToken0Hex := "0x" + data[AmountOffset+AmountToken0Size:AmountOffset+AmountToken0Size+AmountToken0Size] - amountToken1Hex := "0x" + data[AmountOffset+AmountToken0Size+AmountToken1Size:] + hexString := strings.TrimPrefix(rawData, "0x") + data, err := hex.DecodeString(hexString) - return amountHex, amountToken0Hex, amountToken1Hex, nil -} - -func splitMintDatatoHexFields(data string) (string, string, string, error) { - const ( - AmountOffset = 2 - AmountOwnerAddress = 64 - AmountSize = 64 - AmountToken0Size = 64 - AmountToken1Size = 64 - RequiredDataFieldLength = 258 - AmountSkip = AmountOffset + AmountOwnerAddress - ) - - if len(data) != RequiredDataFieldLength { - return "", "", "", fmt.Errorf("the data field length is not of expected size, could not parse amount fields.") + var args = make(map[string]interface{}) + err = abiToUse.UnpackIntoMap(args, eventName, []byte(data)) + if err != nil { + return "", "", err } - amountHex := "0x" + data[AmountSkip:AmountSkip+AmountSize] - amountToken0Hex := "0x" + data[AmountSkip+AmountSize:AmountSkip+AmountSize+AmountToken0Size] - amountToken1Hex := "0x" + data[AmountSkip+AmountSize+AmountToken0Size:] - return amountHex, amountToken0Hex, amountToken1Hex, nil + resAmount0Hex := "0x" + args["amount0"].(*big.Int).Text(16) + resAmount1Hex := "0x" + args["amount1"].(*big.Int).Text(16) + return resAmount0Hex, resAmount1Hex, nil } diff --git a/publisher/internal/analytics/ethereum/ethereum_test.go b/publisher/internal/analytics/ethereum/ethereum_test.go index 125e238..ffe6be3 100644 --- a/publisher/internal/analytics/ethereum/ethereum_test.go +++ b/publisher/internal/analytics/ethereum/ethereum_test.go @@ -168,7 +168,7 @@ func Test_hasTopics(t *testing.T) { } } -func Test_isOrderCorrect(t *testing.T) { +func Test_isToken0StableAndToken1Native(t *testing.T) { tests := []struct { name string input Position @@ -185,7 +185,7 @@ func Test_isOrderCorrect(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - res := test.input.isOrderCorrect() + res := test.input.isToken0StableAndToken1Native() if res != test.trueRes { t.Errorf("isOrderCorrect(%v) = (%v); expected (%v)", test.input, res, test.trueRes) } diff --git a/publisher/internal/analytics/ethereum/operations.go b/publisher/internal/analytics/ethereum/operations.go index 29cb1c5..18f12c2 100644 --- a/publisher/internal/analytics/ethereum/operations.go +++ b/publisher/internal/analytics/ethereum/operations.go @@ -49,8 +49,8 @@ type Removal struct { OperationBase Send analytics.Sender //TODO: Fees earned and collected - //TokenEarned0 TokenTransaction - //TokenEarned1 TokenTransaction + Token0Earned TokenTransaction + Token1Earned TokenTransaction } type Addition struct { @@ -93,9 +93,32 @@ func (add Addition) Save(ts time.Time) error { return add.db.SaveAddition(addition) } -func (rem *Removal) Process(burn WrappedEventLog) error { - burnLog := burn.Log - liqPool := burnLog.Address +func (rem Removal) getCorespondingRemoval(primaryLog WrappedEventLog) (EventLog, error) { + removalLogs, err := rem.cache.GetByTxHashAndLogType(primaryLog.Log.TransactionHash, burnEvent) + if err != nil { + return EventLog{}, err + } + var requiredLog EventLog + for _, renovalLog := range removalLogs { + if renovalLog.Address == primaryLog.Log.Address { // Match liquidity pool + requiredLog = renovalLog + break + } + } + if requiredLog.Address == "" { + return EventLog{}, fmt.Errorf("fetched burn event has no liquidity pool.") + } + return requiredLog, nil +} + +func (rem *Removal) Process(collect WrappedEventLog) error { + liqPool := collect.Log.Address + + burnLog, err := rem.getCorespondingRemoval(collect) + if err != nil { + return err + } + addr0, addr1, found := rem.db.GetPoolPairAddresses(liqPool) if !found { return fmt.Errorf("SKIP - liq. pool is unknown (removal). pool address: %s", liqPool) @@ -106,7 +129,7 @@ func (rem *Removal) Process(burn WrappedEventLog) error { return fmt.Errorf("SKIP - at least one token is unknown in liquidity removal. pool address: %s", liqPool) } - _, token0HexAmount, token1HexAmount, err := splitBurnDatatoHexStrings(burnLog.Data) + token0HexAmount, token1HexAmount, err := convertLogDataToHexAmounts(burnLog.Data, burnEvent) if err != nil { return err } @@ -127,10 +150,13 @@ func (rem *Removal) Process(burn WrappedEventLog) error { }, } rem.Position = *remPosition - - rem.OperationBase.includeTokenPrices(&rem.Position) // 6) Getting token prices - rem.Position.calculatePosition() // 7) Save Liquidity Entry and Liquidity Pool - + rem.Token0.Price = rem.fetchTokenPrice(rem.Token0.Address) + rem.Token1.Price = rem.fetchTokenPrice(rem.Token1.Address) + err = rem.calculateFeesEarned(collect.Log) + if err != nil { + return err + } + rem.Position.calculate() return nil } @@ -148,7 +174,7 @@ func (add *Addition) Process(mint WrappedEventLog) error { } add.Position = *addPosition - transferLogs, err := add.cache.GetByTxHashAndLogType(mintLog.TransactionHash, "TRANSFER") + transferLogs, err := add.cache.GetByTxHashAndLogType(mintLog.TransactionHash, transferEvent) if err != nil { return err } @@ -165,14 +191,15 @@ func (add *Addition) Process(mint WrappedEventLog) error { log.Println("error while adding new pool to database:", err.Error()) } - add.OperationBase.includeTokenPrices(&add.Position) // 6) Getting token prices - add.Position.calculatePosition() // 7) Save Liquidity Entry and Liquidity Pool + add.Token0.Price = add.fetchTokenPrice(add.Token0.Address) + add.Token1.Price = add.fetchTokenPrice(add.Token1.Address) + add.Position.calculate() // 7) Save Liquidity Entry and Liquidity Pool return nil } func (add Addition) savePool(addPos Position) error { - if addPos.isEitherTokenAmountZero() || !addPos.areTokensSet() { + if addPos.isEitherTokenAmountZero() || !addPos.areTokensSet() || (addPos.Token0.Address == addPos.Token1.Address) { return nil } // In this case both tokens were transferred to LP and their order is correct @@ -184,15 +211,19 @@ func (add Addition) savePool(addPos Position) error { } func (rem Removal) String() string { - format := "Removing %f of %s and %f of %s from %s between %f and %f" + format := "Removing %f of %s and %f of %s from %s. Earned %f of %s and %f of %s ($%f)" return fmt.Sprintf(format, rem.Token0.Amount, rem.Token0.Symbol, rem.Token1.Amount, rem.Token1.Symbol, rem.Address, - rem.LowerRatio, - rem.UpperRatio) + rem.Token0Earned.Amount, + rem.Token0.Symbol, + rem.Token1Earned.Amount, + rem.Token1.Symbol, + rem.Token0Earned.Amount*rem.Token0.Price+rem.Token1Earned.Amount*rem.Token1.Price, + ) } func (add Addition) String() string { @@ -223,7 +254,12 @@ func (rem Removal) Publish(send analytics.Sender, publishTo string, timestamp ti {Symbol: rem.Token0.Symbol, Amount: rem.Token0.Amount, Price: rem.Token0.Price}, {Symbol: rem.Token1.Symbol, Amount: rem.Token1.Amount, Price: rem.Token1.Price}, }, - TxHash: rem.TxHash, + Earned: [2]types.TokensEarnedMessage{ + {Symbol: rem.Token0.Symbol, Amount: rem.Token0Earned.Amount, TotalValueUSD: rem.Token0.Price * rem.Token0Earned.Amount}, + {Symbol: rem.Token1.Symbol, Amount: rem.Token1Earned.Amount, TotalValueUSD: rem.Token1.Price * rem.Token1Earned.Amount}, + }, + ValueEarnedUSD: rem.Token0.Price*rem.Token0Earned.Amount + rem.Token1.Price*rem.Token1Earned.Amount, + TxHash: rem.TxHash, } removalJson, err := json.Marshal(&removalMessage) @@ -263,47 +299,59 @@ func (add Addition) Publish(send analytics.Sender, publishTo string, timestamp t // Getting token that was transferred and calculating amount transferred. // Keeping track of tokens involved in current Liq. Add. event. func (add *Addition) handleLiquidityTransfer(mint EventLog, transfer EventLog) { - _, token0HexAmount, token1HexAmount, err := splitMintDatatoHexFields(mint.Data) + token0HexAmount, token1HexAmount, err := convertLogDataToHexAmounts(mint.Data, mintEvent) if err != nil { log.Println("Could not split mint event into Amount fields: ", err.Error()) + return } t, err := add.lookupToken(transfer.Address) if err != nil { log.Println("Failed fetching token information: ", err.Error()) + return } - if transfer.Data == token0HexAmount { + if "0x"+convertHexToBigInt(transfer.Data).Text(16) == token0HexAmount && !strings.EqualFold(transfer.Address, add.Token1.Token.Address) { add.Token0.Token = t add.Token0.Amount = convertTransferAmount(token0HexAmount, t.Decimals) } - if transfer.Data == token1HexAmount { + if "0x"+convertHexToBigInt(transfer.Data).Text(16) == token1HexAmount && !strings.EqualFold(transfer.Address, add.Token0.Token.Address) { add.Token1.Token = t add.Token1.Amount = convertTransferAmount(token1HexAmount, t.Decimals) } } +func (rem *Removal) calculateFeesEarned(collectLog EventLog) error { + token0HexAmount, token1HexAmount, err := convertLogDataToHexAmounts(collectLog.Data, collectEvent) // Token order original as in liquidity pool + if err != nil { + return err + } + + rem.Token0Earned, rem.Token1Earned = rem.Token0, rem.Token1 + rem.Token0Earned.Amount = convertTransferAmount(token0HexAmount, rem.Token0.Decimals) - rem.Token0.Amount + rem.Token1Earned.Amount = convertTransferAmount(token1HexAmount, rem.Token1.Decimals) - rem.Token1.Amount + + if !rem.Position.isToken0StableAndToken1Native() { + rem.Token0Earned, rem.Token1Earned = rem.Token1Earned, rem.Token0Earned + } + + return nil +} + // ---------------------------------------------------------------------- // --------------- OperationBase methods -func (ob OperationBase) includeTokenPrices(pos *Position) { +func (ob OperationBase) fetchTokenPrice(tokAddress string) float64 { // Place here to implement price cache? - if !strings.EqualFold(pos.Token0.Address, "") { - price, err := ob.lookupPrice(pos.Token0.Address) - if err != nil { - log.Println("failed to feetch Token0 price: ", err.Error()) - } - pos.Token0.Price = price.Value + if strings.EqualFold(tokAddress, "") { + return 0.0 } - - if !strings.EqualFold(pos.Token1.Address, "") { - price, err := ob.lookupPrice(pos.Token1.Address) - if err != nil { - log.Println("failed to feetch Token1 price: ", err.Error()) - } - pos.Token1.Price = price.Value + price, err := ob.lookupPrice(tokAddress) + if err != nil { + log.Println("failed to fetch Token0 price: ", err.Error()) } + return price.Value } func (op OperationBase) lookupToken(address string) (repository.Token, error) { @@ -317,12 +365,12 @@ func (op OperationBase) lookupPrice(address string) (repository.TokenPrice, erro // ---------------------------------------------------------------------- // --------------- Position methods -func (pos *Position) calculate() { +func (pos *Position) calculateRatios() { lowerRatio := convertTickToRatio(pos.LowerTick, pos.Token0.Decimals, pos.Token1.Decimals) upperRatio := convertTickToRatio(pos.UpperTick, pos.Token0.Decimals, pos.Token1.Decimals) - if isStableOrNativeInvolved(*pos) && pos.isOrderCorrect() { + if isStableOrNativeInvolved(*pos) && pos.isToken0StableAndToken1Native() { lowerRatio = 1 / lowerRatio upperRatio = 1 / upperRatio } @@ -333,12 +381,12 @@ func (pos *Position) calculate() { pos.LowerRatio, pos.UpperRatio = lowerRatio, upperRatio } -func (pos *Position) calculatePosition() { +func (pos *Position) calculate() { if !pos.areTokensSet() { return } - pos.calculate() // Decoding / expanding "Mint" event + pos.calculateRatios() pos.adjustOrder() if pos.Token0.Price > 0 && pos.Token1.Price > 0 { @@ -348,7 +396,7 @@ func (pos *Position) calculatePosition() { } func (pos *Position) adjustOrder() { - if isStableOrNativeInvolved(*pos) && pos.isOrderCorrect() { + if isStableOrNativeInvolved(*pos) && !pos.isToken0StableAndToken1Native() { pos.Token1, pos.Token0 = pos.Token0, pos.Token1 } } @@ -396,6 +444,10 @@ func (p Position) CanPublish() bool { log.Printf("SKIP - no tokens moved. Tx: %s\n\n", p.TxHash) return false } + if p.Token0.Price == 0.0 || p.Token1.Price == 0.0 { + log.Printf("SKIP - missing price (could not calculate current ratio). Tx: %s\n\n", p.TxHash) + return false + } return true } @@ -404,7 +456,7 @@ func (p Position) areTokensSet() bool { return (!strings.EqualFold(p.Token0.Address, "") && !strings.EqualFold(p.Token1.Address, "")) } -func (p Position) isOrderCorrect() bool { +func (p Position) isToken0StableAndToken1Native() bool { return (strings.EqualFold(p.Token1.Address, addressWETH) || strings.EqualFold(p.Token0.Address, addressUSDC) || strings.EqualFold(p.Token0.Address, addressUSDT)) } diff --git a/publisher/internal/analytics/ethereum/types.go b/publisher/internal/analytics/ethereum/types.go index a9a0865..8546084 100644 --- a/publisher/internal/analytics/ethereum/types.go +++ b/publisher/internal/analytics/ethereum/types.go @@ -70,3 +70,7 @@ func (el *EventLog) isMint() bool { func (el *EventLog) isBurn() bool { return strings.HasPrefix(el.Topics[0], burnSig) } + +func (el *EventLog) isCollect() bool { + return strings.HasPrefix(el.Topics[0], collectSig) +} diff --git a/publisher/internal/analytics/ethereum/utils.go b/publisher/internal/analytics/ethereum/utils.go index 65eb72d..b87f169 100644 --- a/publisher/internal/analytics/ethereum/utils.go +++ b/publisher/internal/analytics/ethereum/utils.go @@ -70,7 +70,7 @@ func (a *Analytics) newWrappedEventLog(eLog EventLog) WrappedEventLog { switch { case eLog.isTransfer(): wel.Instructions = EventInstruction{ - Name: "TRANSFER", + Name: transferEvent, Header: transferEventHeader, Signature: transferSig, Operation: nil, @@ -78,7 +78,7 @@ func (a *Analytics) newWrappedEventLog(eLog EventLog) WrappedEventLog { } case eLog.isMint(): wel.Instructions = EventInstruction{ - Name: "ADDITION", + Name: mintEvent, Header: mintEventHeader, Signature: mintSig, Operation: &Addition{OperationBase: initOpBase}, @@ -86,9 +86,15 @@ func (a *Analytics) newWrappedEventLog(eLog EventLog) WrappedEventLog { } case eLog.isBurn(): wel.Instructions = EventInstruction{ - Name: "REMOVAL", + Name: burnEvent, Header: burnEventHeader, Signature: burnSig, + } + case eLog.isCollect(): + wel.Instructions = EventInstruction{ + Name: collectEvent, + Header: collectEventHeader, + Signature: collectSig, Operation: &Removal{OperationBase: initOpBase}, PublishTo: "remove", } diff --git a/publisher/pkg/types/types.go b/publisher/pkg/types/types.go index e6bb87b..6f144c7 100644 --- a/publisher/pkg/types/types.go +++ b/publisher/pkg/types/types.go @@ -16,14 +16,16 @@ type AdditionMessage struct { } type RemovalMessage struct { - Timestamp time.Time `json:"timestamp"` - Address string `json:"address"` - LowerTokenRatio float64 `json:"lowerTokenRatio"` - CurrentTokenRatio float64 `json:"currentTokenRatio"` - UpperTokenRatio float64 `json:"upperTokenRatio"` - ValueRemovedUSD float64 `json:"totalValueUSD"` - Pair [2]TokenMessage `json:"pair"` - TxHash string `json:"txHash"` + Timestamp time.Time `json:"timestamp"` + Address string `json:"address"` + LowerTokenRatio float64 `json:"lowerTokenRatio"` + CurrentTokenRatio float64 `json:"currentTokenRatio"` + UpperTokenRatio float64 `json:"upperTokenRatio"` + ValueRemovedUSD float64 `json:"totalValueUSD"` + Pair [2]TokenMessage `json:"pair"` + Earned [2]TokensEarnedMessage `json:"earned"` + ValueEarnedUSD float64 `json:"totalEarnedUSD"` + TxHash string `json:"txHash"` } type TokenMessage struct { @@ -31,3 +33,9 @@ type TokenMessage struct { Amount float64 `json:"amount"` Price float64 `json:"priceUSD"` } + +type TokensEarnedMessage struct { + Symbol string `json:"symbol"` + Amount float64 `json:"amount"` + TotalValueUSD float64 `json:"totalValueUSD"` +}