mirror of
https://github.com/planetmint/planetmint-go.git
synced 2025-06-08 07:06:39 +00:00
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:
parent
e80ce7e894
commit
94022e1102
@ -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),
|
||||||
|
51
app/ante/check_machine_decorator.go
Normal file
51
app/ante/check_machine_decorator.go
Normal 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)
|
||||||
|
}
|
172
app/ante/deduct_fee_decorator.go
Normal file
172
app/ante/deduct_fee_decorator.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user