github.com/Finschia/finschia-sdk@v0.49.1/client/tx/factory.go (about)

     1  package tx
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"os"
     8  
     9  	"github.com/spf13/pflag"
    10  
    11  	"github.com/Finschia/finschia-sdk/client"
    12  	"github.com/Finschia/finschia-sdk/client/flags"
    13  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    14  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
    15  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    16  	sdk "github.com/Finschia/finschia-sdk/types"
    17  	"github.com/Finschia/finschia-sdk/types/tx/signing"
    18  )
    19  
    20  // Factory defines a client transaction factory that facilitates generating and
    21  // signing an application-specific transaction.
    22  type Factory struct {
    23  	keybase            keyring.Keyring
    24  	txConfig           client.TxConfig
    25  	accountRetriever   client.AccountRetriever
    26  	accountNumber      uint64
    27  	sequence           uint64
    28  	gas                uint64
    29  	timeoutHeight      uint64
    30  	gasAdjustment      float64
    31  	chainID            string
    32  	memo               string
    33  	fees               sdk.Coins
    34  	gasPrices          sdk.DecCoins
    35  	signMode           signing.SignMode
    36  	simulateAndExecute bool
    37  }
    38  
    39  // NewFactoryCLI creates a new Factory.
    40  func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
    41  	signModeStr := clientCtx.SignModeStr
    42  
    43  	signMode := signing.SignMode_SIGN_MODE_UNSPECIFIED
    44  	switch signModeStr {
    45  	case flags.SignModeDirect:
    46  		signMode = signing.SignMode_SIGN_MODE_DIRECT
    47  	case flags.SignModeLegacyAminoJSON:
    48  		signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
    49  	case flags.SignModeEIP191:
    50  		signMode = signing.SignMode_SIGN_MODE_EIP_191
    51  	}
    52  
    53  	accNum, _ := flagSet.GetUint64(flags.FlagAccountNumber)
    54  	accSeq, _ := flagSet.GetUint64(flags.FlagSequence)
    55  	gasAdj, _ := flagSet.GetFloat64(flags.FlagGasAdjustment)
    56  	memo, _ := flagSet.GetString(flags.FlagNote)
    57  	timeoutHeight, _ := flagSet.GetUint64(flags.FlagTimeoutHeight)
    58  
    59  	gasStr, _ := flagSet.GetString(flags.FlagGas)
    60  	gasSetting, _ := flags.ParseGasSetting(gasStr)
    61  
    62  	f := Factory{
    63  		txConfig:           clientCtx.TxConfig,
    64  		accountRetriever:   clientCtx.AccountRetriever,
    65  		keybase:            clientCtx.Keyring,
    66  		chainID:            clientCtx.ChainID,
    67  		gas:                gasSetting.Gas,
    68  		simulateAndExecute: gasSetting.Simulate,
    69  		accountNumber:      accNum,
    70  		sequence:           accSeq,
    71  		timeoutHeight:      timeoutHeight,
    72  		gasAdjustment:      gasAdj,
    73  		memo:               memo,
    74  		signMode:           signMode,
    75  	}
    76  
    77  	feesStr, _ := flagSet.GetString(flags.FlagFees)
    78  	f = f.WithFees(feesStr)
    79  
    80  	gasPricesStr, _ := flagSet.GetString(flags.FlagGasPrices)
    81  	f = f.WithGasPrices(gasPricesStr)
    82  
    83  	return f
    84  }
    85  
    86  func (f Factory) AccountNumber() uint64                     { return f.accountNumber }
    87  func (f Factory) Sequence() uint64                          { return f.sequence }
    88  func (f Factory) Gas() uint64                               { return f.gas }
    89  func (f Factory) GasAdjustment() float64                    { return f.gasAdjustment }
    90  func (f Factory) Keybase() keyring.Keyring                  { return f.keybase }
    91  func (f Factory) ChainID() string                           { return f.chainID }
    92  func (f Factory) Memo() string                              { return f.memo }
    93  func (f Factory) Fees() sdk.Coins                           { return f.fees }
    94  func (f Factory) GasPrices() sdk.DecCoins                   { return f.gasPrices }
    95  func (f Factory) AccountRetriever() client.AccountRetriever { return f.accountRetriever }
    96  func (f Factory) TimeoutHeight() uint64                     { return f.timeoutHeight }
    97  
    98  // SimulateAndExecute returns the option to simulate and then execute the transaction
    99  // using the gas from the simulation results
   100  func (f Factory) SimulateAndExecute() bool { return f.simulateAndExecute }
   101  
   102  // WithTxConfig returns a copy of the Factory with an updated TxConfig.
   103  func (f Factory) WithTxConfig(g client.TxConfig) Factory {
   104  	f.txConfig = g
   105  	return f
   106  }
   107  
   108  // WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever.
   109  func (f Factory) WithAccountRetriever(ar client.AccountRetriever) Factory {
   110  	f.accountRetriever = ar
   111  	return f
   112  }
   113  
   114  // WithChainID returns a copy of the Factory with an updated chainID.
   115  func (f Factory) WithChainID(chainID string) Factory {
   116  	f.chainID = chainID
   117  	return f
   118  }
   119  
   120  // WithGas returns a copy of the Factory with an updated gas value.
   121  func (f Factory) WithGas(gas uint64) Factory {
   122  	f.gas = gas
   123  	return f
   124  }
   125  
   126  // WithFees returns a copy of the Factory with an updated fee.
   127  func (f Factory) WithFees(fees string) Factory {
   128  	parsedFees, err := sdk.ParseCoinsNormalized(fees)
   129  	if err != nil {
   130  		panic(err)
   131  	}
   132  
   133  	f.fees = parsedFees
   134  	return f
   135  }
   136  
   137  // WithGasPrices returns a copy of the Factory with updated gas prices.
   138  func (f Factory) WithGasPrices(gasPrices string) Factory {
   139  	parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
   140  	if err != nil {
   141  		panic(err)
   142  	}
   143  
   144  	f.gasPrices = parsedGasPrices
   145  	return f
   146  }
   147  
   148  // WithKeybase returns a copy of the Factory with updated Keybase.
   149  func (f Factory) WithKeybase(keybase keyring.Keyring) Factory {
   150  	f.keybase = keybase
   151  	return f
   152  }
   153  
   154  // WithSequence returns a copy of the Factory with an updated sequence number.
   155  func (f Factory) WithSequence(sequence uint64) Factory {
   156  	f.sequence = sequence
   157  	return f
   158  }
   159  
   160  // WithMemo returns a copy of the Factory with an updated memo.
   161  func (f Factory) WithMemo(memo string) Factory {
   162  	f.memo = memo
   163  	return f
   164  }
   165  
   166  // WithAccountNumber returns a copy of the Factory with an updated account number.
   167  func (f Factory) WithAccountNumber(accnum uint64) Factory {
   168  	f.accountNumber = accnum
   169  	return f
   170  }
   171  
   172  // WithGasAdjustment returns a copy of the Factory with an updated gas adjustment.
   173  func (f Factory) WithGasAdjustment(gasAdj float64) Factory {
   174  	f.gasAdjustment = gasAdj
   175  	return f
   176  }
   177  
   178  // WithSimulateAndExecute returns a copy of the Factory with an updated gas
   179  // simulation value.
   180  func (f Factory) WithSimulateAndExecute(sim bool) Factory {
   181  	f.simulateAndExecute = sim
   182  	return f
   183  }
   184  
   185  // SignMode returns the sign mode configured in the Factory
   186  func (f Factory) SignMode() signing.SignMode {
   187  	return f.signMode
   188  }
   189  
   190  // WithSignMode returns a copy of the Factory with an updated sign mode value.
   191  func (f Factory) WithSignMode(mode signing.SignMode) Factory {
   192  	f.signMode = mode
   193  	return f
   194  }
   195  
   196  // WithTimeoutHeight returns a copy of the Factory with an updated timeout height.
   197  func (f Factory) WithTimeoutHeight(height uint64) Factory {
   198  	f.timeoutHeight = height
   199  	return f
   200  }
   201  
   202  // BuildUnsignedTx builds a transaction to be signed given a set of messages.
   203  // Once created, the fee, memo, and messages are set.
   204  func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
   205  	if f.chainID == "" {
   206  		return nil, fmt.Errorf("chain ID required but not specified")
   207  	}
   208  
   209  	fees := f.fees
   210  
   211  	if !f.gasPrices.IsZero() {
   212  		if !fees.IsZero() {
   213  			return nil, errors.New("cannot provide both fees and gas prices")
   214  		}
   215  
   216  		// f.gas is a uint64 and we should convert to LegacyDec
   217  		// without the risk of under/overflow via uint64->int64.
   218  		glDec := sdk.NewDecFromBigInt(new(big.Int).SetUint64(f.gas))
   219  
   220  		// Derive the fees based on the provided gas prices, where
   221  		// fee = ceil(gasPrice * gasLimit).
   222  		fees = make(sdk.Coins, len(f.gasPrices))
   223  
   224  		for i, gp := range f.gasPrices {
   225  			fee := gp.Amount.Mul(glDec)
   226  			fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
   227  		}
   228  	}
   229  
   230  	tx := f.txConfig.NewTxBuilder()
   231  
   232  	if err := tx.SetMsgs(msgs...); err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	tx.SetMemo(f.memo)
   237  	tx.SetFeeAmount(fees)
   238  	tx.SetGasLimit(f.gas)
   239  	tx.SetTimeoutHeight(f.TimeoutHeight())
   240  
   241  	return tx, nil
   242  }
   243  
   244  // PrintUnsignedTx will generate an unsigned transaction and print it to the writer
   245  // specified by ctx.Output. If simulation was requested, the gas will be
   246  // simulated and also printed to the same writer before the transaction is
   247  // printed.
   248  func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error {
   249  	if f.SimulateAndExecute() {
   250  		if clientCtx.Offline {
   251  			return errors.New("cannot estimate gas in offline mode")
   252  		}
   253  
   254  		_, adjusted, err := CalculateGas(clientCtx, f, msgs...)
   255  		if err != nil {
   256  			return err
   257  		}
   258  
   259  		f = f.WithGas(adjusted)
   260  		_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
   261  	}
   262  
   263  	unsignedTx, err := f.BuildUnsignedTx(msgs...)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	json, err := clientCtx.TxConfig.TxJSONEncoder()(unsignedTx.GetTx())
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
   274  }
   275  
   276  // BuildSimTx creates an unsigned tx with an empty single signature and returns
   277  // the encoded transaction or an error if the unsigned transaction cannot be
   278  // built.
   279  func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) {
   280  	txb, err := f.BuildUnsignedTx(msgs...)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	// use the first element from the list of keys in order to generate a valid
   286  	// pubkey that supports multiple algorithms
   287  
   288  	var pk cryptotypes.PubKey = &secp256k1.PubKey{} // use default public key type
   289  
   290  	if f.keybase != nil {
   291  		infos, _ := f.keybase.List()
   292  		if len(infos) == 0 {
   293  			return nil, errors.New("cannot build signature for simulation, key infos slice is empty")
   294  		}
   295  
   296  		// take the first info record just for simulation purposes
   297  		pk = infos[0].GetPubKey()
   298  	}
   299  
   300  	// Create an empty signature literal as the ante handler will populate with a
   301  	// sentinel pubkey.
   302  	sig := signing.SignatureV2{
   303  		PubKey: pk,
   304  		Data: &signing.SingleSignatureData{
   305  			SignMode: f.signMode,
   306  		},
   307  		Sequence: f.Sequence(),
   308  	}
   309  	if err := txb.SetSignatures(sig); err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	return f.txConfig.TxEncoder()(txb.GetTx())
   314  }
   315  
   316  // Prepare ensures the account defined by ctx.GetFromAddress() exists and
   317  // if the account number and/or the account sequence number are zero (not set),
   318  // they will be queried for and set on the provided Factory. A new Factory with
   319  // the updated fields will be returned.
   320  func (f Factory) Prepare(clientCtx client.Context) (Factory, error) {
   321  	fc := f
   322  
   323  	from := clientCtx.GetFromAddress()
   324  
   325  	if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil {
   326  		return fc, err
   327  	}
   328  
   329  	initNum, initSeq := fc.accountNumber, fc.sequence
   330  	if initNum == 0 || initSeq == 0 {
   331  		num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from)
   332  		if err != nil {
   333  			return fc, err
   334  		}
   335  
   336  		if initNum == 0 {
   337  			fc = fc.WithAccountNumber(num)
   338  		}
   339  
   340  		if initSeq == 0 {
   341  			fc = fc.WithSequence(seq)
   342  		}
   343  	}
   344  
   345  	return fc, nil
   346  }