-
Notifications
You must be signed in to change notification settings - Fork 36
/
keeper.go
287 lines (253 loc) · 9.35 KB
/
keeper.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
package keeper
import (
"sort"
"golang.org/x/exp/constraints"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
cosmosauthtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/provenance-io/provenance/x/msgfees/types"
)
const StoreKey = types.ModuleName
type baseAppSimulateFunc func(txBytes []byte) (sdk.GasInfo, *sdk.Result, sdk.Context, error)
// Keeper of the Additional fee store
type Keeper struct {
storeKey storetypes.StoreKey
cdc codec.BinaryCodec
paramSpace paramtypes.Subspace
feeCollectorName string // name of the FeeCollector ModuleAccount
defaultFeeDenom string
simulateFunc baseAppSimulateFunc
txDecoder sdk.TxDecoder
registry cdctypes.InterfaceRegistry
authority string
}
// NewKeeper returns a AdditionalFeeKeeper. It handles:
// CONTRACT: the parameter Subspace must have the param key table already initialized
func NewKeeper(
cdc codec.BinaryCodec,
key storetypes.StoreKey,
paramSpace paramtypes.Subspace,
feeCollectorName string,
defaultFeeDenom string,
simulateFunc baseAppSimulateFunc,
txDecoder sdk.TxDecoder,
registry cdctypes.InterfaceRegistry,
) Keeper {
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
return Keeper{
storeKey: key,
cdc: cdc,
paramSpace: paramSpace,
feeCollectorName: feeCollectorName,
defaultFeeDenom: defaultFeeDenom,
simulateFunc: simulateFunc,
txDecoder: txDecoder,
authority: cosmosauthtypes.NewModuleAddress(govtypes.ModuleName).String(),
registry: registry,
}
}
// GetAuthority is signer of the proposal
func (k Keeper) GetAuthority() string {
return k.authority
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName)
}
func (k Keeper) GetFeeCollectorName() string {
return k.feeCollectorName
}
// GetFloorGasPrice returns the current minimum gas price in sdk.Coin used in calculations for charging additional fees
func (k Keeper) GetFloorGasPrice(ctx sdk.Context) sdk.Coin {
min := types.DefaultFloorGasPrice()
if k.paramSpace.Has(ctx, types.ParamStoreKeyFloorGasPrice) {
k.paramSpace.Get(ctx, types.ParamStoreKeyFloorGasPrice, &min)
}
return min
}
// GetNhashPerUsdMil returns the current nhash amount per usd mil.
//
// Conversions:
// - x nhash/usd-mil = 1,000,000/x usd/hash
// - y usd/hash = 1,000,000/y nhash/usd-mil
//
// Examples:
// - 40,000,000 nhash/usd-mil = 1,000,000/40,000,000 usd/hash = $0.025/hash,
// - $0.040/hash = 1,000,000/0.040 nhash/usd-mil = 25,000,000 nhash/usd-mil
func (k Keeper) GetNhashPerUsdMil(ctx sdk.Context) uint64 {
rateInMils := types.DefaultParams().NhashPerUsdMil
if k.paramSpace.Has(ctx, types.ParamStoreKeyNhashPerUsdMil) {
k.paramSpace.Get(ctx, types.ParamStoreKeyNhashPerUsdMil, &rateInMils)
}
return rateInMils
}
// GetConversionFeeDenom returns the conversion fee denom
func (k Keeper) GetConversionFeeDenom(ctx sdk.Context) string {
conversionFeeDenom := types.DefaultParams().ConversionFeeDenom
if k.paramSpace.Has(ctx, types.ParamStoreKeyConversionFeeDenom) {
k.paramSpace.Get(ctx, types.ParamStoreKeyConversionFeeDenom, &conversionFeeDenom)
}
return conversionFeeDenom
}
// SetMsgFee sets the additional fee schedule for a Msg
func (k Keeper) SetMsgFee(ctx sdk.Context, msgFees types.MsgFee) error {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&msgFees)
store.Set(types.GetMsgFeeKey(msgFees.MsgTypeUrl), bz)
return nil
}
// GetMsgFee returns a MsgFee for the msg type if it exists nil if it does not
func (k Keeper) GetMsgFee(ctx sdk.Context, msgType string) (*types.MsgFee, error) {
store := ctx.KVStore(k.storeKey)
key := types.GetMsgFeeKey(msgType)
bz := store.Get(key)
if len(bz) == 0 {
return nil, nil
}
var msgFee types.MsgFee
if err := k.cdc.Unmarshal(bz, &msgFee); err != nil {
return nil, err
}
return &msgFee, nil
}
// RemoveMsgFee removes MsgFee or returns an error if it does not exist
func (k Keeper) RemoveMsgFee(ctx sdk.Context, msgType string) error {
store := ctx.KVStore(k.storeKey)
key := types.GetMsgFeeKey(msgType)
bz := store.Get(key)
if len(bz) == 0 {
return types.ErrMsgFeeDoesNotExist
}
store.Delete(key)
return nil
}
type Handler func(record types.MsgFee) (stop bool)
// IterateMsgFees iterates all msg fees with the given handler function.
func (k Keeper) IterateMsgFees(ctx sdk.Context, handle func(msgFees types.MsgFee) (stop bool)) error {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.MsgFeeKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
record := types.MsgFee{}
if err := k.cdc.Unmarshal(iterator.Value(), &record); err != nil {
return err
}
if handle(record) {
break
}
}
return nil
}
// DeductFeesDistributions deducts fees from the given account. The fees map contains a key of bech32 addresses to distribute funds to.
// If the key in the map is an empty string, those will go to the fee collector. After all the accounts in fees map are paid out,
// the remainder of remainingFees will be swept to the fee collector account.
func (k Keeper) DeductFeesDistributions(bankKeeper bankkeeper.Keeper, ctx sdk.Context, acc cosmosauthtypes.AccountI, remainingFees sdk.Coins, fees map[string]sdk.Coins) error {
sentCoins := sdk.NewCoins()
for _, key := range sortedKeys(fees) {
coins := fees[key]
if !coins.IsValid() {
return sdkerrors.ErrInsufficientFee.Wrapf("invalid fee amount: %q", fees)
}
if len(key) == 0 {
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), k.feeCollectorName, coins)
if err != nil {
return sdkerrors.ErrInsufficientFunds.Wrap(err.Error())
}
} else {
recipient, err := sdk.AccAddressFromBech32(key)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrap(err.Error())
}
err = bankKeeper.SendCoins(ctx, acc.GetAddress(), recipient, coins)
if err != nil {
return sdkerrors.ErrInsufficientFunds.Wrap(err.Error())
}
}
sentCoins = sentCoins.Add(coins...)
}
unsentFee, neg := remainingFees.SafeSub(sentCoins...)
if neg {
return sdkerrors.ErrInsufficientFunds.Wrapf("negative balance after sending coins to accounts and fee collector: remainingFees: %q, sentCoins: %q, distribution: %v", remainingFees, sentCoins, fees)
}
if !unsentFee.IsZero() {
// sweep the rest of the fees to module
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), k.feeCollectorName, unsentFee)
if err != nil {
return sdkerrors.ErrInsufficientFunds.Wrap(err.Error())
}
}
return nil
}
// ConvertDenomToHash converts usd coin to nhash coin using nhash per usd mil.
// Currently, usd is only supported with nhash to usd mil coming from params
func (k Keeper) ConvertDenomToHash(ctx sdk.Context, coin sdk.Coin) (sdk.Coin, error) {
conversionDenom := k.GetConversionFeeDenom(ctx)
switch coin.Denom {
case types.UsdDenom:
nhashPerMil := int64(k.GetNhashPerUsdMil(ctx))
amount := coin.Amount.Mul(sdk.NewInt(nhashPerMil))
msgFeeCoin := sdk.NewInt64Coin(conversionDenom, amount.Int64())
return msgFeeCoin, nil
case conversionDenom:
return coin, nil
default:
return sdk.Coin{}, sdkerrors.ErrInvalidType.Wrapf("denom not supported for conversion %s", coin.Denom)
}
}
// CalculateAdditionalFeesToBePaid computes the additional fees to be paid for the provided messages.
func (k Keeper) CalculateAdditionalFeesToBePaid(ctx sdk.Context, msgs ...sdk.Msg) (types.MsgFeesDistribution, error) {
msgFeesDistribution := types.MsgFeesDistribution{
RecipientDistributions: make(map[string]sdk.Coins),
}
assessCustomMsgTypeURL := sdk.MsgTypeURL(&types.MsgAssessCustomMsgFeeRequest{})
for _, msg := range msgs {
typeURL := sdk.MsgTypeURL(msg)
msgFees, err := k.GetMsgFee(ctx, typeURL)
if err != nil {
return msgFeesDistribution, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}
if msgFees != nil {
if err := msgFeesDistribution.Increase(msgFees.AdditionalFee, msgFees.RecipientBasisPoints, msgFees.Recipient); err != nil {
return msgFeesDistribution, err
}
}
if typeURL == assessCustomMsgTypeURL {
assessFee, ok := msg.(*types.MsgAssessCustomMsgFeeRequest)
if !ok {
return msgFeesDistribution, sdkerrors.ErrInvalidType.Wrap("unable to convert msg to MsgAssessCustomMsgFeeRequest")
}
msgFeeCoin, err := k.ConvertDenomToHash(ctx, assessFee.Amount)
if err != nil {
return msgFeesDistribution, err
}
points, err := assessFee.GetBips()
if err != nil {
return msgFeesDistribution, err
}
if err := msgFeesDistribution.Increase(msgFeeCoin, points, assessFee.Recipient); err != nil {
return msgFeesDistribution, err
}
}
}
return msgFeesDistribution, nil
}
// sortedKeys gets the keys of a map, sorts them and returns them as a slice.
func sortedKeys[K constraints.Ordered, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
return keys
}