From 586e06bf9fd35a5470646eb3c3e20f94b5f6fe92 Mon Sep 17 00:00:00 2001 From: nadim-az <160955402+nadim-az@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:24:27 -0800 Subject: [PATCH] feat: add profit cli cmd (#94) * feat: add profit cli cmd * update example --- README.md | 6 ++ cmd/solvercli/cmd/profit.go | 107 ++++++++++++++++++++++++++++++++++ db/gen/db/querier.go | 1 + db/gen/db/transactions.sql.go | 42 +++++++++++++ db/queries/transactions.sql | 3 + 5 files changed, 159 insertions(+) create mode 100644 cmd/solvercli/cmd/profit.go diff --git a/README.md b/README.md index 860f1c1..97cb733 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,12 @@ solver rebalances solver settlements ``` +**profit**: Calculate solver total profit + +```shell +solver profit +``` + ### Main Project Modules - transfer monitor: monitors for user transfer intent events and creates pending order fills in the solver database diff --git a/cmd/solvercli/cmd/profit.go b/cmd/solvercli/cmd/profit.go new file mode 100644 index 0000000..6240780 --- /dev/null +++ b/cmd/solvercli/cmd/profit.go @@ -0,0 +1,107 @@ +package cmd + +import ( + "fmt" + dbtypes "github.com/skip-mev/go-fast-solver/db" + "math/big" + + "github.com/skip-mev/go-fast-solver/db/connect" + "github.com/skip-mev/go-fast-solver/db/gen/db" + "github.com/skip-mev/go-fast-solver/shared/lmt" + "github.com/spf13/cobra" + "go.uber.org/zap" + "golang.org/x/net/context" +) + +var profitCmd = &cobra.Command{ + Use: "profit", + Short: "Calculate total solver profit from settlements minus transaction costs", + Long: `Calculate the total profit made by the solver by summing all settlement profits +and subtracting transaction costs. This includes costs from fill transactions, +settlement transactions, and rebalancing transactions.`, + Example: `solver profit`, + Run: calculateProfit, +} + +func calculateProfit(cmd *cobra.Command, args []string) { + ctx := context.Background() + lmt.ConfigureLogger() + ctx = lmt.LoggerContext(ctx) + + dbPath, err := cmd.Flags().GetString("sqlite-db-path") + if err != nil { + lmt.Logger(ctx).Error("Failed to get sqlite-db-path flag", zap.Error(err)) + return + } + + migrationsPath, err := cmd.Flags().GetString("migrations-path") + if err != nil { + lmt.Logger(ctx).Error("Failed to get migrations-path flag", zap.Error(err)) + return + } + + dbConn, err := connect.ConnectAndMigrate(ctx, dbPath, migrationsPath) + if err != nil { + lmt.Logger(ctx).Error("Failed to connect to database", zap.Error(err)) + return + } + defer dbConn.Close() + + queries := db.New(dbConn) + + settlements, err := queries.GetAllOrderSettlementsWithSettlementStatus(ctx, dbtypes.SettlementStatusComplete) + if err != nil { + lmt.Logger(ctx).Error("Failed to get completed settlements", zap.Error(err)) + return + } + + totalProfit := big.NewInt(0) + for _, settlement := range settlements { + profit, ok := new(big.Int).SetString(settlement.Profit, 10) + if !ok { + lmt.Logger(ctx).Error("Failed to parse settlement profit", + zap.String("profit", settlement.Profit), + zap.String("orderID", settlement.OrderID)) + continue + } + totalProfit = totalProfit.Add(totalProfit, profit) + } + + submittedTxs, err := queries.GetAllSubmittedTxs(ctx) + if err != nil { + lmt.Logger(ctx).Error("Failed to get submitted transactions", zap.Error(err)) + return + } + + totalTxCosts := big.NewInt(0) + for _, tx := range submittedTxs { + if tx.TxCostUusdc.Valid { + cost, ok := new(big.Int).SetString(tx.TxCostUusdc.String, 10) + if !ok { + lmt.Logger(ctx).Error("Failed to parse transaction cost", + zap.String("txHash", tx.TxHash), + zap.String("cost", tx.TxCostUusdc.String)) + continue + } + totalTxCosts = totalTxCosts.Add(totalTxCosts, cost) + } + } + + netProfit := new(big.Int).Sub(totalProfit, totalTxCosts) + usdcMultiplier := new(big.Float).SetInt64(1000000) + netProfitUsdc := new(big.Float).Quo( + new(big.Float).SetInt(netProfit), + usdcMultiplier, + ) + + fmt.Printf("\nSolver Profit Summary:\n") + fmt.Printf("Total Settlement Profit: %s USDC\n", + new(big.Float).Quo(new(big.Float).SetInt(totalProfit), usdcMultiplier)) + fmt.Printf("Total Transaction Costs: %s USDC\n", + new(big.Float).Quo(new(big.Float).SetInt(totalTxCosts), usdcMultiplier)) + fmt.Printf("Net Profit: %s USDC\n", netProfitUsdc.Text('f', 6)) +} + +func init() { + rootCmd.AddCommand(profitCmd) +} diff --git a/db/gen/db/querier.go b/db/gen/db/querier.go index c8c5dab..6bc00e7 100644 --- a/db/gen/db/querier.go +++ b/db/gen/db/querier.go @@ -14,6 +14,7 @@ type Querier interface { GetAllOrderSettlementsWithSettlementStatus(ctx context.Context, settlementStatus string) ([]OrderSettlement, error) GetAllOrdersWithOrderStatus(ctx context.Context, orderStatus string) ([]Order, error) GetAllPendingRebalanceTransfers(ctx context.Context) ([]GetAllPendingRebalanceTransfersRow, error) + GetAllSubmittedTxs(ctx context.Context) ([]SubmittedTx, error) GetHyperlaneTransferByMessageSentTx(ctx context.Context, arg GetHyperlaneTransferByMessageSentTxParams) (HyperlaneTransfer, error) GetOrderByOrderID(ctx context.Context, orderID string) (Order, error) GetOrderSettlement(ctx context.Context, arg GetOrderSettlementParams) (OrderSettlement, error) diff --git a/db/gen/db/transactions.sql.go b/db/gen/db/transactions.sql.go index cb87565..3f1e1f6 100644 --- a/db/gen/db/transactions.sql.go +++ b/db/gen/db/transactions.sql.go @@ -10,6 +10,48 @@ import ( "database/sql" ) +const getAllSubmittedTxs = `-- name: GetAllSubmittedTxs :many +SELECT id, created_at, updated_at, order_id, order_settlement_id, hyperlane_transfer_id, chain_id, tx_hash, raw_tx, tx_type, tx_status, tx_status_message, tx_cost_uusdc, rebalance_transfer_id FROM submitted_txs +` + +func (q *Queries) GetAllSubmittedTxs(ctx context.Context) ([]SubmittedTx, error) { + rows, err := q.db.QueryContext(ctx, getAllSubmittedTxs) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SubmittedTx + for rows.Next() { + var i SubmittedTx + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.OrderID, + &i.OrderSettlementID, + &i.HyperlaneTransferID, + &i.ChainID, + &i.TxHash, + &i.RawTx, + &i.TxType, + &i.TxStatus, + &i.TxStatusMessage, + &i.TxCostUusdc, + &i.RebalanceTransferID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getSubmittedTxsByHyperlaneTransferId = `-- name: GetSubmittedTxsByHyperlaneTransferId :many SELECT id, created_at, updated_at, order_id, order_settlement_id, hyperlane_transfer_id, chain_id, tx_hash, raw_tx, tx_type, tx_status, tx_status_message, tx_cost_uusdc, rebalance_transfer_id FROM submitted_txs WHERE hyperlane_transfer_id = ? ` diff --git a/db/queries/transactions.sql b/db/queries/transactions.sql index b946825..41951e3 100644 --- a/db/queries/transactions.sql +++ b/db/queries/transactions.sql @@ -17,3 +17,6 @@ WHERE tx_hash = ? AND chain_id = ? RETURNING *; -- name: GetSubmittedTxsByOrderStatusAndType :many SELECT submitted_txs.* FROM submitted_txs INNER JOIN orders on submitted_txs.order_id = orders.id WHERE orders.order_status = ? AND submitted_txs.tx_type = ?; + +-- name: GetAllSubmittedTxs :many +SELECT * FROM submitted_txs;