From 2eaf8788f20fcc16ebbfa6bc1621d555ca39871e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Aug 2024 06:33:35 +0000 Subject: [PATCH 1/4] chain simulation script --- .vscode/launch.json | 15 +- Makefile | 3 + config.json | 2 +- domain/chainsimulate/chain_simulate.go | 146 ++++++++++++++++++ go.mod | 6 +- .../ordebook_filler_ingest_plugin.go | 3 +- .../plugins/orderbookfiller/osmosis_swap.go | 79 +--------- router/usecase/pools/export_test.go | 1 - router/usecase/pools/routable_result_pool.go | 58 +++---- router/usecase/quote_in_given_out.go | 39 +++++ router/usecase/route/route.go | 29 ++++ tests/chainsimulate/chain_simulate_api.go | 88 +++++++++++ tests/chainsimulate/main.go | 102 ++++++++++++ tests/chainsimulate/sqs_simulate_api.go | 1 + 14 files changed, 460 insertions(+), 112 deletions(-) create mode 100644 domain/chainsimulate/chain_simulate.go create mode 100644 tests/chainsimulate/chain_simulate_api.go create mode 100644 tests/chainsimulate/main.go create mode 100644 tests/chainsimulate/sqs_simulate_api.go diff --git a/.vscode/launch.json b/.vscode/launch.json index dc0d77a6c..0d2e7e0d7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -161,6 +161,19 @@ "console": "integratedTerminal", "justMyCode": true, "python": "${workspaceFolder}/tests/venv/bin/python3" - } + }, + { + "name": "Launch Simulate", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/tests/chainsimulate", + "env": { + "SQS_TOKEN_OUT": "1000uion", + "SQS_TOKEN_IN_DENOM": "uosmo", + "SQS_CHAIN_ADDRESS": "osmo1q8709l2656zjtg567xnrxjr6j35a2pvwhxxms2", + }, + "cwd": "${workspaceFolder}" + }, ] } diff --git a/Makefile b/Makefile index 20110fd84..113b1eaa5 100644 --- a/Makefile +++ b/Makefile @@ -210,6 +210,9 @@ e2e-install-requirements: e2e-update-requirements: pip freeze > tests/requirements.txt +e2e-run-chainsimulate: + SQS_TOKEN_OUT=1000uion SQS_TOKEN_IN_DENOM=uosmo SQS_CHAIN_ADDRESS=osmo1q8709l2656zjtg567xnrxjr6j35a2pvwhxxms2 go run tests/chainsimulate/*.go + # Set DATADOG_API_KEY in the environment datadog-agent-start: export DATADOG_API_KEY=@@REDACTED@@; \ diff --git a/config.json b/config.json index 74213c017..f470b180c 100644 --- a/config.json +++ b/config.json @@ -73,7 +73,7 @@ "worker-min-pool-liquidity-cap": 1 }, "passthrough":{ - "numia-url": "https://public-osmosis-api.numia.xyz", + "numia-url": "https://public-osmosis-api.numia.dev", "timeseries-url": "https://stage-proxy-data-api.osmosis-labs.workers.dev", "apr-fetch-interval-minutes": 5, "pool-fees-fetch-interval-minutes": 5 diff --git a/domain/chainsimulate/chain_simulate.go b/domain/chainsimulate/chain_simulate.go new file mode 100644 index 000000000..6f2220cfb --- /dev/null +++ b/domain/chainsimulate/chain_simulate.go @@ -0,0 +1,146 @@ +package chainsimulatedomain + +import ( + "context" + "encoding/json" + "io" + "log" + "net" + "net/http" + "strconv" + "time" + + "github.com/cosmos/cosmos-sdk/client/tx" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/cosmos/ibc-go/v7/testing/simapp" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + + sdk "github.com/cosmos/cosmos-sdk/types" + + txtypes "github.com/cosmos/cosmos-sdk/types/tx" +) + +type AccountInfo struct { + Sequence string `json:"sequence"` + AccountNumber string `json:"account_number"` +} + +type AccountResult struct { + Account AccountInfo `json:"account"` +} + +var client = &http.Client{ + Timeout: 10 * time.Second, // Adjusted timeout to 10 seconds + Transport: otelhttp.NewTransport(http.DefaultTransport), +} + +const ( + chainID = "osmosis-1" + gasAdjustment = 1.02 +) + +var ( + encodingConfig = simapp.MakeTestEncodingConfig() +) + +// SimulateMsgs simulates the execution of a transaction and returns the +// simulation response obtained by the query and the adjusted gas amount. +func SimulateMsgs(ctx context.Context, clientCtx gogogrpc.ClientConn, lcd, address string, msgs []sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { + accSeq, accNum := GetInitialSequence(ctx, lcd, address) + + txFactory := tx.Factory{} + txFactory = txFactory.WithTxConfig(encodingConfig.TxConfig) + txFactory = txFactory.WithAccountNumber(accNum) + txFactory = txFactory.WithSequence(accSeq) + txFactory = txFactory.WithChainID(chainID) + txFactory = txFactory.WithGasAdjustment(gasAdjustment) + + // Estimate transaction + gasResult, adjustedGasUsed, err := CalculateGas(ctx, clientCtx, txFactory, msgs...) + if err != nil { + return nil, adjustedGasUsed, err + } + + return gasResult, adjustedGasUsed, nil +} + +// CalculateGas simulates the execution of a transaction and returns the +// simulation response obtained by the query and the adjusted gas amount. +func CalculateGas( + ctx context.Context, + clientCtx gogogrpc.ClientConn, txf tx.Factory, msgs ...sdk.Msg, +) (*txtypes.SimulateResponse, uint64, error) { + txBytes, err := txf.BuildSimTx(msgs...) + if err != nil { + return nil, 0, err + } + + txSvcClient := txtypes.NewServiceClient(clientCtx) + simRes, err := txSvcClient.Simulate(ctx, &txtypes.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return nil, 0, err + } + + return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil +} + +// GetInitialSequence fetches the initial sequence and account number for a given address +// from the LCD endpoint. It returns the sequence and account number as uint64 values. +func GetInitialSequence(ctx context.Context, lcd string, address string) (uint64, uint64) { + resp, err := httpGet(ctx, lcd+"/cosmos/auth/v1beta1/accounts/"+address) + if err != nil { + log.Printf("Failed to get initial sequence: %v", err) + return 0, 0 + } + + var accountRes AccountResult + err = json.Unmarshal(resp, &accountRes) + if err != nil { + log.Printf("Failed to unmarshal account result: %v", err) + return 0, 0 + } + + seqint, err := strconv.ParseUint(accountRes.Account.Sequence, 10, 64) + if err != nil { + log.Printf("Failed to convert sequence to int: %v", err) + return 0, 0 + } + + accnum, err := strconv.ParseUint(accountRes.Account.AccountNumber, 10, 64) + if err != nil { + log.Printf("Failed to convert account number to int: %v", err) + return 0, 0 + } + + return seqint, accnum +} + +// httpGet performs an HTTP GET request to the specified URL and returns the response body. +func httpGet(ctx context.Context, url string) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + resp, err := client.Do(req) + if err != nil { + netErr, ok := err.(net.Error) + if ok && netErr.Timeout() { + log.Printf("Request to %s timed out, continuing...", url) + return nil, nil + } + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil +} diff --git a/go.mod b/go.mod index 477702568..4bb2d4014 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( cosmossdk.io/api v0.3.1 cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/errors v1.0.1 // indirect + cosmossdk.io/errors v1.0.1 cosmossdk.io/log v1.3.0 // indirect cosmossdk.io/tools/rosetta v0.2.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect @@ -90,7 +90,7 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.3 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/gogoproto v1.4.11 // indirect + github.com/cosmos/gogoproto v1.4.11 github.com/cosmos/iavl v1.1.2-0.20240405173644-e52f7630d3b7 // indirect github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 // indirect github.com/cosmos/ibc-apps/modules/async-icq/v7 v7.1.1 // indirect @@ -218,7 +218,7 @@ require ( go.etcd.io/bbolt v1.3.8 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 go.uber.org/multierr v1.11.0 // indirect diff --git a/ingest/usecase/plugins/orderbookfiller/ordebook_filler_ingest_plugin.go b/ingest/usecase/plugins/orderbookfiller/ordebook_filler_ingest_plugin.go index 31e087a18..1c078536d 100644 --- a/ingest/usecase/plugins/orderbookfiller/ordebook_filler_ingest_plugin.go +++ b/ingest/usecase/plugins/orderbookfiller/ordebook_filler_ingest_plugin.go @@ -8,6 +8,7 @@ import ( "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/sqs/domain" + chainsimulatedomain "github.com/osmosis-labs/sqs/domain/chainsimulate" "github.com/osmosis-labs/sqs/domain/keyring" "github.com/osmosis-labs/sqs/domain/mvc" orderbookplugindomain "github.com/osmosis-labs/sqs/domain/orderbook/plugin" @@ -344,7 +345,7 @@ func (o *orderbookFillerIngestPlugin) tryFill(ctx blockctx.BlockCtxI) error { // Simulate transaction messages sdkMsgs := txCtx.GetSDKMsgs() - _, adjustedGasAmount, err := o.simulateMsgs(ctx.AsGoCtx(), sdkMsgs) + _, adjustedGasAmount, err := chainsimulatedomain.SimulateMsgs(ctx.AsGoCtx(), o.passthroughGRPCClient.GetChainGRPCClient(), LCD, o.keyring.GetAddress().String(), sdkMsgs) if err != nil { return err } diff --git a/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go b/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go index 080335da5..b51437129 100644 --- a/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go +++ b/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go @@ -2,18 +2,15 @@ package orderbookfiller import ( "context" - "encoding/json" "fmt" "io" "log" "net" "net/http" "os" - "strconv" "time" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - gogogrpc "github.com/cosmos/gogoproto/grpc" "go.uber.org/zap" cometrpc "github.com/cometbft/cometbft/rpc/client/http" @@ -21,13 +18,13 @@ import ( tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/ibc-go/v7/testing/simapp" "github.com/osmosis-labs/osmosis/osmomath" poolmanagertypes "github.com/osmosis-labs/osmosis/v25/x/poolmanager/types" "github.com/osmosis-labs/sqs/domain" + chainsimulatedomain "github.com/osmosis-labs/sqs/domain/chainsimulate" orderbookplugindomain "github.com/osmosis-labs/sqs/domain/orderbook/plugin" blockctx "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbookfiller/context/block" msgctx "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbookfiller/context/msg" @@ -72,35 +69,6 @@ func init() { } } -func getInitialSequence(ctx context.Context, address string) (uint64, uint64) { - resp, err := httpGet(ctx, LCD+"/cosmos/auth/v1beta1/accounts/"+address) - if err != nil { - log.Printf("Failed to get initial sequence: %v", err) - return 0, 0 - } - - var accountRes AccountResult - err = json.Unmarshal(resp, &accountRes) - if err != nil { - log.Printf("Failed to unmarshal account result: %v", err) - return 0, 0 - } - - seqint, err := strconv.ParseUint(accountRes.Account.Sequence, 10, 64) - if err != nil { - log.Printf("Failed to convert sequence to int: %v", err) - return 0, 0 - } - - accnum, err := strconv.ParseUint(accountRes.Account.AccountNumber, 10, 64) - if err != nil { - log.Printf("Failed to convert account number to int: %v", err) - return 0, 0 - } - - return seqint, accnum -} - var client = &http.Client{ Timeout: 10 * time.Second, // Adjusted timeout to 10 seconds Transport: otelhttp.NewTransport(http.DefaultTransport), @@ -183,7 +151,7 @@ func (o *orderbookFillerIngestPlugin) executeTx(blockCtx blockctx.BlockCtxI) (re // First round: we gather all the signer infos. We use the "set empty // signature" hack to do that. - accSequence, accNumber := getInitialSequence(blockCtx.AsGoCtx(), o.keyring.GetAddress().String()) + accSequence, accNumber := chainsimulatedomain.GetInitialSequence(blockCtx.AsGoCtx(), LCD, o.keyring.GetAddress().String()) sigV2 := signing.SignatureV2{ PubKey: privKey.PubKey(), Data: &signing.SingleSignatureData{ @@ -268,7 +236,7 @@ func (o *orderbookFillerIngestPlugin) simulateSwapExactAmountIn(ctx blockctx.Blo } // Estimate transaction - gasResult, adjustedGasUsed, err := o.simulateMsgs(ctx.AsGoCtx(), []sdk.Msg{swapMsg}) + gasResult, adjustedGasUsed, err := chainsimulatedomain.SimulateMsgs(ctx.AsGoCtx(), o.passthroughGRPCClient.GetChainGRPCClient(), LCD, o.keyring.GetAddress().String(), []sdk.Msg{swapMsg}) if err != nil { return nil, err } @@ -309,47 +277,6 @@ func (o *orderbookFillerIngestPlugin) simulateSwapExactAmountIn(ctx blockctx.Blo return msgCtx, nil } -func (o *orderbookFillerIngestPlugin) simulateMsgs(ctx context.Context, msgs []sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { - accSeq, accNum := getInitialSequence(ctx, o.keyring.GetAddress().String()) - - txFactory := tx.Factory{} - txFactory = txFactory.WithTxConfig(encodingConfig.TxConfig) - txFactory = txFactory.WithAccountNumber(accNum) - txFactory = txFactory.WithSequence(accSeq) - txFactory = txFactory.WithChainID(chainID) - txFactory = txFactory.WithGasAdjustment(1.02) - - // Estimate transaction - gasResult, adjustedGasUsed, err := CalculateGas(ctx, o.passthroughGRPCClient.GetChainGRPCClient(), txFactory, msgs...) - if err != nil { - return nil, adjustedGasUsed, err - } - - return gasResult, adjustedGasUsed, nil -} - -// CalculateGas simulates the execution of a transaction and returns the -// simulation response obtained by the query and the adjusted gas amount. -func CalculateGas( - ctx context.Context, - clientCtx gogogrpc.ClientConn, txf tx.Factory, msgs ...sdk.Msg, -) (*txtypes.SimulateResponse, uint64, error) { - txBytes, err := txf.BuildSimTx(msgs...) - if err != nil { - return nil, 0, err - } - - txSvcClient := txtypes.NewServiceClient(clientCtx) - simRes, err := txSvcClient.Simulate(ctx, &txtypes.SimulateRequest{ - TxBytes: txBytes, - }) - if err != nil { - return nil, 0, err - } - - return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil -} - // broadcastTransaction broadcasts a transaction to the chain. // Returning the result and error. func broadcastTransaction(ctx context.Context, txBytes []byte, rpcEndpoint string) (*coretypes.ResultBroadcastTx, error) { diff --git a/router/usecase/pools/export_test.go b/router/usecase/pools/export_test.go index 17fbbfe55..abe940c5f 100644 --- a/router/usecase/pools/export_test.go +++ b/router/usecase/pools/export_test.go @@ -13,7 +13,6 @@ type ( RoutableCFMMPoolImpl = routableBalancerPoolImpl RoutableConcentratedPoolImpl = routableConcentratedPoolImpl RoutableTransmuterPoolImpl = routableTransmuterPoolImpl - RoutableResultPoolImpl = routableResultPoolImpl RoutableAlloyTransmuterPoolImpl = routableAlloyTransmuterPoolImpl RoutableOrderbookPoolImpl = routableOrderbookPoolImpl ) diff --git a/router/usecase/pools/routable_result_pool.go b/router/usecase/pools/routable_result_pool.go index a24d7ee61..e051f7077 100644 --- a/router/usecase/pools/routable_result_pool.go +++ b/router/usecase/pools/routable_result_pool.go @@ -18,13 +18,13 @@ import ( ) var ( - _ domain.RoutablePool = &routableResultPoolImpl{} - _ domain.RoutableResultPool = &routableResultPoolImpl{} + _ domain.RoutablePool = &RoutableResultPoolImpl{} + _ domain.RoutableResultPool = &RoutableResultPoolImpl{} ) -// routableResultPoolImpl is a generalized implementation that is returned to the client +// RoutableResultPoolImpl is a generalized implementation that is returned to the client // side in quotes. It contains all the relevant pool data needed for Osmosis frontend -type routableResultPoolImpl struct { +type RoutableResultPoolImpl struct { ID uint64 "json:\"id\"" Type poolmanagertypes.PoolType "json:\"type\"" Balances sdk.Coins "json:\"balances\"" @@ -36,23 +36,23 @@ type routableResultPoolImpl struct { } // GetCodeID implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetCodeID() uint64 { +func (r *RoutableResultPoolImpl) GetCodeID() uint64 { panic("unimplemented") } // SetInDenom implements domain.RoutablePool. -func (r *routableResultPoolImpl) SetInDenom(denom string) { +func (r *RoutableResultPoolImpl) SetInDenom(denom string) { r.TokenInDenom = denom } // SetOutDenom implements domain.RoutablePool. -func (r *routableResultPoolImpl) SetOutDenom(denom string) { +func (r *RoutableResultPoolImpl) SetOutDenom(denom string) { r.TokenOutDenom = denom } // NewRoutableResultPool returns the new routable result pool with the given parameters. func NewRoutableResultPool(ID uint64, poolType poolmanagertypes.PoolType, spreadFactor osmomath.Dec, tokenOutDenom string, takerFee osmomath.Dec, codeID uint64) domain.RoutablePool { - return &routableResultPoolImpl{ + return &RoutableResultPoolImpl{ ID: ID, Type: poolType, SpreadFactor: spreadFactor, @@ -64,7 +64,7 @@ func NewRoutableResultPool(ID uint64, poolType poolmanagertypes.PoolType, spread // NewExactAmountOutRoutableResultPool returns the new routable result pool with the given parameters. func NewExactAmountOutRoutableResultPool(ID uint64, poolType poolmanagertypes.PoolType, spreadFactor osmomath.Dec, tokenInDenom string, takerFee osmomath.Dec, codeID uint64) domain.RoutablePool { - return &routableResultPoolImpl{ + return &RoutableResultPoolImpl{ ID: ID, Type: poolType, SpreadFactor: spreadFactor, @@ -75,12 +75,12 @@ func NewExactAmountOutRoutableResultPool(ID uint64, poolType poolmanagertypes.Po } // GetId implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetId() uint64 { +func (r *RoutableResultPoolImpl) GetId() uint64 { return r.ID } // GetPoolDenoms implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetPoolDenoms() []string { +func (r *RoutableResultPoolImpl) GetPoolDenoms() []string { denoms := make([]string, len(r.Balances)) for i, balance := range r.Balances { denoms[i] = balance.Denom @@ -90,7 +90,7 @@ func (r *routableResultPoolImpl) GetPoolDenoms() []string { } // GetSQSPoolModel implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetSQSPoolModel() sqsdomain.SQSPool { +func (r *RoutableResultPoolImpl) GetSQSPoolModel() sqsdomain.SQSPool { return sqsdomain.SQSPool{ Balances: r.Balances, PoolDenoms: r.GetPoolDenoms(), @@ -99,88 +99,88 @@ func (r *routableResultPoolImpl) GetSQSPoolModel() sqsdomain.SQSPool { } // GetTickModel implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetTickModel() (*sqsdomain.TickModel, error) { +func (r *RoutableResultPoolImpl) GetTickModel() (*sqsdomain.TickModel, error) { return nil, errors.New("not implemented") } // GetPoolLiquidityCap implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetPoolLiquidityCap() math.Int { +func (r *RoutableResultPoolImpl) GetPoolLiquidityCap() math.Int { return osmomath.Int{} } // GetType implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetType() poolmanagertypes.PoolType { +func (r *RoutableResultPoolImpl) GetType() poolmanagertypes.PoolType { return r.Type } // GetUnderlyingPool implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetUnderlyingPool() poolmanagertypes.PoolI { +func (r *RoutableResultPoolImpl) GetUnderlyingPool() poolmanagertypes.PoolI { return nil } // Validate implements domain.RoutablePool. -func (*routableResultPoolImpl) Validate(minUOSMOTVL math.Int) error { +func (*RoutableResultPoolImpl) Validate(minUOSMOTVL math.Int) error { return nil } // CalculateTokenOutByTokenIn implements RoutablePool. -func (r *routableResultPoolImpl) CalculateTokenOutByTokenIn(ctx context.Context, tokenIn sdk.Coin) (sdk.Coin, error) { +func (r *RoutableResultPoolImpl) CalculateTokenOutByTokenIn(ctx context.Context, tokenIn sdk.Coin) (sdk.Coin, error) { return sdk.Coin{}, errors.New("not implemented") } // GetTokenOutDenom implements RoutablePool. -func (r *routableResultPoolImpl) GetTokenOutDenom() string { +func (r *RoutableResultPoolImpl) GetTokenOutDenom() string { return r.TokenOutDenom } // GetTokenInDenom implements RoutablePool. -func (r *routableResultPoolImpl) GetTokenInDenom() string { +func (r *RoutableResultPoolImpl) GetTokenInDenom() string { return r.TokenInDenom } // String implements domain.RoutablePool. -func (r *routableResultPoolImpl) String() string { +func (r *RoutableResultPoolImpl) String() string { return fmt.Sprintf("pool (%d), pool type (%d), pool denoms (%v)", r.GetId(), r.GetType(), r.GetPoolDenoms()) } // ChargeTakerFee implements domain.RoutablePool. // Charges the taker fee for the given token in and returns the token in after the fee has been charged. -func (r *routableResultPoolImpl) ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin) { +func (r *RoutableResultPoolImpl) ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin) { tokenInAfterTakerFee, _ := poolmanager.CalcTakerFeeExactIn(tokenIn, r.TakerFee) return tokenInAfterTakerFee } // GetTakerFee implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetTakerFee() math.LegacyDec { +func (r *RoutableResultPoolImpl) GetTakerFee() math.LegacyDec { return r.TakerFee } // GetBalances implements domain.RoutableResultPool. -func (r *routableResultPoolImpl) GetBalances() sdk.Coins { +func (r *RoutableResultPoolImpl) GetBalances() sdk.Coins { return r.Balances } // SetTokenInDenom implements domain.RoutablePool. -func (r *routableResultPoolImpl) SetTokenInDenom(tokenInDenom string) { +func (r *RoutableResultPoolImpl) SetTokenInDenom(tokenInDenom string) { r.TokenInDenom = tokenInDenom } // SetTokenOutDenom implements domain.RoutablePool. -func (r *routableResultPoolImpl) SetTokenOutDenom(tokenOutDenom string) { +func (r *RoutableResultPoolImpl) SetTokenOutDenom(tokenOutDenom string) { r.TokenOutDenom = tokenOutDenom } // GetSpreadFactor implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetSpreadFactor() math.LegacyDec { +func (r *RoutableResultPoolImpl) GetSpreadFactor() math.LegacyDec { return r.SpreadFactor } // CalcSpotPrice implements domain.RoutablePool. -func (r *routableResultPoolImpl) CalcSpotPrice(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error) { +func (r *RoutableResultPoolImpl) CalcSpotPrice(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error) { panic("not implemented") } // GetSQSType implements domain.RoutablePool. -func (r *routableResultPoolImpl) GetSQSType() domain.SQSPoolType { +func (r *RoutableResultPoolImpl) GetSQSType() domain.SQSPoolType { return domain.Result } diff --git a/router/usecase/quote_in_given_out.go b/router/usecase/quote_in_given_out.go index 4e1214534..c2ec2e698 100644 --- a/router/usecase/quote_in_given_out.go +++ b/router/usecase/quote_in_given_out.go @@ -2,6 +2,8 @@ package usecase import ( "context" + "encoding/json" + "fmt" "github.com/osmosis-labs/sqs/domain" "github.com/osmosis-labs/sqs/log" @@ -70,3 +72,40 @@ func (q *quoteExactAmountOut) PrepareResult(ctx context.Context, scalingFactor o return q.Route, q.EffectiveFee, nil } + +func (q *quoteExactAmountOut) GetRoute() []domain.SplitRoute { + return q.Route +} + +func (q *quoteExactAmountOut) UnmarshalJSON(data []byte) error { + type Alias quoteExactAmountOut + aux := &struct { + Route json.RawMessage `json:"route"` + *Alias + }{ + Alias: (*Alias)(q), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Parse Route + var routes []json.RawMessage + if err := json.Unmarshal(aux.Route, &routes); err != nil { + return fmt.Errorf("failed to parse Route: %w", err) + } + + q.Route = make([]domain.SplitRoute, len(routes)) + for i, routeData := range routes { + // Parse RouteImpl + var routeWithAmounts RouteWithOutAmount + if err := json.Unmarshal(routeData, &routeWithAmounts); err != nil { + return fmt.Errorf("failed to parse routeWithAmounts: %w", err) + } + + q.Route[i] = &routeWithAmounts + } + + return nil +} diff --git a/router/usecase/route/route.go b/router/usecase/route/route.go index 297ee90d3..ebc23787a 100644 --- a/router/usecase/route/route.go +++ b/router/usecase/route/route.go @@ -2,6 +2,7 @@ package route import ( "context" + "encoding/json" "fmt" "strings" @@ -184,3 +185,31 @@ func (r *RouteImpl) GetTokenInDenom() string { func (r *RouteImpl) ContainsGeneralizedCosmWasmPool() bool { return r.HasGeneralizedCosmWasmPool } + +func (r *RouteImpl) UnmarshalJSON(data []byte) error { + type Alias RouteImpl + aux := &struct { + Pools []json.RawMessage `json:"pools"` + *Alias + }{ + Alias: (*Alias)(r), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + r.Pools = make([]domain.RoutablePool, len(aux.Pools)) + for i, poolData := range aux.Pools { + var resultPool pools.RoutableResultPoolImpl + + err := json.Unmarshal(poolData, &resultPool) + if err != nil { + return err + } + + r.Pools[i] = &resultPool + } + + return nil +} diff --git a/tests/chainsimulate/chain_simulate_api.go b/tests/chainsimulate/chain_simulate_api.go new file mode 100644 index 000000000..0cd146dcf --- /dev/null +++ b/tests/chainsimulate/chain_simulate_api.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "fmt" + + tenderminapi "cosmossdk.io/api/cosmos/base/tendermint/v1beta1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/sqs/domain" + chainsimulatedomain "github.com/osmosis-labs/sqs/domain/chainsimulate" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/osmosis-labs/osmosis/osmomath" + poolmanagertypes "github.com/osmosis-labs/osmosis/v25/x/poolmanager/types" +) + +type ChainClient struct { + grpcClient grpc.ClientConnInterface + chainLCD string +} + +func (c *ChainClient) SimulateSwapExactAmountOut(ctx context.Context, address string, routes []poolmanagertypes.SwapAmountOutRoute, tokenOut sdk.Coin, slippageBound osmomath.Int) (osmomath.Int, error) { + swapMsg := &poolmanagertypes.MsgSwapExactAmountOut{ + Sender: address, + Routes: routes, + TokenOut: tokenOut, + TokenInMaxAmount: slippageBound, + } + + resp, _, err := chainsimulatedomain.SimulateMsgs(ctx, c.grpcClient, c.chainLCD, address, []sdk.Msg{swapMsg}) + if err != nil { + return osmomath.Int{}, err + } + + respMsgs := resp.Result.MsgResponses + if len(respMsgs) != 1 { + return osmomath.Int{}, fmt.Errorf("expected 1 message response, got %d", len(respMsgs)) + } + + msgSwapExactAmountInResponse := poolmanagertypes.MsgSwapExactAmountOutResponse{} + + if err := msgSwapExactAmountInResponse.Unmarshal(respMsgs[0].Value); err != nil { + return osmomath.Int{}, err + } + + return msgSwapExactAmountInResponse.TokenInAmount, nil +} + +func createGRPCGatewayClient(ctx context.Context, grpcGatewayEndpoint string, chainLCD string) (*ChainClient, error) { + grpcClient, err := grpc.NewClient(grpcGatewayEndpoint, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + ) + if err != nil { + return nil, err + } + + tendermintGRPCClient := tenderminapi.NewServiceClient(grpcClient) + if _, err := tendermintGRPCClient.GetLatestBlock(ctx, &tenderminapi.GetLatestBlockRequest{}); err != nil { + return nil, err + } + + return &ChainClient{ + grpcClient: grpcClient, + chainLCD: chainLCD, + }, nil +} + +func constructSwapAmountOutRoute(routes []domain.SplitRoute) ([]poolmanagertypes.SwapAmountOutRoute, error) { + if len(routes) != 1 { + return nil, fmt.Errorf("invalid split route length must be 1, was %d", len(routes)) + } + + chainExactOutRoutes := []poolmanagertypes.SwapAmountOutRoute{} + + for _, pool := range routes[0].GetPools() { + chainExactOutRoutes = append(chainExactOutRoutes, poolmanagertypes.SwapAmountOutRoute{ + PoolId: pool.GetId(), + TokenInDenom: pool.GetTokenInDenom(), + }) + } + + reverseSlice(chainExactOutRoutes) + + return chainExactOutRoutes, nil +} diff --git a/tests/chainsimulate/main.go b/tests/chainsimulate/main.go new file mode 100644 index 000000000..5b9371716 --- /dev/null +++ b/tests/chainsimulate/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/osmomath" + "github.com/osmosis-labs/sqs/sqsutil/sqshttp" + + routerusecase "github.com/osmosis-labs/sqs/router/usecase" + "github.com/osmosis-labs/sqs/router/usecase/routertesting" +) + +func main() { + fmt.Println("Starting chainsimulate") + + grpcGatewayEndpoint := os.Getenv("SQS_GRPC_GATEWAY_ENDPOINT") + if grpcGatewayEndpoint == "" { + grpcGatewayEndpoint = "127.0.0.1:9090" + } + + fmt.Println("grpcGatewayEndpoint", grpcGatewayEndpoint) + + chainLCD := os.Getenv("SQS_CHAIN_LCD") + if chainLCD == "" { + chainLCD = "http://127.0.0.1:1317" + } + + fmt.Println("chainLCD", chainLCD) + + chainAddress := os.Getenv("SQS_CHAIN_ADDRESS") + if chainAddress == "" { + chainAddress = "osmo1q8709l2656zjtg567xnrxjr6j35a2pvwhxxms2" + } + + fmt.Println("chainAddress", chainAddress) + + tokenOutStr := os.Getenv("SQS_TOKEN_OUT") + if tokenOutStr == "" { + tokenOutStr = "1000000uosmo" + } + + var tokenOut sdk.Coin + tokenOut, err := sdk.ParseCoinNormalized(tokenOutStr) + if err != nil { + panic(err) + } + + fmt.Println("tokenOut", tokenOut) + + tokenInDenom := os.Getenv("SQS_TOKEN_IN_DENOM") + if tokenInDenom == "" { + tokenInDenom = routertesting.USDC + } + + fmt.Println("tokenInDenom", tokenInDenom) + + ctx := context.Background() + + quote, err := sqshttp.Get[routerusecase.QuoteExactAmountOut](&http.Client{}, "http://localhost:9092", fmt.Sprintf("/router/quote?tokenOut=%s&tokenInDenom=%s&singleRoute=true", tokenOut, tokenInDenom)) + if err != nil { + panic(err) + } + + chainRoute, err := constructSwapAmountOutRoute(quote.GetRoute()) + if err != nil { + panic(err) + } + + fmt.Println("chainRoute", chainRoute) + + chainClient, err := createGRPCGatewayClient(ctx, grpcGatewayEndpoint, chainLCD) + if err != nil { + panic(err) + } + + fivePercentSlippageBound := quote.AmountIn.ToLegacyDec().Mul(osmomath.MustNewDecFromStr("1.1")).TruncateInt() + + actualIn, err := chainClient.SimulateSwapExactAmountOut(ctx, chainAddress, chainRoute, tokenOut, fivePercentSlippageBound) + if err != nil { + panic(err) + } + + fmt.Println("simulation successful") + + fmt.Printf("\n\n\nResults:\n") + fmt.Println("sqs simulate amount out", quote.AmountIn) + fmt.Printf("chain amount out: %s\n", actualIn) + + percentDiff := actualIn.Sub(quote.AmountIn).Abs().ToLegacyDec().Quo(quote.AmountIn.ToLegacyDec()).Abs() + + fmt.Println("percent diff", percentDiff) +} + +func reverseSlice[T any](s []T) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} diff --git a/tests/chainsimulate/sqs_simulate_api.go b/tests/chainsimulate/sqs_simulate_api.go new file mode 100644 index 000000000..85f0393b7 --- /dev/null +++ b/tests/chainsimulate/sqs_simulate_api.go @@ -0,0 +1 @@ +package main \ No newline at end of file From 09d2eb3c2603e6c3e43521b9bbec2d41641a68a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Aug 2024 07:14:00 +0000 Subject: [PATCH 2/4] updates --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 113b1eaa5..94d718596 100644 --- a/Makefile +++ b/Makefile @@ -211,7 +211,7 @@ e2e-update-requirements: pip freeze > tests/requirements.txt e2e-run-chainsimulate: - SQS_TOKEN_OUT=1000uion SQS_TOKEN_IN_DENOM=uosmo SQS_CHAIN_ADDRESS=osmo1q8709l2656zjtg567xnrxjr6j35a2pvwhxxms2 go run tests/chainsimulate/*.go + SQS_TOKEN_OUT=10000uion SQS_TOKEN_IN_DENOM=uosmo SQS_CHAIN_ADDRESS=osmo1q8709l2656zjtg567xnrxjr6j35a2pvwhxxms2 go run tests/chainsimulate/*.go # Set DATADOG_API_KEY in the environment datadog-agent-start: From 2cd816d394bdcdd53f5999e02393abf0eca84f47 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Aug 2024 07:27:50 +0000 Subject: [PATCH 3/4] remove unused --- .../plugins/orderbookfiller/osmosis_swap.go | 37 ------------------- tests/chainsimulate/sqs_simulate_api.go | 2 +- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go b/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go index b51437129..98cf518c1 100644 --- a/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go +++ b/ingest/usecase/plugins/orderbookfiller/osmosis_swap.go @@ -3,10 +3,6 @@ package orderbookfiller import ( "context" "fmt" - "io" - "log" - "net" - "net/http" "os" "time" @@ -28,7 +24,6 @@ import ( orderbookplugindomain "github.com/osmosis-labs/sqs/domain/orderbook/plugin" blockctx "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbookfiller/context/block" msgctx "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbookfiller/context/msg" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) const ( @@ -69,38 +64,6 @@ func init() { } } -var client = &http.Client{ - Timeout: 10 * time.Second, // Adjusted timeout to 10 seconds - Transport: otelhttp.NewTransport(http.DefaultTransport), -} - -func httpGet(ctx context.Context, url string) ([]byte, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, err - } - - resp, err := client.Do(req) - if err != nil { - netErr, ok := err.(net.Error) - if ok && netErr.Timeout() { - log.Printf("Request to %s timed out, continuing...", url) - return nil, nil - } - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return body, nil -} - // executeTx executes a transaction with the given tx context and block gas price. // It returns the response, the transaction body and an error if any. // It waits for 5 seconds before returning. diff --git a/tests/chainsimulate/sqs_simulate_api.go b/tests/chainsimulate/sqs_simulate_api.go index 85f0393b7..06ab7d0f9 100644 --- a/tests/chainsimulate/sqs_simulate_api.go +++ b/tests/chainsimulate/sqs_simulate_api.go @@ -1 +1 @@ -package main \ No newline at end of file +package main From de88ee6b738118aa6c686ea45c890264b138e2d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Aug 2024 07:34:34 +0000 Subject: [PATCH 4/4] updates --- tests/chainsimulate/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/chainsimulate/main.go b/tests/chainsimulate/main.go index 5b9371716..e61b70e4f 100644 --- a/tests/chainsimulate/main.go +++ b/tests/chainsimulate/main.go @@ -87,8 +87,8 @@ func main() { fmt.Println("simulation successful") fmt.Printf("\n\n\nResults:\n") - fmt.Println("sqs simulate amount out", quote.AmountIn) - fmt.Printf("chain amount out: %s\n", actualIn) + fmt.Println("sqs simulate amount in", quote.AmountIn) + fmt.Printf("chain amount in: %s\n", actualIn) percentDiff := actualIn.Sub(quote.AmountIn).Abs().ToLegacyDec().Quo(quote.AmountIn.ToLegacyDec()).Abs()