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

refactor: decouple base fee fetching as part of quote from simulation (backport #550) #551

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

<<<<<<< HEAD
=======
## Unreleased

- #548 - Return base fee in /quote regardless of simulation success.
- #547 - Add /quote simulation for "out given in" single routes.
- #526 - Refactor gas estimation APIs
- #524 - Claimbot

>>>>>>> fcbf7b6 (refactor: decouple base fee fetching as part of quote from simulation (#550))
## v26.1.0

e42b32bc SQS-412 | Active Orders Query: SSE (#518)
Expand Down
18 changes: 18 additions & 0 deletions app/sidecar_query_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

ingestrpcdelivry "github.com/osmosis-labs/sqs/ingest/delivery/grpc"
ingestusecase "github.com/osmosis-labs/sqs/ingest/usecase"
"github.com/osmosis-labs/sqs/ingest/usecase/plugins/basefee"
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"
Expand Down Expand Up @@ -210,7 +211,20 @@ 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
}
<<<<<<< HEAD
routerHttpDelivery.NewRouterHandler(e, routerUsecase, tokensUseCase, logger)
=======

grpcClient := passthroughGRPCClient.GetChainGRPCClient()
gasCalculator := tx.NewMsgSimulator(grpcClient, tx.CalculateGas, routerRepository)
quoteSimulator := quotesimulator.NewQuoteSimulator(
gasCalculator,
app.GetEncodingConfig(),
types.NewQueryClient(grpcClient),
config.ChainID,
)
routerHttpDelivery.NewRouterHandler(e, routerUsecase, tokensUseCase, quoteSimulator, logger)
>>>>>>> fcbf7b6 (refactor: decouple base fee fetching as part of quote from simulation (#550))

// Create a Numia HTTP client
passthroughConfig := config.Passthrough
Expand Down Expand Up @@ -309,6 +323,10 @@ func NewSideCarQueryServer(appCodec codec.Codec, config domain.Config, logger lo
}
}

// Unconditionally register the base fee fetcher.
baseFeeFetcherPlugin := basefee.NewEndBlockUpdatePlugin(routerRepository, txfeestypes.NewQueryClient(grpcClient), logger)
ingestUseCase.RegisterEndBlockProcessPlugin(baseFeeFetcherPlugin)

// Register chain info use case as a listener to the pool liquidity compute worker (healthcheck).
poolLiquidityComputeWorker.RegisterListener(chainInfoUseCase)

Expand Down
21 changes: 21 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,27 @@ const docTemplate = `{
"description": "Boolean flag indicating whether to apply exponents to the spot price. False by default.",
"name": "applyExponents",
"in": "query"
<<<<<<< HEAD
=======
},
{
"type": "string",
"description": "Address of the simulator to simulate the quote. If provided, the quote will be simulated.",
"name": "simulatorAddress",
"in": "query"
},
{
"type": "string",
"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"
>>>>>>> fcbf7b6 (refactor: decouple base fee fetching as part of quote from simulation (#550))
}
],
"responses": {
Expand Down
21 changes: 21 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,27 @@
"description": "Boolean flag indicating whether to apply exponents to the spot price. False by default.",
"name": "applyExponents",
"in": "query"
<<<<<<< HEAD
=======
},
{
"type": "string",
"description": "Address of the simulator to simulate the quote. If provided, the quote will be simulated.",
"name": "simulatorAddress",
"in": "query"
},
{
"type": "string",
"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"
>>>>>>> fcbf7b6 (refactor: decouple base fee fetching as part of quote from simulation (#550))
}
],
"responses": {
Expand Down
18 changes: 18 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,24 @@ paths:
in: query
name: applyExponents
type: boolean
<<<<<<< HEAD
=======
- description: Address of the simulator to simulate the quote. If provided,
the quote will be simulated.
in: query
name: simulatorAddress
type: string
- description: Slippage tolerance multiplier for the simulation. If simulatorAddress
is provided, this must be provided.
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
>>>>>>> fcbf7b6 (refactor: decouple base fee fetching as part of quote from simulation (#550))
produces:
- application/json
responses:
Expand Down
9 changes: 9 additions & 0 deletions domain/base_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package domain

Check failure on line 1 in domain/base_fee.go

View workflow job for this annotation

GitHub Actions / Run linter

: # github.com/osmosis-labs/sqs/domain

import "github.com/osmosis-labs/osmosis/osmomath"

// BaseFee holds the denom and current base fee
type BaseFee struct {
Denom string
CurrentFee osmomath.Dec
}
195 changes: 195 additions & 0 deletions domain/cosmos/tx/msg_simulator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package tx

import (
"context"
"errors"

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"
"github.com/osmosis-labs/sqs/domain"
"github.com/osmosis-labs/sqs/domain/keyring"
routerrepo "github.com/osmosis-labs/sqs/router/repository"
"google.golang.org/grpc"

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,
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)

// 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,
encodingConfig cosmosclient.TxConfig,
account *authtypes.BaseAccount,
chainID string,
msg ...sdk.Msg,
) domain.TxFeeInfo
}

// NewMsgSimulator creates a new GasCalculator instance.
func NewMsgSimulator(clientCtx gogogrpc.ClientConn, calculateGas CalculateGasFn, memoryRouterRepository routerrepo.RouterRepository) MsgSimulator {
return &txGasCalulator{
clientCtx: clientCtx,
calculateGas: calculateGas,
memoryRouterRepository: memoryRouterRepository,
}
}

// 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 grpc.ClientConnInterface
calculateGas CalculateGasFn
memoryRouterRepository routerrepo.BaseFeeRepository
}

// 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,
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
}

priceInfo := c.PriceMsgs(ctx, encodingConfig.TxConfig, account, chainID, msg...)
if priceInfo.Err != "" {
return nil, errors.New(priceInfo.Err)
}

txBuilder.SetGasLimit(priceInfo.AdjustedGasUsed)
txBuilder.SetFeeAmount(sdk.Coins{priceInfo.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
}

// PriceMsgs implements MsgSimulator.
func (c *txGasCalulator) PriceMsgs(ctx context.Context, encodingConfig cosmosclient.TxConfig, account *authtypes.BaseAccount, chainID string, msg ...sdk.Msg) domain.TxFeeInfo {
baseFee := c.memoryRouterRepository.GetBaseFee()
if baseFee.CurrentFee.IsNil() || baseFee.CurrentFee.IsZero() {
return domain.TxFeeInfo{Err: "base fee is zero or nil"}
}
if baseFee.Denom == "" {
return domain.TxFeeInfo{Err: "base fee denom is empty"}
}

_, gasAdjusted, err := c.SimulateMsgs(
encodingConfig,
account,
chainID,
msg,
)
if err != nil {
return domain.TxFeeInfo{Err: err.Error(), BaseFee: baseFee.CurrentFee}
}

feeAmount := CalculateFeeAmount(baseFee.CurrentFee, gasAdjusted)

return domain.TxFeeInfo{
AdjustedGasUsed: gasAdjusted,
FeeCoin: sdk.Coin{Denom: baseFee.Denom, Amount: feeAmount},
BaseFee: baseFee.CurrentFee,
Err: "",
}
}

// 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
}
Loading
Loading