-
Notifications
You must be signed in to change notification settings - Fork 0
/
fee.go
148 lines (122 loc) · 4.93 KB
/
fee.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package ante
import (
"fmt"
sdk "github.com/evoblockchain/evochain/libs/cosmos-sdk/types"
"github.com/evoblockchain/evochain/libs/cosmos-sdk/x/auth/exported"
"github.com/evoblockchain/evochain/libs/cosmos-sdk/x/auth/keeper"
"github.com/evoblockchain/evochain/libs/cosmos-sdk/x/auth/types"
sdkerrors "github.com/evoblockchain/evochain/libs/cosmos-sdk/types/errors"
)
var (
_ FeeTx = (*types.StdTx)(nil) // assert StdTx implements FeeTx
)
// FeeTx defines the interface to be implemented by Tx to use the FeeDecorators
type FeeTx interface {
sdk.Tx
GetGas() uint64
GetFee() sdk.Coins
FeePayer(ctx sdk.Context) sdk.AccAddress
}
// MempoolFeeDecorator will check if the transaction's fee is at least as large
// as the local validator's minimum gasFee (defined in validator config).
// If fee is too low, decorator returns error and tx is rejected from mempool.
// Note this only applies when ctx.CheckTx = true
// If fee is high enough or not CheckTx, then call next AnteHandler
// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator
type MempoolFeeDecorator struct{}
func NewMempoolFeeDecorator() MempoolFeeDecorator {
return MempoolFeeDecorator{}
}
func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()
// Ensure that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.IsCheckTx() && !simulate {
minGasPrices := ctx.MinGasPrices()
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))
// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
glDec := sdk.NewDec(int64(gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewDecCoinFromDec(gp.Denom, fee)
}
if !feeCoins.IsAnyGTE(requiredFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
}
return next(ctx, tx, simulate)
}
// DeductFeeDecorator deducts fees from the first signer of the tx
// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error
// Call next AnteHandler if fees successfully deducted
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
type DeductFeeDecorator struct {
ak keeper.AccountKeeper
supplyKeeper types.SupplyKeeper
}
func NewDeductFeeDecorator(ak keeper.AccountKeeper, sk types.SupplyKeeper) DeductFeeDecorator {
return DeductFeeDecorator{
ak: ak,
supplyKeeper: sk,
}
}
func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
if addr := dfd.supplyKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName))
}
feePayer := feeTx.FeePayer(ctx)
feePayerAcc := dfd.ak.GetAccount(ctx, feePayer)
if feePayerAcc == nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", feePayer)
}
// deduct the fees
if !feeTx.GetFee().IsZero() {
err = DeductFees(dfd.supplyKeeper, ctx, feePayerAcc, feeTx.GetFee())
if err != nil {
return ctx, err
}
}
return next(ctx, tx, simulate)
}
// DeductFees deducts fees from the given account.
//
// NOTE: We could use the BankKeeper (in addition to the AccountKeeper, because
// the BankKeeper doesn't give us accounts), but it seems easier to do this.
func DeductFees(supplyKeeper types.SupplyKeeper, ctx sdk.Context, acc exported.Account, fees sdk.Coins) error {
blockTime := ctx.BlockTime()
coins := acc.GetCoins()
if !fees.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
// verify the account has enough funds to pay for fees
_, hasNeg := coins.SafeSub(fees)
if hasNeg {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds,
"insufficient funds to pay for fees; %s < %s", coins, fees)
}
// Validate the account has enough "spendable" coins as this will cover cases
// such as vesting accounts.
spendableCoins := acc.SpendableCoins(blockTime)
if _, hasNeg := spendableCoins.SafeSub(fees); hasNeg {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds,
"insufficient funds to pay for fees; %s < %s", spendableCoins, fees)
}
err := supplyKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
return nil
}