diff --git a/app/sidecar_query_server.go b/app/sidecar_query_server.go index b0b13d945..7b85c98fe 100644 --- a/app/sidecar_query_server.go +++ b/app/sidecar_query_server.go @@ -20,8 +20,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/osmosis-labs/osmosis/v27/app" - txfeestypes "github.com/osmosis-labs/osmosis/v27/x/txfees/types" + "github.com/osmosis-labs/osmosis/v26/app" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" "github.com/osmosis-labs/sqs/domain/cosmos/auth/types" ingestrpcdelivry "github.com/osmosis-labs/sqs/ingest/delivery/grpc" ingestusecase "github.com/osmosis-labs/sqs/ingest/usecase" @@ -217,10 +217,11 @@ func NewSideCarQueryServer(appCodec codec.Codec, config domain.Config, logger lo } grpcClient := passthroughGRPCClient.GetChainGRPCClient() - gasCalculator := tx.NewMsgSimulator(grpcClient, tx.CalculateGas, routerRepository) + gasCalculator := tx.NewGasCalculator(grpcClient, tx.CalculateGas) quoteSimulator := quotesimulator.NewQuoteSimulator( gasCalculator, app.GetEncodingConfig(), + txfeestypes.NewQueryClient(grpcClient), types.NewQueryClient(grpcClient), config.ChainID, ) diff --git a/docs/docs.go b/docs/docs.go index 8be2da856..b0c539691 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -311,12 +311,6 @@ const docTemplate = `{ "description": "Slippage tolerance multiplier for the simulation. If simulatorAddress is provided, this must be provided.", "name": "simulationSlippageTolerance", "in": "query" - }, - { - "type": "boolean", - "description": "Boolean flag indicating whether to append the base fee to the quote. False by default.", - "name": "appendBaseFee", - "in": "query" } ], "responses": { diff --git a/docs/swagger.json b/docs/swagger.json index fbf23dfb7..00a6aa56b 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -302,12 +302,6 @@ "description": "Slippage tolerance multiplier for the simulation. If simulatorAddress is provided, this must be provided.", "name": "simulationSlippageTolerance", "in": "query" - }, - { - "type": "boolean", - "description": "Boolean flag indicating whether to append the base fee to the quote. False by default.", - "name": "appendBaseFee", - "in": "query" } ], "responses": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 521c7f5f3..2c581e93a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -417,11 +417,6 @@ paths: in: query name: simulationSlippageTolerance type: string - - description: Boolean flag indicating whether to append the base fee to the - quote. False by default. - in: query - name: appendBaseFee - type: boolean produces: - application/json responses: diff --git a/domain/cosmos/tx/msg_simulator.go b/domain/cosmos/tx/msg_simulator.go index 0778fffd0..9a88df574 100644 --- a/domain/cosmos/tx/msg_simulator.go +++ b/domain/cosmos/tx/msg_simulator.go @@ -38,6 +38,17 @@ type MsgSimulator interface { chainID string, msgs []sdk.Msg, ) (*txtypes.SimulateResponse, uint64, error) + + // PriceMsgs simulates the execution of the given messages and returns the gas used and the fee coin, + // which is the fee amount in the base denomination. + PriceMsgs( + ctx context.Context, + txfeesClient txfeestypes.QueryClient, + encodingConfig cosmosclient.TxConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (uint64, sdk.Coin, error) } // NewGasCalculator creates a new GasCalculator instance. @@ -79,23 +90,13 @@ func (c *txGasCalulator) BuildTx( return nil, err } - _, gas, err := c.SimulateMsgs( - encodingConfig.TxConfig, - account, - chainID, - msg, - ) + gasAdjusted, feecoin, err := c.PriceMsgs(ctx, txfeesClient, encodingConfig.TxConfig, account, chainID, msg...) if err != nil { return nil, err } - txBuilder.SetGasLimit(gas) - feecoin, err := CalculateFeeCoin(ctx, txfeesClient, gas) - if err != nil { - return nil, err - } - - txBuilder.SetFeeAmount(sdk.NewCoins(feecoin)) + txBuilder.SetGasLimit(gasAdjusted) + txBuilder.SetFeeAmount(sdk.Coins{feecoin}) sigV2 := BuildSignatures(privKey.PubKey(), nil, account.Sequence) err = txBuilder.SetSignatures(sigV2) @@ -143,6 +144,26 @@ func (c *txGasCalulator) SimulateMsgs(encodingConfig cosmosclient.TxConfig, acco return gasResult, adjustedGasUsed, nil } +// PriceMsgs implements MsgSimulator. +func (c *txGasCalulator) PriceMsgs(ctx context.Context, txfeesClient txfeestypes.QueryClient, encodingConfig cosmosclient.TxConfig, account *authtypes.BaseAccount, chainID string, msg ...sdk.Msg) (uint64, sdk.Coin, error) { + _, gasAdjusted, err := c.SimulateMsgs( + encodingConfig, + account, + chainID, + msg, + ) + if err != nil { + return 0, sdk.Coin{}, err + } + + feeCoin, err := CalculateFeeCoin(ctx, txfeesClient, gasAdjusted) + if err != nil { + return 0, sdk.Coin{}, err + } + + return gasAdjusted, feeCoin, nil +} + // CalculateGas calculates the gas required for a transaction using the provided transaction factory and messages. func CalculateGas( clientCtx gogogrpc.ClientConn, diff --git a/domain/cosmos/tx/msg_simulator_test.go b/domain/cosmos/tx/msg_simulator_test.go index 20849e3ab..c18ac22af 100644 --- a/domain/cosmos/tx/msg_simulator_test.go +++ b/domain/cosmos/tx/msg_simulator_test.go @@ -7,11 +7,30 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/sqs/domain/cosmos/tx" "github.com/osmosis-labs/sqs/domain/mocks" "github.com/stretchr/testify/assert" ) +const ( + testChainID = "test-chain" + testKey = "6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159" + testDenom = "eth" + testBaseFee = "0.1" + testGasUsed = uint64(50) + testAmount = int64(5) +) + +var ( + testAccount = &authtypes.BaseAccount{ + Sequence: 13, + AccountNumber: 1, + } + testMsg = newMsg("sender", "contract", `{"payload": "hello contract"}`) + testTxJSON = []byte(`{"body":{"messages":[{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"sender","contract":"contract","msg":{"payload":"hello contract"},"funds":[]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A+9dbfKKCHgfmiV2XUWelqidYzZhHR+KtNMvcSzWjdPQ"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"13"}],"fee":{"amount":[{"denom":"eth","amount":"5"}],"gas_limit":"50","payer":"","granter":""},"tip":null},"signatures":["aRlC8F2MnDA50tNNTJUk7zPvH/xc5c3Av+yaGQEiU0l0AXJxUdzOUxWHiC74D9ltvbsk0HzWbb+2uetCjdQdfA=="]}`) +) + func TestSimulateMsgs(t *testing.T) { tests := []struct { name string @@ -25,39 +44,35 @@ func TestSimulateMsgs(t *testing.T) { }{ { name: "Successful simulation", - account: &authtypes.BaseAccount{AccountNumber: 1, Sequence: 1}, - chainID: "test-chain", - msgs: []sdk.Msg{newMsg("sender", "contract", `{}`)}, + account: testAccount, + chainID: testChainID, + msgs: []sdk.Msg{testMsg}, setupMocks: func(calculator mocks.GetCalculateGasMock) tx.CalculateGasFn { - return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, 50, nil) + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, testGasUsed, nil) }, expectedSimulateResponse: &txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, - expectedGas: 50, + expectedGas: testGasUsed, expectedError: nil, }, { name: "Simulation error", account: &authtypes.BaseAccount{AccountNumber: 2, Sequence: 2}, - chainID: "test-chain", + chainID: testChainID, msgs: []sdk.Msg{}, setupMocks: func(calculator mocks.GetCalculateGasMock) tx.CalculateGasFn { - return calculator(&txtypes.SimulateResponse{}, 3, assert.AnError) + return calculator(&txtypes.SimulateResponse{}, testGasUsed, assert.AnError) }, expectedSimulateResponse: nil, - expectedGas: 3, + expectedGas: testGasUsed, expectedError: assert.AnError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Setup the mock calculateGasFnMock := tt.setupMocks(mocks.DefaultGetCalculateGasMock) - - // Create the gas calculator gasCalculator := tx.NewGasCalculator(nil, calculateGasFnMock) - // Call the function result, gas, err := gasCalculator.SimulateMsgs( encodingConfig.TxConfig, tt.account, @@ -65,7 +80,6 @@ func TestSimulateMsgs(t *testing.T) { tt.msgs, ) - // Assert the results assert.Equal(t, tt.expectedSimulateResponse, result) assert.Equal(t, tt.expectedGas, gas) if tt.expectedError != nil { @@ -91,27 +105,23 @@ func TestBuildTx(t *testing.T) { { name: "Valid transaction", setupMocks: func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn { - keyring.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - txFeesClient.WithBaseDenom("eth", nil) - txFeesClient.WithGetEipBaseFee("0.1", nil) + keyring.WithGetKey(testKey) + txFeesClient.WithBaseDenom(testDenom, nil) + txFeesClient.WithGetEipBaseFee(testBaseFee, nil) - return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, 50, nil) - }, - account: &authtypes.BaseAccount{ - Sequence: 13, - AccountNumber: 1, + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, testGasUsed, nil) }, - chainID: "test-chain", - msgs: []sdk.Msg{newMsg("sender", "contract", `{"payload": "hello contract"}`)}, - expectedJSON: []byte(`{"body":{"messages":[{"@type":"/cosmwasm.wasm.v1.MsgExecuteContract","sender":"sender","contract":"contract","msg":{"payload":"hello contract"},"funds":[]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A+9dbfKKCHgfmiV2XUWelqidYzZhHR+KtNMvcSzWjdPQ"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"13"}],"fee":{"amount":[{"denom":"eth","amount":"5"}],"gas_limit":"50","payer":"","granter":""},"tip":null},"signatures":["aRlC8F2MnDA50tNNTJUk7zPvH/xc5c3Av+yaGQEiU0l0AXJxUdzOUxWHiC74D9ltvbsk0HzWbb+2uetCjdQdfA=="]}`), + account: testAccount, + chainID: testChainID, + msgs: []sdk.Msg{testMsg}, + expectedJSON: testTxJSON, expectedError: false, }, { name: "Error building transaction", setupMocks: func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn { - keyring.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - - return calculator(&txtypes.SimulateResponse{}, 50, assert.AnError) + keyring.WithGetKey(testKey) + return calculator(&txtypes.SimulateResponse{}, testGasUsed, assert.AnError) }, account: &authtypes.BaseAccount{ Sequence: 8, @@ -126,10 +136,7 @@ func TestBuildTx(t *testing.T) { txFeesClient := mocks.TxFeesQueryClient{} keyring := mocks.Keyring{} - // Setup the mock calculateGasFnMock := tc.setupMocks(mocks.DefaultGetCalculateGasMock, &txFeesClient, &keyring) - - // Create the gas calculator msgSimulator := tx.NewGasCalculator(nil, calculateGasFnMock) txBuilder, err := msgSimulator.BuildTx( @@ -151,10 +158,93 @@ func TestBuildTx(t *testing.T) { txJSONBytes, err := encodingConfig.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) assert.NoError(t, err) - - // Add more specific assertions here based on the expected output assert.Equal(t, string(tc.expectedJSON), string(txJSONBytes)) } }) } } + +func TestPriceMsgs(t *testing.T) { + testCases := []struct { + name string + setupMocks func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn + account *authtypes.BaseAccount + chainID string + msgs []sdk.Msg + expectedGas uint64 + expectedFeeCoin sdk.Coin + expectedError bool + }{ + { + name: "Valid transaction", + setupMocks: func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn { + keyring.WithGetKey(testKey) + txFeesClient.WithBaseDenom(testDenom, nil) + txFeesClient.WithGetEipBaseFee(testBaseFee, nil) + + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, testGasUsed, nil) + }, + account: testAccount, + chainID: testChainID, + msgs: []sdk.Msg{testMsg}, + expectedGas: testGasUsed, + expectedFeeCoin: sdk.Coin{Denom: testDenom, Amount: osmomath.NewInt(testAmount)}, + expectedError: false, + }, + { + name: "Error building transaction", + setupMocks: func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn { + keyring.WithGetKey(testKey) + return calculator(&txtypes.SimulateResponse{}, testGasUsed, assert.AnError) + }, + account: &authtypes.BaseAccount{ + Sequence: 8, + AccountNumber: 51, + }, + expectedError: true, + }, + { + name: "Error calculating fee coin", + setupMocks: func(calculator mocks.GetCalculateGasMock, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) tx.CalculateGasFn { + keyring.WithGetKey(testKey) + txFeesClient.WithBaseDenom(testDenom, assert.AnError) + + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, testGasUsed, nil) + }, + account: testAccount, + chainID: testChainID, + msgs: []sdk.Msg{testMsg}, + expectedGas: testGasUsed, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + txFeesClient := mocks.TxFeesQueryClient{} + keyring := mocks.Keyring{} + + calculateGasFnMock := tc.setupMocks(mocks.DefaultGetCalculateGasMock, &txFeesClient, &keyring) + msgSimulator := tx.NewGasCalculator(nil, calculateGasFnMock) + + gasUsed, feeCoin, err := msgSimulator.PriceMsgs( + context.Background(), + &txFeesClient, + encodingConfig.TxConfig, + tc.account, + tc.chainID, + tc.msgs..., + ) + + if tc.expectedError { + assert.Error(t, err) + assert.Equal(t, uint64(0), gasUsed) + assert.Equal(t, sdk.Coin{}, feeCoin) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedGas, gasUsed) + assert.Equal(t, tc.expectedFeeCoin, feeCoin) + } + }) + } +} diff --git a/domain/mocks/msg_simulator_mock.go b/domain/mocks/msg_simulator_mock.go index f6b59c307..86c014d5f 100644 --- a/domain/mocks/msg_simulator_mock.go +++ b/domain/mocks/msg_simulator_mock.go @@ -30,6 +30,15 @@ type MsgSimulatorMock struct { chainID string, msgs []sdk.Msg, ) (*txtypes.SimulateResponse, uint64, error) + + PriceMsgsFn func( + ctx context.Context, + txfeesClient txfeestypes.QueryClient, + encodingConfig client.TxConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (uint64, sdk.Coin, error) } var _ sqstx.MsgSimulator = &MsgSimulatorMock{} @@ -59,3 +68,15 @@ func (m *MsgSimulatorMock) SimulateMsgs( } panic("SimulateMsgsFn not implemented") } + +// PriceMsgs implements tx.MsgSimulator. +func (m *MsgSimulatorMock) PriceMsgs(ctx context.Context, txfeesClient txfeestypes.QueryClient, encodingConfig client.TxConfig, account *authtypes.BaseAccount, chainID string, msg ...interface { + ProtoMessage() + Reset() + String() string +}) (uint64, sdk.Coin, error) { + if m.PriceMsgsFn != nil { + return m.PriceMsgsFn(ctx, txfeesClient, encodingConfig, account, chainID, msg...) + } + panic("PriceMsgsFn not implemented") +} diff --git a/domain/mocks/quote_mock.go b/domain/mocks/quote_mock.go index f60600198..4c682d271 100644 --- a/domain/mocks/quote_mock.go +++ b/domain/mocks/quote_mock.go @@ -63,7 +63,7 @@ func (m *MockQuote) PrepareResult(ctx context.Context, scalingFactor math.Legacy } // SetQuotePriceInfo implements domain.Quote. -func (m *MockQuote) SetQuotePriceInfo(info *domain.TxFeeInfo) { +func (m *MockQuote) SetQuotePriceInfo(info *domain.QuotePriceInfo) { panic("unimplemented") } diff --git a/domain/mocks/quote_simulator_mock.go b/domain/mocks/quote_simulator_mock.go index 5b6c2bed6..355e0499b 100644 --- a/domain/mocks/quote_simulator_mock.go +++ b/domain/mocks/quote_simulator_mock.go @@ -4,15 +4,16 @@ import ( "context" "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/sqs/domain" ) type QuoteSimulatorMock struct { - SimulateQuoteFn func(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) domain.TxFeeInfo + SimulateQuoteFn func(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) (uint64, types.Coin, error) } // SimulateQuote implements domain.QuoteSimulator. -func (q *QuoteSimulatorMock) SimulateQuote(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) domain.TxFeeInfo { +func (q *QuoteSimulatorMock) SimulateQuote(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) (uint64, types.Coin, error) { if q.SimulateQuoteFn != nil { return q.SimulateQuoteFn(ctx, quote, slippageToleranceMultiplier, simulatorAddress) } diff --git a/domain/passthrough/passthrough_grpc_client.go b/domain/passthrough/passthrough_grpc_client.go index 0725e9bc9..bc1affcdb 100644 --- a/domain/passthrough/passthrough_grpc_client.go +++ b/domain/passthrough/passthrough_grpc_client.go @@ -9,8 +9,8 @@ import ( distribution "github.com/cosmos/cosmos-sdk/x/distribution/types" staking "github.com/cosmos/cosmos-sdk/x/staking/types" math "github.com/osmosis-labs/osmosis/osmomath" - concentratedLiquidity "github.com/osmosis-labs/osmosis/v27/x/concentrated-liquidity/client/queryproto" - lockup "github.com/osmosis-labs/osmosis/v27/x/lockup/types" + concentratedLiquidity "github.com/osmosis-labs/osmosis/v26/x/concentrated-liquidity/client/queryproto" + lockup "github.com/osmosis-labs/osmosis/v26/x/lockup/types" polarisgrpc "github.com/osmosis-labs/sqs/delivery/grpc" "google.golang.org/grpc" ) diff --git a/domain/quote_simulator.go b/domain/quote_simulator.go index a79f4fba6..ce116cb34 100644 --- a/domain/quote_simulator.go +++ b/domain/quote_simulator.go @@ -3,6 +3,7 @@ package domain import ( "context" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/osmomath" ) @@ -13,5 +14,10 @@ type QuoteSimulator interface { // - Only direct (non-split) quotes are supported. // Retursn error if: // - Simulator address does not have enough funds to pay for the quote. - SimulateQuote(ctx context.Context, quote Quote, slippageToleranceMultiplier osmomath.Dec, simulatorAddress string) TxFeeInfo + SimulateQuote(ctx context.Context, quote Quote, slippageToleranceMultiplier osmomath.Dec, simulatorAddress string) (uint64, sdk.Coin, error) +} + +type QuotePriceInfo struct { + AdjustedGasUsed uint64 `json:"adjusted_gas_used"` + FeeCoin sdk.Coin `json:"fee_coin"` } diff --git a/domain/router.go b/domain/router.go index a4780372d..748718cf9 100644 --- a/domain/router.go +++ b/domain/router.go @@ -73,7 +73,7 @@ type Quote interface { PrepareResult(ctx context.Context, scalingFactor osmomath.Dec, logger log.Logger) ([]SplitRoute, osmomath.Dec, error) // SetQuotePriceInfo sets the quote price info. - SetQuotePriceInfo(info *TxFeeInfo) + SetQuotePriceInfo(info *QuotePriceInfo) String() string } diff --git a/quotesimulator/quote_simulator.go b/quotesimulator/quote_simulator.go index 416c605f7..c36c33087 100644 --- a/quotesimulator/quote_simulator.go +++ b/quotesimulator/quote_simulator.go @@ -4,37 +4,41 @@ import ( "context" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v27/app/params" + "github.com/osmosis-labs/osmosis/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" "github.com/osmosis-labs/sqs/domain" "github.com/osmosis-labs/sqs/domain/cosmos/auth/types" "github.com/osmosis-labs/sqs/domain/cosmos/tx" - poolmanagertypes "github.com/osmosis-labs/osmosis/v27/x/poolmanager/types" + poolmanagertypes "github.com/osmosis-labs/osmosis/v26/x/poolmanager/types" ) // quoteSimulator simulates a quote and returns the gas adjusted amount and the fee coin. type quoteSimulator struct { msgSimulator tx.MsgSimulator encodingConfig params.EncodingConfig + txFeesClient txfeestypes.QueryClient accountQueryClient types.QueryClient chainID string } -func NewQuoteSimulator(msgSimulator tx.MsgSimulator, encodingConfig params.EncodingConfig, accountQueryClient types.QueryClient, chainID string) *quoteSimulator { +func NewQuoteSimulator(msgSimulator tx.MsgSimulator, encodingConfig params.EncodingConfig, txFeesClient txfeestypes.QueryClient, accountQueryClient types.QueryClient, chainID string) *quoteSimulator { return "eSimulator{ msgSimulator: msgSimulator, encodingConfig: encodingConfig, + txFeesClient: txFeesClient, accountQueryClient: accountQueryClient, chainID: chainID, } } // SimulateQuote implements domain.QuoteSimulator -func (q *quoteSimulator) SimulateQuote(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier osmomath.Dec, simulatorAddress string) domain.TxFeeInfo { +func (q *quoteSimulator) SimulateQuote(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier osmomath.Dec, simulatorAddress string) (uint64, sdk.Coin, error) { route := quote.GetRoute() if len(route) != 1 { - return domain.TxFeeInfo{Err: fmt.Sprintf("route length must be 1, got %d", len(route))} + return 0, sdk.Coin{}, fmt.Errorf("route length must be 1, got %d", len(route)) } poolsInRoute := route[0].GetPools() @@ -63,10 +67,16 @@ func (q *quoteSimulator) SimulateQuote(ctx context.Context, quote domain.Quote, // Get the account for the simulator address baseAccount, err := q.accountQueryClient.GetAccount(ctx, simulatorAddress) if err != nil { - return domain.TxFeeInfo{Err: err.Error()} + return 0, sdk.Coin{}, err } - return q.msgSimulator.PriceMsgs(ctx, q.encodingConfig.TxConfig, baseAccount, q.chainID, swapMsg) + // Price the message + gasAdjusted, feeCoin, err := q.msgSimulator.PriceMsgs(ctx, q.txFeesClient, q.encodingConfig.TxConfig, baseAccount, q.chainID, swapMsg) + if err != nil { + return 0, sdk.Coin{}, err + } + + return gasAdjusted, feeCoin, nil } var _ domain.QuoteSimulator = "eSimulator{} diff --git a/quotesimulator/quote_simulator_test.go b/quotesimulator/quote_simulator_test.go index d93896016..88935bd67 100644 --- a/quotesimulator/quote_simulator_test.go +++ b/quotesimulator/quote_simulator_test.go @@ -11,7 +11,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v27/app/params" + "github.com/osmosis-labs/osmosis/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" "github.com/osmosis-labs/sqs/domain" "github.com/osmosis-labs/sqs/domain/mocks" ) @@ -31,7 +32,6 @@ func TestSimulateQuote(t *testing.T) { simulatorAddress string expectedGasAdjusted uint64 expectedFeeCoin sdk.Coin - expectedBaseFee osmomath.Dec expectError bool expectedErrorMsg string }{ @@ -41,7 +41,6 @@ func TestSimulateQuote(t *testing.T) { simulatorAddress: "osmo13t8prr8hu7hkuksnfrd25vpvvnrfxr223k59ph", expectedGasAdjusted: 100000, expectedFeeCoin: sdk.NewCoin("uosmo", osmomath.NewInt(10000)), - expectedBaseFee: osmomath.NewDecWithPrec(5, 1), expectError: false, }, } @@ -83,18 +82,16 @@ func TestSimulateQuote(t *testing.T) { msgSimulator := &mocks.MsgSimulatorMock{ PriceMsgsFn: func( ctx context.Context, + txfeesClient txfeestypes.QueryClient, encodingConfig client.TxConfig, account *authtypes.BaseAccount, chainID string, msg ...sdk.Msg, - ) domain.TxFeeInfo { - return domain.TxFeeInfo{ - AdjustedGasUsed: tt.expectedGasAdjusted, - FeeCoin: tt.expectedFeeCoin, - BaseFee: osmomath.NewDecWithPrec(5, 1), - } + ) (uint64, sdk.Coin, error) { + return tt.expectedGasAdjusted, tt.expectedFeeCoin, nil }, } + txFeesClient := &mocks.TxFeesQueryClient{} accountQueryClient := &mocks.AuthQueryClientMock{ GetAccountFunc: func(ctx context.Context, address string) (*authtypes.BaseAccount, error) { return &authtypes.BaseAccount{ @@ -107,12 +104,13 @@ func TestSimulateQuote(t *testing.T) { simulator := NewQuoteSimulator( msgSimulator, params.EncodingConfig{}, + txFeesClient, accountQueryClient, "osmosis-1", ) // System under test - priceInfo := simulator.SimulateQuote( + gasAdjusted, feeCoin, err := simulator.SimulateQuote( context.Background(), mockQuote, tt.slippageToleranceMultiplier, @@ -121,13 +119,12 @@ func TestSimulateQuote(t *testing.T) { // Assert results if tt.expectError { - assert.NotEmpty(t, priceInfo.Err) - assert.Contains(t, priceInfo.Err, tt.expectedErrorMsg) + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErrorMsg) } else { - assert.Empty(t, priceInfo.Err) - assert.Equal(t, tt.expectedGasAdjusted, priceInfo.AdjustedGasUsed) - assert.Equal(t, tt.expectedFeeCoin, priceInfo.FeeCoin) - assert.Equal(t, tt.expectedBaseFee, priceInfo.BaseFee) + assert.NoError(t, err) + assert.Equal(t, tt.expectedGasAdjusted, gasAdjusted) + assert.Equal(t, tt.expectedFeeCoin, feeCoin) } }) } diff --git a/router/delivery/http/router_handler.go b/router/delivery/http/router_handler.go index 9ef2dbc33..702936b28 100644 --- a/router/delivery/http/router_handler.go +++ b/router/delivery/http/router_handler.go @@ -73,7 +73,6 @@ func NewRouterHandler(e *echo.Echo, us mvc.RouterUsecase, tu mvc.TokensUsecase, // @Param applyExponents query bool false "Boolean flag indicating whether to apply exponents to the spot price. False by default." // @Param simulatorAddress query string false "Address of the simulator to simulate the quote. If provided, the quote will be simulated." // @Param simulationSlippageTolerance query string false "Slippage tolerance multiplier for the simulation. If simulatorAddress is provided, this must be provided." -// @Param appendBaseFee query bool false "Boolean flag indicating whether to append the base fee to the quote. False by default." // @Success 200 {object} domain.Quote "The computed best route quote" // @Router /router/quote [get] func (a *RouterHandler) GetOptimalQuote(c echo.Context) (err error) { @@ -160,15 +159,15 @@ func (a *RouterHandler) GetOptimalQuote(c echo.Context) (err error) { // Only "out given in" swap method is supported for simulation. Thus, we also check for tokenOutDenom being set. simulatorAddress := req.SimulatorAddress if req.SingleRoute && simulatorAddress != "" && req.SwapMethod() == domain.TokenSwapMethodExactIn { - priceInfo := a.QuoteSimulator.SimulateQuote(ctx, quote, req.SlippageToleranceMultiplier, simulatorAddress) + gasUsed, feeCoin, err := a.QuoteSimulator.SimulateQuote(ctx, quote, req.SlippageToleranceMultiplier, simulatorAddress) + if err != nil { + return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) + } // Set the quote price info. - quote.SetQuotePriceInfo(&priceInfo) - } - - if req.AppendBaseFee { - quote.SetQuotePriceInfo(&domain.TxFeeInfo{ - BaseFee: a.RUsecase.GetBaseFee().CurrentFee, + quote.SetQuotePriceInfo(&domain.QuotePriceInfo{ + AdjustedGasUsed: gasUsed, + FeeCoin: feeCoin, }) } diff --git a/router/delivery/http/router_handler_test.go b/router/delivery/http/router_handler_test.go index 23d2784ca..0d23d1d3c 100644 --- a/router/delivery/http/router_handler_test.go +++ b/router/delivery/http/router_handler_test.go @@ -92,47 +92,14 @@ func (s *RouterHandlerSuite) TestGetOptimalQuote() { }, }, QuoteSimulator: &mocks.QuoteSimulatorMock{ - SimulateQuoteFn: func(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) domain.TxFeeInfo { - return domain.TxFeeInfo{ - AdjustedGasUsed: 1_000_000, - FeeCoin: sdk.NewCoin("uosmo", math.NewInt(1000)), - BaseFee: osmomath.NewDecWithPrec(5, 1), - } + SimulateQuoteFn: func(ctx context.Context, quote domain.Quote, slippageToleranceMultiplier math.LegacyDec, simulatorAddress string) (uint64, sdk.Coin, error) { + return 1_000_000, sdk.NewCoin("uosmo", math.NewInt(1000)), nil }, }, }, expectedStatusCode: http.StatusOK, expectedResponse: s.MustReadFile("../../usecase/routertesting/parsing/quote_amount_in_response_simulated.json"), }, - { - name: "valid exact in request with base fee", - queryParams: map[string]string{ - "tokenIn": "1000ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5", - "tokenOutDenom": "ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4", - "singleRoute": "true", - "applyExponents": "true", - "appendBaseFee": "true", - }, - handler: &routerdelivery.RouterHandler{ - TUsecase: &mocks.TokensUsecaseMock{ - IsValidChainDenomFunc: func(chainDenom string) bool { - return true - }, - }, - RUsecase: &mocks.RouterUsecaseMock{ - GetOptimalQuoteFunc: func(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom string, opts ...domain.RouterOption) (domain.Quote, error) { - return s.NewExactAmountInQuote(poolOne, poolTwo, poolThree), nil - }, - - BaseFee: domain.BaseFee{ - Denom: "uosmo", - CurrentFee: osmomath.NewDecWithPrec(5, 1), - }, - }, - }, - expectedStatusCode: http.StatusOK, - expectedResponse: s.MustReadFile("../../usecase/routertesting/parsing/quote_amount_in_response_base_fee.json"), - }, { name: "valid exact out request", queryParams: map[string]string{ diff --git a/router/types/get_quote_request.go b/router/types/get_quote_request.go index 11bd32c6e..2894ce0e7 100644 --- a/router/types/get_quote_request.go +++ b/router/types/get_quote_request.go @@ -19,7 +19,6 @@ type GetQuoteRequest struct { SingleRoute bool SimulatorAddress string SlippageToleranceMultiplier osmomath.Dec - AppendBaseFee bool HumanDenoms bool ApplyExponents bool } @@ -68,11 +67,6 @@ func (r *GetQuoteRequest) UnmarshalHTTPRequest(c echo.Context) error { r.SimulatorAddress = simulatorAddress r.SlippageToleranceMultiplier = slippageToleranceDec - r.AppendBaseFee, err = domain.ParseBooleanQueryParam(c, "appendBaseFee") - if err != nil { - return err - } - return nil } diff --git a/router/usecase/quote_out_given_in.go b/router/usecase/quote_out_given_in.go index b4a89c6bb..a3411c602 100644 --- a/router/usecase/quote_out_given_in.go +++ b/router/usecase/quote_out_given_in.go @@ -36,13 +36,13 @@ func NewQuoteExactAmountOut(q *QuoteExactAmountIn) *quoteExactAmountOut { // quoteExactAmountIn is a quote implementation for token swap method exact in. type quoteExactAmountIn struct { - AmountIn sdk.Coin "json:\"amount_in\"" - AmountOut osmomath.Int "json:\"amount_out\"" - Route []domain.SplitRoute "json:\"route\"" - EffectiveFee osmomath.Dec "json:\"effective_fee\"" - PriceImpact osmomath.Dec "json:\"price_impact\"" - InBaseOutQuoteSpotPrice osmomath.Dec "json:\"in_base_out_quote_spot_price\"" - PriceInfo *domain.TxFeeInfo `json:"price_info,omitempty"` + AmountIn sdk.Coin "json:\"amount_in\"" + AmountOut osmomath.Int "json:\"amount_out\"" + Route []domain.SplitRoute "json:\"route\"" + EffectiveFee osmomath.Dec "json:\"effective_fee\"" + PriceImpact osmomath.Dec "json:\"price_impact\"" + InBaseOutQuoteSpotPrice osmomath.Dec "json:\"in_base_out_quote_spot_price\"" + PriceInfo *domain.QuotePriceInfo `json:"price_info,omitempty"` } // PrepareResult implements domain.Quote. @@ -154,6 +154,6 @@ func (q *quoteExactAmountIn) GetInBaseOutQuoteSpotPrice() osmomath.Dec { } // SetQuotePriceInfo implements domain.Quote. -func (q *quoteExactAmountIn) SetQuotePriceInfo(info *domain.TxFeeInfo) { +func (q *quoteExactAmountIn) SetQuotePriceInfo(info *domain.QuotePriceInfo) { q.PriceInfo = info } diff --git a/router/usecase/routertesting/parsing/quote_amount_in_response_simulated.json b/router/usecase/routertesting/parsing/quote_amount_in_response_simulated.json index e034fadc6..43eae6696 100644 --- a/router/usecase/routertesting/parsing/quote_amount_in_response_simulated.json +++ b/router/usecase/routertesting/parsing/quote_amount_in_response_simulated.json @@ -52,7 +52,6 @@ "fee_coin": { "denom": "uosmo", "amount": "1000" - }, - "base_fee": "0.500000000000000000" + } } }