package app
import (
"fmt"
"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
ctypes "github.com/joinresistance/space-pussy/types"
wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante"
"github.com/cosmos/ibc-go/v3/modules/core/keeper"
bandwidthkeeper "github.com/joinresistance/space-pussy/x/bandwidth/keeper"
bandwidthtypes "github.com/joinresistance/space-pussy/x/bandwidth/types"
graphtypes "github.com/joinresistance/space-pussy/x/graph/types"
)
type HandlerBaseOptions struct {
AccountKeeper ante.AccountKeeper
BankKeeper bankkeeper.Keeper
FeegrantKeeper ante.FeegrantKeeper
BandwidthMeter *bandwidthkeeper.BandwidthMeter
SignModeHandler authsigning.SignModeHandler
SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error
}
type HandlerOptions struct {
HandlerBaseOptions
IBCKeeper *keeper.Keeper
WasmConfig *wasmTypes.WasmConfig
TXCounterStoreKey sdk.StoreKey
}
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler")
}
if options.BankKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler")
}
if options.BandwidthMeter == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bandwidth meter is required for AnteHandler")
}
if options.SignModeHandler == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}
if options.WasmConfig == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "wasm config is required for ante builder")
}
if options.TXCounterStoreKey == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tx counter key is required for ante builder")
}
var sigGasConsumer = options.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = ante.DefaultSigVerificationGasConsumer
}
anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), wasmkeeper.NewCountTXDecorator(options.TXCounterStoreKey),
ante.NewRejectExtensionOptionsDecorator(),
NewMempoolFeeDecorator(),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
NewDeductFeeBandRouterDecorator(options.AccountKeeper, options.BankKeeper, options.BandwidthMeter, options.FeegrantKeeper),
ante.NewSetPubKeyDecorator(options.AccountKeeper), ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewAnteDecorator(options.IBCKeeper),
}
return sdk.ChainAnteDecorators(anteDecorators...), nil
}
type DeductFeeBandDecorator struct {
ak ante.AccountKeeper
bankKeeper bankkeeper.Keeper
bandMeter *bandwidthkeeper.BandwidthMeter
feegrantKeeper ante.FeegrantKeeper
}
func NewDeductFeeBandRouterDecorator(
ak ante.AccountKeeper,
bk bankkeeper.Keeper,
bm *bandwidthkeeper.BandwidthMeter,
fk ante.FeegrantKeeper,
) DeductFeeBandDecorator {
return DeductFeeBandDecorator{
ak: ak,
bankKeeper: bk,
bandMeter: bm,
feegrantKeeper: fk,
}
}
func (drd DeductFeeBandDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeFlag := false
feeSplitFlag := false
bandwidthFlag := false
program2pay := sdk.AccAddress{}
for _, msg := range tx.GetMsgs() {
if (sdk.MsgTypeURL(msg) == sdk.MsgTypeURL(&graphtypes.MsgCyberlink{})) {
bandwidthFlag = true
}
if (sdk.MsgTypeURL(msg) != sdk.MsgTypeURL(&graphtypes.MsgCyberlink{})) {
feeFlag = true
}
if sdk.MsgTypeURL(msg) == sdk.MsgTypeURL(&wasmtypes.MsgExecuteContract{}) {
executeTx, ok := msg.(*wasm.MsgExecuteContract)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Msg must be a MsgExecuteContract")
}
feeSplitFlag = true
program2pay, err = sdk.AccAddressFromBech32(executeTx.Contract)
if err != nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "Address must be valid")
}
}
}
if bandwidthFlag && (feeFlag || feeSplitFlag) {
return ctx, sdkerrors.Wrap(sdkerrors.ErrNotSupported, "Support only batch of cyberlinks")
}
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
if addr := drd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName))
}
fee := feeTx.GetFee()
feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()
deductFeesFrom := feePayer
if feeGranter != nil {
if drd.feegrantKeeper == nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled")
} else if !feeGranter.Equals(feePayer) {
err := drd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs())
if err != nil {
return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer)
}
}
deductFeesFrom = feeGranter
}
deductFeesFromAcc := drd.ak.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", feePayer)
}
if feeSplitFlag {
if !feeTx.GetFee().IsZero() {
err = DeductFees(drd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee(), program2pay)
if err != nil {
return ctx, err
}
}
events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()),
)}
ctx.EventManager().EmitEvents(events)
return next(ctx, tx, simulate)
}
if feeFlag {
if !feeTx.GetFee().IsZero() {
err = DeductFees(drd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee(), nil)
if err != nil {
return ctx, err
}
}
events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()),
)}
ctx.EventManager().EmitEvents(events)
return next(ctx, tx, simulate)
}
txCost := drd.bandMeter.GetPricedTotalCyberlinksCost(ctx, tx)
accountBandwidth := drd.bandMeter.GetCurrentAccountBandwidth(ctx, deductFeesFromAcc.GetAddress())
currentBlockSpentBandwidth := drd.bandMeter.GetCurrentBlockSpentBandwidth(ctx)
maxBlockBandwidth := drd.bandMeter.GetMaxBlockBandwidth(ctx)
if !simulate {
if !accountBandwidth.HasEnoughRemained(txCost) {
return ctx, bandwidthtypes.ErrNotEnoughBandwidth
} else if (txCost + currentBlockSpentBandwidth) > maxBlockBandwidth {
return ctx, bandwidthtypes.ErrExceededMaxBlockBandwidth
} else {
if !ctx.IsCheckTx() && !ctx.IsReCheckTx() {
err = drd.bandMeter.ConsumeAccountBandwidth(ctx, accountBandwidth, txCost)
if err != nil {
return ctx, err
}
drd.bandMeter.AddToBlockBandwidth(txCost)
}
}
}
return next(ctx, tx, simulate)
}
func DeductFees(bankKeeper bankkeeper.Keeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins, program2pay sdk.AccAddress) error {
if !fees.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
if program2pay == nil {
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
} else {
feeInCYB := sdk.NewDec(fees.AmountOf(ctypes.CYB).Int64())
toProgram := feeInCYB.Mul(sdk.NewDecWithPrec(80, 2))
toValidators := feeInCYB.Sub(toProgram)
toValidatorsAmount := sdk.NewCoins(sdk.NewCoin(ctypes.CYB, toValidators.RoundInt()))
toProgramAmount := sdk.NewCoins(sdk.NewCoin(ctypes.CYB, toProgram.RoundInt()))
err := bankKeeper.SendCoins(ctx, acc.GetAddress(), program2pay, toProgramAmount)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
err = bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, toValidatorsAmount)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
}
return nil
}
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.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}
feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()
cyberlinksNoFeeFlag := true
for _, msg := range tx.GetMsgs() {
if (sdk.MsgTypeURL(msg) != sdk.MsgTypeURL(&graphtypes.MsgCyberlink{})) {
cyberlinksNoFeeFlag = false
break
}
}
if ctx.IsCheckTx() && !simulate && !cyberlinksNoFeeFlag {
minGasPrices := ctx.MinGasPrices()
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))
glDec := sdk.NewDec(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 ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
}
return next(ctx, tx, simulate)
}