Refactor Gas/Fee Model (#3258)
This commit is contained in:
parent
8f7a222308
commit
36d1736a08
|
@ -95,6 +95,11 @@ IMPROVEMENTS
|
|||
* [\#3158](https://github.com/cosmos/cosmos-sdk/pull/3158) Validate slashing genesis
|
||||
* [\#3172](https://github.com/cosmos/cosmos-sdk/pull/3172) Support minimum fees in a local testnet.
|
||||
* [\#3250](https://github.com/cosmos/cosmos-sdk/pull/3250) Refactor integration tests and increase coverage
|
||||
* [\#3248](https://github.com/cosmos/cosmos-sdk/issues/3248) Refactor tx fee
|
||||
model:
|
||||
* Validators specify minimum gas prices instead of minimum fees
|
||||
* Clients may provide either fees or gas prices directly
|
||||
* The gas prices of a tx must meet a validator's minimum
|
||||
* [\#2859](https://github.com/cosmos/cosmos-sdk/issues/2859) Rename `TallyResult` in gov proposals to `FinalTallyResult`
|
||||
* [\#3286](https://github.com/cosmos/cosmos-sdk/pull/3286) Fix `gaiad gentx` printout of account's addresses, i.e. user bech32 instead of hex.
|
||||
|
||||
|
|
|
@ -74,8 +74,9 @@ type BaseApp struct {
|
|||
// TODO move this in the future to baseapp param store on main store.
|
||||
consensusParams *abci.ConsensusParams
|
||||
|
||||
// spam prevention
|
||||
minimumFees sdk.Coins
|
||||
// The minimum gas prices a validator is willing to accept for processing a
|
||||
// transaction. This is mainly used for DoS and spam prevention.
|
||||
minGasPrices sdk.DecCoins
|
||||
|
||||
// flag for sealing
|
||||
sealed bool
|
||||
|
@ -213,13 +214,17 @@ func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (app *BaseApp) setMinimumFees(fees sdk.Coins) { app.minimumFees = fees }
|
||||
func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
|
||||
app.minGasPrices = gasPrices
|
||||
}
|
||||
|
||||
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
|
||||
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
|
||||
if isCheckTx {
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).WithMinimumFees(app.minimumFees)
|
||||
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).
|
||||
WithMinGasPrices(app.minGasPrices)
|
||||
}
|
||||
|
||||
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
|
||||
}
|
||||
|
||||
|
@ -240,7 +245,7 @@ func (app *BaseApp) setCheckState(header abci.Header) {
|
|||
ms := app.cms.CacheMultiStore()
|
||||
app.checkState = &state{
|
||||
ms: ms,
|
||||
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinimumFees(app.minimumFees),
|
||||
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinGasPrices(app.minGasPrices),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,8 +460,9 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
|||
}
|
||||
|
||||
// Cache wrap the commit-multistore for safety.
|
||||
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger).
|
||||
WithMinimumFees(app.minimumFees)
|
||||
ctx := sdk.NewContext(
|
||||
app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger,
|
||||
).WithMinGasPrices(app.minGasPrices)
|
||||
|
||||
// Passes the rest of the path as an argument to the querier.
|
||||
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
|
||||
|
|
|
@ -18,13 +18,14 @@ func SetPruning(opts sdk.PruningOptions) func(*BaseApp) {
|
|||
return func(bap *BaseApp) { bap.cms.SetPruning(opts) }
|
||||
}
|
||||
|
||||
// SetMinimumFees returns an option that sets the minimum fees on the app.
|
||||
func SetMinimumFees(minFees string) func(*BaseApp) {
|
||||
fees, err := sdk.ParseCoins(minFees)
|
||||
// SetMinimumGasPrices returns an option that sets the minimum gas prices on the app.
|
||||
func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
|
||||
gasPrices, err := sdk.ParseDecCoins(gasPricesStr)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid minimum fees: %v", err))
|
||||
panic(fmt.Sprintf("invalid minimum gas prices: %v", err))
|
||||
}
|
||||
return func(bap *BaseApp) { bap.setMinimumFees(fees) }
|
||||
|
||||
return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) }
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetName(name string) {
|
||||
|
|
|
@ -30,6 +30,7 @@ const (
|
|||
FlagSequence = "sequence"
|
||||
FlagMemo = "memo"
|
||||
FlagFees = "fees"
|
||||
FlagGasPrices = "gas-prices"
|
||||
FlagAsync = "async"
|
||||
FlagJson = "json"
|
||||
FlagPrintResponse = "print-response"
|
||||
|
@ -79,6 +80,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
|||
c.Flags().Uint64(FlagSequence, 0, "Sequence number to sign the tx")
|
||||
c.Flags().String(FlagMemo, "", "Memo to send along with transaction")
|
||||
c.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 10stake,1atom")
|
||||
c.Flags().String(FlagGasPrices, "", "Gas prices to determine the transaction fee (e.g. 0.00001stake)")
|
||||
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
|
||||
c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
|
||||
|
|
|
@ -262,7 +262,8 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) {
|
|||
payload := authrest.SignBody{
|
||||
Tx: msg,
|
||||
BaseReq: utils.NewBaseReq(
|
||||
name1, pw, "", viper.GetString(client.FlagChainID), "", "", accnum, sequence, nil, false, false,
|
||||
name1, pw, "", viper.GetString(client.FlagChainID), "", "",
|
||||
accnum, sequence, nil, nil, false, false,
|
||||
),
|
||||
}
|
||||
json, err := cdc.MarshalJSON(payload)
|
||||
|
|
|
@ -2238,6 +2238,10 @@ definitions:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Coin"
|
||||
gas_prices:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/DecCoin"
|
||||
generate_only:
|
||||
type: boolean
|
||||
example: false
|
||||
|
|
|
@ -647,7 +647,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence
|
|||
payload := authrest.SignBody{
|
||||
Tx: msg,
|
||||
BaseReq: utils.NewBaseReq(
|
||||
name, password, "", chainID, "", "", accnum, sequence, nil, false, false,
|
||||
name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false,
|
||||
),
|
||||
}
|
||||
json, err := cdc.MarshalJSON(payload)
|
||||
|
@ -703,8 +703,9 @@ func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, ad
|
|||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
baseReq := utils.NewBaseReq(name, password, memo, chainID, gas,
|
||||
fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees,
|
||||
baseReq := utils.NewBaseReq(
|
||||
name, password, memo, chainID, gas,
|
||||
fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil,
|
||||
generateOnly, simulate,
|
||||
)
|
||||
|
||||
|
@ -736,7 +737,7 @@ func doDelegate(t *testing.T, port, name, password string,
|
|||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
msg := msgDelegationsInput{
|
||||
BaseReq: baseReq,
|
||||
DelegatorAddr: delAddr,
|
||||
|
@ -770,7 +771,7 @@ func doUndelegate(t *testing.T, port, name, password string,
|
|||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
msg := msgUndelegateInput{
|
||||
BaseReq: baseReq,
|
||||
DelegatorAddr: delAddr,
|
||||
|
@ -806,7 +807,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string,
|
|||
sequence := acc.GetSequence()
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
|
||||
msg := msgBeginRedelegateInput{
|
||||
BaseReq: baseReq,
|
||||
|
@ -1037,7 +1038,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA
|
|||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
|
||||
pr := postProposalReq{
|
||||
Title: "Test",
|
||||
|
@ -1133,7 +1134,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk
|
|||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
|
||||
dr := depositReq{
|
||||
Depositor: proposerAddr,
|
||||
|
@ -1187,7 +1188,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac
|
|||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false)
|
||||
|
||||
vr := voteReq{
|
||||
Voter: proposerAddr,
|
||||
|
@ -1319,7 +1320,7 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.
|
|||
func doUnjail(t *testing.T, port, seed, name, password string,
|
||||
valAddr sdk.ValAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) {
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, false, false)
|
||||
baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false)
|
||||
|
||||
ur := unjailReq{
|
||||
BaseReq: baseReq,
|
||||
|
|
|
@ -101,23 +101,25 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, txBldr
|
|||
// BaseReq defines a structure that can be embedded in other request structures
|
||||
// that all share common "base" fields.
|
||||
type BaseReq struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Memo string `json:"memo"`
|
||||
ChainID string `json:"chain_id"`
|
||||
AccountNumber uint64 `json:"account_number"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
Fees sdk.Coins `json:"fees"`
|
||||
Gas string `json:"gas"`
|
||||
GasAdjustment string `json:"gas_adjustment"`
|
||||
GenerateOnly bool `json:"generate_only"`
|
||||
Simulate bool `json:"simulate"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Memo string `json:"memo"`
|
||||
ChainID string `json:"chain_id"`
|
||||
AccountNumber uint64 `json:"account_number"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
Fees sdk.Coins `json:"fees"`
|
||||
GasPrices sdk.DecCoins `json:"gas_prices"`
|
||||
Gas string `json:"gas"`
|
||||
GasAdjustment string `json:"gas_adjustment"`
|
||||
GenerateOnly bool `json:"generate_only"`
|
||||
Simulate bool `json:"simulate"`
|
||||
}
|
||||
|
||||
// NewBaseReq creates a new basic request instance and sanitizes its values
|
||||
func NewBaseReq(
|
||||
name, password, memo, chainID string, gas, gasAdjustment string,
|
||||
accNumber, seq uint64, fees sdk.Coins, genOnly, simulate bool) BaseReq {
|
||||
accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool,
|
||||
) BaseReq {
|
||||
|
||||
return BaseReq{
|
||||
Name: strings.TrimSpace(name),
|
||||
|
@ -125,6 +127,7 @@ func NewBaseReq(
|
|||
Memo: strings.TrimSpace(memo),
|
||||
ChainID: strings.TrimSpace(chainID),
|
||||
Fees: fees,
|
||||
GasPrices: gasPrices,
|
||||
Gas: strings.TrimSpace(gas),
|
||||
GasAdjustment: strings.TrimSpace(gasAdjustment),
|
||||
AccountNumber: accNumber,
|
||||
|
@ -136,11 +139,10 @@ func NewBaseReq(
|
|||
|
||||
// Sanitize performs basic sanitization on a BaseReq object.
|
||||
func (br BaseReq) Sanitize() BaseReq {
|
||||
newBr := NewBaseReq(
|
||||
return NewBaseReq(
|
||||
br.Name, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
|
||||
br.AccountNumber, br.Sequence, br.Fees, br.GenerateOnly, br.Simulate,
|
||||
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate,
|
||||
)
|
||||
return newBr
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of a BaseReq. If custom validation
|
||||
|
@ -152,18 +154,28 @@ func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
|
|||
case len(br.Password) == 0:
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified")
|
||||
return false
|
||||
|
||||
case len(br.ChainID) == 0:
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
|
||||
return false
|
||||
case !br.Fees.IsValid():
|
||||
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid or insufficient fees")
|
||||
|
||||
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
|
||||
// both fees and gas prices were provided
|
||||
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
|
||||
return false
|
||||
|
||||
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
|
||||
// neither fees or gas prices were provided
|
||||
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(br.Name) == 0 {
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -203,8 +215,12 @@ func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req i
|
|||
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
||||
//
|
||||
// NOTE: Also see CompleteAndBroadcastTxCli.
|
||||
// NOTE: Also see x/staking/client/rest/tx.go delegationsRequestHandlerFn.
|
||||
func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec) {
|
||||
// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn.
|
||||
func CompleteAndBroadcastTxREST(
|
||||
w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext,
|
||||
baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec,
|
||||
) {
|
||||
|
||||
gasAdjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
|
||||
if !ok {
|
||||
return
|
||||
|
@ -216,9 +232,11 @@ func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx c
|
|||
return
|
||||
}
|
||||
|
||||
txBldr := authtxb.NewTxBuilder(GetTxEncoder(cdc), baseReq.AccountNumber,
|
||||
txBldr := authtxb.NewTxBuilder(
|
||||
GetTxEncoder(cdc), baseReq.AccountNumber,
|
||||
baseReq.Sequence, gas, gasAdjustment, baseReq.Simulate,
|
||||
baseReq.ChainID, baseReq.Memo, baseReq.Fees)
|
||||
baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices,
|
||||
)
|
||||
|
||||
if baseReq.Simulate || simulateAndExecute {
|
||||
if gasAdjustment < 0 {
|
||||
|
|
|
@ -50,36 +50,30 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
|
|||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
fees := fmt.Sprintf("--minimum_fees=%s,%s", sdk.NewInt64Coin(feeDenom, 2), sdk.NewInt64Coin(denom, 2))
|
||||
minGasPrice, _ := sdk.NewDecFromStr("0.000006")
|
||||
fees := fmt.Sprintf(
|
||||
"--minimum_gas_prices=%s,%s",
|
||||
sdk.NewDecCoinFromDec(feeDenom, minGasPrice),
|
||||
sdk.NewDecCoinFromDec(fee2Denom, minGasPrice),
|
||||
)
|
||||
proc := f.GDStart(fees)
|
||||
defer proc.Stop(false)
|
||||
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
// fooAddr := f.KeyAddress(keyFoo)
|
||||
|
||||
// Check the amount of coins in the foo account to ensure that the right amount exists
|
||||
fooAcc := f.QueryAccount(f.KeyAddress(keyFoo))
|
||||
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf(denom).Int64())
|
||||
|
||||
// Send a transaction that will get rejected
|
||||
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10))
|
||||
success, _, _ := f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10))
|
||||
require.False(f.T, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure tx w/ correct fees (staking) pass
|
||||
txFees := fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(denom, 23))
|
||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(denom, 10), txFees)
|
||||
// Ensure tx w/ correct fees pass
|
||||
txFees := fmt.Sprintf("--fees=%s,%s", sdk.NewInt64Coin(feeDenom, 2), sdk.NewInt64Coin(fee2Denom, 2))
|
||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fee2Denom, 10), txFees)
|
||||
require.True(f.T, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure tx w/ correct fees (feetoken) pass
|
||||
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 23))
|
||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(feeDenom, 10), txFees)
|
||||
require.True(f.T, success)
|
||||
tests.WaitForNextNBlocksTM(2, f.Port)
|
||||
|
||||
// Ensure tx w/ improper fees (footoken) fails
|
||||
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(fooDenom, 23))
|
||||
// Ensure tx w/ improper fees fails
|
||||
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 5))
|
||||
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10), txFees)
|
||||
require.False(f.T, success)
|
||||
|
||||
|
@ -87,12 +81,46 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
|
|||
f.Cleanup()
|
||||
}
|
||||
|
||||
func TestGaiaCLIGasPrices(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
minGasPrice, _ := sdk.NewDecFromStr("0.000006")
|
||||
proc := f.GDStart(fmt.Sprintf("--minimum_gas_prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)))
|
||||
defer proc.Stop(false)
|
||||
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
// insufficient gas prices (tx fails)
|
||||
badGasPrice, _ := sdk.NewDecFromStr("0.000003")
|
||||
success, _, _ := f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50),
|
||||
fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, badGasPrice)))
|
||||
require.False(t, success)
|
||||
|
||||
// wait for a block confirmation
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// sufficient gas prices (tx passes)
|
||||
success, _, _ = f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 50),
|
||||
fmt.Sprintf("--gas-prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)))
|
||||
require.True(t, success)
|
||||
|
||||
// wait for a block confirmation
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
f.Cleanup()
|
||||
}
|
||||
|
||||
func TestGaiaCLIFeesDeduction(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := f.GDStart(fmt.Sprintf("--minimum_fees=%s", sdk.NewInt64Coin(fooDenom, 1)))
|
||||
minGasPrice, _ := sdk.NewDecFromStr("0.000006")
|
||||
proc := f.GDStart(fmt.Sprintf("--minimum_gas_prices=%s", sdk.NewDecCoinFromDec(feeDenom, minGasPrice)))
|
||||
defer proc.Stop(false)
|
||||
|
||||
// Save key addresses for later use
|
||||
|
@ -100,12 +128,12 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
|
|||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
fooAcc := f.QueryAccount(fooAddr)
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
fooAmt := fooAcc.GetCoins().AmountOf(fooDenom)
|
||||
|
||||
// test simulation
|
||||
success, _, _ := f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 1000),
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(fooDenom, 1)), "--dry-run")
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)), "--dry-run")
|
||||
require.True(t, success)
|
||||
|
||||
// Wait for a block
|
||||
|
@ -113,12 +141,12 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
|
|||
|
||||
// ensure state didn't change
|
||||
fooAcc = f.QueryAccount(fooAddr)
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
|
||||
// insufficient funds (coins + fees) tx fails
|
||||
success, _, _ = f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 1000),
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(fooDenom, 1)))
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 10000000),
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
|
||||
require.False(t, success)
|
||||
|
||||
// Wait for a block
|
||||
|
@ -126,12 +154,12 @@ func TestGaiaCLIFeesDeduction(t *testing.T) {
|
|||
|
||||
// ensure state didn't change
|
||||
fooAcc = f.QueryAccount(fooAddr)
|
||||
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
require.Equal(t, fooAmt.Int64(), fooAcc.GetCoins().AmountOf(fooDenom).Int64())
|
||||
|
||||
// test success (transfer = coins + fees)
|
||||
success, _, _ = f.TxSend(
|
||||
keyFoo, barAddr, sdk.NewInt64Coin(fooDenom, 500),
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(fooDenom, 300)))
|
||||
fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 2)))
|
||||
require.True(t, success)
|
||||
|
||||
f.Cleanup()
|
||||
|
|
|
@ -30,14 +30,16 @@ const (
|
|||
denom = "stake"
|
||||
keyFoo = "foo"
|
||||
keyBar = "bar"
|
||||
keyBaz = "baz"
|
||||
keyFooBarBaz = "foobarbaz"
|
||||
fooDenom = "footoken"
|
||||
feeDenom = "feetoken"
|
||||
fee2Denom = "fee2token"
|
||||
keyBaz = "baz"
|
||||
keyFooBarBaz = "foobarbaz"
|
||||
)
|
||||
|
||||
var startCoins = sdk.Coins{
|
||||
sdk.NewInt64Coin(feeDenom, 1000),
|
||||
sdk.NewInt64Coin(feeDenom, 1000000),
|
||||
sdk.NewInt64Coin(fee2Denom, 1000000),
|
||||
sdk.NewInt64Coin(fooDenom, 1000),
|
||||
sdk.NewInt64Coin(denom, 150),
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
|
@ -16,9 +13,11 @@ import (
|
|||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
|
@ -56,9 +55,10 @@ func main() {
|
|||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
|
||||
return app.NewGaiaApp(logger, db, traceStore, true,
|
||||
return app.NewGaiaApp(
|
||||
logger, db, traceStore, true,
|
||||
baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))),
|
||||
baseapp.SetMinimumFees(viper.GetString("minimum_fees")),
|
||||
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ var (
|
|||
flagNodeDaemonHome = "node-daemon-home"
|
||||
flagNodeCliHome = "node-cli-home"
|
||||
flagStartingIPAddress = "starting-ip-address"
|
||||
flagMinimumFees = "minimum-fees"
|
||||
)
|
||||
|
||||
const nodeDirPerm = 0755
|
||||
|
@ -82,7 +81,8 @@ Example:
|
|||
client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created",
|
||||
)
|
||||
cmd.Flags().String(
|
||||
flagMinimumFees, fmt.Sprintf("1%s", stakingtypes.DefaultBondDenom), "Validator minimum fees",
|
||||
server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", stakingtypes.DefaultBondDenom),
|
||||
"Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)",
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
@ -104,7 +104,7 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
|
|||
valPubKeys := make([]crypto.PubKey, numValidators)
|
||||
|
||||
gaiaConfig := srvconfig.DefaultConfig()
|
||||
gaiaConfig.MinFees = viper.GetString(flagMinimumFees)
|
||||
gaiaConfig.MinGasPrices = viper.GetString(server.FlagMinGasPrices)
|
||||
|
||||
var (
|
||||
accs []app.GenesisAccount
|
||||
|
|
|
@ -128,6 +128,33 @@ gaiacli keys show --multisig-threshold K name1 name2 name3 [...]
|
|||
For more information regarding how to generate, sign and broadcast transactions with a
|
||||
multi signature account see [Multisig Transactions](#multisig-transactions).
|
||||
|
||||
### Fees & Gas
|
||||
|
||||
Each transaction may either supply fees or gas prices, but not both. Most users
|
||||
will typically provide fees as this is the cost you will end up incurring for
|
||||
the transaction being included in the ledger.
|
||||
|
||||
Validator's have a minimum gas price (multi-denom) configuration and they use
|
||||
this value when when determining if they should include the transaction in a block
|
||||
during `CheckTx`, where `gasPrices >= minGasPrices`. Note, your transaction must
|
||||
supply fees that match all the denominations the validator requires.
|
||||
|
||||
__Note__: With such a mechanism in place, validators may start to prioritize
|
||||
txs by `gasPrice` in the mempool, so providing higher fees or gas prices may yield
|
||||
higher tx priority.
|
||||
|
||||
e.g.
|
||||
|
||||
```bash
|
||||
gaiacli tx send ... --fees=100photino
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
gaiacli tx send ... --gas-prices=0.000001stake
|
||||
```
|
||||
|
||||
### Account
|
||||
|
||||
#### Get Tokens
|
||||
|
|
|
@ -7,13 +7,15 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultMinimumFees = ""
|
||||
defaultMinGasPrices = ""
|
||||
)
|
||||
|
||||
// BaseConfig defines the server's basic configuration
|
||||
type BaseConfig struct {
|
||||
// Tx minimum fee
|
||||
MinFees string `mapstructure:"minimum_fees"`
|
||||
// The minimum gas prices a validator is willing to accept for processing a
|
||||
// transaction. A transaction's fees must meet the minimum of each denomination
|
||||
// specified in this config (e.g. 0.01photino,0.0001stake).
|
||||
MinGasPrices string `mapstructure:"minimum_gas_prices"`
|
||||
}
|
||||
|
||||
// Config defines the server's top level configuration
|
||||
|
@ -21,17 +23,27 @@ type Config struct {
|
|||
BaseConfig `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// SetMinimumFee sets the minimum fee.
|
||||
func (c *Config) SetMinimumFees(fees sdk.Coins) { c.MinFees = fees.String() }
|
||||
// SetMinGasPrices sets the validator's minimum gas prices.
|
||||
func (c *Config) SetMinGasPrices(gasPrices sdk.DecCoins) {
|
||||
c.MinGasPrices = gasPrices.String()
|
||||
}
|
||||
|
||||
// SetMinimumFee sets the minimum fee.
|
||||
func (c *Config) MinimumFees() sdk.Coins {
|
||||
fees, err := sdk.ParseCoins(c.MinFees)
|
||||
// GetMinGasPrices returns the validator's minimum gas prices based on the set
|
||||
// configuration.
|
||||
func (c *Config) GetMinGasPrices() sdk.DecCoins {
|
||||
gasPrices, err := sdk.ParseDecCoins(c.MinGasPrices)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid minimum fees: %v", err))
|
||||
panic(fmt.Sprintf("invalid minimum gas prices: %v", err))
|
||||
}
|
||||
return fees
|
||||
|
||||
return gasPrices
|
||||
}
|
||||
|
||||
// DefaultConfig returns server's default configuration.
|
||||
func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} }
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseConfig{
|
||||
MinGasPrices: defaultMinGasPrices,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
require.True(t, cfg.MinimumFees().IsZero())
|
||||
require.True(t, cfg.GetMinGasPrices().IsZero())
|
||||
}
|
||||
|
||||
func TestSetMinimumFees(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.SetMinimumFees(sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(100))})
|
||||
require.Equal(t, "100foo", cfg.MinFees)
|
||||
cfg.SetMinGasPrices(sdk.DecCoins{sdk.NewDecCoin("foo", 5)})
|
||||
require.Equal(t, "5.000000000000000000foo", cfg.MinGasPrices)
|
||||
}
|
||||
|
|
|
@ -13,8 +13,10 @@ const defaultConfigTemplate = `# This is a TOML config file.
|
|||
|
||||
##### main base config options #####
|
||||
|
||||
# Validators reject any tx from the mempool with less than the minimum fee per gas.
|
||||
minimum_fees = "{{ .BaseConfig.MinFees }}"
|
||||
# The minimum gas prices a validator is willing to accept for processing a
|
||||
# transaction. A transaction's fees must meet the minimum of each denomination
|
||||
# specified in this config (e.g. 0.01photino,0.0001stake).
|
||||
minimum_gas_prices = "{{ .BaseConfig.MinGasPrices }}"
|
||||
`
|
||||
|
||||
var configTemplate *template.Template
|
||||
|
@ -34,7 +36,8 @@ func ParseConfig() (*Config, error) {
|
|||
return conf, err
|
||||
}
|
||||
|
||||
// WriteConfigFile renders config using the template and writes it to configFilePath.
|
||||
// WriteConfigFile renders config using the template and writes it to
|
||||
// configFilePath.
|
||||
func WriteConfigFile(configFilePath string, config *Config) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
|
|
|
@ -15,12 +15,13 @@ import (
|
|||
"github.com/tendermint/tendermint/proxy"
|
||||
)
|
||||
|
||||
// Tendermint full-node start flags
|
||||
const (
|
||||
flagWithTendermint = "with-tendermint"
|
||||
flagAddress = "address"
|
||||
flagTraceStore = "trace-store"
|
||||
flagPruning = "pruning"
|
||||
flagMinimumFees = "minimum_fees"
|
||||
FlagMinGasPrices = "minimum_gas_prices"
|
||||
)
|
||||
|
||||
// StartCmd runs the service passed in, either stand-alone or in-process with
|
||||
|
@ -47,7 +48,10 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
|
|||
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
|
||||
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
|
||||
cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything")
|
||||
cmd.Flags().String(flagMinimumFees, "", "Minimum fees validator will accept for transactions")
|
||||
cmd.Flags().String(
|
||||
FlagMinGasPrices, "",
|
||||
"Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.0001stake)",
|
||||
)
|
||||
|
||||
// add support for all Tendermint-specific command line options
|
||||
tcmd.AddNodeFlags(cmd)
|
||||
|
|
|
@ -299,41 +299,6 @@ func (coins Coins) IsAllLTE(coinsB Coins) bool {
|
|||
return coinsB.IsAllGTE(coins)
|
||||
}
|
||||
|
||||
// IsAnyGTE returns true iff coins contains at least one denom that is present
|
||||
// at a greater or equal amount in coinsB; it returns false otherwise.
|
||||
//
|
||||
// NOTE: IsAnyGTE operates under the invariant that coins are sorted by
|
||||
// denominations.
|
||||
func (coins Coins) IsAnyGTE(coinsB Coins) bool {
|
||||
if len(coinsB) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
j := 0
|
||||
for _, coin := range coins {
|
||||
searchOther := true // terminator in case coins breaks the sorted invariant
|
||||
|
||||
for j < len(coinsB) && searchOther {
|
||||
switch strings.Compare(coin.Denom, coinsB[j].Denom) {
|
||||
case -1:
|
||||
// coin denom in less than the current other coin, so move to next coin
|
||||
searchOther = false
|
||||
case 0:
|
||||
if coin.IsGTE(coinsB[j]) {
|
||||
return true
|
||||
}
|
||||
|
||||
fallthrough // skip to next other coin
|
||||
case 1:
|
||||
// coin denom is greater than the current other coin, so move to next other coin
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsZero returns true if there are no coins or all coins are zero.
|
||||
func (coins Coins) IsZero() bool {
|
||||
for _, coin := range coins {
|
||||
|
@ -492,10 +457,12 @@ func (coins Coins) Sort() Coins {
|
|||
|
||||
var (
|
||||
// Denominations can be 3 ~ 16 characters long.
|
||||
reDnm = `[[:alpha:]][[:alnum:]]{2,15}`
|
||||
reAmt = `[[:digit:]]+`
|
||||
reSpc = `[[:space:]]*`
|
||||
reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm))
|
||||
reDnm = `[[:alpha:]][[:alnum:]]{2,15}`
|
||||
reAmt = `[[:digit:]]+`
|
||||
reDecAmt = `[[:digit:]]*\.[[:digit:]]+`
|
||||
reSpc = `[[:space:]]*`
|
||||
reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm))
|
||||
reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnm))
|
||||
)
|
||||
|
||||
// ParseCoin parses a cli input for one coin type, returning errors if invalid.
|
||||
|
|
|
@ -368,22 +368,6 @@ func TestCoinsLTE(t *testing.T) {
|
|||
assert.True(t, Coins{}.IsAllLTE(Coins{{"a", one}}))
|
||||
}
|
||||
|
||||
func TestCoinsIsAnyGTE(t *testing.T) {
|
||||
one := NewInt(1)
|
||||
two := NewInt(2)
|
||||
|
||||
assert.False(t, Coins{}.IsAnyGTE(Coins{}))
|
||||
assert.False(t, Coins{{"a", one}}.IsAnyGTE(Coins{}))
|
||||
assert.False(t, Coins{}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.False(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", two}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", two}}.IsAnyGTE(Coins{{"a", two}, {"b", one}}))
|
||||
assert.True(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.True(t, Coins{{"a", two}}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.True(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", one}, {"b", two}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", two}}.IsAnyGTE(Coins{{"a", one}, {"b", one}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", one}}.IsAnyGTE(Coins{{"a", one}, {"b", two}}))
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
one := NewInt(1)
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo
|
|||
c = c.WithLogger(logger)
|
||||
c = c.WithVoteInfos(nil)
|
||||
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||
c = c.WithMinimumFees(Coins{})
|
||||
c = c.WithMinGasPrices(DecCoins{})
|
||||
c = c.WithConsensusParams(nil)
|
||||
return c
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ const (
|
|||
contextKeyVoteInfos
|
||||
contextKeyGasMeter
|
||||
contextKeyBlockGasMeter
|
||||
contextKeyMinimumFees
|
||||
contextKeyMinGasPrices
|
||||
contextKeyConsensusParams
|
||||
)
|
||||
|
||||
|
@ -169,7 +169,7 @@ func (c Context) BlockGasMeter() GasMeter { return c.Value(contextKeyBlockGasMet
|
|||
|
||||
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
|
||||
|
||||
func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) }
|
||||
func (c Context) MinGasPrices() DecCoins { return c.Value(contextKeyMinGasPrices).(DecCoins) }
|
||||
|
||||
func (c Context) ConsensusParams() *abci.ConsensusParams {
|
||||
return c.Value(contextKeyConsensusParams).(*abci.ConsensusParams)
|
||||
|
@ -222,8 +222,8 @@ func (c Context) WithIsCheckTx(isCheckTx bool) Context {
|
|||
return c.withValue(contextKeyIsCheckTx, isCheckTx)
|
||||
}
|
||||
|
||||
func (c Context) WithMinimumFees(minFees Coins) Context {
|
||||
return c.withValue(contextKeyMinimumFees, minFees)
|
||||
func (c Context) WithMinGasPrices(gasPrices DecCoins) Context {
|
||||
return c.withValue(contextKeyMinGasPrices, gasPrices)
|
||||
}
|
||||
|
||||
func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context {
|
||||
|
|
|
@ -163,7 +163,7 @@ func TestContextWithCustom(t *testing.T) {
|
|||
logger := NewMockLogger()
|
||||
voteinfos := []abci.VoteInfo{{}}
|
||||
meter := types.NewGasMeter(10000)
|
||||
minFees := types.Coins{types.NewInt64Coin("feetoken", 1)}
|
||||
minGasPrices := types.DecCoins{types.NewDecCoin("feetoken", 1)}
|
||||
|
||||
ctx = types.NewContext(nil, header, ischeck, logger)
|
||||
require.Equal(t, header, ctx.BlockHeader())
|
||||
|
@ -174,7 +174,7 @@ func TestContextWithCustom(t *testing.T) {
|
|||
WithTxBytes(txbytes).
|
||||
WithVoteInfos(voteinfos).
|
||||
WithGasMeter(meter).
|
||||
WithMinimumFees(minFees)
|
||||
WithMinGasPrices(minGasPrices)
|
||||
require.Equal(t, height, ctx.BlockHeight())
|
||||
require.Equal(t, chainid, ctx.ChainID())
|
||||
require.Equal(t, ischeck, ctx.IsCheckTx())
|
||||
|
@ -182,5 +182,5 @@ func TestContextWithCustom(t *testing.T) {
|
|||
require.Equal(t, logger, ctx.Logger())
|
||||
require.Equal(t, voteinfos, ctx.VoteInfos())
|
||||
require.Equal(t, meter, ctx.GasMeter())
|
||||
require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feetoken", 1)})
|
||||
require.Equal(t, minGasPrices, ctx.MinGasPrices())
|
||||
}
|
||||
|
|
|
@ -2,9 +2,15 @@ package types
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decimal Coin
|
||||
|
||||
// Coins which can have additional decimal points
|
||||
type DecCoin struct {
|
||||
Denom string `json:"denom"`
|
||||
|
@ -12,6 +18,13 @@ type DecCoin struct {
|
|||
}
|
||||
|
||||
func NewDecCoin(denom string, amount int64) DecCoin {
|
||||
if amount < 0 {
|
||||
panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount))
|
||||
}
|
||||
if strings.ToLower(denom) != denom {
|
||||
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom))
|
||||
}
|
||||
|
||||
return DecCoin{
|
||||
Denom: denom,
|
||||
Amount: NewDec(amount),
|
||||
|
@ -19,6 +32,13 @@ func NewDecCoin(denom string, amount int64) DecCoin {
|
|||
}
|
||||
|
||||
func NewDecCoinFromDec(denom string, amount Dec) DecCoin {
|
||||
if amount.LT(ZeroDec()) {
|
||||
panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount))
|
||||
}
|
||||
if strings.ToLower(denom) != denom {
|
||||
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom))
|
||||
}
|
||||
|
||||
return DecCoin{
|
||||
Denom: denom,
|
||||
Amount: amount,
|
||||
|
@ -26,6 +46,13 @@ func NewDecCoinFromDec(denom string, amount Dec) DecCoin {
|
|||
}
|
||||
|
||||
func NewDecCoinFromCoin(coin Coin) DecCoin {
|
||||
if coin.Amount.LT(ZeroInt()) {
|
||||
panic(fmt.Sprintf("negative decimal coin amount: %v\n", coin.Amount))
|
||||
}
|
||||
if strings.ToLower(coin.Denom) != coin.Denom {
|
||||
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", coin.Denom))
|
||||
}
|
||||
|
||||
return DecCoin{
|
||||
Denom: coin.Denom,
|
||||
Amount: NewDecFromInt(coin.Amount),
|
||||
|
@ -55,7 +82,21 @@ func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) {
|
|||
return NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change}
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
// IsPositive returns true if coin amount is positive.
|
||||
//
|
||||
// TODO: Remove once unsigned integers are used.
|
||||
func (coin DecCoin) IsPositive() bool {
|
||||
return coin.Amount.IsPositive()
|
||||
}
|
||||
|
||||
// String implements the Stringer interface for DecCoin. It returns a
|
||||
// human-readable representation of a decimal coin.
|
||||
func (coin DecCoin) String() string {
|
||||
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decimal Coins
|
||||
|
||||
// coins with decimal
|
||||
type DecCoins []DecCoin
|
||||
|
@ -68,6 +109,21 @@ func NewDecCoins(coins Coins) DecCoins {
|
|||
return dcs
|
||||
}
|
||||
|
||||
// String implements the Stringer interface for DecCoins. It returns a
|
||||
// human-readable representation of decimal coins.
|
||||
func (coins DecCoins) String() string {
|
||||
if len(coins) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
out := ""
|
||||
for _, coin := range coins {
|
||||
out += fmt.Sprintf("%v,", coin.String())
|
||||
}
|
||||
|
||||
return out[:len(out)-1]
|
||||
}
|
||||
|
||||
// return the coins with trunctated decimals, and return the change
|
||||
func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) {
|
||||
changeSum := DecCoins{}
|
||||
|
@ -201,3 +257,115 @@ func (coins DecCoins) IsZero() bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsValid asserts the DecCoins are sorted, have positive amount, and Denom
|
||||
// does not contain upper case characters.
|
||||
func (coins DecCoins) IsValid() bool {
|
||||
switch len(coins) {
|
||||
case 0:
|
||||
return true
|
||||
|
||||
case 1:
|
||||
if strings.ToLower(coins[0].Denom) != coins[0].Denom {
|
||||
return false
|
||||
}
|
||||
return coins[0].IsPositive()
|
||||
|
||||
default:
|
||||
// check single coin case
|
||||
if !(DecCoins{coins[0]}).IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
lowDenom := coins[0].Denom
|
||||
for _, coin := range coins[1:] {
|
||||
if strings.ToLower(coin.Denom) != coin.Denom {
|
||||
return false
|
||||
}
|
||||
if coin.Denom <= lowDenom {
|
||||
return false
|
||||
}
|
||||
if !coin.IsPositive() {
|
||||
return false
|
||||
}
|
||||
|
||||
// we compare each coin against the last denom
|
||||
lowDenom = coin.Denom
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sorting
|
||||
|
||||
var _ sort.Interface = Coins{}
|
||||
|
||||
//nolint
|
||||
func (coins DecCoins) Len() int { return len(coins) }
|
||||
func (coins DecCoins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom }
|
||||
func (coins DecCoins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] }
|
||||
|
||||
// Sort is a helper function to sort the set of decimal coins in-place.
|
||||
func (coins DecCoins) Sort() DecCoins {
|
||||
sort.Sort(coins)
|
||||
return coins
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parsing
|
||||
|
||||
// ParseDecCoin parses a decimal coin from a string, returning an error if
|
||||
// invalid. An empty string is considered invalid.
|
||||
func ParseDecCoin(coinStr string) (coin DecCoin, err error) {
|
||||
coinStr = strings.TrimSpace(coinStr)
|
||||
|
||||
matches := reDecCoin.FindStringSubmatch(coinStr)
|
||||
if matches == nil {
|
||||
return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr)
|
||||
}
|
||||
|
||||
amountStr, denomStr := matches[1], matches[2]
|
||||
|
||||
amount, err := NewDecFromStr(amountStr)
|
||||
if err != nil {
|
||||
return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr))
|
||||
}
|
||||
|
||||
if denomStr != strings.ToLower(denomStr) {
|
||||
return DecCoin{}, fmt.Errorf("denom cannot contain upper case characters: %s", denomStr)
|
||||
}
|
||||
|
||||
return NewDecCoinFromDec(denomStr, amount), nil
|
||||
}
|
||||
|
||||
// ParseDecCoins will parse out a list of decimal coins separated by commas.
|
||||
// If nothing is provided, it returns nil DecCoins. Returned decimal coins are
|
||||
// sorted.
|
||||
func ParseDecCoins(coinsStr string) (coins DecCoins, err error) {
|
||||
coinsStr = strings.TrimSpace(coinsStr)
|
||||
if len(coinsStr) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
coinStrs := strings.Split(coinsStr, ",")
|
||||
for _, coinStr := range coinStrs {
|
||||
coin, err := ParseDecCoin(coinStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coins = append(coins, coin)
|
||||
}
|
||||
|
||||
// sort coins for determinism
|
||||
coins.Sort()
|
||||
|
||||
// validate coins before returning
|
||||
if !coins.IsValid() {
|
||||
return nil, fmt.Errorf("parsed decimal coins are invalid: %#v", coins)
|
||||
}
|
||||
|
||||
return coins, nil
|
||||
}
|
||||
|
|
|
@ -3,30 +3,80 @@ package types
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewDecCoin(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoin("a", 5)
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoin("a", 0)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoin("A", 5)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoin("a", -5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDecCoinFromDec(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromDec("a", NewDec(5))
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromDec("a", ZeroDec())
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromDec("A", NewDec(5))
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromDec("a", NewDec(-5))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDecCoinFromCoin(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(5)})
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(0)})
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"A", NewInt(5)})
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(-5)})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecCoinIsPositive(t *testing.T) {
|
||||
dc := NewDecCoin("a", 5)
|
||||
require.True(t, dc.IsPositive())
|
||||
|
||||
dc = NewDecCoin("a", 0)
|
||||
require.False(t, dc.IsPositive())
|
||||
}
|
||||
|
||||
func TestPlusDecCoin(t *testing.T) {
|
||||
decCoinA1 := DecCoin{"A", NewDecWithPrec(11, 1)}
|
||||
decCoinA2 := DecCoin{"A", NewDecWithPrec(22, 1)}
|
||||
decCoinB1 := DecCoin{"B", NewDecWithPrec(11, 1)}
|
||||
decCoinA1 := NewDecCoinFromDec("a", NewDecWithPrec(11, 1))
|
||||
decCoinA2 := NewDecCoinFromDec("a", NewDecWithPrec(22, 1))
|
||||
decCoinB1 := NewDecCoinFromDec("b", NewDecWithPrec(11, 1))
|
||||
|
||||
// regular add
|
||||
res := decCoinA1.Plus(decCoinA1)
|
||||
require.Equal(t, decCoinA2, res, "sum of coins is incorrect")
|
||||
|
||||
// bad denom add
|
||||
assert.Panics(t, func() {
|
||||
require.Panics(t, func() {
|
||||
decCoinA1.Plus(decCoinB1)
|
||||
}, "expected panic on sum of different denoms")
|
||||
|
||||
}
|
||||
|
||||
func TestPlusDecCoins(t *testing.T) {
|
||||
one := NewDec(1)
|
||||
zero := NewDec(0)
|
||||
negone := NewDec(-1)
|
||||
two := NewDec(2)
|
||||
|
||||
cases := []struct {
|
||||
|
@ -34,11 +84,9 @@ func TestPlusDecCoins(t *testing.T) {
|
|||
inputTwo DecCoins
|
||||
expected DecCoins
|
||||
}{
|
||||
{DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", two}, {"B", two}}},
|
||||
{DecCoins{{"A", zero}, {"B", one}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"B", one}}},
|
||||
{DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", one}, {"B", zero}}, DecCoins{{"A", negone}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", negone}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", negone}}},
|
||||
{DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", two}, {"b", two}}},
|
||||
{DecCoins{{"a", zero}, {"b", one}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"b", one}}},
|
||||
{DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins(nil)},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range cases {
|
||||
|
@ -46,3 +94,132 @@ func TestPlusDecCoins(t *testing.T) {
|
|||
require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortDecCoins(t *testing.T) {
|
||||
good := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
NewDecCoin("tree", 1),
|
||||
}
|
||||
empty := DecCoins{
|
||||
NewDecCoin("gold", 0),
|
||||
}
|
||||
badSort1 := DecCoins{
|
||||
NewDecCoin("tree", 1),
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
badSort2 := DecCoins{ // both are after the first one, but the second and third are in the wrong order
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("tree", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
badAmt := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("tree", 0),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
dup := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
coins DecCoins
|
||||
before, after bool // valid before/after sort
|
||||
}{
|
||||
{good, true, true},
|
||||
{empty, false, false},
|
||||
{badSort1, false, true},
|
||||
{badSort2, false, true},
|
||||
{badAmt, false, false},
|
||||
{dup, false, false},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range cases {
|
||||
require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex)
|
||||
tc.coins.Sort()
|
||||
require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCoinsIsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input DecCoins
|
||||
expected bool
|
||||
}{
|
||||
{DecCoins{}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(-5)}}, false},
|
||||
{DecCoins{DecCoin{"A", NewDec(5)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"B", NewDec(100000)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(-100000)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(-5)}, DecCoin{"b", NewDec(100000)}}, false},
|
||||
{DecCoins{DecCoin{"A", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res := tc.input.IsValid()
|
||||
require.Equal(t, tc.expected, res, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDecCoins(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expectedResult DecCoins
|
||||
expectedErr bool
|
||||
}{
|
||||
{"", nil, false},
|
||||
{"4stake", nil, true},
|
||||
{"5.5atom,4stake", nil, true},
|
||||
{"0.0stake", nil, true},
|
||||
{"0.004STAKE", nil, true},
|
||||
{
|
||||
"0.004stake",
|
||||
DecCoins{NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision))},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"5.04atom,0.004stake",
|
||||
DecCoins{
|
||||
NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)),
|
||||
NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res, err := ParseDecCoins(tc.input)
|
||||
if tc.expectedErr {
|
||||
require.Error(t, err, "expected error for test case #%d, input: %v", i, tc.input)
|
||||
} else {
|
||||
require.NoError(t, err, "unexpected error for test case #%d, input: %v", i, tc.input)
|
||||
require.Equal(t, tc.expectedResult, res, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCoinsString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input DecCoins
|
||||
expected string
|
||||
}{
|
||||
{DecCoins{}, ""},
|
||||
{
|
||||
DecCoins{
|
||||
NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)),
|
||||
NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)),
|
||||
},
|
||||
"5.040000000000000000atom,0.004000000000000000stake",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
out := tc.input.String()
|
||||
require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -347,7 +347,7 @@ func chopPrecisionAndRound(d *big.Int) *big.Int {
|
|||
return d
|
||||
}
|
||||
|
||||
// get the trucated quotient and remainder
|
||||
// get the truncated quotient and remainder
|
||||
quo, rem := d, big.NewInt(0)
|
||||
quo, rem = quo.QuoRem(d, precisionReuse, rem)
|
||||
|
||||
|
@ -419,6 +419,26 @@ func (d Dec) TruncateDec() Dec {
|
|||
return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int))
|
||||
}
|
||||
|
||||
// Ceil returns the smallest interger value (as a decimal) that is greater than
|
||||
// or equal to the given decimal.
|
||||
func (d Dec) Ceil() Dec {
|
||||
tmp := new(big.Int).Set(d.Int)
|
||||
|
||||
quo, rem := tmp, big.NewInt(0)
|
||||
quo, rem = quo.QuoRem(tmp, precisionReuse, rem)
|
||||
|
||||
// no need to round with a zero remainder regardless of sign
|
||||
if rem.Cmp(zeroInt) == 0 {
|
||||
return NewDecFromBigInt(quo)
|
||||
}
|
||||
|
||||
if rem.Sign() == -1 {
|
||||
return NewDecFromBigInt(quo)
|
||||
}
|
||||
|
||||
return NewDecFromBigInt(quo.Add(quo, oneInt))
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
// reuse nil values
|
||||
|
|
|
@ -384,3 +384,24 @@ func TestDecMulInt(t *testing.T) {
|
|||
require.Equal(t, tc.want, got, "Incorrect result on test case %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCeil(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input Dec
|
||||
expected Dec
|
||||
}{
|
||||
{NewDecWithPrec(1000000000000000, Precision), NewDec(1)}, // 0.001 => 1.0
|
||||
{NewDecWithPrec(-1000000000000000, Precision), ZeroDec()}, // -0.001 => 0.0
|
||||
{ZeroDec(), ZeroDec()}, // 0.0 => 0.0
|
||||
{NewDecWithPrec(900000000000000000, Precision), NewDec(1)}, // 0.9 => 1.0
|
||||
{NewDecWithPrec(4001000000000000000, Precision), NewDec(5)}, // 4.001 => 5.0
|
||||
{NewDecWithPrec(-4001000000000000000, Precision), NewDec(-4)}, // -4.001 => -4.0
|
||||
{NewDecWithPrec(4700000000000000000, Precision), NewDec(5)}, // 4.7 => 5.0
|
||||
{NewDecWithPrec(-4700000000000000000, Precision), NewDec(-4)}, // -4.7 => -4.0
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res := tc.input.Ceil()
|
||||
require.Equal(t, tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,6 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: Allow this to be configurable in the same way as minimum fees.
|
||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/3101
|
||||
gasPerUnitCost uint64 = 10000 // how much gas = 1 atom
|
||||
)
|
||||
|
||||
// NewAnteHandler returns an AnteHandler that checks and increments sequence
|
||||
// numbers, checks signatures & account numbers, and deducts fees from the first
|
||||
// signer.
|
||||
|
@ -44,7 +38,7 @@ func NewAnteHandler(ak AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler {
|
|||
// if this is a CheckTx. This is only for local mempool purposes, and thus
|
||||
// is only ran on check tx.
|
||||
if ctx.IsCheckTx() && !simulate {
|
||||
res := EnsureSufficientMempoolFees(ctx, stdTx)
|
||||
res := EnsureSufficientMempoolFees(ctx, stdTx.Fee)
|
||||
if !res.IsOK() {
|
||||
return newCtx, res, true
|
||||
}
|
||||
|
@ -262,19 +256,6 @@ func consumeMultisignatureVerificationGas(meter sdk.GasMeter,
|
|||
}
|
||||
}
|
||||
|
||||
func adjustFeesByGas(fees sdk.Coins, gas uint64) sdk.Coins {
|
||||
gasCost := gas / gasPerUnitCost
|
||||
gasFees := make(sdk.Coins, len(fees))
|
||||
|
||||
// TODO: Make this not price all coins in the same way
|
||||
// TODO: Undo int64 casting once unsigned integers are supported for coins
|
||||
for i := 0; i < len(fees); i++ {
|
||||
gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, int64(gasCost))
|
||||
}
|
||||
|
||||
return fees.Plus(gasFees)
|
||||
}
|
||||
|
||||
// DeductFees deducts fees from the given account.
|
||||
//
|
||||
// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because
|
||||
|
@ -313,28 +294,30 @@ func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Resu
|
|||
// enough fees to cover a proposer's minimum fees. An result object is returned
|
||||
// indicating success or failure.
|
||||
//
|
||||
// NOTE: This should only be called during CheckTx as it cannot be part of
|
||||
// TODO: Account for transaction size.
|
||||
//
|
||||
// Contract: This should only be called during CheckTx as it cannot be part of
|
||||
// consensus.
|
||||
func EnsureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result {
|
||||
// Currently we use a very primitive gas pricing model with a constant
|
||||
// gasPrice where adjustFeesByGas handles calculating the amount of fees
|
||||
// required based on the provided gas.
|
||||
//
|
||||
// TODO:
|
||||
// - Make the gasPrice not a constant, and account for tx size.
|
||||
// - Make Gas an unsigned integer and use tx basic validation
|
||||
if stdTx.Fee.Gas <= 0 {
|
||||
return sdk.ErrInternal(fmt.Sprintf("gas supplied must be a positive integer: %d", stdTx.Fee.Gas)).Result()
|
||||
}
|
||||
requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas)
|
||||
func EnsureSufficientMempoolFees(ctx sdk.Context, stdFee StdFee) sdk.Result {
|
||||
minGasPrices := ctx.MinGasPrices()
|
||||
if !minGasPrices.IsZero() {
|
||||
requiredFees := make(sdk.Coins, len(minGasPrices))
|
||||
|
||||
// NOTE: !A.IsAllGTE(B) is not the same as A.IsAllLT(B).
|
||||
if !ctx.MinimumFees().IsZero() && !stdTx.Fee.Amount.IsAnyGTE(requiredFees) {
|
||||
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor
|
||||
return sdk.ErrInsufficientFee(
|
||||
fmt.Sprintf(
|
||||
"insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees),
|
||||
).Result()
|
||||
// Determine the required fees by multiplying each required minimum gas
|
||||
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
|
||||
glDec := sdk.NewDec(int64(stdFee.Gas))
|
||||
for i, gp := range minGasPrices {
|
||||
fee := gp.Amount.Mul(glDec)
|
||||
requiredFees[i] = sdk.NewInt64Coin(gp.Denom, fee.Ceil().RoundInt64())
|
||||
}
|
||||
|
||||
if !stdFee.Amount.IsAllGTE(requiredFees) {
|
||||
return sdk.ErrInsufficientFee(
|
||||
fmt.Sprintf(
|
||||
"insufficient fees; got: %q required: %q", stdFee.Amount, requiredFees,
|
||||
),
|
||||
).Result()
|
||||
}
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
|
|
|
@ -635,25 +635,6 @@ func expectedGasCostByKeys(pubkeys []crypto.PubKey) uint64 {
|
|||
}
|
||||
return cost
|
||||
}
|
||||
func TestAdjustFeesByGas(t *testing.T) {
|
||||
type args struct {
|
||||
fee sdk.Coins
|
||||
gas uint64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want sdk.Coins
|
||||
}{
|
||||
{"nil coins", args{sdk.Coins{}, 100000}, sdk.Coins{}},
|
||||
{"nil coins", args{sdk.Coins{sdk.NewInt64Coin("a", 10), sdk.NewInt64Coin("b", 0)}, 100000}, sdk.Coins{sdk.NewInt64Coin("a", 20), sdk.NewInt64Coin("b", 10)}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.True(t, tt.want.IsEqual(adjustFeesByGas(tt.args.fee, tt.args.gas)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountSubkeys(t *testing.T) {
|
||||
genPubKeys := func(n int) []crypto.PubKey {
|
||||
|
@ -723,3 +704,51 @@ func TestAnteHandlerSigLimitExceeded(t *testing.T) {
|
|||
tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee)
|
||||
checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeTooManySignatures)
|
||||
}
|
||||
|
||||
func TestEnsureSufficientMempoolFees(t *testing.T) {
|
||||
// setup
|
||||
input := setupTestInput()
|
||||
ctx := input.ctx.WithMinGasPrices(
|
||||
sdk.DecCoins{
|
||||
sdk.NewDecCoinFromDec("photino", sdk.NewDecWithPrec(1000000, sdk.Precision)), // 0.0001photino
|
||||
sdk.NewDecCoinFromDec("stake", sdk.NewDecWithPrec(10000, sdk.Precision)), // 0.000001stake
|
||||
},
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
input StdFee
|
||||
expectedOK bool
|
||||
}{
|
||||
{NewStdFee(200000, sdk.Coins{sdk.NewInt64Coin("stake", 1)}), false},
|
||||
{NewStdFee(200000, sdk.Coins{sdk.NewInt64Coin("photino", 20)}), false},
|
||||
{
|
||||
NewStdFee(
|
||||
200000,
|
||||
sdk.Coins{
|
||||
sdk.NewInt64Coin("photino", 20),
|
||||
sdk.NewInt64Coin("stake", 1),
|
||||
},
|
||||
),
|
||||
true,
|
||||
},
|
||||
{
|
||||
NewStdFee(
|
||||
200000,
|
||||
sdk.Coins{
|
||||
sdk.NewInt64Coin("atom", 2),
|
||||
sdk.NewInt64Coin("photino", 20),
|
||||
sdk.NewInt64Coin("stake", 1),
|
||||
},
|
||||
),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res := EnsureSufficientMempoolFees(ctx, tc.input)
|
||||
require.Equal(
|
||||
t, tc.expectedOK, res.IsOK(),
|
||||
"unexpected result; tc #%d, input: %v, log: %v", i, tc.input, res.Log,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ type SignBody struct {
|
|||
// nolint: unparam
|
||||
// sign tx REST handler
|
||||
func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var m SignBody
|
||||
|
||||
|
@ -51,7 +50,9 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha
|
|||
false,
|
||||
m.BaseReq.ChainID,
|
||||
m.Tx.GetMemo(),
|
||||
m.Tx.Fee.Amount)
|
||||
m.Tx.Fee.Amount,
|
||||
nil,
|
||||
)
|
||||
|
||||
signedTx, err := txBldr.SignStdTx(m.BaseReq.Name, m.BaseReq.Password, m.Tx, m.AppendSig)
|
||||
if keyerror.IsErrKeyNotFound(err) {
|
||||
|
|
|
@ -23,10 +23,15 @@ type TxBuilder struct {
|
|||
chainID string
|
||||
memo string
|
||||
fees sdk.Coins
|
||||
gasPrices sdk.DecCoins
|
||||
}
|
||||
|
||||
// NewTxBuilder returns a new initialized TxBuilder
|
||||
func NewTxBuilder(txEncoder sdk.TxEncoder, accNumber, seq, gas uint64, gasAdj float64, simulateAndExecute bool, chainID, memo string, fees sdk.Coins) TxBuilder {
|
||||
// NewTxBuilder returns a new initialized TxBuilder.
|
||||
func NewTxBuilder(
|
||||
txEncoder sdk.TxEncoder, accNumber, seq, gas uint64, gasAdj float64,
|
||||
simulateAndExecute bool, chainID, memo string, fees sdk.Coins, gasPrices sdk.DecCoins,
|
||||
) TxBuilder {
|
||||
|
||||
return TxBuilder{
|
||||
txEncoder: txEncoder,
|
||||
accountNumber: accNumber,
|
||||
|
@ -37,6 +42,7 @@ func NewTxBuilder(txEncoder sdk.TxEncoder, accNumber, seq, gas uint64, gasAdj fl
|
|||
chainID: chainID,
|
||||
memo: memo,
|
||||
fees: fees,
|
||||
gasPrices: gasPrices,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +58,11 @@ func NewTxBuilderFromCLI() TxBuilder {
|
|||
chainID: viper.GetString(client.FlagChainID),
|
||||
memo: viper.GetString(client.FlagMemo),
|
||||
}
|
||||
return txbldr.WithFees(viper.GetString(client.FlagFees))
|
||||
|
||||
txbldr = txbldr.WithFees(viper.GetString(client.FlagFees))
|
||||
txbldr = txbldr.WithGasPrices(viper.GetString(client.FlagGasPrices))
|
||||
|
||||
return txbldr
|
||||
}
|
||||
|
||||
// GetTxEncoder returns the transaction encoder
|
||||
|
@ -83,6 +93,9 @@ func (bldr TxBuilder) GetMemo() string { return bldr.memo }
|
|||
// GetFees returns the fees for the transaction
|
||||
func (bldr TxBuilder) GetFees() sdk.Coins { return bldr.fees }
|
||||
|
||||
// GetGasPrices returns the gas prices set for the transaction, if any.
|
||||
func (bldr TxBuilder) GetGasPrices() sdk.DecCoins { return bldr.gasPrices }
|
||||
|
||||
// WithTxEncoder returns a copy of the context with an updated codec.
|
||||
func (bldr TxBuilder) WithTxEncoder(txEncoder sdk.TxEncoder) TxBuilder {
|
||||
bldr.txEncoder = txEncoder
|
||||
|
@ -107,10 +120,22 @@ func (bldr TxBuilder) WithFees(fees string) TxBuilder {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bldr.fees = parsedFees
|
||||
return bldr
|
||||
}
|
||||
|
||||
// WithGasPrices returns a copy of the context with updated gas prices.
|
||||
func (bldr TxBuilder) WithGasPrices(gasPrices string) TxBuilder {
|
||||
parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bldr.gasPrices = parsedGasPrices
|
||||
return bldr
|
||||
}
|
||||
|
||||
// WithSequence returns a copy of the context with an updated sequence number.
|
||||
func (bldr TxBuilder) WithSequence(sequence uint64) TxBuilder {
|
||||
bldr.sequence = sequence
|
||||
|
@ -137,13 +162,30 @@ func (bldr TxBuilder) Build(msgs []sdk.Msg) (StdSignMsg, error) {
|
|||
return StdSignMsg{}, errors.Errorf("chain ID required but not specified")
|
||||
}
|
||||
|
||||
fees := bldr.fees
|
||||
if !bldr.gasPrices.IsZero() {
|
||||
if !fees.IsZero() {
|
||||
return StdSignMsg{}, errors.New("cannot provide both fees and gas prices")
|
||||
}
|
||||
|
||||
glDec := sdk.NewDec(int64(bldr.gas))
|
||||
|
||||
// Derive the fees based on the provided gas prices, where
|
||||
// fee = ceil(gasPrice * gasLimit).
|
||||
fees = make(sdk.Coins, len(bldr.gasPrices))
|
||||
for i, gp := range bldr.gasPrices {
|
||||
fee := gp.Amount.Mul(glDec)
|
||||
fees[i] = sdk.NewInt64Coin(gp.Denom, fee.Ceil().RoundInt64())
|
||||
}
|
||||
}
|
||||
|
||||
return StdSignMsg{
|
||||
ChainID: bldr.chainID,
|
||||
AccountNumber: bldr.accountNumber,
|
||||
Sequence: bldr.sequence,
|
||||
Memo: bldr.memo,
|
||||
Msgs: msgs,
|
||||
Fee: auth.NewStdFee(bldr.gas, bldr.fees),
|
||||
Fee: auth.NewStdFee(bldr.gas, fees),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -30,6 +30,7 @@ func TestTxBuilderBuild(t *testing.T) {
|
|||
ChainID string
|
||||
Memo string
|
||||
Fees sdk.Coins
|
||||
GasPrices sdk.DecCoins
|
||||
}
|
||||
defaultMsg := []sdk.Msg{sdk.NewTestMsg(addr)}
|
||||
tests := []struct {
|
||||
|
@ -43,27 +44,56 @@ func TestTxBuilderBuild(t *testing.T) {
|
|||
TxEncoder: auth.DefaultTxEncoder(codec.New()),
|
||||
AccountNumber: 1,
|
||||
Sequence: 1,
|
||||
Gas: 100,
|
||||
Gas: 200000,
|
||||
GasAdjustment: 1.1,
|
||||
SimulateGas: false,
|
||||
ChainID: "test-chain",
|
||||
Memo: "hello from Voyager !",
|
||||
Fees: sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(1))},
|
||||
Memo: "hello from Voyager 1!",
|
||||
Fees: sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))},
|
||||
},
|
||||
defaultMsg,
|
||||
StdSignMsg{
|
||||
ChainID: "test-chain",
|
||||
AccountNumber: 1,
|
||||
Sequence: 1,
|
||||
Memo: "hello from Voyager !",
|
||||
Memo: "hello from Voyager 1!",
|
||||
Msgs: defaultMsg,
|
||||
Fee: auth.NewStdFee(100, sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(1))}),
|
||||
Fee: auth.NewStdFee(200000, sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))}),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
fields{
|
||||
TxEncoder: auth.DefaultTxEncoder(codec.New()),
|
||||
AccountNumber: 1,
|
||||
Sequence: 1,
|
||||
Gas: 200000,
|
||||
GasAdjustment: 1.1,
|
||||
SimulateGas: false,
|
||||
ChainID: "test-chain",
|
||||
Memo: "hello from Voyager 2!",
|
||||
GasPrices: sdk.DecCoins{sdk.NewDecCoinFromDec(stakingtypes.DefaultBondDenom, sdk.NewDecWithPrec(10000, sdk.Precision))},
|
||||
},
|
||||
defaultMsg,
|
||||
StdSignMsg{
|
||||
ChainID: "test-chain",
|
||||
AccountNumber: 1,
|
||||
Sequence: 1,
|
||||
Memo: "hello from Voyager 2!",
|
||||
Msgs: defaultMsg,
|
||||
Fee: auth.NewStdFee(200000, sdk.Coins{sdk.NewCoin(stakingtypes.DefaultBondDenom, sdk.NewInt(1))}),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
bldr := NewTxBuilder(tc.fields.TxEncoder, tc.fields.AccountNumber, tc.fields.Sequence, tc.fields.Gas, tc.fields.GasAdjustment, tc.fields.SimulateGas, tc.fields.ChainID, tc.fields.Memo, tc.fields.Fees)
|
||||
bldr := NewTxBuilder(
|
||||
tc.fields.TxEncoder, tc.fields.AccountNumber, tc.fields.Sequence,
|
||||
tc.fields.Gas, tc.fields.GasAdjustment, tc.fields.SimulateGas,
|
||||
tc.fields.ChainID, tc.fields.Memo, tc.fields.Fees, tc.fields.GasPrices,
|
||||
)
|
||||
|
||||
got, err := bldr.Build(tc.msgs)
|
||||
require.Equal(t, tc.wantErr, (err != nil), "TxBuilder.Build() error = %v, wantErr %v, tc %d", err, tc.wantErr, i)
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
|
|
Loading…
Reference in New Issue