diff --git a/CHANGELOG.md b/CHANGELOG.md index 844ceb7e1..b99a9864b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## Unreleased + +- #526 - Refactor gas estimation APIs +- #524 - Claimbot + ## v26.1.0 e42b32bc SQS-412 | Active Orders Query: SSE (#518) diff --git a/domain/cosmos/tx/gas.go b/domain/cosmos/tx/gas.go deleted file mode 100644 index 935f392d8..000000000 --- a/domain/cosmos/tx/gas.go +++ /dev/null @@ -1,43 +0,0 @@ -package tx - -import ( - txclient "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - - gogogrpc "github.com/cosmos/gogoproto/grpc" -) - -// GasCalculator is an interface for calculating gas for a transaction. -type GasCalculator interface { - CalculateGas(txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) -} - -// NewGasCalculator creates a new GasCalculator instance. -func NewGasCalculator(clientCtx gogogrpc.ClientConn) GasCalculator { - return &TxGasCalulator{ - clientCtx: clientCtx, - } -} - -// TxGasCalulator is a GasCalculator implementation that uses simulated transactions to calculate gas. -type TxGasCalulator struct { - clientCtx gogogrpc.ClientConn -} - -// CalculateGas calculates the gas required for a transaction using the provided transaction factory and messages. -func (c *TxGasCalulator) CalculateGas( - txf txclient.Factory, - msgs ...sdk.Msg, -) (*txtypes.SimulateResponse, uint64, error) { - gasResult, adjustedGasUsed, err := txclient.CalculateGas( - c.clientCtx, - txf, - msgs..., - ) - if err != nil { - return nil, adjustedGasUsed, err - } - - return gasResult, adjustedGasUsed, nil -} diff --git a/domain/cosmos/tx/msg_simulator.go b/domain/cosmos/tx/msg_simulator.go new file mode 100644 index 000000000..0778fffd0 --- /dev/null +++ b/domain/cosmos/tx/msg_simulator.go @@ -0,0 +1,162 @@ +package tx + +import ( + "context" + + cosmosclient "github.com/cosmos/cosmos-sdk/client" + txclient "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/osmosis-labs/osmosis/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" + "github.com/osmosis-labs/sqs/domain/keyring" + + gogogrpc "github.com/cosmos/gogoproto/grpc" +) + +// MsgSimulator is an interface for calculating gas for a transaction. +type MsgSimulator interface { + BuildTx( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (cosmosclient.TxBuilder, error) + + // SimulateMsgs simulates the execution of the given messages and returns the simulation response, + // adjusted gas used, and any error encountered. It uses the provided gRPC client, encoding config, + // account details, and chain ID to create a transaction factory for the simulation. + SimulateMsgs( + encodingConfig cosmosclient.TxConfig, + account *authtypes.BaseAccount, + chainID string, + msgs []sdk.Msg, + ) (*txtypes.SimulateResponse, uint64, error) +} + +// NewGasCalculator creates a new GasCalculator instance. +func NewGasCalculator(clientCtx gogogrpc.ClientConn, calculateGas CalculateGasFn) MsgSimulator { + return &txGasCalulator{ + clientCtx: clientCtx, + calculateGas: calculateGas, + } +} + +// CalculateGasFn is a function type that calculates the gas for a transaction. +type CalculateGasFn func(clientCtx gogogrpc.ClientConn, txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) + +// txGasCalulator is a GasCalculator implementation that uses simulated transactions to calculate gas. +type txGasCalulator struct { + clientCtx gogogrpc.ClientConn + calculateGas CalculateGasFn +} + +// BuildTx constructs a transaction using the provided parameters and messages. +// Returns a TxBuilder and any error encountered. +func (c *txGasCalulator) BuildTx( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, +) (cosmosclient.TxBuilder, error) { + key := keyring.GetKey() + privKey := &secp256k1.PrivKey{Key: key.Bytes()} + + // Create and sign the transaction + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + + err := txBuilder.SetMsgs(msg...) + if err != nil { + return nil, err + } + + _, gas, err := c.SimulateMsgs( + 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)) + + sigV2 := BuildSignatures(privKey.PubKey(), nil, account.Sequence) + err = txBuilder.SetSignatures(sigV2) + if err != nil { + return nil, err + } + + signerData := BuildSignerData(chainID, account.AccountNumber, account.Sequence) + + signed, err := txclient.SignWithPrivKey( + ctx, + signingtypes.SignMode_SIGN_MODE_DIRECT, signerData, + txBuilder, privKey, encodingConfig.TxConfig, account.Sequence) + if err != nil { + return nil, err + } + + err = txBuilder.SetSignatures(signed) + if err != nil { + return nil, err + } + + return txBuilder, nil +} + +// SimulateMsgs implements MsgSimulator. +func (c *txGasCalulator) SimulateMsgs(encodingConfig cosmosclient.TxConfig, account *authtypes.BaseAccount, chainID string, msgs []sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { + txFactory := txclient.Factory{} + txFactory = txFactory.WithTxConfig(encodingConfig) + txFactory = txFactory.WithAccountNumber(account.AccountNumber) + txFactory = txFactory.WithSequence(account.Sequence) + txFactory = txFactory.WithChainID(chainID) + txFactory = txFactory.WithGasAdjustment(1.05) + + // Estimate transaction + gasResult, adjustedGasUsed, err := c.calculateGas( + c.clientCtx, + txFactory, + msgs..., + ) + if err != nil { + return nil, adjustedGasUsed, err + } + + return gasResult, adjustedGasUsed, nil +} + +// CalculateGas calculates the gas required for a transaction using the provided transaction factory and messages. +func CalculateGas( + clientCtx gogogrpc.ClientConn, + txf txclient.Factory, + msgs ...sdk.Msg, +) (*txtypes.SimulateResponse, uint64, error) { + gasResult, adjustedGasUsed, err := txclient.CalculateGas( + clientCtx, + txf, + msgs..., + ) + if err != nil { + return nil, adjustedGasUsed, err + } + + return gasResult, adjustedGasUsed, nil +} diff --git a/domain/cosmos/tx/msg_simulator_test.go b/domain/cosmos/tx/msg_simulator_test.go new file mode 100644 index 000000000..20849e3ab --- /dev/null +++ b/domain/cosmos/tx/msg_simulator_test.go @@ -0,0 +1,160 @@ +package tx_test + +import ( + "context" + "testing" + + 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/sqs/domain/cosmos/tx" + "github.com/osmosis-labs/sqs/domain/mocks" + "github.com/stretchr/testify/assert" +) + +func TestSimulateMsgs(t *testing.T) { + tests := []struct { + name string + account *authtypes.BaseAccount + chainID string + msgs []sdk.Msg + setupMocks func(calculator mocks.GetCalculateGasMock) tx.CalculateGasFn + expectedSimulateResponse *txtypes.SimulateResponse + expectedGas uint64 + expectedError error + }{ + { + name: "Successful simulation", + account: &authtypes.BaseAccount{AccountNumber: 1, Sequence: 1}, + chainID: "test-chain", + msgs: []sdk.Msg{newMsg("sender", "contract", `{}`)}, + setupMocks: func(calculator mocks.GetCalculateGasMock) tx.CalculateGasFn { + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, 50, nil) + }, + expectedSimulateResponse: &txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, + expectedGas: 50, + expectedError: nil, + }, + { + name: "Simulation error", + account: &authtypes.BaseAccount{AccountNumber: 2, Sequence: 2}, + chainID: "test-chain", + msgs: []sdk.Msg{}, + setupMocks: func(calculator mocks.GetCalculateGasMock) tx.CalculateGasFn { + return calculator(&txtypes.SimulateResponse{}, 3, assert.AnError) + }, + expectedSimulateResponse: nil, + expectedGas: 3, + 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, + tt.chainID, + tt.msgs, + ) + + // Assert the results + assert.Equal(t, tt.expectedSimulateResponse, result) + assert.Equal(t, tt.expectedGas, gas) + if tt.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tt.expectedError, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestBuildTx(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 + expectedJSON []byte + expectedError bool + }{ + { + 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) + + return calculator(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, 50, nil) + }, + account: &authtypes.BaseAccount{ + Sequence: 13, + AccountNumber: 1, + }, + 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=="]}`), + 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) + }, + account: &authtypes.BaseAccount{ + Sequence: 8, + AccountNumber: 51, + }, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(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( + context.Background(), + &keyring, + &txFeesClient, + encodingConfig, + tc.account, + tc.chainID, + tc.msgs..., + ) + + if tc.expectedError { + assert.Error(t, err) + assert.Nil(t, txBuilder) + } else { + assert.NoError(t, err) + assert.NotNil(t, txBuilder) + + 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)) + } + }) + } +} diff --git a/domain/cosmos/tx/tx.go b/domain/cosmos/tx/tx.go index 5c7353e0e..aad0f4760 100644 --- a/domain/cosmos/tx/tx.go +++ b/domain/cosmos/tx/tx.go @@ -4,89 +4,16 @@ package tx import ( "context" - "github.com/osmosis-labs/sqs/domain/keyring" - "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v26/app/params" txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" - cosmosClient "github.com/cosmos/cosmos-sdk/client" - txclient "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// BuildTx constructs a transaction using the provided parameters and messages. -// Returns a TxBuilder and any error encountered. -func BuildTx( - ctx context.Context, - keyring keyring.Keyring, - txfeesClient txfeestypes.QueryClient, - gasCalculator GasCalculator, - encodingConfig params.EncodingConfig, - account *authtypes.BaseAccount, - chainID string, - msg ...sdk.Msg, -) (cosmosClient.TxBuilder, error) { - key := keyring.GetKey() - privKey := &secp256k1.PrivKey{Key: key.Bytes()} - - // Create and sign the transaction - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - - err := txBuilder.SetMsgs(msg...) - if err != nil { - return nil, err - } - - _, gas, err := SimulateMsgs( - gasCalculator, - encodingConfig, - 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)) - - sigV2 := BuildSignatures(privKey.PubKey(), nil, account.Sequence) - err = txBuilder.SetSignatures(sigV2) - if err != nil { - return nil, err - } - - signerData := BuildSignerData(chainID, account.AccountNumber, account.Sequence) - - signed, err := txclient.SignWithPrivKey( - ctx, - signingtypes.SignMode_SIGN_MODE_DIRECT, signerData, - txBuilder, privKey, encodingConfig.TxConfig, account.Sequence) - if err != nil { - return nil, err - } - - err = txBuilder.SetSignatures(signed) - if err != nil { - return nil, err - } - - return txBuilder, nil -} - // SendTx broadcasts a transaction to the chain, returning the result and error. func SendTx(ctx context.Context, txServiceClient txtypes.ServiceClient, txBytes []byte) (*sdk.TxResponse, error) { // We then call the BroadcastTx method on this client. @@ -104,35 +31,6 @@ func SendTx(ctx context.Context, txServiceClient txtypes.ServiceClient, txBytes return resp.TxResponse, nil } -// SimulateMsgs simulates the execution of the given messages and returns the simulation response, -// adjusted gas used, and any error encountered. It uses the provided gRPC client, encoding config, -// account details, and chain ID to create a transaction factory for the simulation. -func SimulateMsgs( - gasCalculator GasCalculator, - encodingConfig params.EncodingConfig, - account *authtypes.BaseAccount, - chainID string, - msgs []sdk.Msg, -) (*txtypes.SimulateResponse, uint64, error) { - txFactory := txclient.Factory{} - txFactory = txFactory.WithTxConfig(encodingConfig.TxConfig) - txFactory = txFactory.WithAccountNumber(account.AccountNumber) - txFactory = txFactory.WithSequence(account.Sequence) - txFactory = txFactory.WithChainID(chainID) - txFactory = txFactory.WithGasAdjustment(1.15) - - // Estimate transaction - gasResult, adjustedGasUsed, err := gasCalculator.CalculateGas( - txFactory, - msgs..., - ) - if err != nil { - return nil, adjustedGasUsed, err - } - - return gasResult, adjustedGasUsed, nil -} - // BuildSignatures creates a SignatureV2 object using the provided public key, signature, and sequence number. // This is used in the process of building and signing transactions. func BuildSignatures(publicKey cryptotypes.PubKey, signature []byte, sequence uint64) signingtypes.SignatureV2 { diff --git a/domain/cosmos/tx/tx_test.go b/domain/cosmos/tx/tx_test.go index a48857d5a..615b970a6 100644 --- a/domain/cosmos/tx/tx_test.go +++ b/domain/cosmos/tx/tx_test.go @@ -17,7 +17,6 @@ import ( txtypes "github.com/cosmos/cosmos-sdk/types/tx" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" @@ -38,83 +37,6 @@ var ( } ) -func TestBuildTx(t *testing.T) { - testCases := []struct { - name string - setupMocks func(calculator *mocks.GasCalculator, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) - account *authtypes.BaseAccount - chainID string - msgs []sdk.Msg - expectedJSON []byte - expectedError bool - }{ - { - name: "Valid transaction", - setupMocks: func(calculator *mocks.GasCalculator, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) { - calculator.WithCalculateGas(nil, 50, nil) - keyring.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - txFeesClient.WithBaseDenom("eth", nil) - txFeesClient.WithGetEipBaseFee("0.1", nil) - }, - account: &authtypes.BaseAccount{ - Sequence: 13, - AccountNumber: 1, - }, - 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=="]}`), - expectedError: false, - }, - { - name: "Error building transaction", - setupMocks: func(calculator *mocks.GasCalculator, txFeesClient *mocks.TxFeesQueryClient, keyring *mocks.Keyring) { - calculator.WithCalculateGas(nil, 50, assert.AnError) - keyring.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - }, - account: &authtypes.BaseAccount{ - Sequence: 8, - AccountNumber: 51, - }, - expectedError: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - gasCalculator := mocks.GasCalculator{} - txFeesClient := mocks.TxFeesQueryClient{} - keyring := mocks.Keyring{} - - tc.setupMocks(&gasCalculator, &txFeesClient, &keyring) - - txBuilder, err := sqstx.BuildTx( - context.Background(), - &keyring, - &txFeesClient, - &gasCalculator, - encodingConfig, - tc.account, - tc.chainID, - tc.msgs..., - ) - - if tc.expectedError { - assert.Error(t, err) - assert.Nil(t, txBuilder) - } else { - assert.NoError(t, err) - assert.NotNil(t, txBuilder) - - 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 TestSendTx(t *testing.T) { newBroadcastTxFunc := func(txResponse *txtypes.BroadcastTxResponse, err error) func(ctx context.Context, in *txtypes.BroadcastTxRequest, opts ...grpc.CallOption) (*txtypes.BroadcastTxResponse, error) { return func(ctx context.Context, in *txtypes.BroadcastTxRequest, opts ...grpc.CallOption) (*txtypes.BroadcastTxResponse, error) { @@ -163,71 +85,6 @@ func TestSendTx(t *testing.T) { } } -func TestSimulateMsgs(t *testing.T) { - tests := []struct { - name string - account *authtypes.BaseAccount - chainID string - msgs []sdk.Msg - setupMocks func(calculator *mocks.GasCalculator) - expectedSimulateResponse *txtypes.SimulateResponse - expectedGas uint64 - expectedError error - }{ - { - name: "Successful simulation", - account: &authtypes.BaseAccount{AccountNumber: 1, Sequence: 1}, - chainID: "test-chain", - msgs: []sdk.Msg{newMsg("sender", "contract", `{}`)}, - setupMocks: func(calculator *mocks.GasCalculator) { - calculator.WithCalculateGas(&txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, 50, nil) - }, - expectedSimulateResponse: &txtypes.SimulateResponse{GasInfo: &sdk.GasInfo{GasUsed: 100000}}, - expectedGas: 50, - expectedError: nil, - }, - { - name: "Simulation error", - account: &authtypes.BaseAccount{AccountNumber: 2, Sequence: 2}, - chainID: "test-chain", - msgs: []sdk.Msg{}, - setupMocks: func(calculator *mocks.GasCalculator) { - calculator.WithCalculateGas(nil, 3, assert.AnError) - }, - expectedSimulateResponse: nil, - expectedGas: 3, - expectedError: assert.AnError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - calculator := mocks.GasCalculator{} - - tt.setupMocks(&calculator) - - // Call the function - result, gas, err := sqstx.SimulateMsgs( - &calculator, - encodingConfig, - tt.account, - tt.chainID, - tt.msgs, - ) - - // Assert the results - assert.Equal(t, tt.expectedSimulateResponse, result) - assert.Equal(t, tt.expectedGas, gas) - if tt.expectedError != nil { - assert.Error(t, err) - assert.Equal(t, tt.expectedError, err) - } else { - assert.NoError(t, err) - } - }) - } -} - func TestBuildSignatures(t *testing.T) { tests := []struct { name string diff --git a/domain/mocks/calculate_gas_mock.go b/domain/mocks/calculate_gas_mock.go new file mode 100644 index 000000000..583dd3f1a --- /dev/null +++ b/domain/mocks/calculate_gas_mock.go @@ -0,0 +1,17 @@ +package mocks + +import ( + txclient "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "github.com/osmosis-labs/sqs/domain/cosmos/tx" +) + +type GetCalculateGasMock func(simulateResponse *txtypes.SimulateResponse, gasUsed uint64, err error) tx.CalculateGasFn + +var DefaultGetCalculateGasMock GetCalculateGasMock = func(simulateResponse *txtypes.SimulateResponse, gasUsed uint64, err error) tx.CalculateGasFn { + return func(clientCtx gogogrpc.ClientConn, txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { + return simulateResponse, gasUsed, err + } +} diff --git a/domain/mocks/gas_calculator.go b/domain/mocks/gas_calculator.go deleted file mode 100644 index ef9d40369..000000000 --- a/domain/mocks/gas_calculator.go +++ /dev/null @@ -1,25 +0,0 @@ -package mocks - -import ( - txclient "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" -) - -type GasCalculator struct { - CalculateGasFunc func(txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) -} - -func (m *GasCalculator) CalculateGas(txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { - if m.CalculateGasFunc != nil { - return m.CalculateGasFunc(txf, msgs...) - } - - panic("GasCalculator.CalculateGasFunc not implemented") -} - -func (m *GasCalculator) WithCalculateGas(response *txtypes.SimulateResponse, n uint64, err error) { - m.CalculateGasFunc = func(txf txclient.Factory, msgs ...sdk.Msg) (*txtypes.SimulateResponse, uint64, error) { - return response, n, err - } -} diff --git a/domain/mocks/msg_simulator_mock.go b/domain/mocks/msg_simulator_mock.go new file mode 100644 index 000000000..f6b59c307 --- /dev/null +++ b/domain/mocks/msg_simulator_mock.go @@ -0,0 +1,61 @@ +package mocks + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/client" + 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/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" + sqstx "github.com/osmosis-labs/sqs/domain/cosmos/tx" + "github.com/osmosis-labs/sqs/domain/keyring" +) + +type MsgSimulatorMock struct { + BuildTxFn func( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (client.TxBuilder, error) + + SimulateMsgsFn func( + encodingConfig client.TxConfig, + account *authtypes.BaseAccount, + chainID string, + msgs []sdk.Msg, + ) (*txtypes.SimulateResponse, uint64, error) +} + +var _ sqstx.MsgSimulator = &MsgSimulatorMock{} + +func (m *MsgSimulatorMock) BuildTx(ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, +) (client.TxBuilder, error) { + if m.BuildTxFn != nil { + return m.BuildTxFn(ctx, keyring, txfeesClient, encodingConfig, account, chainID, msg...) + } + panic("BuildTxFn not implemented") +} + +func (m *MsgSimulatorMock) SimulateMsgs( + encodingConfig client.TxConfig, + account *authtypes.BaseAccount, + chainID string, + msgs []sdk.Msg, +) (*txtypes.SimulateResponse, uint64, error) { + if m.SimulateMsgsFn != nil { + return m.SimulateMsgsFn(encodingConfig, account, chainID, msgs) + } + panic("SimulateMsgsFn not implemented") +} diff --git a/domain/mocks/tx_builder_mock.go b/domain/mocks/tx_builder_mock.go new file mode 100644 index 000000000..fa591dc70 --- /dev/null +++ b/domain/mocks/tx_builder_mock.go @@ -0,0 +1,131 @@ +package mocks + +import ( + cosmosclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +type TxBuilderMock struct { + AddAuxSignerDataFn func(tx.AuxSignerData) error + + GetTxFn func() signing.Tx + + SetFeeAmountFn func(amount types.Coins) + + SetFeeGranterFn func(feeGranter types.AccAddress) + + SetFeePayerFn func(feePayer types.AccAddress) + + SetGasLimitFn func(limit uint64) + + SetMemoFn func(memo string) + + SetMsgsFn func(msgs ...interface { + ProtoMessage() + Reset() + String() string + }) error + + SetSignaturesFn func(signatures ...signingtypes.SignatureV2) error + + SetTimeoutHeightFn func(height uint64) +} + +// AddAuxSignerData implements client.TxBuilder. +func (t *TxBuilderMock) AddAuxSignerData(auxSignerData tx.AuxSignerData) error { + if t.AddAuxSignerDataFn != nil { + return t.AddAuxSignerDataFn(auxSignerData) + } + return nil +} + +// GetTx implements client.TxBuilder. +func (t *TxBuilderMock) GetTx() signing.Tx { + if t.GetTxFn != nil { + return t.GetTxFn() + } + + panic("unimplemented") +} + +// SetFeeAmount implements client.TxBuilder. +func (t *TxBuilderMock) SetFeeAmount(amount types.Coins) { + if t.SetFeeAmountFn != nil { + t.SetFeeAmountFn(amount) + } + + panic("unimplemented") +} + +// SetFeeGranter implements client.TxBuilder. +func (t *TxBuilderMock) SetFeeGranter(feeGranter types.AccAddress) { + if t.SetFeeGranterFn != nil { + t.SetFeeGranterFn(feeGranter) + } + + panic("unimplemented") +} + +// SetFeePayer implements client.TxBuilder. +func (t *TxBuilderMock) SetFeePayer(feePayer types.AccAddress) { + if t.SetFeePayerFn != nil { + t.SetFeePayerFn(feePayer) + } + + panic("unimplemented") +} + +// SetGasLimit implements client.TxBuilder. +func (t *TxBuilderMock) SetGasLimit(limit uint64) { + if t.SetGasLimitFn != nil { + t.SetGasLimitFn(limit) + } + + panic("unimplemented") +} + +// SetMemo implements client.TxBuilder. +func (t *TxBuilderMock) SetMemo(memo string) { + if t.SetMemoFn != nil { + t.SetMemoFn(memo) + } + + panic("unimplemented") +} + +// SetMsgs implements client.TxBuilder. +func (t *TxBuilderMock) SetMsgs(msgs ...interface { + ProtoMessage() + Reset() + String() string +}) error { + if t.SetMsgsFn != nil { + return t.SetMsgsFn(msgs...) + } + + panic("unimplemented") +} + +// SetSignatures implements client.TxBuilder. +func (t *TxBuilderMock) SetSignatures(signatures ...signingtypes.SignatureV2) error { + if t.SetSignaturesFn != nil { + return t.SetSignaturesFn(signatures...) + } + + panic("unimplemented") +} + +// SetTimeoutHeight implements client.TxBuilder. +func (t *TxBuilderMock) SetTimeoutHeight(height uint64) { + if t.SetTimeoutHeightFn != nil { + t.SetTimeoutHeightFn(height) + } + + panic("unimplemented") +} + +var _ cosmosclient.TxBuilder = &TxBuilderMock{} diff --git a/domain/mocks/tx_config_mock.go b/domain/mocks/tx_config_mock.go new file mode 100644 index 000000000..fda5f8985 --- /dev/null +++ b/domain/mocks/tx_config_mock.go @@ -0,0 +1,68 @@ +package mocks + +import ( + "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/types" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +type TxConfigMock struct { + TxEncoderFn func() types.TxEncoder +} + +// MarshalSignatureJSON implements client.TxConfig. +func (t *TxConfigMock) MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error) { + panic("unimplemented") +} + +// NewTxBuilder implements client.TxConfig. +func (t *TxConfigMock) NewTxBuilder() client.TxBuilder { + panic("unimplemented") +} + +// SignModeHandler implements client.TxConfig. +func (t *TxConfigMock) SignModeHandler() *signing.HandlerMap { + panic("unimplemented") +} + +// SigningContext implements client.TxConfig. +func (t *TxConfigMock) SigningContext() *signing.Context { + panic("unimplemented") +} + +// TxDecoder implements client.TxConfig. +func (t *TxConfigMock) TxDecoder() types.TxDecoder { + panic("unimplemented") +} + +// TxEncoder implements client.TxConfig. +func (t *TxConfigMock) TxEncoder() types.TxEncoder { + if t.TxEncoderFn != nil { + return t.TxEncoderFn() + } + + panic("unimplemented") +} + +// TxJSONDecoder implements client.TxConfig. +func (t *TxConfigMock) TxJSONDecoder() types.TxDecoder { + panic("unimplemented") +} + +// TxJSONEncoder implements client.TxConfig. +func (t *TxConfigMock) TxJSONEncoder() types.TxEncoder { + panic("unimplemented") +} + +// UnmarshalSignatureJSON implements client.TxConfig. +func (t *TxConfigMock) UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error) { + panic("unimplemented") +} + +// WrapTxBuilder implements client.TxConfig. +func (t *TxConfigMock) WrapTxBuilder(types.Tx) (client.TxBuilder, error) { + panic("unimplemented") +} + +var _ client.TxConfig = &TxConfigMock{} diff --git a/domain/mocks/tx_mock.go b/domain/mocks/tx_mock.go new file mode 100644 index 000000000..02f5f1094 --- /dev/null +++ b/domain/mocks/tx_mock.go @@ -0,0 +1,135 @@ +package mocks + +import ( + "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +var _ signing.Tx = &TxMock{} + +type TxMock struct { + FeeGranterFn func() []byte + FeePayerFn func() []byte + GetFeeFn func() sdk.Coins + GetGasFn func() uint64 + GetMemoFn func() string + GetMsgsFn func() []proto.Message + GetMsgsV2Fn func() ([]protoreflect.ProtoMessage, error) + GetPubKeysFn func() ([]types.PubKey, error) + GetSignaturesV2Fn func() ([]signingtypes.SignatureV2, error) + GetSignersFn func() ([][]byte, error) + GetTimeoutHeightFn func() uint64 + ValidateBasicFn func() error +} + +// FeeGranter implements signing.Tx. +func (t *TxMock) FeeGranter() []byte { + if t.FeeGranterFn != nil { + return t.FeeGranterFn() + } + + panic("unimplemented") +} + +// FeePayer implements signing.Tx. +func (t *TxMock) FeePayer() []byte { + if t.FeePayerFn != nil { + return t.FeePayerFn() + } + + panic("unimplemented") +} + +// GetFee implements signing.Tx. +func (t *TxMock) GetFee() sdk.Coins { + if t.GetFeeFn != nil { + return t.GetFeeFn() + } + + panic("unimplemented") +} + +// GetGas implements signing.Tx. +func (t *TxMock) GetGas() uint64 { + if t.GetGasFn != nil { + return t.GetGasFn() + } + + panic("unimplemented") +} + +// GetMemo implements signing.Tx. +func (t *TxMock) GetMemo() string { + if t.GetMemoFn != nil { + return t.GetMemoFn() + } + + panic("unimplemented") +} + +// GetMsgs implements signing.Tx. +func (t *TxMock) GetMsgs() []proto.Message { + if t.GetMsgsFn != nil { + return t.GetMsgsFn() + } + + panic("unimplemented") +} + +// GetMsgsV2 implements signing.Tx. +func (t *TxMock) GetMsgsV2() ([]protoreflect.ProtoMessage, error) { + if t.GetMsgsV2Fn != nil { + return t.GetMsgsV2Fn() + } + + panic("unimplemented") +} + +// GetPubKeys implements signing.Tx. +func (t *TxMock) GetPubKeys() ([]types.PubKey, error) { + if t.GetPubKeysFn != nil { + return t.GetPubKeysFn() + } + + panic("unimplemented") +} + +// GetSignaturesV2 implements signing.Tx. +func (t *TxMock) GetSignaturesV2() ([]signingtypes.SignatureV2, error) { + if t.GetSignaturesV2Fn != nil { + return t.GetSignaturesV2Fn() + } + + panic("unimplemented") +} + +// GetSigners implements signing.Tx. +func (t *TxMock) GetSigners() ([][]byte, error) { + if t.GetSignersFn != nil { + return t.GetSignersFn() + } + + panic("unimplemented") +} + +// GetTimeoutHeight implements signing.Tx. +func (t *TxMock) GetTimeoutHeight() uint64 { + if t.GetTimeoutHeightFn != nil { + return t.GetTimeoutHeightFn() + } + + panic("unimplemented") +} + +// ValidateBasic implements signing.Tx. +func (t *TxMock) ValidateBasic() error { + if t.ValidateBasicFn != nil { + return t.ValidateBasicFn() + } + + panic("unimplemented") +} diff --git a/ingest/usecase/plugins/orderbook/claimbot/config.go b/ingest/usecase/plugins/orderbook/claimbot/config.go index cc25934bd..3046cbc0a 100644 --- a/ingest/usecase/plugins/orderbook/claimbot/config.go +++ b/ingest/usecase/plugins/orderbook/claimbot/config.go @@ -20,7 +20,7 @@ type Config struct { OrderbookUsecase mvc.OrderBookUsecase AccountQueryClient authtypes.QueryClient TxfeesClient txfeestypes.QueryClient - GasCalculator sqstx.GasCalculator + MsgSimulator sqstx.MsgSimulator TxServiceClient txtypes.ServiceClient ChainID string Logger log.Logger @@ -46,7 +46,7 @@ func NewConfig( OrderbookUsecase: orderbookusecase, AccountQueryClient: authtypes.NewQueryClient(grpcClient), TxfeesClient: txfeestypes.NewQueryClient(grpcClient), - GasCalculator: sqstx.NewGasCalculator(grpcClient), + MsgSimulator: sqstx.NewGasCalculator(grpcClient, sqstx.CalculateGas), TxServiceClient: txtypes.NewServiceClient(grpcClient), Logger: logger.Named("claimbot"), ChainID: chainID, diff --git a/ingest/usecase/plugins/orderbook/claimbot/export_test.go b/ingest/usecase/plugins/orderbook/claimbot/export_test.go index ad9ee4597..3a117ac59 100644 --- a/ingest/usecase/plugins/orderbook/claimbot/export_test.go +++ b/ingest/usecase/plugins/orderbook/claimbot/export_test.go @@ -9,6 +9,7 @@ import ( "github.com/osmosis-labs/sqs/domain/mvc" orderbookdomain "github.com/osmosis-labs/sqs/domain/orderbook" + "github.com/osmosis-labs/osmosis/v26/app/params" txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" "github.com/osmosis-labs/osmosis/osmomath" @@ -21,6 +22,12 @@ import ( // ProcessedOrderbook is order alias data structure for testing purposes. type ProcessedOrderbook = processedOrderbook +var ( + EncodingConfig = encodingConfig + + DefaultEncodingConfigFn = defaultEncodingConfigFn +) + // ProcessOrderbooksAndGetClaimableOrders is test wrapper for processOrderbooksAndGetClaimableOrders. // This function is exported for testing purposes. func ProcessOrderbooksAndGetClaimableOrders( @@ -34,18 +41,19 @@ func ProcessOrderbooksAndGetClaimableOrders( // SendBatchClaimTx a test wrapper for sendBatchClaimTx. // This function is used only for testing purposes. -func SendBatchClaimTx( +func SendBatchClaimTxInternal( ctx context.Context, keyring keyring.Keyring, txfeesClient txfeestypes.QueryClient, - gasCalculator sqstx.GasCalculator, + msgSimulator sqstx.MsgSimulator, txServiceClient txtypes.ServiceClient, chainID string, account *authtypes.BaseAccount, contractAddress string, claims orderbookdomain.Orders, + getEncodingConfig func() params.EncodingConfig, ) (*sdk.TxResponse, error) { - return sendBatchClaimTx(ctx, keyring, txfeesClient, gasCalculator, txServiceClient, chainID, account, contractAddress, claims) + return sendBatchClaimTxInternal(ctx, keyring, txfeesClient, msgSimulator, txServiceClient, chainID, account, contractAddress, claims, getEncodingConfig) } // PrepareBatchClaimMsg is a test wrapper for prepareBatchClaimMsg. diff --git a/ingest/usecase/plugins/orderbook/claimbot/plugin.go b/ingest/usecase/plugins/orderbook/claimbot/plugin.go index c9042c220..625cbad99 100644 --- a/ingest/usecase/plugins/orderbook/claimbot/plugin.go +++ b/ingest/usecase/plugins/orderbook/claimbot/plugin.go @@ -173,7 +173,7 @@ func (o *claimbot) processOrderbookOrders(ctx context.Context, account *authtype ctx, o.config.Keyring, o.config.TxfeesClient, - o.config.GasCalculator, + o.config.MsgSimulator, o.config.TxServiceClient, o.config.ChainID, account, diff --git a/ingest/usecase/plugins/orderbook/claimbot/tx.go b/ingest/usecase/plugins/orderbook/claimbot/tx.go index 7456ebde0..2ab6eb494 100644 --- a/ingest/usecase/plugins/orderbook/claimbot/tx.go +++ b/ingest/usecase/plugins/orderbook/claimbot/tx.go @@ -9,17 +9,22 @@ import ( "github.com/osmosis-labs/sqs/domain/keyring" orderbookdomain "github.com/osmosis-labs/sqs/domain/orderbook" - "github.com/osmosis-labs/osmosis/v26/app" - txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" 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/v26/app" + "github.com/osmosis-labs/osmosis/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" ) var ( - encodingConfig = app.MakeEncodingConfig() + // Note: we monkey patch the encoding config in tests + encodingConfig params.EncodingConfig = app.MakeEncodingConfig() + + defaultEncodingConfigFn = func() params.EncodingConfig { + return encodingConfig + } ) // sendBatchClaimTx prepares and sends a batch claim transaction to the blockchain. @@ -28,13 +33,32 @@ func sendBatchClaimTx( ctx context.Context, keyring keyring.Keyring, txfeesClient txfeestypes.QueryClient, - gasCalculator sqstx.GasCalculator, + msgSimulator sqstx.MsgSimulator, txServiceClient txtypes.ServiceClient, chainID string, account *authtypes.BaseAccount, contractAddress string, claims orderbookdomain.Orders, ) (*sdk.TxResponse, error) { + return sendBatchClaimTxInternal(ctx, keyring, txfeesClient, msgSimulator, txServiceClient, chainID, account, contractAddress, claims, defaultEncodingConfigFn) +} + +// sendBatchClaimTxInternal is a helper function that prepares and sends a batch claim transaction to the blockchain. +// It takes an encoding config function as a parameter to allow for customization of the encoding config in tests. +func sendBatchClaimTxInternal( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + msgSimulator sqstx.MsgSimulator, + txServiceClient txtypes.ServiceClient, + chainID string, + account *authtypes.BaseAccount, + contractAddress string, + claims orderbookdomain.Orders, + getEncodingConfig func() params.EncodingConfig, +) (*sdk.TxResponse, error) { + encodingConfig := getEncodingConfig() + address := keyring.GetAddress().String() msgBytes, err := prepareBatchClaimMsg(claims) @@ -44,7 +68,7 @@ func sendBatchClaimTx( msg := buildExecuteContractMsg(address, contractAddress, msgBytes) - tx, err := sqstx.BuildTx(ctx, keyring, txfeesClient, gasCalculator, encodingConfig, account, chainID, msg) + tx, err := msgSimulator.BuildTx(ctx, keyring, txfeesClient, encodingConfig, account, chainID, msg) if err != nil { return nil, fmt.Errorf("failed to build transaction: %w", err) } diff --git a/ingest/usecase/plugins/orderbook/claimbot/tx_test.go b/ingest/usecase/plugins/orderbook/claimbot/tx_test.go index 79bc6c652..75031f928 100644 --- a/ingest/usecase/plugins/orderbook/claimbot/tx_test.go +++ b/ingest/usecase/plugins/orderbook/claimbot/tx_test.go @@ -4,26 +4,36 @@ import ( "context" "testing" - "github.com/osmosis-labs/sqs/domain/mocks" - orderbookdomain "github.com/osmosis-labs/sqs/domain/orderbook" - "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbook/claimbot" - + cosmosclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/signing" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - + "github.com/osmosis-labs/osmosis/v26/app" + "github.com/osmosis-labs/osmosis/v26/app/params" + txfeestypes "github.com/osmosis-labs/osmosis/v26/x/txfees/types" + "github.com/osmosis-labs/sqs/domain/keyring" + "github.com/osmosis-labs/sqs/domain/mocks" + orderbookdomain "github.com/osmosis-labs/sqs/domain/orderbook" + "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbook/claimbot" "github.com/stretchr/testify/assert" "google.golang.org/grpc" ) func TestSendBatchClaimTx(t *testing.T) { + const mockedTxBytes = "mocked-tx-bytes" + tests := []struct { - name string - chainID string - contractAddress string - claims orderbookdomain.Orders - setupMocks func(*mocks.Keyring, *authtypes.BaseAccount, *mocks.TxFeesQueryClient, *mocks.GasCalculator, *mocks.TxServiceClient) - setSendTxFunc func() []byte + name string + chainID string + contractAddress string + claims orderbookdomain.Orders + setupMocks func(*mocks.Keyring, *authtypes.BaseAccount, *mocks.TxFeesQueryClient, *mocks.MsgSimulatorMock, *mocks.TxServiceClient) + setSendTxFunc func() []byte + + getEncodingConfigFn func() params.EncodingConfig + expectedResponse *sdk.TxResponse expectedError bool }{ @@ -33,17 +43,29 @@ func TestSendBatchClaimTx(t *testing.T) { claims: orderbookdomain.Orders{ {TickId: 13, OrderId: 99}, }, - setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, gasCalculator *mocks.GasCalculator, txServiceClient *mocks.TxServiceClient) { + setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, msgSimulator *mocks.MsgSimulatorMock, txServiceClient *mocks.TxServiceClient) { keyringMock.WithGetAddress("osmo0address") keyringMock.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") account = &authtypes.BaseAccount{ AccountNumber: 3, Sequence: 31, } - gasCalculator.WithCalculateGas(nil, 0, assert.AnError) // Fail BuildTx + // Fail BuildTx + msgSimulator.BuildTxFn = func( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (cosmosclient.TxBuilder, error) { + return nil, assert.AnError + } }, - expectedResponse: &sdk.TxResponse{}, - expectedError: true, + getEncodingConfigFn: claimbot.DefaultEncodingConfigFn, + expectedResponse: &sdk.TxResponse{}, + expectedError: true, }, { name: "SendTx returns error", @@ -51,10 +73,24 @@ func TestSendBatchClaimTx(t *testing.T) { claims: orderbookdomain.Orders{ {TickId: 13, OrderId: 99}, }, - setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, gasCalculator *mocks.GasCalculator, txServiceClient *mocks.TxServiceClient) { + setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, msgSimulator *mocks.MsgSimulatorMock, txServiceClient *mocks.TxServiceClient) { keyringMock.WithGetAddress("osmo5address") keyringMock.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - gasCalculator.WithCalculateGas(nil, 51, nil) + msgSimulator.BuildTxFn = func( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (cosmosclient.TxBuilder, error) { + return &mocks.TxBuilderMock{ + GetTxFn: func() signing.Tx { + return &mocks.TxMock{} + }, + }, nil + } txfeesClient.WithBaseDenom("uosmo", nil) txfeesClient.WithGetEipBaseFee("0.2", nil) account = &authtypes.BaseAccount{ @@ -63,8 +99,9 @@ func TestSendBatchClaimTx(t *testing.T) { } txServiceClient.WithBroadcastTx(nil, assert.AnError) // SendTx returns error }, - expectedResponse: &sdk.TxResponse{}, - expectedError: true, + getEncodingConfigFn: claimbot.DefaultEncodingConfigFn, + expectedResponse: &sdk.TxResponse{}, + expectedError: true, }, { name: "Successful transaction", @@ -74,10 +111,24 @@ func TestSendBatchClaimTx(t *testing.T) { {TickId: 1, OrderId: 100}, {TickId: 2, OrderId: 200}, }, - setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, gasCalculator *mocks.GasCalculator, txServiceClient *mocks.TxServiceClient) { + setupMocks: func(keyringMock *mocks.Keyring, account *authtypes.BaseAccount, txfeesClient *mocks.TxFeesQueryClient, msgSimulator *mocks.MsgSimulatorMock, txServiceClient *mocks.TxServiceClient) { keyringMock.WithGetAddress("osmo1address") keyringMock.WithGetKey("6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159") - gasCalculator.WithCalculateGas(nil, 51, nil) + msgSimulator.BuildTxFn = func( + ctx context.Context, + keyring keyring.Keyring, + txfeesClient txfeestypes.QueryClient, + encodingConfig params.EncodingConfig, + account *authtypes.BaseAccount, + chainID string, + msg ...sdk.Msg, + ) (cosmosclient.TxBuilder, error) { + return &mocks.TxBuilderMock{ + GetTxFn: func() signing.Tx { + return &mocks.TxMock{} + }, + }, nil + } txfeesClient.WithBaseDenom("uosmo", nil) txfeesClient.WithGetEipBaseFee("0.15", nil) account = &authtypes.BaseAccount{ @@ -93,8 +144,21 @@ func TestSendBatchClaimTx(t *testing.T) { }, nil } }, + + getEncodingConfigFn: func() params.EncodingConfig { + encoding := app.MakeEncodingConfig() + encoding.TxConfig = &mocks.TxConfigMock{ + TxEncoderFn: func() types.TxEncoder { + return func(tx types.Tx) ([]byte, error) { + return []byte(mockedTxBytes), nil + } + }, + } + return encoding + }, + expectedResponse: &sdk.TxResponse{ - Data: "\n\x90\x01\n\x8d\x01\n$/cosmwasm.wasm.v1.MsgExecuteContract\x12e\n\x1fosmo1daek6me3v9jxgun9wdes7m4n5q\x12\x14osmo1contractaddress\x1a,{\"batch_claim\":{\"orders\":[[1,100],[2,200]]}}\x12`\nN\nF\n\x1f/cosmos.crypto.secp256k1.PubKey\x12#\n!\x03\xef]m\xf2\x8a\bx\x1f\x9a%v]E\x9e\x96\xa8\x9dc6a\x1d\x1f\x8a\xb4\xd3/q,֍\xd3\xd0\x12\x04\n\x02\b\x01\x12\x0e\n\n\n\x05uosmo\x12\x018\x103\x1a@\x1dI\xb5/D\xd0L\v2\xacg\x91\xb3;b+\xdb\xf6\xe0\x1c\x92\xee\xb8d\xc4&%<ڵ\x81\xd6u\xeb-\xf0ੌ\xf5\xa8);\x19\xfc%@\r\xfb2\x05AI\x13\xf3)=\n\xcf~\xb0\"\xf0\xb1", + Data: string(mockedTxBytes), }, expectedError: false, }, @@ -102,16 +166,18 @@ func TestSendBatchClaimTx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() keyring := mocks.Keyring{} account := authtypes.BaseAccount{} txFeesClient := mocks.TxFeesQueryClient{} - gasCalculator := mocks.GasCalculator{} txServiceClient := mocks.TxServiceClient{} - tt.setupMocks(&keyring, &account, &txFeesClient, &gasCalculator, &txServiceClient) + txSimulatorMock := mocks.MsgSimulatorMock{} + + tt.setupMocks(&keyring, &account, &txFeesClient, &txSimulatorMock, &txServiceClient) - response, err := claimbot.SendBatchClaimTx(ctx, &keyring, &txFeesClient, &gasCalculator, &txServiceClient, tt.chainID, &account, tt.contractAddress, tt.claims) + response, err := claimbot.SendBatchClaimTxInternal(ctx, &keyring, &txFeesClient, &txSimulatorMock, &txServiceClient, tt.chainID, &account, tt.contractAddress, tt.claims, tt.getEncodingConfigFn) if tt.expectedError { assert.Error(t, err) } else {