diff --git a/cmd/microservice-ethereum-track-amm-positions/main.go b/cmd/microservice-ethereum-track-amm-positions/main.go index e648c8694..2e71536f6 100644 --- a/cmd/microservice-ethereum-track-amm-positions/main.go +++ b/cmd/microservice-ethereum-track-amm-positions/main.go @@ -5,29 +5,52 @@ package main import ( - "github.com/fluidity-money/fluidity-app/common/ethereum" - "github.com/fluidity-money/fluidity-app/common/ethereum/amm" "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/queue" ammQueue "github.com/fluidity-money/fluidity-app/lib/queues/amm" ethQueue "github.com/fluidity-money/fluidity-app/lib/queues/ethereum" ethTypes "github.com/fluidity-money/fluidity-app/lib/types/ethereum" "github.com/fluidity-money/fluidity-app/lib/util" + ammTimescale "github.com/fluidity-money/fluidity-app/lib/databases/timescale/amm" + + "github.com/fluidity-money/fluidity-app/common/ethereum" + "github.com/fluidity-money/fluidity-app/common/ethereum/amm" + ethLongtail "github.com/fluidity-money/fluidity-app/common/ethereum/longtail" + + "github.com/ethereum/go-ethereum/ethclient" ) const ( // EnvAmmAddress to track events emitted by the AMM EnvAmmAddress = `FLU_ETHEREUM_AMM_ADDRESS` + + // EnvEthereumHttpUrl is the url to use to connect to the HTTP Geth endpoint for delta + // lookups + EnvEthereumHttpUrl = `FLU_ETHEREUM_HTTP_URL` ) func main() { var ( ammAddress_ = util.GetEnvOrFatal(EnvAmmAddress) + gethHttpUrl = util.PickEnvOrFatal(EnvEthereumHttpUrl) ) + ethClient, err := ethclient.Dial(gethHttpUrl) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Message = "Failed to connect to Geth Websocket!" + k.Payload = err + }) + } + + defer ethClient.Close() + ammAddress := ethTypes.AddressFromString(ammAddress_) ethQueue.Logs(func(log_ ethQueue.Log) { + log.Debugf("got a log: %v", log_) + if log_.Address != ammAddress { return } @@ -49,7 +72,7 @@ func main() { case amm.AmmAbi.Events["MintPosition"].ID: handleMint(log_) case amm.AmmAbi.Events["UpdatePositionLiquidity"].ID: - handleUpdate(log_) + handleUpdate(ethClient, ammAddress, log_) default: // swaps are handled in microservice-eth-user-actions and in the apps server log.App(func(k *log.Log) { @@ -75,7 +98,7 @@ func handleMint(log_ ethQueue.Log) { queue.SendMessage(ammQueue.TopicPositionMint, mint) } -func handleUpdate(log_ ethQueue.Log) { +func handleUpdate(client *ethclient.Client, ammAddress ethTypes.Address, log_ ethQueue.Log) { update, err := amm.DecodeUpdatePosition(log_) if err != nil { @@ -85,5 +108,17 @@ func handleUpdate(log_ ethQueue.Log) { }) } + // get the pool associated with this position + + positionId := update.Id + + pool := ammTimescale.GetPositionPool(positionId) + + // get the delta so we can use it later in the database + + delta := ethLongtail.GetPositionLiquidity(client, ammAddress, pool, positionId) + + update.Delta = delta + queue.SendMessage(ammQueue.TopicPositionUpdate, update) } diff --git a/common/ethereum/amm/abi.go b/common/ethereum/amm/abi.go index 144fa3f6e..a367b40fc 100644 --- a/common/ethereum/amm/abi.go +++ b/common/ethereum/amm/abi.go @@ -92,6 +92,10 @@ type ( Id misc.BigInt `json:"id"` Token0 misc.BigInt `json:"token0"` Token1 misc.BigInt `json:"token1"` + + // Delta is needed by the AMM positions microservice so we can calculate who + // receives rewards. So this is added by the microservice that unpacks this data. + Delta misc.BigInt `json:"delta"` } AmmEventCollectFees struct { Id misc.BigInt diff --git a/common/ethereum/longtail/abi.json b/common/ethereum/longtail/abi.json new file mode 100644 index 000000000..364114057 --- /dev/null +++ b/common/ethereum/longtail/abi.json @@ -0,0 +1,26 @@ +[ + { + "type": "function", + "name": "positionLiquidity8D11C045", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "address" + }, + { + "name": "id", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "nonpayable" + } +] diff --git a/common/ethereum/longtail/init.go b/common/ethereum/longtail/init.go new file mode 100644 index 000000000..49d058ab0 --- /dev/null +++ b/common/ethereum/longtail/init.go @@ -0,0 +1,21 @@ +// Copyright 2022 Fluidity Money. All rights reserved. Use of this +// source code is governed by a GPL-style license that can be found in the +// LICENSE.md file. + +package longtail + +import ( + "bytes" + + ethAbi "github.com/ethereum/go-ethereum/accounts/abi" +) + +func init() { + longtailReader := bytes.NewBuffer(longtailBytes) + + var err error + + if longtailAbi, err = ethAbi.JSON(longtailReader); err != nil { + panic(err) + } +} diff --git a/common/ethereum/longtail/longtail.go b/common/ethereum/longtail/longtail.go new file mode 100644 index 000000000..945ad3d78 --- /dev/null +++ b/common/ethereum/longtail/longtail.go @@ -0,0 +1,79 @@ +// Copyright 2022 Fluidity Money. All rights reserved. Use of this +// source code is governed by a GPL-style license that can be found in the +// LICENSE.md file. + +package longtail + +import ( + _ "embed" + "math/big" + + "github.com/fluidity-money/fluidity-app/common/ethereum" + "github.com/fluidity-money/fluidity-app/lib/log" + "github.com/fluidity-money/fluidity-app/lib/types/misc" + libEth "github.com/fluidity-money/fluidity-app/lib/types/ethereum" + + ethAbi "github.com/ethereum/go-ethereum/accounts/abi" + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +// Context for logging +const Context = "LONGTAIL" + +//go:embed abi.json +var longtailBytes []byte + +var longtailAbi ethAbi.ABI + +// GetPositionLiquidity for a pool and position ID given +func GetPositionLiquidity(client *ethclient.Client, amm_, pool_ libEth.Address, id misc.BigInt) misc.BigInt { + var ( + amm = ethCommon.HexToAddress(amm_.String()) + pool = ethCommon.HexToAddress(pool_.String()) + ) + log.Debug(func(k *log.Log) { + k.Context = Context + + k.Format( + "Using the Longtail AMM pool %v to get a position's liquidity", + pool, + ) + + k.Payload = id + }) + + resp, err := ethereum.StaticCall( + client, + amm, + longtailAbi, + "positionLiquidity8D11C045", + pool, + id, + ) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Context = Context + + k.Format( + "Failed to do a static call to positionLiquidity8D11C045! Pool address %v, id %v", + pool, + id, + ) + + k.Payload = err + }) + } + + liquidity, ok := resp[0].(*big.Int) + + if !ok { + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Format("Incorrect position liquidity type %T", resp[0]) + }) + } + + return misc.NewBigIntFromInt(*liquidity) +} diff --git a/lib/databases/timescale/amm/amm.go b/lib/databases/timescale/amm/amm.go index 9233ef67c..36ce4dc15 100644 --- a/lib/databases/timescale/amm/amm.go +++ b/lib/databases/timescale/amm/amm.go @@ -5,15 +5,17 @@ import ( "fmt" "math/big" - "github.com/fluidity-money/fluidity-app/common/ethereum/amm" "github.com/fluidity-money/fluidity-app/lib/log" "github.com/fluidity-money/fluidity-app/lib/timescale" "github.com/fluidity-money/fluidity-app/lib/types/applications" - "github.com/fluidity-money/fluidity-app/lib/types/ethereum" "github.com/fluidity-money/fluidity-app/lib/types/misc" "github.com/fluidity-money/fluidity-app/lib/types/network" token_details "github.com/fluidity-money/fluidity-app/lib/types/token-details" "github.com/fluidity-money/fluidity-app/lib/types/worker" + "github.com/fluidity-money/fluidity-app/lib/types/ethereum" + + + "github.com/fluidity-money/fluidity-app/common/ethereum/amm" ) const ( @@ -78,6 +80,29 @@ func InsertAmmPosition(mint amm.AmmEventPositionMint) { } } +func GetPositionPool(id misc.BigInt) (pool ethereum.Address) { + timescaleClient := timescale.Client() + + statementText := fmt.Sprintf( + `SELECT pool_address FROM %v WHERE position_id = $1`, + TableAmmPositions, + ) + + row := timescaleClient.QueryRow(statementText, id) + + err := row.Scan(&pool) + + if err != nil { + log.Fatal(func(k *log.Log) { + k.Context = Context + k.Message = "Failed to update a position's liquidity!" + k.Payload = err + }) + } + + return pool +} + func UpdateAmmPosition(update amm.AmmEventPositionUpdate) { timescaleClient := timescale.Client()