100 set fees to be 1 plmnt for a tx (#114)

* replace DeductFeeDecorator and adjust e2e tests
* reorganize decorators and add comments

---------

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
Lorenz Herzberger 2023-10-03 09:01:39 +02:00 committed by GitHub
parent e80ce7e894
commit 94022e1102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 233 additions and 54 deletions

View File

@ -1,9 +1,6 @@
package ante package ante
import ( import (
assettypes "github.com/planetmint/planetmint-go/x/asset/types"
machinetypes "github.com/planetmint/planetmint-go/x/machine/types"
errorsmod "cosmossdk.io/errors" errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -13,49 +10,6 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
) )
type CheckMachineDecorator struct {
mk MachineKeeper
}
func NewCheckMachineDecorator(mk MachineKeeper) CheckMachineDecorator {
return CheckMachineDecorator{
mk: mk,
}
}
func (cm CheckMachineDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
for _, msg := range tx.GetMsgs() {
switch sdk.MsgTypeURL(msg) {
case "/planetmintgo.asset.MsgNotarizeAsset":
notarizeMsg, ok := msg.(*assettypes.MsgNotarizeAsset)
if ok {
_, found := cm.mk.GetMachineIndexByAddress(ctx, notarizeMsg.GetCreator())
if !found {
return ctx, errorsmod.Wrapf(machinetypes.ErrMachineNotFound, "error during CheckTx or ReCheckTx")
}
}
case "/planetmintgo.machine.MsgAttestMachine":
attestMsg, ok := msg.(*machinetypes.MsgAttestMachine)
if ok {
if attestMsg.GetCreator() != attestMsg.Machine.GetAddress() {
return ctx, errorsmod.Wrapf(machinetypes.ErrMachineIsNotCreator, "error during CheckTx or ReCheckTx")
}
_, activated, found := cm.mk.GetTrustAnchor(ctx, attestMsg.Machine.MachineId)
if !found {
return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorNotFound, "error during CheckTx or ReCheckTx")
}
if activated {
return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorAlreadyInUse, "error during CheckTx or ReCheckTx")
}
}
default:
continue
}
}
return next(ctx, tx, simulate)
}
// HandlerOptions are the options required for constructing a default SDK AnteHandler. // HandlerOptions are the options required for constructing a default SDK AnteHandler.
type HandlerOptions struct { type HandlerOptions struct {
AccountKeeper AccountKeeper AccountKeeper AccountKeeper
@ -64,7 +18,7 @@ type HandlerOptions struct {
FeegrantKeeper FeegrantKeeper FeegrantKeeper FeegrantKeeper
SignModeHandler authsigning.SignModeHandler SignModeHandler authsigning.SignModeHandler
SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
TxFeeChecker ante.TxFeeChecker TxFeeChecker TxFeeChecker
MachineKeeper MachineKeeper MachineKeeper MachineKeeper
} }
@ -96,7 +50,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewValidateMemoDecorator(options.AccountKeeper),
NewCheckMachineDecorator(options.MachineKeeper), NewCheckMachineDecorator(options.MachineKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper), ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),

View File

@ -0,0 +1,51 @@
package ante
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
assettypes "github.com/planetmint/planetmint-go/x/asset/types"
machinetypes "github.com/planetmint/planetmint-go/x/machine/types"
)
type CheckMachineDecorator struct {
mk MachineKeeper
}
func NewCheckMachineDecorator(mk MachineKeeper) CheckMachineDecorator {
return CheckMachineDecorator{
mk: mk,
}
}
func (cm CheckMachineDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
for _, msg := range tx.GetMsgs() {
switch sdk.MsgTypeURL(msg) {
case "/planetmintgo.asset.MsgNotarizeAsset":
notarizeMsg, ok := msg.(*assettypes.MsgNotarizeAsset)
if ok {
_, found := cm.mk.GetMachineIndexByAddress(ctx, notarizeMsg.GetCreator())
if !found {
return ctx, errorsmod.Wrapf(machinetypes.ErrMachineNotFound, "error during CheckTx or ReCheckTx")
}
}
case "/planetmintgo.machine.MsgAttestMachine":
attestMsg, ok := msg.(*machinetypes.MsgAttestMachine)
if ok {
if attestMsg.GetCreator() != attestMsg.Machine.GetAddress() {
return ctx, errorsmod.Wrapf(machinetypes.ErrMachineIsNotCreator, "error during CheckTx or ReCheckTx")
}
_, activated, found := cm.mk.GetTrustAnchor(ctx, attestMsg.Machine.MachineId)
if !found {
return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorNotFound, "error during CheckTx or ReCheckTx")
}
if activated {
return ctx, errorsmod.Wrapf(machinetypes.ErrTrustAnchorAlreadyInUse, "error during CheckTx or ReCheckTx")
}
}
default:
continue
}
}
return next(ctx, tx, simulate)
}

View File

@ -0,0 +1,172 @@
package ante
import (
"fmt"
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
// TxFeeChecker check if the provided fee is enough and returns the effective fee and tx priority,
// the effective fee should be deducted later, and the priority should be returned in abci response.
type TxFeeChecker func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, error)
// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx.
// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error.
// Call next AnteHandler if fees successfully deducted.
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
type DeductFeeDecorator struct {
accountKeeper AccountKeeper
bankKeeper authtypes.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker TxFeeChecker
}
func NewDeductFeeDecorator(ak AccountKeeper, bk authtypes.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) DeductFeeDecorator {
if tfc == nil {
tfc = checkTxFee
}
return DeductFeeDecorator{
accountKeeper: ak,
bankKeeper: bk,
feegrantKeeper: fk,
txFeeChecker: tfc,
}
}
func checkTxFee(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()
if ctx.IsCheckTx() {
// min-gas-prices is used to determine which coins are to be deducted and if they are provided as fee
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 := sdkmath.LegacyNewDec(int64(gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
if !feeCoins.IsAnyGTE(requiredFees) {
return nil, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
}
return feeCoins, nil
}
func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
}
var (
err error
)
fee := feeTx.GetFee()
if !simulate {
fee, err = dfd.txFeeChecker(ctx, tx)
if err != nil {
return ctx, err
}
}
if err := dfd.checkDeductFee(ctx, tx, fee); err != nil {
return ctx, err
}
return next(ctx, tx, simulate)
}
func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error {
feeTx, ok := sdkTx.(sdk.FeeTx)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil {
return fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName)
}
feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()
deductFeesFrom := feePayer
// if feegranter set deduct fee from feegranter account.
// this works with only when feegrant enabled.
if feeGranter != nil {
if dfd.feegrantKeeper == nil {
return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled")
} else if !feeGranter.Equals(feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs())
if err != nil {
return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer)
}
}
deductFeesFrom = feeGranter
}
deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom)
}
// deduct the fees
if !fee.IsZero() {
err := dfd.deductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee)
if err != nil {
return err
}
}
events := sdk.Events{
sdk.NewEvent(
sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()),
sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()),
),
}
ctx.EventManager().EmitEvents(events)
return nil
}
// DeductFees deducts fees from the given account.
func (dfd DeductFeeDecorator) deductFees(bankKeeper authtypes.BankKeeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins) error {
// check if exactly one fee is provided and is greater than 0
if !fees.IsValid() && len(fees) == 1 {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
// take exactly on token as fee no matter what is provided
denoms := fees.Denoms()
feesToCollect := sdk.Coins{sdk.NewCoin(denoms[0], sdk.OneInt())}
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), authtypes.FeeCollectorName, feesToCollect)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
return nil
}

View File

@ -2,6 +2,7 @@ package dao
import ( import (
"fmt" "fmt"
"github.com/planetmint/planetmint-go/config" "github.com/planetmint/planetmint-go/config"
"github.com/planetmint/planetmint-go/testutil/network" "github.com/planetmint/planetmint-go/testutil/network"
"github.com/planetmint/planetmint-go/testutil/sample" "github.com/planetmint/planetmint-go/testutil/sample"
@ -119,17 +120,18 @@ func (s *E2ETestSuite) TestDistributeCollectedFees() {
err = s.network.WaitForNextBlock() err = s.network.WaitForNextBlock()
s.Require().NoError(err) s.Require().NoError(err)
// assert that alice has 6 of 20 paid fee tokens based on 5000 stake of 15000 total stake // assert that alice has 0 of 2 paid fee tokens based on 5000 stake of 15000 total stake
out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{ out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{
aliceAddr.String(), aliceAddr.String(),
}) })
assert.Contains(s.T(), out.String(), "6") assert.NotContains(s.T(), out.String(), "node0token")
s.Require().NoError(err) s.Require().NoError(err)
// assert that bob has 13 of 20 paid fee tokens based on 10000 stake of 15000 total stake // assert that bob has 1 of 2 paid fee tokens based on 10000 stake of 15000 total stake
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{ out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{
bobAddr.String(), bobAddr.String(),
}) })
assert.Contains(s.T(), out.String(), "13") assert.Contains(s.T(), out.String(), "1")
assert.Contains(s.T(), out.String(), "node0token")
s.Require().NoError(err) s.Require().NoError(err)
} }

View File

@ -79,7 +79,7 @@ func DefaultConfig() network.Config {
ChainID: chainID, ChainID: chainID,
NumValidators: 1, NumValidators: 1,
BondDenom: sdk.DefaultBondDenom, BondDenom: sdk.DefaultBondDenom,
MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), MinGasPrices: fmt.Sprintf("0.000003%s", sdk.DefaultBondDenom),
AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction),
StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction),
BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction),

View File

@ -29,7 +29,7 @@ const Name = "machine"
const Amount = "1000stake" const Amount = "1000stake"
// Fees is the amount of fees to use in tests // Fees is the amount of fees to use in tests
const Fees = "2stake" const Fees = "1stake"
// DefaultDerivationPath is the BIP44Prefix for PLMNT (see https://github.com/satoshilabs/slips/blob/master/slip-0044.md) // DefaultDerivationPath is the BIP44Prefix for PLMNT (see https://github.com/satoshilabs/slips/blob/master/slip-0044.md)
const DefaultDerivationPath = "m/44'/8680'/0'/0/0" const DefaultDerivationPath = "m/44'/8680'/0'/0/0"