Skip to content

Commit

Permalink
feat: simulate swap as part of quotes (#547)
Browse files Browse the repository at this point in the history
* feat: simulate swap as part of quotes

* try adding e2e test

* updates

* lint

* fix test

* swagger

* attempt fix grpc wiring

* fix e2e sim test

* updates
  • Loading branch information
p0mvn authored and deividaspetraitis committed Nov 25, 2024
1 parent 863d154 commit eb7d0f9
Show file tree
Hide file tree
Showing 19 changed files with 243 additions and 154 deletions.
7 changes: 4 additions & 3 deletions app/sidecar_query_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
)
Expand Down
6 changes: 0 additions & 6 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 0 additions & 6 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
5 changes: 0 additions & 5 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
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")
}
Loading

0 comments on commit eb7d0f9

Please sign in to comment.