Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: simulate swap as part of quotes #547

Merged
merged 9 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion app/sidecar_query_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

"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"
orderbookclaimbot "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbook/claimbot"
orderbookfillbot "github.com/osmosis-labs/sqs/ingest/usecase/plugins/orderbook/fillbot"
orderbookrepository "github.com/osmosis-labs/sqs/orderbook/repository"
orderbookusecase "github.com/osmosis-labs/sqs/orderbook/usecase"
"github.com/osmosis-labs/sqs/quotesimulator"
"github.com/osmosis-labs/sqs/sqsutil/datafetchers"

chaininforepo "github.com/osmosis-labs/sqs/chaininfo/repository"
Expand All @@ -43,6 +47,7 @@ import (

"github.com/osmosis-labs/sqs/domain"
"github.com/osmosis-labs/sqs/domain/cache"
"github.com/osmosis-labs/sqs/domain/cosmos/tx"
"github.com/osmosis-labs/sqs/domain/keyring"
"github.com/osmosis-labs/sqs/domain/mvc"
orderbookgrpcclientdomain "github.com/osmosis-labs/sqs/domain/orderbook/grpcclient"
Expand Down Expand Up @@ -210,7 +215,17 @@ func NewSideCarQueryServer(appCodec codec.Codec, config domain.Config, logger lo
if err := tokenshttpdelivery.NewTokensHandler(e, *config.Pricing, tokensUseCase, pricingSimpleRouterUsecase, logger); err != nil {
return nil, err
}
routerHttpDelivery.NewRouterHandler(e, routerUsecase, tokensUseCase, logger)

grpcClient := passthroughGRPCClient.GetChainGRPCClient()
gasCalculator := tx.NewGasCalculator(grpcClient, tx.CalculateGas)
quoteSimulator := quotesimulator.NewQuoteSimulator(
gasCalculator,
app.GetEncodingConfig(),
txfeestypes.NewQueryClient(grpcClient),
types.NewQueryClient(grpcClient),
config.ChainID,
)
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
routerHttpDelivery.NewRouterHandler(e, routerUsecase, tokensUseCase, quoteSimulator, logger)

// Create a Numia HTTP client
passthroughConfig := config.Passthrough
Expand Down
47 changes: 34 additions & 13 deletions domain/cosmos/tx/msg_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
154 changes: 122 additions & 32 deletions domain/cosmos/tx/msg_simulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,47 +44,42 @@ 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,
tt.chainID,
tt.msgs,
)

// Assert the results
assert.Equal(t, tt.expectedSimulateResponse, result)
assert.Equal(t, tt.expectedGas, gas)
if tt.expectedError != nil {
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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)
}
})
}
}
21 changes: 21 additions & 0 deletions domain/mocks/msg_simulator_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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")
}
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading