diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index df40b0a4..392d8d28 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -29,7 +29,7 @@ func (suite *AnteTestSuite) TestAnteHandler() { suite.Require().NoError(acc.SetSequence(1)) suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - suite.Require().NoError(suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt(10000000000))) + suite.FundAccount(suite.ctx, acc.GetAddress(), big.NewInt(10000000000)) suite.app.FeemarketKeeper.SetBaseFee(suite.ctx, big.NewInt(100)) } diff --git a/app/ante/charge_gasfree_fees.go b/app/ante/charge_gasfree_fees.go new file mode 100644 index 00000000..906b094b --- /dev/null +++ b/app/ante/charge_gasfree_fees.go @@ -0,0 +1,59 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + gasfreekeeper "github.com/althea-net/althea-L1/x/gasfree/keeper" + microtxkeeper "github.com/althea-net/althea-L1/x/microtx/keeper" + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" +) + +// nolint: exhaustruct +var microtxMsgType string = sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}) + +// ChargeGasfreeFeesDecorator enables custom fee charging for gas-free transactions on a per-message basis +type ChargeGasfreeFeesDecorator struct { + ak AccountKeeper + gasfreeKeeper gasfreekeeper.Keeper + microtxKeeper microtxkeeper.Keeper +} + +func NewChargeGasfreeFeesDecorator(ak AccountKeeper, gasfreeKeeper gasfreekeeper.Keeper, microtxKeeper microtxkeeper.Keeper) ChargeGasfreeFeesDecorator { + return ChargeGasfreeFeesDecorator{ + ak: ak, + gasfreeKeeper: gasfreeKeeper, + microtxKeeper: microtxKeeper, + } +} + +// AnteHandle charges fees for gas-free transactions on a case-by-case basis +func (satd ChargeGasfreeFeesDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // Handle any microtxs individually + err := satd.DeductAnyMicrotxFees(ctx, tx) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to deduct microtx fees") + } + + return next(ctx, tx, simulate) +} + +func (satd ChargeGasfreeFeesDecorator) DeductAnyMicrotxFees(ctx sdk.Context, tx sdk.Tx) error { + // Only deduct Microtx fees in the AnteHandler if they are currently configured as gasfree messages + if !satd.gasfreeKeeper.IsGasFreeMsgType(ctx, microtxMsgType) { + return nil + } + + for _, msg := range tx.GetMsgs() { + msgMicrotx, isMicrotx := msg.(*microtxtypes.MsgMicrotx) + if isMicrotx { + feeCollected, err := satd.microtxKeeper.DeductMsgMicrotxFee(ctx, msgMicrotx) + if err != nil { + return sdkerrors.Wrap(err, "unable to collect microtx fee prior to msg execution") + } + ctx.EventManager().EmitEvent(microtxtypes.NewEventMicrotxFeeCollected(msgMicrotx.Sender, *feeCollected)) + } + } + + return nil +} diff --git a/app/ante/charge_gasfree_fees_test.go b/app/ante/charge_gasfree_fees_test.go new file mode 100644 index 00000000..e01f7703 --- /dev/null +++ b/app/ante/charge_gasfree_fees_test.go @@ -0,0 +1,197 @@ +package ante_test + +import ( + "fmt" + "math/big" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + altheaconfig "github.com/althea-net/althea-L1/config" + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" +) + +// -------------- +// In this file we test that the ChargeGasfreeFeesDecorator only charges fees where applicable, so we have 4 checked scenarios: +// 1. There are no gasfree messages (No gasfree fees should be charged) +// 2. MsgMicrotx is the only gasfree message (expected, charge a % fee of the microtx transfer amount) +// 3. MsgSend is the only gasfree message (No gasfree fees should be charged because there is no logic for that) +// 4. MsgSend AND MsgMicrotx are the only gasfree messages (Only charge a % fee of the microtx transfer amount) +// Note that there is only one special gasfree fee deduction for MsgMicrotx so the tests do not make full assertions about MsgSend deductions + +func runGasfreeTests(suite *AnteTestSuite, gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx sdk.Context, msgMicrotxTx, msgSendTx, bothTx sdk.Tx, addr sdk.AccAddress, testDenom string) error { + baseDenom := altheaconfig.BaseDenom + + // --- Microtx is the only Gasfree Msg type --- + // Only charge baseDenom fees for microtx + if err := runGasfreeTest(suite, true, gasfreeMicrotxCtx, msgMicrotxTx, addr, baseDenom, testDenom); err != nil { + return err + } + // Charge testDenom fees for the others + if err := runGasfreeTest(suite, false, gasfreeMicrotxCtx, msgSendTx, addr, testDenom, "fake"); err != nil { + return err + } + if err := runGasfreeTest(suite, false, gasfreeMicrotxCtx, bothTx, addr, testDenom, "fake"); err != nil { + return err + } + + // --- Send is the only Gasfree Msg type --- + if err := runGasfreeTest(suite, false, gasfreeSendCtx, msgMicrotxTx, addr, testDenom, "fake"); err != nil { + return err + } + // Here we only want to see that there is no error, no balances should change + if err := runNoChangeGasfreeTest(suite, false, gasfreeSendCtx, msgMicrotxTx, addr, testDenom); err != nil { + return err + } + + if err := runGasfreeTest(suite, false, gasfreeSendCtx, bothTx, addr, testDenom, "fake"); err != nil { + return err + } + + // --- No Gasfree Msg types --- + if err := runGasfreeTest(suite, false, noGasfreeCtx, msgMicrotxTx, addr, testDenom, "fake"); err != nil { + return err + } + if err := runGasfreeTest(suite, false, noGasfreeCtx, msgSendTx, addr, testDenom, "fake"); err != nil { + return err + } + if err := runGasfreeTest(suite, false, noGasfreeCtx, bothTx, addr, testDenom, "fake"); err != nil { + return err + } + + // --- Both Gasfree Msg types --- + if err := runGasfreeTest(suite, true, bothGasfreeCtx, msgMicrotxTx, addr, baseDenom, testDenom); err != nil { + return err + } + // Here we only want to see that there is no error, no balances should change + if err := runNoChangeGasfreeTest(suite, true, bothGasfreeCtx, msgSendTx, addr, testDenom); err != nil { + return err + } + if err := runGasfreeTest(suite, true, bothGasfreeCtx, bothTx, addr, baseDenom, testDenom); err != nil { + return err + } + return nil +} + +func runGasfreeTest(suite *AnteTestSuite, expPass bool, ctx sdk.Context, tx sdk.Tx, addr sdk.AccAddress, shouldChangeDenom, shouldNotChangeDenom string) error { + shouldChangeBalance := suite.app.BankKeeper.GetBalance(ctx, addr, shouldChangeDenom) + shouldNotChangeBalance := suite.app.BankKeeper.GetBalance(ctx, addr, shouldNotChangeDenom) + + cached, _ := ctx.CacheContext() + if _, err := suite.anteHandler(cached, tx, false); err != nil && expPass { + return err + } + + if !expPass { + return nil + } + + shouldChangeBalanceAfter := suite.app.BankKeeper.GetBalance(cached, addr, shouldChangeDenom) + shouldNotChangeBalanceAfter := suite.app.BankKeeper.GetBalance(cached, addr, shouldNotChangeDenom) + + var shouldChangeDiff, shouldNotChangeDiff sdk.Int + if shouldChangeBalance.Amount.GT(shouldChangeBalanceAfter.Amount) { + shouldChangeDiff = shouldChangeBalance.Amount.Sub(shouldChangeBalanceAfter.Amount) + } else { + shouldChangeDiff = shouldChangeBalanceAfter.Amount.Sub(shouldChangeBalance.Amount) + } + + if shouldNotChangeBalance.Amount.GT(shouldNotChangeBalanceAfter.Amount) { + shouldNotChangeDiff = shouldNotChangeBalance.Amount.Sub(shouldNotChangeBalanceAfter.Amount) + } else { + shouldNotChangeDiff = shouldNotChangeBalanceAfter.Amount.Sub(shouldNotChangeBalance.Amount) + } + + if !shouldNotChangeDiff.IsZero() { + return fmt.Errorf("expected no change in %s balance, got a difference of +/- %s", shouldNotChangeDenom, shouldNotChangeDiff) + } + + if shouldChangeDiff.IsZero() { + return fmt.Errorf("expected a change in %s balance, got no change", shouldChangeDenom) + } + return nil +} + +func runNoChangeGasfreeTest(suite *AnteTestSuite, expPass bool, ctx sdk.Context, tx sdk.Tx, addr sdk.AccAddress, shouldNotChangeDenom string) error { + shouldNotChangeBalance := suite.app.BankKeeper.GetBalance(ctx, addr, shouldNotChangeDenom) + + cached, _ := ctx.CacheContext() + if _, err := suite.anteHandler(cached, tx, false); err != nil && expPass { + return err + } + + if !expPass { + return nil + } + + shouldNotChangeBalanceAfter := suite.app.BankKeeper.GetBalance(cached, addr, shouldNotChangeDenom) + + var shouldNotChangeDiff sdk.Int + + if shouldNotChangeBalance.Amount.GT(shouldNotChangeBalanceAfter.Amount) { + shouldNotChangeDiff = shouldNotChangeBalance.Amount.Sub(shouldNotChangeBalanceAfter.Amount) + } else { + shouldNotChangeDiff = shouldNotChangeBalanceAfter.Amount.Sub(shouldNotChangeBalance.Amount) + } + + if !shouldNotChangeDiff.IsZero() { + return fmt.Errorf("expected no change in %s balance, got a difference of +/- %s", shouldNotChangeDenom, shouldNotChangeDiff) + } + + return nil +} + +// Checks that the gasfree fees decorator and only the gasfree fees decorator charges a fee where applicable +func (suite *AnteTestSuite) TestChargeGasfreeFeesDecorator() { + suite.SetupTest() + + testDenom := "test" + suite.Require().NoError(suite.app.BankKeeper.MintCoins(suite.ctx, evmtypes.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000000))))) + metadata := banktypes.Metadata{ + Base: testDenom, + Display: testDenom, + Name: testDenom, + Description: "Test coin", + DenomUnits: []*banktypes.DenomUnit{{Denom: testDenom, Exponent: 0, Aliases: []string{}}}, + Symbol: strings.ToUpper(testDenom), + } + suite.app.BankKeeper.SetDenomMetaData(suite.ctx, metadata) + + suite.ctx = suite.ctx.WithIsCheckTx(true) // use checkTx true to trigger mempool fee decorator + suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(altheaconfig.BaseDenom, sdk.NewInt(100)), sdk.NewDecCoin(testDenom, sdk.NewInt(100)))) + privKey := suite.NewCosmosPrivkey() + addr := sdk.AccAddress(privKey.PubKey().Address().Bytes()) + holding := int64(10000000000) + suite.FundAccount(suite.ctx, addr, big.NewInt(holding)) + suite.Require().NoError(suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, evmtypes.ModuleName, addr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(holding))))) + denom := altheaconfig.BaseDenom + amount := sdk.NewInt(200000) + var feeAmount int64 = 100000000 + + // Create messages whose fees are the testDenom, we only want to see the testDenom balance change on non-gasfree txs + msgSendMsg := suite.CreateTestCosmosMsgSend(sdk.NewInt(0), denom, amount, addr, addr) + msgSendMsg.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(feeAmount)))) + msgSendTx := suite.CreateSignedCosmosTx(suite.ctx, msgSendMsg, privKey) + msgMicrotxMsg := suite.CreateTestCosmosMsgMicrotx(sdk.NewInt(0), denom, amount, addr, addr) + msgMicrotxMsg.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(feeAmount)))) + msgMicrotxTx := suite.CreateSignedCosmosTx(suite.ctx, msgMicrotxMsg, privKey) + bothMsgs := suite.CreateTestCosmosMsgMicrotxMsgSend(sdk.NewInt(0), denom, amount, addr, addr) + bothMsgs.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(feeAmount)))) + bothTx := suite.CreateSignedCosmosTx(suite.ctx, bothMsgs, privKey) + + gasfreeMicrotxCtx, _ := suite.ctx.CacheContext() + gasfreeSendCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(gasfreeSendCtx, []string{sdk.MsgTypeURL(&banktypes.MsgSend{})}) + noGasfreeCtx, _ := suite.ctx.CacheContext() + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(noGasfreeCtx, []string{}) + bothGasfreeCtx, _ := suite.ctx.CacheContext() + + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(bothGasfreeCtx, []string{sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), sdk.MsgTypeURL(&banktypes.MsgSend{})}) + + // Expect the error from the mempool fee decorator to contain something like "insufficient fees; got: x required: provided fee < minimum global feey" + suite.Require().NoError(runGasfreeTests(suite, gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx, msgMicrotxTx, msgSendTx, bothTx, addr, testDenom)) +} diff --git a/app/ante/cosmos_handler_test.go b/app/ante/cosmos_handler_test.go new file mode 100644 index 00000000..37b9ece4 --- /dev/null +++ b/app/ante/cosmos_handler_test.go @@ -0,0 +1,184 @@ +package ante_test + +import ( + "math/big" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + altheaconfig "github.com/althea-net/althea-L1/config" + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" +) + +// ---------------- +// This file checks that the configured bypass decorators are being bypassed as expected, which gives 4 possible scenarios: +// 1. There are no gasfree messages (no bypass) +// 2. MsgMicrotx is the only gasfree message (expected, only bypass on txs containing only MsgMicrotx) +// 3. MsgSend is the only gasfree message (only bypass on txs containing only MsgSend) +// 4. MsgSend AND MsgMicrotx are the only gasfree messages (only bypass on txs containing MsgSend AND/OR MsgMicrotx) + +func runBypassTest(suite *AnteTestSuite, expErrorStr string, gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx sdk.Context, msgMicrotxTx, msgSendTx, bothTx sdk.Tx) error { + // --- Microtx is the only Gasfree Msg type --- + // Expect bypass for the Microtx tx + cached, _ := gasfreeMicrotxCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgMicrotxTx, false); err != nil { + return sdkerrors.Wrap(err, "microtx gasfree expected no error") + } + // Expect failure for the Send tx + cached, _ = gasfreeMicrotxCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgSendTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "microtx gasfree sent send - expected error") + } + // Expect failure for both msg tx + cached, _ = gasfreeMicrotxCtx.CacheContext() + if _, err := suite.anteHandler(cached, bothTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "microtx gasfree sent send and microtx - expected error") + } + + // --- Send is the only Gasfree Msg type --- + cached, _ = gasfreeSendCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgMicrotxTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "send gasfree sent microtx - expected error") + } + // Expect failure for the Send tx + cached, _ = gasfreeSendCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgSendTx, false); err != nil { + return sdkerrors.Wrap(err, "send gasfree expected no error") + } + // Expect failure for both msg tx + cached, _ = gasfreeSendCtx.CacheContext() + if _, err := suite.anteHandler(cached, bothTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "send gasfree sent send and microtx - expected error") + } + + // --- No Gasfree Msg types --- + // Expect failure for the Microtx tx + cached, _ = noGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgMicrotxTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "no gasfree msgs sent microtx - expected error") + } + // Expect failure for the Send tx + cached, _ = noGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgSendTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "no gasfree msgs sent send - expected error") + } + // Expect failure for both msg tx + cached, _ = noGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, bothTx, false); !strings.Contains(err.Error(), expErrorStr) { + return sdkerrors.Wrap(err, "no gasfree msgs sent send and microtx - expected error") + } + + // --- Send and Microtx are gasfree Msg types --- + // Expect success on all txs + cached, _ = bothGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgMicrotxTx, false); err != nil { + return sdkerrors.Wrap(err, "send + microtx gasfree expected no error") + } + cached, _ = bothGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, msgSendTx, false); err != nil { + return sdkerrors.Wrap(err, "send + microtx gasfree expected no error") + } + cached, _ = bothGasfreeCtx.CacheContext() + if _, err := suite.anteHandler(cached, bothTx, false); err != nil { + return sdkerrors.Wrap(err, "send + microtx gasfree expected no error") + } + return nil + +} + +// Checks that the MempoolFee antedecorator is bypassed for applicable txs +func (suite *AnteTestSuite) TestCosmosAnteHandlerMempoolFeeBypass() { + suite.SetupTest() + suite.ctx = suite.ctx.WithIsCheckTx(true) // use checkTx true to trigger mempool fee decorator + suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(altheaconfig.BaseDenom, sdk.NewInt(100)))) + privKey := suite.NewCosmosPrivkey() + addr := sdk.AccAddress(privKey.PubKey().Address().Bytes()) + suite.FundAccount(suite.ctx, addr, big.NewInt(10000000000)) + denom := altheaconfig.BaseDenom + amount := sdk.NewInt(200000) + + msgSendMsg := suite.CreateTestCosmosMsgSend(sdk.NewInt(0), denom, amount, addr, addr) + msgSendMsg.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(altheaconfig.BaseDenom, sdk.NewInt(1)))) + msgSendTx := suite.CreateSignedCosmosTx(suite.ctx, msgSendMsg, privKey) + msgMicrotxTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotx(sdk.NewInt(0), denom, amount, addr, addr), privKey) + bothTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotxMsgSend(sdk.NewInt(0), denom, amount, addr, addr), privKey) + + gasfreeMicrotxCtx, _ := suite.ctx.CacheContext() + gasfreeSendCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(gasfreeSendCtx, []string{sdk.MsgTypeURL(&banktypes.MsgSend{})}) + noGasfreeCtx, _ := suite.ctx.CacheContext() + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(noGasfreeCtx, []string{}) + bothGasfreeCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(bothGasfreeCtx, []string{sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), sdk.MsgTypeURL(&banktypes.MsgSend{})}) + + // Expect the error from the mempool fee decorator to contain something like "insufficient fees; got: x required: provided fee < minimum global feey" + suite.Require().NoError(runBypassTest(suite, "insufficient fees; got:", gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx, msgMicrotxTx, msgSendTx, bothTx)) +} + +// Checks that the MinGasPrices antedecorator is bypassed for applicable txs +// nolint: dupl +func (suite *AnteTestSuite) TestCosmosAnteHandlerMinGasPricesBypass() { + suite.SetupTest() + suite.ctx = suite.ctx.WithIsCheckTx(false) // use checkTx false to avoid triggering mempool fee decorator + feemarketParams := suite.app.FeemarketKeeper.GetParams(suite.ctx) + feemarketParams.MinGasPrice = sdk.NewDec(100) + suite.app.FeemarketKeeper.SetParams(suite.ctx, feemarketParams) // Set the min gas price to trigger failure in the MinGasPricesDecorator + privKey := suite.NewCosmosPrivkey() + addr := sdk.AccAddress(privKey.PubKey().Address().Bytes()) + suite.FundAccount(suite.ctx, addr, big.NewInt(10000000000)) + denom := altheaconfig.BaseDenom + amount := sdk.NewInt(200000) + + msgSendTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgSend(sdk.NewInt(10), denom, amount, addr, addr), privKey) + msgMicrotxTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotx(sdk.NewInt(10), denom, amount, addr, addr), privKey) + bothTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotxMsgSend(sdk.NewInt(10), denom, amount, addr, addr), privKey) + + gasfreeMicrotxCtx, _ := suite.ctx.CacheContext() + gasfreeSendCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(gasfreeSendCtx, []string{sdk.MsgTypeURL(&banktypes.MsgSend{})}) + noGasfreeCtx, _ := suite.ctx.CacheContext() + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(noGasfreeCtx, []string{}) + bothGasfreeCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(bothGasfreeCtx, []string{sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), sdk.MsgTypeURL(&banktypes.MsgSend{})}) + + // Expect the error from the MinGasPricesDecorator to contain something like "provided fee < minimum global fee (x < y). Please increase the gas price." + suite.Require().NoError(runBypassTest(suite, "provided fee < minimum global fee", gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx, msgMicrotxTx, msgSendTx, bothTx)) +} + +// Checks that the DeductFee antedecorator is bypassed for applicable txs +// nolint: dupl +func (suite *AnteTestSuite) TestCosmosAnteHandlerDeductFeeBypass() { + suite.SetupTest() + suite.ctx = suite.ctx.WithIsCheckTx(false) // use checkTx false to avoid triggering mempool fee decorator + feemarketParams := suite.app.FeemarketKeeper.GetParams(suite.ctx) + feemarketParams.MinGasPrice = sdk.NewDec(0) + suite.app.FeemarketKeeper.SetParams(suite.ctx, feemarketParams) // Set the min gas price to avoid failure from MinGasPricesDecorator + privKey := suite.NewCosmosPrivkey() + addr := sdk.AccAddress(privKey.PubKey().Address().Bytes()) + suite.FundAccount(suite.ctx, addr, big.NewInt(1)) + denom := altheaconfig.BaseDenom + amount := sdk.NewInt(2) + + msgSendTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgSend(sdk.NewInt(2), denom, amount, addr, addr), privKey) + msgMicrotxTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotx(sdk.NewInt(2), denom, amount, addr, addr), privKey) + bothTx := suite.CreateSignedCosmosTx(suite.ctx, suite.CreateTestCosmosMsgMicrotxMsgSend(sdk.NewInt(2), denom, amount, addr, addr), privKey) + + gasfreeMicrotxCtx, _ := suite.ctx.CacheContext() + gasfreeSendCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(gasfreeSendCtx, []string{sdk.MsgTypeURL(&banktypes.MsgSend{})}) + noGasfreeCtx, _ := suite.ctx.CacheContext() + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(noGasfreeCtx, []string{}) + bothGasfreeCtx, _ := suite.ctx.CacheContext() + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(bothGasfreeCtx, []string{sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), sdk.MsgTypeURL(&banktypes.MsgSend{})}) + + // Expect the error to be a wrapped insufficient funds error + suite.Require().NoError(runBypassTest(suite, "insufficient funds: insufficient funds", gasfreeMicrotxCtx, gasfreeSendCtx, noGasfreeCtx, bothGasfreeCtx, msgMicrotxTx, msgSendTx, bothTx)) +} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 1b67b55b..1fbf8c41 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -16,6 +16,10 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" vestingtypes "github.com/Canto-Network/Canto/v5/x/vesting/types" + + "github.com/althea-net/althea-L1/x/gasfree" + gasfreekeeper "github.com/althea-net/althea-L1/x/gasfree/keeper" + microtxkeeper "github.com/althea-net/althea-L1/x/microtx/keeper" ) // HandlerOptions defines the list of module keepers required to run the canto @@ -32,6 +36,8 @@ type HandlerOptions struct { SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error Cdc codec.BinaryCodec MaxTxGasWanted uint64 + GasfreeKeeper *gasfreekeeper.Keeper + MicrotxKeeper *microtxkeeper.Keeper } // Validate checks if the keepers are defined @@ -54,6 +60,12 @@ func (options HandlerOptions) Validate() error { if options.EvmKeeper == nil { return sdkerrors.Wrap(sdkerrors.ErrLogic, "evm keeper is required for AnteHandler") } + if options.GasfreeKeeper == nil { + return sdkerrors.Wrap(sdkerrors.ErrLogic, "gasfree keeper is required for AnteHandler") + } + if options.MicrotxKeeper == nil { + return sdkerrors.Wrap(sdkerrors.ErrLogic, "microtx keeper is required for AnteHandler") + } return nil } @@ -84,12 +96,17 @@ func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { ante.NewSetUpContextDecorator(), ante.NewRejectExtensionOptionsDecorator(), ante.NewValidateBasicDecorator(), - ante.NewMempoolFeeDecorator(), - ethante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), + // Gasfree txs ignore the mempool fee requirement + gasfree.NewSelectiveBypassDecorator(*options.GasfreeKeeper, ante.NewMempoolFeeDecorator()), + // Gasfree txs ignore the min gas price requirement + gasfree.NewSelectiveBypassDecorator(*options.GasfreeKeeper, ethante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper)), ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), - ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + // Gasfree txs do not have fees deducted the normal way, their fees will be deducted separately + gasfree.NewSelectiveBypassDecorator(*options.GasfreeKeeper, ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper)), + // Charge gas fees for gasfree messages + NewChargeGasfreeFeesDecorator(options.AccountKeeper, *options.GasfreeKeeper, *options.MicrotxKeeper), NewVestingDelegationDecorator(options.AccountKeeper, options.StakingKeeper, options.Cdc), NewValidatorCommissionDecorator(options.Cdc), // SetPubKeyDecorator must be called before all signature verification decorators diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 2027ce98..69e7a2b5 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -2,7 +2,6 @@ package ante_test import ( "encoding/json" - "math" "math/big" "testing" "time" @@ -50,6 +49,7 @@ import ( althea "github.com/althea-net/althea-L1/app" ante "github.com/althea-net/althea-L1/app/ante" altheaconfig "github.com/althea-net/althea-L1/config" + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" ) type AnteTestSuite struct { @@ -62,7 +62,6 @@ type AnteTestSuite struct { oldAnteHandler sdk.AnteHandler ethSigner ethtypes.Signer enableFeemarket bool - enableLondonHF bool evmParamsOption func(*evmtypes.Params) } @@ -92,13 +91,7 @@ func (suite *AnteTestSuite) SetupTest() { evmGenesis := evmtypes.DefaultGenesisState() evmGenesis.Params.EvmDenom = altheaconfig.BaseDenom evmGenesis.Params.AllowUnprotectedTxs = false - if !suite.enableLondonHF { - maxInt := sdk.NewInt(math.MaxInt64) - evmGenesis.Params.ChainConfig.LondonBlock = &maxInt - evmGenesis.Params.ChainConfig.ArrowGlacierBlock = &maxInt - evmGenesis.Params.ChainConfig.GrayGlacierBlock = &maxInt - evmGenesis.Params.ChainConfig.MergeNetsplitBlock = &maxInt - } + if suite.evmParamsOption != nil { suite.evmParamsOption(&evmGenesis.Params) } @@ -124,16 +117,7 @@ func (suite *AnteTestSuite) SetupTest() { suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) // nolint: exhaustruct - anteHandler := ante.NewAnteHandler(ante.HandlerOptions{ - AccountKeeper: suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - EvmKeeper: suite.app.EvmKeeper, - FeegrantKeeper: nil, - IBCKeeper: suite.app.IbcKeeper, - FeeMarketKeeper: suite.app.FeemarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: althea.SigVerificationGasConsumer, - }) + anteHandler := ante.NewAnteHandler(suite.app.NewAnteHandlerOptions(simapp.EmptyAppOptions{})) suite.anteHandler = anteHandler @@ -208,9 +192,7 @@ func Setup(isCheckTx bool, patchGenesis func(*althea.AltheaApp, althea.GenesisSt func TestAnteTestSuite(t *testing.T) { // nolint: exhaustruct - suite.Run(t, &AnteTestSuite{ - enableLondonHF: true, - }) + suite.Run(t, &AnteTestSuite{}) } func (s *AnteTestSuite) NewEthermintPrivkey() *ethsecp256k1.PrivKey { @@ -229,6 +211,10 @@ func (s *AnteTestSuite) NewEthermintPrivkey() *ethsecp256k1.PrivKey { func (s *AnteTestSuite) NewCosmosPrivkey() *secp256k1.PrivKey { return secp256k1.GenPrivKey() } +func (s *AnteTestSuite) FundAccount(ctx sdk.Context, acc sdk.AccAddress, amount *big.Int) { + address := common.BytesToAddress(acc.Bytes()) + s.Require().NoError(s.app.EvmKeeper.SetBalance(s.ctx, address, amount)) +} func (s *AnteTestSuite) BuildTestEVMTx( from common.Address, @@ -347,6 +333,16 @@ func (suite *AnteTestSuite) CreateTestCosmosMsgSend(gasPrice sdk.Int, denom stri return suite.CreateTestCosmosTxBuilder(gasPrice, denom, banktypes.NewMsgSend(from, to, sdk.NewCoins(sdk.NewCoin(denom, amount)))) } +func (suite *AnteTestSuite) CreateTestCosmosMsgMicrotx(gasPrice sdk.Int, denom string, amount sdk.Int, from sdk.AccAddress, to sdk.AccAddress) client.TxBuilder { + return suite.CreateTestCosmosTxBuilder(gasPrice, denom, microtxtypes.NewMsgMicrotx(from.String(), to.String(), sdk.NewCoin(denom, amount))) +} + +func (suite *AnteTestSuite) CreateTestCosmosMsgMicrotxMsgSend(gasPrice sdk.Int, denom string, amount sdk.Int, from sdk.AccAddress, to sdk.AccAddress) client.TxBuilder { + msgMicrotx := microtxtypes.NewMsgMicrotx(from.String(), to.String(), sdk.NewCoin(denom, amount)) + msgSend := banktypes.NewMsgSend(from, to, sdk.NewCoins(sdk.NewCoin(denom, amount))) + return suite.CreateTestCosmosTxBuilder(gasPrice, denom, msgMicrotx, msgSend) +} + func (suite *AnteTestSuite) CreateTestCosmosTxBuilder(gasPrice sdk.Int, denom string, msgs ...sdk.Msg) client.TxBuilder { txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() @@ -407,6 +403,12 @@ func (suite *AnteTestSuite) SignTestCosmosTx(chainId string, txBuilder client.Tx return txBuilder } +func (suite *AnteTestSuite) CreateSignedCosmosTx(ctx sdk.Context, txBuilder client.TxBuilder, priv cryptotypes.PrivKey) sdk.Tx { + addr := sdk.AccAddress(priv.PubKey().Address().Bytes()) + acc := suite.app.AccountKeeper.GetAccount(ctx, addr) + return suite.SignTestCosmosTx(suite.ctx.ChainID(), txBuilder, priv, acc.GetAccountNumber(), acc.GetSequence()).GetTx() +} + func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { // Build MsgSend recipient := sdk.AccAddress(common.Address{}.Bytes()) diff --git a/app/app.go b/app/app.go index a2f0f0b1..30b8e881 100644 --- a/app/app.go +++ b/app/app.go @@ -121,6 +121,9 @@ import ( "github.com/althea-net/althea-L1/app/ante" altheaappparams "github.com/althea-net/althea-L1/app/params" altheacfg "github.com/althea-net/althea-L1/config" + "github.com/althea-net/althea-L1/x/gasfree" + gasfreekeeper "github.com/althea-net/althea-L1/x/gasfree/keeper" + gasfreetypes "github.com/althea-net/althea-L1/x/gasfree/types" lockup "github.com/althea-net/althea-L1/x/lockup" lockupkeeper "github.com/althea-net/althea-L1/x/lockup/keeper" lockuptypes "github.com/althea-net/althea-L1/x/lockup/types" @@ -205,6 +208,7 @@ var ( erc20types.ModuleName: {authtypes.Minter, authtypes.Burner}, lockuptypes.ModuleName: nil, microtxtypes.ModuleName: nil, + gasfreetypes.ModuleName: nil, feemarkettypes.ModuleName: nil, icatypes.ModuleName: nil, } @@ -256,6 +260,7 @@ type AltheaApp struct { // nolint: golint IbcTransferKeeper *ibctransferkeeper.Keeper LockupKeeper *lockupkeeper.Keeper MicrotxKeeper *microtxkeeper.Keeper + GasfreeKeeper *gasfreekeeper.Keeper EvmKeeper *evmkeeper.Keeper Erc20Keeper *erc20keeper.Keeper FeemarketKeeper *feemarketkeeper.Keeper @@ -274,6 +279,9 @@ type AltheaApp struct { // nolint: golint // Configurator Configurator *module.Configurator + + // amino and proto encoding + EncodingConfig altheaappparams.EncodingConfig } // ValidateMembers checks for unexpected values, typically nil values needed for the chain to operate @@ -334,6 +342,9 @@ func (app AltheaApp) ValidateMembers() { if app.MicrotxKeeper == nil { panic("Nil MicrotxKeeper!") } + if app.GasfreeKeeper == nil { + panic("Nil GasfreeKeeper!") + } if app.EvmKeeper == nil { panic("Nil EvmKeeper!") } @@ -397,7 +408,8 @@ func NewAltheaApp( ibchost.StoreKey, upgradetypes.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, erc20types.StoreKey, evmtypes.StoreKey, feemarkettypes.StoreKey, - lockuptypes.StoreKey, microtxtypes.StoreKey, icahosttypes.StoreKey, + lockuptypes.StoreKey, microtxtypes.StoreKey, gasfreetypes.StoreKey, + icahosttypes.StoreKey, ) // Transient keys which only last for a block before being wiped // Params uses thsi to track whether some parameter changed this block or not @@ -417,6 +429,7 @@ func NewAltheaApp( keys: keys, tKeys: tkeys, memKeys: memKeys, + EncodingConfig: encodingConfig, } // -------------------------------------------------------------------------- @@ -657,9 +670,15 @@ func NewAltheaApp( ) app.LockupKeeper = &lockupKeeper + // Gasfree allows for gasless transactions by bypassing the gas charging ante handlers for specific txs consisting of + // governance controlled message types. These txs are charged fees out-of-band in a separate ante handler + gasfreeKeeper := gasfreekeeper.NewKeeper(appCodec, keys[gasfreetypes.StoreKey], app.GetSubspace(gasfreetypes.ModuleName)) + app.GasfreeKeeper = &gasfreeKeeper + // Microtx enables peer-to-peer automated microtransactions to form the payment layer for Althea-based networks microtxKeeper := microtxkeeper.NewKeeper( - keys[microtxtypes.StoreKey], app.GetSubspace(microtxtypes.ModuleName), appCodec, &bankKeeper, &accountKeeper, &evmKeeper, &erc20Keeper, + keys[microtxtypes.StoreKey], app.GetSubspace(microtxtypes.ModuleName), appCodec, + &bankKeeper, &accountKeeper, &evmKeeper, &erc20Keeper, &gasfreeKeeper, ) app.MicrotxKeeper = µtxKeeper @@ -738,8 +757,9 @@ func NewAltheaApp( ibc.NewAppModule(&ibcKeeper), params.NewAppModule(paramsKeeper), ibcTransferAppModule, + gasfree.NewAppModule(gasfreeKeeper), lockup.NewAppModule(lockupKeeper, bankKeeper), - microtx.NewAppModule(microtxKeeper, accountKeeper, bankKeeper), + microtx.NewAppModule(microtxKeeper, accountKeeper), evm.NewAppModule(&evmKeeper, accountKeeper), erc20.NewAppModule(erc20Keeper, accountKeeper), feemarket.NewAppModule(feemarketKeeper), @@ -773,6 +793,7 @@ func NewAltheaApp( authz.ModuleName, govtypes.ModuleName, paramstypes.ModuleName, + gasfreetypes.ModuleName, lockuptypes.ModuleName, microtxtypes.ModuleName, erc20types.ModuleName, @@ -801,6 +822,7 @@ func NewAltheaApp( genutiltypes.ModuleName, authz.ModuleName, paramstypes.ModuleName, + gasfreetypes.ModuleName, lockuptypes.ModuleName, microtxtypes.ModuleName, erc20types.ModuleName, @@ -826,6 +848,7 @@ func NewAltheaApp( ibctransfertypes.ModuleName, authz.ModuleName, paramstypes.ModuleName, + gasfreetypes.ModuleName, lockuptypes.ModuleName, microtxtypes.ModuleName, erc20types.ModuleName, @@ -875,20 +898,8 @@ func NewAltheaApp( app.SetEndBlocker(app.EndBlocker) // Create the chain of mempool Tx filter functions, aka the AnteHandler - maxGasWanted := cast.ToUint64(appOpts.Get(ethermintsrvflags.EVMMaxTxGasWanted)) - options := ante.HandlerOptions{ - AccountKeeper: accountKeeper, - BankKeeper: bankKeeper, - IBCKeeper: &ibcKeeper, - FeeMarketKeeper: feemarketKeeper, - StakingKeeper: stakingKeeper, - EvmKeeper: &evmKeeper, - FeegrantKeeper: nil, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: SigVerificationGasConsumer, - Cdc: appCodec, - MaxTxGasWanted: maxGasWanted, - } + + options := app.NewAnteHandlerOptions(appOpts) if err := options.Validate(); err != nil { panic(fmt.Errorf("invalid antehandler options: %v", err)) } @@ -1112,6 +1123,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(ibchost.ModuleName) paramsKeeper.Subspace(lockuptypes.ModuleName) paramsKeeper.Subspace(microtxtypes.ModuleName) + paramsKeeper.Subspace(gasfreetypes.ModuleName) paramsKeeper.Subspace(evmtypes.ModuleName) paramsKeeper.Subspace(erc20types.ModuleName) paramsKeeper.Subspace(feemarkettypes.ModuleName) @@ -1124,3 +1136,22 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino func (app *AltheaApp) registerUpgradeHandlers() { // No op } + +func (app *AltheaApp) NewAnteHandlerOptions(appOpts servertypes.AppOptions) ante.HandlerOptions { + maxGasWanted := cast.ToUint64(appOpts.Get(ethermintsrvflags.EVMMaxTxGasWanted)) + return ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + IBCKeeper: app.IbcKeeper, + FeeMarketKeeper: app.FeemarketKeeper, + StakingKeeper: app.StakingKeeper, + EvmKeeper: app.EvmKeeper, + FeegrantKeeper: nil, + SignModeHandler: app.EncodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: SigVerificationGasConsumer, + Cdc: app.AppCodec(), + MaxTxGasWanted: maxGasWanted, + GasfreeKeeper: app.GasfreeKeeper, + MicrotxKeeper: app.MicrotxKeeper, + } +} diff --git a/proto/althea/gasfree/v1/genesis.proto b/proto/althea/gasfree/v1/genesis.proto new file mode 100644 index 00000000..8ee81d87 --- /dev/null +++ b/proto/althea/gasfree/v1/genesis.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package althea.gasfree.v1; + +option go_package = "github.com/althea-net/althea-L1/x/gasfree/types"; + +// Params struct +message Params { + // Messages with one of these types will not be charged gas fees in the + // AnteHandler, but will later be charged some form of fee in the Msg handler + repeated string gas_free_message_types = 1; +} + +message GenesisState { + Params params = 1; +} \ No newline at end of file diff --git a/proto/althea/gasfree/v1/query.proto b/proto/althea/gasfree/v1/query.proto new file mode 100644 index 00000000..25da3adf --- /dev/null +++ b/proto/althea/gasfree/v1/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package althea.gasfree.v1; + +import "google/api/annotations.proto"; +import "gogoproto/gogo.proto"; +import "althea/gasfree/v1/genesis.proto"; + +option go_package = "github.com/althea-net/althea-L1/x/gasfree/types"; + +// Query defines the gRPC querier service. +service Query { + // Params retrieves the total set of onboarding parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/althea/gasfree/v1/params"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/proto/althea/microtx/v1/msgs.proto b/proto/althea/microtx/v1/msgs.proto index c637b8e5..2b26baaf 100644 --- a/proto/althea/microtx/v1/msgs.proto +++ b/proto/althea/microtx/v1/msgs.proto @@ -42,7 +42,13 @@ message EventMicrotx { string receiver = 2; repeated cosmos.base.v1beta1.Coin amounts = 3 [ (gogoproto.nullable) = false ]; - repeated cosmos.base.v1beta1.Coin fee = 4 [ (gogoproto.nullable) = false ]; +} + +// A type for the block's event log, Microtx fees may be collected out-of-band and so +// this event may be emitted in an AnteHandler or during the Msg handler +message EventMicrotxFeeCollected { + string sender = 1; + repeated cosmos.base.v1beta1.Coin fee = 2 [ (gogoproto.nullable) = false ]; } // A type for the block's event log recording when a Liquid Infrastructure account diff --git a/x/gasfree/ante.go b/x/gasfree/ante.go new file mode 100644 index 00000000..5696dc7f --- /dev/null +++ b/x/gasfree/ante.go @@ -0,0 +1,70 @@ +package gasfree + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/althea-net/althea-L1/x/gasfree/keeper" +) + +// NewSelectiveBypassDecorator returns an AnteDecorator which will not execute the +// bypassable decorator for any Txs which **only** contain messages +// of types in the GasFreeMessageTypes set +// This decorator is meant to avoid an exempt Tx from being kicked out of the mempool, +// instead allowing alternative fees collection in the message handler or in secondary AnteHandlers +func NewSelectiveBypassDecorator(gasfreeKeeper keeper.Keeper, bypassable sdk.AnteDecorator) SelectiveBypassDecorator { + return SelectiveBypassDecorator{gasfreeKeeper, bypassable} +} + +// SelectiveBypassDecorator enables AnteHandler bypassing for Txs containing only GasFreeMessageTypes +type SelectiveBypassDecorator struct { + gasfreeKeeper keeper.Keeper + bypassable sdk.AnteDecorator +} + +// AnteHandle first checks to see if the tx contains **only** messages in the GasFreeMessageTypes set, if so it will +// skip calling the bypassable AnteDecorator. Otherwise, the bypassable AnteDecorator will be called as normal. +func (sbd SelectiveBypassDecorator) AnteHandle( + ctx sdk.Context, + tx sdk.Tx, + simulate bool, + next sdk.AnteHandler, +) (newCtx sdk.Context, err error) { + gasFree, err := sbd.gasfreeKeeper.IsGasFreeTx(ctx, sbd.gasfreeKeeper, tx) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to check AnteDecorator can be bypassed") + } + + if !gasFree { + return sbd.bypassable.AnteHandle(ctx, tx, simulate, next) + } else { + return next(ctx, tx, simulate) + } +} + +/* TODO: Handle ICA messages (not mission critical, they will just not be supported by the gasfree module if not considered) + +var data icatypes.InterchainAccountPacketData + +if err := icatypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks + return nil, sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data") +} + +switch data.Type { +case icatypes.EXECUTE_TX: + msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data) + if err != nil { + return nil, err + } + + txResponse, err := k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs) + if err != nil { + return nil, err + } + + return txResponse, nil +default: + return nil, icatypes.ErrUnknownDataType +} +*/ diff --git a/x/gasfree/ante_test.go b/x/gasfree/ante_test.go new file mode 100644 index 00000000..19b3fefe --- /dev/null +++ b/x/gasfree/ante_test.go @@ -0,0 +1,169 @@ +package gasfree_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/althea-net/althea-L1/x/gasfree" + "github.com/althea-net/althea-L1/x/gasfree/types" + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" +) + +// -------------- +// This file checks the SelectiveBypassDecorator by wrapping the test BypassIndicatorDecorator with it, +// the BypassIndicatorDecorator's AnteHandlerRuns value should only increment on non-gasfree messages + +// NewSelectiveBypassDecorator makes a test antehandler which indicates how many times it has been run +func NewBypassIndicatorDecorator() BypassIndicatorDecorator { + var runs int = 0 + return BypassIndicatorDecorator{&runs} +} + +// SelectiveBypassDecorator is a test antehandler which indicates how many times AnteHandle has been run +type BypassIndicatorDecorator struct { + AnteHandlerRuns *int +} + +// AnteHandle simply increments the anteHandlerRuns counter +func (bid BypassIndicatorDecorator) AnteHandle( + ctx sdk.Context, + tx sdk.Tx, + simulate bool, + next sdk.AnteHandler, +) (newCtx sdk.Context, err error) { + *bid.AnteHandlerRuns = *bid.AnteHandlerRuns + 1 + return next(ctx, tx, simulate) +} + +func (suite *GasfreeTestSuite) TestSelectiveBypassAnteDecorator() { + suite.SetupTest() + + var ( + bypassIndicator BypassIndicatorDecorator + anteHandler sdk.AnteHandler + txs []sdk.Tx + ) + + caseSetup := func() { + bypassIndicator = NewBypassIndicatorDecorator() + anteHandler = sdk.ChainAnteDecorators(gasfree.NewSelectiveBypassDecorator(*suite.app.GasfreeKeeper, bypassIndicator)) + } + + testCases := []struct { + name string + malleate func() + deferFunc func() + genState *types.GenesisState + expPass bool + }{ + { + name: "empty no bypass", + malleate: func() { + tx := suite.app.EncodingConfig.TxConfig.NewTxBuilder().GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(1, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + { + name: "single microtx bypass", + malleate: func() { + builder := suite.app.EncodingConfig.TxConfig.NewTxBuilder() + // nolint: exhaustruct + suite.Require().NoError(builder.SetMsgs(µtxtypes.MsgMicrotx{})) + tx := builder.GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(0, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + { + name: "multi microtx bypass", + malleate: func() { + builder := suite.app.EncodingConfig.TxConfig.NewTxBuilder() + // nolint: exhaustruct + suite.Require().NoError(builder.SetMsgs(µtxtypes.MsgMicrotx{}, µtxtypes.MsgMicrotx{}, µtxtypes.MsgMicrotx{})) + tx := builder.GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(0, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + { + name: "single microtx single send no bypass", + malleate: func() { + builder := suite.app.EncodingConfig.TxConfig.NewTxBuilder() + // nolint: exhaustruct + suite.Require().NoError(builder.SetMsgs(µtxtypes.MsgMicrotx{}, &banktypes.MsgSend{})) + tx := builder.GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(1, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + { + name: "multi microtx multi send no bypass", + malleate: func() { + builder := suite.app.EncodingConfig.TxConfig.NewTxBuilder() + // nolint: exhaustruct + suite.Require().NoError(builder.SetMsgs(µtxtypes.MsgMicrotx{}, &banktypes.MsgSend{}, µtxtypes.MsgMicrotx{}, &banktypes.MsgSend{}, µtxtypes.MsgMicrotx{}, &banktypes.MsgSend{})) + tx := builder.GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(1, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + { + name: "single microtx single send gov bypass", + malleate: func() { + // nolint: exhaustruct + suite.app.GasfreeKeeper.SetGasFreeMessageTypes(suite.ctx, []string{sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), sdk.MsgTypeURL(&banktypes.MsgSend{})}) + builder := suite.app.EncodingConfig.TxConfig.NewTxBuilder() + // nolint: exhaustruct + suite.Require().NoError(builder.SetMsgs(µtxtypes.MsgMicrotx{}, &banktypes.MsgSend{})) + tx := builder.GetTx() + txs = []sdk.Tx{tx} + }, + deferFunc: func() { + suite.Require().Equal(0, *bypassIndicator.AnteHandlerRuns) + }, + genState: types.DefaultGenesisState(), + expPass: true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + caseSetup() + tc.malleate() + + for _, tx := range txs { + _, err := anteHandler(ctx, tx, false) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + } + tc.deferFunc() + }) + } + +} diff --git a/x/gasfree/cli/query.go b/x/gasfree/cli/query.go new file mode 100644 index 00000000..0afcf996 --- /dev/null +++ b/x/gasfree/cli/query.go @@ -0,0 +1,53 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/althea-net/althea-L1/x/gasfree/types" +) + +// GetQueryCmd bundles all the query subcmds together so they appear under the `query` or `q` subcommand +func GetQueryCmd() *cobra.Command { + // nolint: exhaustruct + gasfreeQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the gasfree module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + gasfreeQueryCmd.AddCommand([]*cobra.Command{ + CmdQueryParams(), + }...) + + return gasfreeQueryCmd +} + +// CmdQueryParams fetches the current microtx params +func CmdQueryParams() *cobra.Command { + // nolint: exhaustruct + cmd := &cobra.Command{ + Use: "params", + Args: cobra.NoArgs, + Short: "Query gasfree params", + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(&res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/gasfree/keeper/genesis.go b/x/gasfree/keeper/genesis.go new file mode 100644 index 00000000..95e18ff4 --- /dev/null +++ b/x/gasfree/keeper/genesis.go @@ -0,0 +1,23 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/althea-net/althea-L1/x/gasfree/types" +) + +// InitGenesis starts a chain from a genesis state +func InitGenesis(ctx sdk.Context, k Keeper, data types.GenesisState) { + params := data.Params + k.SetGasFreeMessageTypes(ctx, params.GetGasFreeMessageTypes()) +} + +// ExportGenesis exports all the state needed to restart the chain +// from the current state of the chain +func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState { + return types.GenesisState{ + Params: &types.Params{ + GasFreeMessageTypes: k.GetGasFreeMessageTypes(ctx), + }, + } +} diff --git a/x/gasfree/keeper/grpc_query.go b/x/gasfree/keeper/grpc_query.go new file mode 100644 index 00000000..13e0c586 --- /dev/null +++ b/x/gasfree/keeper/grpc_query.go @@ -0,0 +1,22 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/althea-net/althea-L1/x/gasfree/types" +) + +// nolint: exhaustruct +// Enforce via type assertion that the Keeper functions as a query server +var _ types.QueryServer = Keeper{} + +// Params queries the params of the microtx module +func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + p, err := k.GetParamsIfSet(sdk.UnwrapSDKContext(c)) + if err != nil { + return nil, err // Force an empty response on error + } + return &types.QueryParamsResponse{Params: p}, nil +} diff --git a/x/gasfree/keeper/keeper.go b/x/gasfree/keeper/keeper.go new file mode 100644 index 00000000..bf6bbbed --- /dev/null +++ b/x/gasfree/keeper/keeper.go @@ -0,0 +1,133 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/althea-net/althea-L1/x/gasfree/types" +) + +type Keeper struct { + storeKey sdk.StoreKey + paramSpace paramstypes.Subspace + Cdc codec.Codec +} + +func NewKeeper(cdc codec.Codec, storeKey sdk.StoreKey, paramSpace paramstypes.Subspace) Keeper { + // set KeyTable if it has not already been set + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + k := Keeper{ + paramSpace: paramSpace, + storeKey: storeKey, + Cdc: cdc, + } + + return k +} + +// GetParamsIfSet will return the current params, but will return an error if the +// chain is still initializing. By error checking this function is safe to use in +// handling genesis transactions. +func (k Keeper) GetParamsIfSet(ctx sdk.Context) (params types.Params, err error) { + for _, pair := range params.ParamSetPairs() { + if !k.paramSpace.Has(ctx, pair.Key) { + return types.Params{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "the param key %s has not been set", string(pair.Key)) + } + k.paramSpace.Get(ctx, pair.Key, pair.Value) + } + + return +} + +func (k Keeper) GetGasFreeMessageTypes(ctx sdk.Context) []string { + gasFreeMessageTypes := types.DefaultParams().GasFreeMessageTypes + k.paramSpace.GetIfExists(ctx, types.GasFreeMessageTypesKey, &gasFreeMessageTypes) + return gasFreeMessageTypes +} + +func (k Keeper) SetGasFreeMessageTypes(ctx sdk.Context, gasFreeMessageTypes []string) { + k.paramSpace.Set(ctx, types.GasFreeMessageTypesKey, &gasFreeMessageTypes) +} + +func (k Keeper) GetGasFreeMessageTypesSet(ctx sdk.Context) map[string]struct{} { + return createSet(k.GetGasFreeMessageTypes(ctx)) +} + +func createSet(strings []string) map[string]struct{} { + type void struct{} + var member void + set := make(map[string]struct{}) + + for _, str := range strings { + if _, present := set[str]; present { + continue + } + set[str] = member + } + + return set +} + +// Checks if the given Tx contains only messages in the GasFreeMessageTypes set +func (k Keeper) IsGasFreeTx(ctx sdk.Context, keeper Keeper, tx sdk.Tx) (bool, error) { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false, nil + } + + gasFreeMessageSet := k.GetGasFreeMessageTypesSet(ctx) + for _, msg := range msgs { + switch msg := msg.(type) { + // Since authz MsgExec holds Msgs inside of it, all those inner Msgs must be checked + case *authz.MsgExec: + for _, m := range msg.Msgs { + var inner sdk.Msg + err := k.Cdc.UnpackAny(m, &inner) + if err != nil { + return false, + sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unable to unpack authz msgexec message: %v", err) + } + // Check if the inner Msg is acceptable or not, returning an error kicks this whole Tx out of the mempool + if !k.IsGasFreeMsg(gasFreeMessageSet, inner) { + return false, nil + } + } + // TODO: If ICA is used, those Msgs must be checked as well or they could be missed + default: + // Check if this Msg is acceptable or not, returning an error kicks this whole Tx out of the mempool + if !k.IsGasFreeMsg(gasFreeMessageSet, msg) { + return false, nil + } + } + } + + // Discovered only gas-free messages + return true, nil +} + +// IsGasFreeMsg checks if the given msg is in the given gasFreeMessageTypes set, and if it shoudl be +func (k Keeper) IsGasFreeMsg(gasFreeMessageTypes map[string]struct{}, msg sdk.Msg) bool { + return inSet(gasFreeMessageTypes, sdk.MsgTypeURL(msg)) +} + +// IsGasFreeMsgType checks if the given msgType is one of the GasFreeMessageTypes +func (k Keeper) IsGasFreeMsgType(ctx sdk.Context, msgType string) bool { + msgTypes := k.GetGasFreeMessageTypes(ctx) + for _, t := range msgTypes { + if t == msgType { + return true + } + } + return false +} + +func inSet(set map[string]struct{}, key string) bool { + _, present := set[key] + return present +} diff --git a/x/gasfree/module.go b/x/gasfree/module.go new file mode 100644 index 00000000..e8f17b7e --- /dev/null +++ b/x/gasfree/module.go @@ -0,0 +1,197 @@ +package gasfree + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/althea-net/althea-L1/x/gasfree/keeper" + "github.com/althea-net/althea-L1/x/gasfree/types" + "github.com/althea-net/althea-L1/x/microtx/client/cli" +) + +// type check to ensure the interface is properly implemented +var ( + // nolint: exhaustruct + _ module.AppModule = AppModule{} + // nolint: exhaustruct + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic object for module implementation +type AppModuleBasic struct{} + +// Name implements app module basic +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec implements app module basic +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + // types.RegisterCodec(cdc) +} + +// DefaultGenesis implements app module basic +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis implements app module basic +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var data types.GenesisState + if err := cdc.UnmarshalJSON(bz, &data); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return data.ValidateBasic() +} + +// RegisterRESTRoutes implements app module basic +func (AppModuleBasic) RegisterRESTRoutes(ctx client.Context, rtr *mux.Router) { +} + +// GetQueryCmd implements app module basic +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() + +} + +// GetTxCmd implements app module basic +func (AppModuleBasic) GetTxCmd() *cobra.Command { + // nolint: exhaustruct + return &cobra.Command{} +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module. +// also implements app module basic +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + if err != nil { + panic("Failed to register query handler") + } +} + +// RegisterInterfaces implements app module basic +func (b AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + // types.RegisterInterfaces(registry) +} + +//____________________________________________________________________________ + +// AppModule object for module implementation +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper +} + +// NewAppModule creates a new AppModule Object +func NewAppModule(k keeper.Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: k, + } +} + +// Name implements app module +func (AppModule) Name() string { + return types.ModuleName +} + +func (am AppModule) ConsensusVersion() uint64 { + return 1 +} + +// RegisterInvariants implements app module +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + // TODO: Any invariants to check? +} + +// Route implements app module +func (am AppModule) Route() sdk.Route { + // TODO: What should happen here? + return sdk.Route{} +} + +// QuerierRoute implements app module +func (am AppModule) QuerierRoute() string { + return "" +} + +// LegacyQuerierHandler returns the distribution module sdk.Querier. +func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { + return nil +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { +} + +// InitGenesis initializes the genesis state for this module and implements app module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + keeper.InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis exports the current genesis state to a json.RawMessage +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := keeper.ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(&gs) +} + +// BeginBlock implements app module +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock implements app module +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + // TODO: What should happen here? + return nil +} + +//____________________________________________________________________________ + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the distribution module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + // TODO: implement lockup simulation stuffs +} + +// ProposalContents returns all the distribution content functions used to +// simulate governance proposals. +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + // TODO: implement lockupi simulation stuffs + return nil +} + +// RandomizedParams creates randomized distribution param changes for the simulator. +func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + // TODO: implement lockupi simulation stuffs + return nil +} + +// RegisterStoreDecoder registers a decoder for distribution module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + // TODO: implement lockup simulation stuffs + // sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + // TODO: implement lockup simulation stuffs + return nil +} diff --git a/x/gasfree/module_test.go b/x/gasfree/module_test.go new file mode 100644 index 00000000..ccbcae7c --- /dev/null +++ b/x/gasfree/module_test.go @@ -0,0 +1,230 @@ +package gasfree_test + +import ( + "encoding/json" + "testing" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" + tmjson "github.com/tendermint/tendermint/libs/json" + tmlog "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" + dbm "github.com/tendermint/tm-db" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/evmos/ethermint/tests" + ethermint "github.com/evmos/ethermint/types" + "github.com/evmos/ethermint/x/evm" + "github.com/evmos/ethermint/x/evm/statedb" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + althea "github.com/althea-net/althea-L1/app" + altheaconfig "github.com/althea-net/althea-L1/config" +) + +type GasfreeTestSuite struct { + suite.Suite + + ctx sdk.Context + handler sdk.Handler + app *althea.AltheaApp + + signer keyring.Signer + ethSigner ethtypes.Signer + from common.Address +} + +// / DoSetupTest setup test environment, it uses `require.TestingT` to support both `testing.T` and `testing.B`. +func (suite *GasfreeTestSuite) DoSetupTest(t require.TestingT) { + checkTx := false + + // account key + priv, err := ethsecp256k1.GenerateKey() + require.NoError(t, err) + address := common.BytesToAddress(priv.PubKey().Address().Bytes()) + suite.signer = tests.NewSigner(priv) + suite.from = address + // consensus key + priv, err = ethsecp256k1.GenerateKey() + require.NoError(t, err) + consAddress := sdk.ConsAddress(priv.PubKey().Address()) + + suite.app = Setup(checkTx, func(app *althea.AltheaApp, genesis althea.GenesisState) althea.GenesisState { + evmGenesis := evmtypes.DefaultGenesisState() + evmGenesis.Params.EvmDenom = altheaconfig.BaseDenom + evmGenesis.Params.AllowUnprotectedTxs = false + + genesis[evmtypes.ModuleName] = app.AppCodec().MustMarshalJSON(evmGenesis) + return genesis + }) + + coins := sdk.NewCoins(sdk.NewCoin(altheaconfig.BaseDenom, sdk.NewInt(100000000000000))) + genesisState := althea.ModuleBasics.DefaultGenesis(suite.app.AppCodec()) + b32address := sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), priv.PubKey().Address().Bytes()) + balances := []banktypes.Balance{ + { + Address: b32address, + Coins: coins, + }, + { + Address: suite.app.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName).String(), + Coins: coins, + }, + } + // Update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(sdk.NewCoin(altheaconfig.BaseDenom, sdk.NewInt(200000000000000))), []banktypes.Metadata{}) + genesisState[banktypes.ModuleName] = suite.app.AppCodec().MustMarshalJSON(bankGenesis) + + stateBytes, err := tmjson.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // Initialize the chain + suite.app.InitChain( + // nolint: exhaustruct + abci.RequestInitChain{ + ChainId: "althea_417834-1", + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + + // nolint: exhaustruct + suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{ + Height: 1, + ChainID: "althea_417834-1", + Time: time.Now().UTC(), + ProposerAddress: consAddress.Bytes(), + // nolint: exhaustruct + Version: tmversion.Consensus{ + Block: version.BlockProtocol, + }, + // nolint: exhaustruct + LastBlockId: tmproto.BlockID{ + Hash: tmhash.Sum([]byte("block_id")), + PartSetHeader: tmproto.PartSetHeader{ + Total: 11, + Hash: tmhash.Sum([]byte("partset_header")), + }, + }, + AppHash: tmhash.Sum([]byte("app")), + DataHash: tmhash.Sum([]byte("data")), + EvidenceHash: tmhash.Sum([]byte("evidence")), + ValidatorsHash: tmhash.Sum([]byte("validators")), + NextValidatorsHash: tmhash.Sum([]byte("next_validators")), + ConsensusHash: tmhash.Sum([]byte("consensus")), + LastResultsHash: tmhash.Sum([]byte("last_result")), + }) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry()) + evmtypes.RegisterQueryServer(queryHelper, suite.app.EvmKeeper) + + acc := ðermint.EthAccount{ + BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(address.Bytes()), nil, 0, 0), + CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(), + } + + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + valAddr := sdk.ValAddress(address.Bytes()) + // nolint: exhaustruct + validator, err := stakingtypes.NewValidator(valAddr, priv.PubKey(), stakingtypes.Description{}) + require.NoError(t, err) + + err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) + require.NoError(t, err) + err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) + require.NoError(t, err) + suite.app.StakingKeeper.SetValidator(suite.ctx, validator) + + suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + suite.handler = evm.NewHandler(suite.app.EvmKeeper) +} + +// Setup initializes a new Althea app. A Nop logger is set in AltheaApp. +func Setup(isCheckTx bool, patchGenesis func(*althea.AltheaApp, althea.GenesisState) althea.GenesisState) *althea.AltheaApp { + db := dbm.NewMemDB() + app := althea.NewAltheaApp(tmlog.NewNopLogger(), db, nil, true, map[int64]bool{}, althea.DefaultNodeHome, 5, althea.MakeEncodingConfig(), simapp.EmptyAppOptions{}) + if !isCheckTx { + // init chain must be called to stop deliverState from being nil + genesisState := althea.NewDefaultGenesisState() + if patchGenesis != nil { + genesisState = patchGenesis(app, genesisState) + } + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + if err != nil { + panic(err) + } + + // Initialize the chain + app.InitChain( + // nolint: exhaustruct + abci.RequestInitChain{ + ChainId: "althea_417834-1", + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + } + + return app +} + +// DefaultConsensusParams defines the default Tendermint consensus params used in +// EthermintApp testing. +// nolint: exhaustruct +var DefaultConsensusParams = &abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxBytes: 200000, + MaxGas: -1, // no limit + }, + Evidence: &tmproto.EvidenceParams{ + MaxAgeNumBlocks: 302400, + MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration + MaxBytes: 10000, + }, + Validator: &tmproto.ValidatorParams{ + PubKeyTypes: []string{ + tmtypes.ABCIPubKeyTypeEd25519, + }, + }, +} + +func (suite *GasfreeTestSuite) SetupTest() { + suite.DoSetupTest(suite.T()) +} + +func (suite *GasfreeTestSuite) SignTx(tx *evmtypes.MsgEthereumTx) { + tx.From = suite.from.String() + err := tx.Sign(suite.ethSigner, suite.signer) + suite.Require().NoError(err) +} + +func (suite *GasfreeTestSuite) StateDB() *statedb.StateDB { + return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) +} + +func TestGasfreeTestSuite(t *testing.T) { + // nolint: exhaustruct + suite.Run(t, &GasfreeTestSuite{}) +} diff --git a/x/gasfree/types/errors.go b/x/gasfree/types/errors.go new file mode 100644 index 00000000..11dcd34b --- /dev/null +++ b/x/gasfree/types/errors.go @@ -0,0 +1,11 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const RootCodespace = "gasfree" + +var ( + ErrInvalidParams = sdkerrors.Register(RootCodespace, 1, "invalid params") +) diff --git a/x/gasfree/types/genesis.go b/x/gasfree/types/genesis.go new file mode 100644 index 00000000..fa79f8e7 --- /dev/null +++ b/x/gasfree/types/genesis.go @@ -0,0 +1,59 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + microtxtypes "github.com/althea-net/althea-L1/x/microtx/types" +) + +// DefaultGenesisState creates a simple GenesisState suitible for testing +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +func DefaultParams() *Params { + return &Params{ + GasFreeMessageTypes: []string{ + // nolint: exhaustruct + sdk.MsgTypeURL(µtxtypes.MsgMicrotx{}), + }, + } +} + +func (s GenesisState) ValidateBasic() error { + if s.Params == nil { + return ErrInvalidParams + } + if err := ValidateGasFreeMessageTypes(s.Params.GasFreeMessageTypes); err != nil { + return sdkerrors.Wrap(err, "Invalid GasFreeMessageTypes GenesisState") + } + return nil +} + +func ValidateGasFreeMessageTypes(i interface{}) error { + _, ok := i.([]string) + if !ok { + return fmt.Errorf("invalid gas free message types type: %T", i) + } + + return nil +} + +// ParamKeyTable for auth module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{ + GasFreeMessageTypes: []string{}, + }) +} + +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(GasFreeMessageTypesKey, &p.GasFreeMessageTypes, ValidateGasFreeMessageTypes), + } +} diff --git a/x/gasfree/types/genesis.pb.go b/x/gasfree/types/genesis.pb.go new file mode 100644 index 00000000..2e18b3a4 --- /dev/null +++ b/x/gasfree/types/genesis.pb.go @@ -0,0 +1,504 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: althea/gasfree/v1/genesis.proto + +package types + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params struct +type Params struct { + // Messages with one of these types will not be charged gas fees in the + // AnteHandler, but will later be charged some form of fee in the Msg handler + GasFreeMessageTypes []string `protobuf:"bytes,1,rep,name=gas_free_message_types,json=gasFreeMessageTypes,proto3" json:"gas_free_message_types,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_1e21bc10ce13ce59, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetGasFreeMessageTypes() []string { + if m != nil { + return m.GasFreeMessageTypes + } + return nil +} + +type GenesisState struct { + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_1e21bc10ce13ce59, []int{1} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "althea.gasfree.v1.Params") + proto.RegisterType((*GenesisState)(nil), "althea.gasfree.v1.GenesisState") +} + +func init() { proto.RegisterFile("althea/gasfree/v1/genesis.proto", fileDescriptor_1e21bc10ce13ce59) } + +var fileDescriptor_1e21bc10ce13ce59 = []byte{ + // 220 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xcc, 0x29, 0xc9, + 0x48, 0x4d, 0xd4, 0x4f, 0x4f, 0x2c, 0x4e, 0x2b, 0x4a, 0x4d, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, + 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0x28, 0xd0, + 0x83, 0x2a, 0xd0, 0x2b, 0x33, 0x54, 0xb2, 0xe5, 0x62, 0x0b, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0x16, + 0x32, 0xe6, 0x12, 0x4b, 0x4f, 0x2c, 0x8e, 0x07, 0x49, 0xc4, 0xe7, 0xa6, 0x16, 0x17, 0x27, 0xa6, + 0xa7, 0xc6, 0x97, 0x54, 0x16, 0xa4, 0x16, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0x70, 0x06, 0x09, 0xa7, + 0x27, 0x16, 0xbb, 0x15, 0xa5, 0xa6, 0xfa, 0x42, 0xe4, 0x42, 0x40, 0x52, 0x4a, 0x8e, 0x5c, 0x3c, + 0xee, 0x10, 0x2b, 0x82, 0x4b, 0x12, 0x4b, 0x52, 0x85, 0x0c, 0xb9, 0xd8, 0x0a, 0xc0, 0xc6, 0x49, + 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x49, 0xea, 0x61, 0x58, 0xa9, 0x07, 0xb1, 0x2f, 0x08, 0xaa, + 0xd0, 0xc9, 0xf3, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, + 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xf4, 0xd3, 0x33, + 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x21, 0xc6, 0xe8, 0xe6, 0xa5, 0x96, 0xc0, + 0x98, 0x3e, 0x86, 0xfa, 0x15, 0x70, 0xaf, 0x82, 0x1d, 0x9a, 0xc4, 0x06, 0xf6, 0xa6, 0x31, 0x20, + 0x00, 0x00, 0xff, 0xff, 0x7d, 0x3c, 0xf7, 0xc5, 0x09, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.GasFreeMessageTypes) > 0 { + for iNdEx := len(m.GasFreeMessageTypes) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.GasFreeMessageTypes[iNdEx]) + copy(dAtA[i:], m.GasFreeMessageTypes[iNdEx]) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.GasFreeMessageTypes[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.GasFreeMessageTypes) > 0 { + for _, s := range m.GasFreeMessageTypes { + l = len(s) + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GasFreeMessageTypes", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GasFreeMessageTypes = append(m.GasFreeMessageTypes, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/gasfree/types/genesis_test.go b/x/gasfree/types/genesis_test.go new file mode 100644 index 00000000..c090974e --- /dev/null +++ b/x/gasfree/types/genesis_test.go @@ -0,0 +1,26 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidation(t *testing.T) { + defaultGenesis := DefaultGenesisState() + err := defaultGenesis.ValidateBasic() + assert.Nil(t, err, "error produced from default genesis ValidateBasic %v", err) + err = ValidateGasFreeMessageTypes(defaultGenesis.Params.GasFreeMessageTypes) + assert.Nil(t, err, "error produced from default gasFreeMessageTypes validation") + + // It is not easy to construct an invalid GenesisState, as the only check is for + // the GasFreeMessageTypes param, which only performs a type check + // so here we pass nil as the best attempt + + // nolint: exhaustruct + badGenesis := GenesisState{Params: nil} + err = ValidateGasFreeMessageTypes(badGenesis) + assert.NotNil(t, err, "badGenesis gasFreeMessageTypes did not produce an error in validation fn") + err = badGenesis.ValidateBasic() + assert.NotNil(t, err, "badGenesis did not produce an error in validation fn") +} diff --git a/x/gasfree/types/query.pb.go b/x/gasfree/types/query.pb.go new file mode 100644 index 00000000..5557c6cd --- /dev/null +++ b/x/gasfree/types/query.pb.go @@ -0,0 +1,535 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: althea/gasfree/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_7725dca9511d36d5, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7725dca9511d36d5, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "althea.gasfree.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "althea.gasfree.v1.QueryParamsResponse") +} + +func init() { proto.RegisterFile("althea/gasfree/v1/query.proto", fileDescriptor_7725dca9511d36d5) } + +var fileDescriptor_7725dca9511d36d5 = []byte{ + // 283 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0xcc, 0x29, 0xc9, + 0x48, 0x4d, 0xd4, 0x4f, 0x4f, 0x2c, 0x4e, 0x2b, 0x4a, 0x4d, 0xd5, 0x2f, 0x33, 0xd4, 0x2f, 0x2c, + 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0x48, 0xeb, 0x41, 0xa5, + 0xf5, 0xca, 0x0c, 0xa5, 0x64, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x13, 0x0b, 0x32, 0xf5, + 0x13, 0xf3, 0xf2, 0xf2, 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0x21, 0x1a, 0xa4, 0x44, 0xd2, + 0xf3, 0xd3, 0xf3, 0xc1, 0x4c, 0x7d, 0x10, 0x0b, 0x2a, 0x2a, 0x8f, 0x69, 0x4b, 0x7a, 0x6a, 0x5e, + 0x6a, 0x71, 0x26, 0x54, 0x9b, 0x92, 0x08, 0x97, 0x50, 0x20, 0xc8, 0xda, 0x80, 0xc4, 0xa2, 0xc4, + 0xdc, 0xe2, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x3f, 0x2e, 0x61, 0x14, 0xd1, 0xe2, + 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x73, 0x2e, 0xb6, 0x02, 0xb0, 0x88, 0x04, 0xa3, 0x02, 0xa3, + 0x06, 0xb7, 0x91, 0xa4, 0x1e, 0x86, 0x2b, 0xf5, 0x20, 0x5a, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, + 0x08, 0x82, 0x2a, 0x37, 0x6a, 0x66, 0xe4, 0x62, 0x05, 0x1b, 0x28, 0x54, 0xc5, 0xc5, 0x06, 0x51, + 0x21, 0xa4, 0x8a, 0x45, 0x33, 0xa6, 0x53, 0xa4, 0xd4, 0x08, 0x29, 0x83, 0xb8, 0x4d, 0x49, 0xb1, + 0xe9, 0xf2, 0x93, 0xc9, 0x4c, 0xd2, 0x42, 0x92, 0xfa, 0x98, 0x5e, 0x86, 0xb8, 0xc2, 0xc9, 0xf3, + 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, + 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xf4, 0xd3, 0x33, 0x4b, 0x32, 0x4a, + 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xa1, 0xda, 0x75, 0xf3, 0x52, 0x4b, 0x60, 0x4c, 0x1f, 0x43, 0xfd, + 0x0a, 0xb8, 0x71, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xd0, 0x33, 0x06, 0x04, 0x00, + 0x00, 0xff, 0xff, 0x05, 0x49, 0x19, 0x5d, 0xc6, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params retrieves the total set of onboarding parameters. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/althea.gasfree.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params retrieves the total set of onboarding parameters. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/althea.gasfree.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "althea.gasfree.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "althea/gasfree/v1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/gasfree/types/query.pb.gw.go b/x/gasfree/types/query.pb.gw.go new file mode 100644 index 00000000..57c7feae --- /dev/null +++ b/x/gasfree/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: althea/gasfree/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"althea", "gasfree", "v1", "params"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/gasfree/types/types.go b/x/gasfree/types/types.go new file mode 100644 index 00000000..ec244a77 --- /dev/null +++ b/x/gasfree/types/types.go @@ -0,0 +1,16 @@ +package types + +const ( + // ModuleName is the name of the module + ModuleName = "gasfree" + + // StoreKey to be used when creating the KVStore + StoreKey = ModuleName +) + +var ( + // GasFreeMessageTypesKey Indexes the GasFreeMessageTypes array, the collection of messages which + // will NOT be charged gas immediately when they execute, but must define an alternate gas payment + // method in their Msg handler + GasFreeMessageTypesKey = []byte("gasFreeMessageTypes") +) diff --git a/x/lockup/ante.go b/x/lockup/ante.go index 247bc0ed..8c9ba623 100644 --- a/x/lockup/ante.go +++ b/x/lockup/ante.go @@ -40,7 +40,7 @@ func (wad WrappedAnteHandler) AnteHandle( return next(modCtx, tx, simulate) } -// NewAnteHandler returns an AnteHandler that ensures any transaction under a locked chain +// NewLockupAnteHandler returns an AnteHandler that ensures any transaction under a locked chain // originates from a LockExempt address func NewLockupAnteHandler(lockupKeeper keeper.Keeper, cdc codec.Codec) sdk.AnteHandler { return sdk.ChainAnteDecorators(NewLockupAnteDecorator(lockupKeeper, cdc)) diff --git a/x/microtx/keeper/keeper.go b/x/microtx/keeper/keeper.go index 04410db2..243c9e55 100644 --- a/x/microtx/keeper/keeper.go +++ b/x/microtx/keeper/keeper.go @@ -15,6 +15,7 @@ import ( erc20keeper "github.com/Canto-Network/Canto/v5/x/erc20/keeper" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" + gasfreekeeper "github.com/althea-net/althea-L1/x/gasfree/keeper" "github.com/althea-net/althea-L1/x/microtx/types" ) @@ -30,6 +31,7 @@ type Keeper struct { accountKeeper *authkeeper.AccountKeeper evmKeeper *evmkeeper.Keeper erc20Keeper *erc20keeper.Keeper + gasfreeKeeper *gasfreekeeper.Keeper } // Check for nil members @@ -46,6 +48,9 @@ func (k Keeper) ValidateMembers() { if k.erc20Keeper == nil { panic("Nil erc20Keeper!") } + if k.gasfreeKeeper == nil { + panic("Nil gasfreeKeeper!") + } } // NewKeeper returns a new instance of the microtx keeper @@ -57,6 +62,7 @@ func NewKeeper( accKeeper *authkeeper.AccountKeeper, evmKeeper *evmkeeper.Keeper, erc20Keeper *erc20keeper.Keeper, + gasfreeKeeper *gasfreekeeper.Keeper, ) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { @@ -72,6 +78,7 @@ func NewKeeper( accountKeeper: accKeeper, evmKeeper: evmKeeper, erc20Keeper: erc20Keeper, + gasfreeKeeper: gasfreeKeeper, } k.ValidateMembers() diff --git a/x/microtx/keeper/msg_server.go b/x/microtx/keeper/msg_server.go index 2b7f4f68..10f64e70 100644 --- a/x/microtx/keeper/msg_server.go +++ b/x/microtx/keeper/msg_server.go @@ -62,49 +62,87 @@ func (m msgServer) Microtx(c context.Context, msg *types.MsgMicrotx) (*types.Msg // Microtx implements the transfer of funds from sender to receiver // Due to the function of Liquid Infrastructure Accounts, any Microtx must transfer only EVM compatible bank coins func (k Keeper) Microtx(ctx sdk.Context, sender sdk.AccAddress, receiver sdk.AccAddress, amount sdk.Coin) error { - var erc20Amount common.Address // `amount.Denom` converted to an ERC20 address - // The native token is automatically usable within the EVM - if amount.Denom != config.BaseDenom { - // Ensure the input tokens are actively registered as an ERC20-convertible token - pair, found := k.erc20Keeper.GetTokenPair(ctx, k.erc20Keeper.GetTokenPairID(ctx, amount.Denom)) - if !found { - return sdkerrors.Wrapf(types.ErrInvalidMicrotx, "token %v is not registered as an erc20, only evm-compatible tokens may be used", amount.Denom) - } - if !pair.Enabled { - return sdkerrors.Wrapf(types.ErrInvalidMicrotx, "token %v is registered as an erc20 (%v), but the pair is not enabled", amount.Denom, pair.Erc20Address) - } - // Collect the ERC20s for later use in funneling - erc20Amount = common.HexToAddress(pair.Erc20Address) + erc20Address, err := k.ValidateAndGetERC20Address(ctx, amount) + if err != nil { + return err } - feeCollected, err := k.DeductMicrotxFee(ctx, sender, amount) - - if err != nil { - return sdkerrors.Wrap(err, "unable to collect fees") + // If MsgMicrotx is not a gas free msg, then the fees should be charged here since they were not charged in the antehandler + // nolint: exhaustruct + if !k.gasfreeKeeper.IsGasFreeMsgType(ctx, sdk.MsgTypeURL(&types.MsgMicrotx{})) { + collected, err := k.DeductMicrotxFee(ctx, sender, amount) + if err != nil { + return sdkerrors.Wrap(err, "unable to collect MsgMicrotx fees") + } + ctx.EventManager().EmitEvent(types.NewEventMicrotxFeeCollected(sender.String(), *collected)) } + // Perform the transfer now that fees have been collected err = k.bankKeeper.SendCoins(ctx, sender, receiver, sdk.NewCoins(amount)) - if err != nil { return sdkerrors.Wrap(err, "unable to send tokens via the bank module") } // Emit an event for the block's event log ctx.EventManager().EmitEvent( - types.NewEventMicrotx(sender.String(), receiver.String(), amount, *feeCollected), + types.NewEventMicrotx(sender.String(), receiver.String(), amount), ) // Detect if this is a Liquid Infrastructure Account and if so // migrate balances to the NFT if the amounts are in excess of any configured threshold k.Logger(ctx).Debug("Detecting and funneling excess balances for liquid infrastructure accounts") - if err := k.RedirectLiquidAccountExcessBalance(ctx, receiver, erc20Amount); err != nil { + if err := k.RedirectLiquidAccountExcessBalance(ctx, receiver, erc20Address); err != nil { return sdkerrors.Wrapf(err, "failed to redirect excess balance") } return nil } -// checkAndDeductSendToEthFees asserts that the minimum chainFee has been met for the given sendAmount +func (k Keeper) ValidateAndGetERC20Address(ctx sdk.Context, amount sdk.Coin) (common.Address, error) { + var erc20Address common.Address + // The native token is automatically usable within the EVM + if amount.Denom != config.BaseDenom { + // Ensure the input tokens are actively registered as an ERC20-convertible token + pair, found := k.erc20Keeper.GetTokenPair(ctx, k.erc20Keeper.GetTokenPairID(ctx, amount.Denom)) + if !found { + return common.Address{}, sdkerrors.Wrapf(types.ErrInvalidMicrotx, "token %v is not registered as an erc20, only evm-compatible tokens may be used", amount.Denom) + } + if !pair.Enabled { + return common.Address{}, sdkerrors.Wrapf(types.ErrInvalidMicrotx, "token %v is registered as an erc20 (%v), but the pair is not enabled", amount.Denom, pair.Erc20Address) + } + // Collect the ERC20 address for later use in funneling + erc20Address = common.HexToAddress(pair.Erc20Address) + } + + return erc20Address, nil +} + +// DeductMsgMicrotxFee is expected to be called from the AnteHandler to deduct the fee for the Msg +// It is possible for MsgMicrotx to not be a gasfree message type, since governance controls the list, +// in that case the fee should be deducted in the Msg handler +// +// WARNING: Do **NOT** call this from the MsgMicrotx handler, as it will result in bad event logs, call DeductMicrotxFee instead +func (k Keeper) DeductMsgMicrotxFee(ctx sdk.Context, msg *types.MsgMicrotx) (feeCollected *sdk.Coin, err error) { + _, err = k.ValidateAndGetERC20Address(ctx, msg.Amount) + if err != nil { + return nil, sdkerrors.Wrap(err, "unable to deduct Microtx fees") + } + + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return nil, err + } + + feeCollected, err = k.DeductMicrotxFee(ctx, sender, msg.Amount) + + if err != nil { + return nil, sdkerrors.Wrap(err, "unable to collect fees") + } + + return +} + +// DeductMicrotxFee will check and deduct the MsgMicrotx fee for the given sendAmount, based on the MicrotxFeeBasisPoints param value func (k Keeper) DeductMicrotxFee(ctx sdk.Context, sender sdk.AccAddress, sendAmount sdk.Coin) (feeCollected *sdk.Coin, err error) { // Compute the minimum fees which must be paid microtxFeeBasisPoints, err := k.GetMicrotxFeeBasisPoints(ctx) @@ -143,8 +181,8 @@ func (k Keeper) DeductMicrotxFee(ctx sdk.Context, sender sdk.AccAddress, sendAmo // getMicrotxFeeForAmount Computes the fee a user must pay for any input `amount`, given the current `basisPoints` func (k Keeper) getMicrotxFeeForAmount(amount sdk.Int, basisPoints uint64) sdk.Int { return sdk.NewDecFromInt(amount). - QuoInt64(int64(BasisPointDivisor)). MulInt64(int64(basisPoints)). + QuoInt64(int64(BasisPointDivisor)). TruncateInt() } diff --git a/x/microtx/module.go b/x/microtx/module.go index 4afa88a2..717a7608 100644 --- a/x/microtx/module.go +++ b/x/microtx/module.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" abci "github.com/tendermint/tendermint/abci/types" "github.com/althea-net/althea-L1/x/microtx/client/cli" @@ -98,7 +97,6 @@ type AppModule struct { AppModuleBasic keeper keeper.Keeper accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper } func (am AppModule) ConsensusVersion() uint64 { @@ -106,12 +104,11 @@ func (am AppModule) ConsensusVersion() uint64 { } // NewAppModule creates a new AppModule Object -func NewAppModule(k keeper.Keeper, accountKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper) AppModule { +func NewAppModule(k keeper.Keeper, accountKeeper authkeeper.AccountKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: k, accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } diff --git a/x/microtx/types/events.go b/x/microtx/types/events.go index 87c62f47..9d802d56 100644 --- a/x/microtx/types/events.go +++ b/x/microtx/types/events.go @@ -16,7 +16,10 @@ const ( MicrotxKeySender = "sender" MicrotxKeyReceiver = "receiver" MicrotxKeyAmount = "amount" - MicrotxKeyFee = "fee" + + EventTypeMicrotxFeeCollected = "microtx-fee-collected" + MicrotxFeeCollectedKeySender = "sender" + MicrotxFeeCollectedKeyFee = "fee" EventTypeBalanceRedirect = "balance-redirect" RedirectKeyReceiver = "receiver" @@ -28,13 +31,20 @@ const ( LiquifyKeyNFTAddress = "nft-address" ) -func NewEventMicrotx(sender string, receiver string, amount sdk.Coin, fee sdk.Coin) sdk.Event { +func NewEventMicrotx(sender string, receiver string, amount sdk.Coin) sdk.Event { return sdk.NewEvent( EventTypeMicrotx, sdk.NewAttribute(MicrotxKeySender, sender), sdk.NewAttribute(MicrotxKeyReceiver, receiver), sdk.NewAttribute(MicrotxKeyAmount, amount.String()), - sdk.NewAttribute(MicrotxKeyFee, fee.String()), + ) +} + +func NewEventMicrotxFeeCollected(sender string, fee sdk.Coin) sdk.Event { + return sdk.NewEvent( + EventTypeMicrotxFeeCollected, + sdk.NewAttribute(MicrotxFeeCollectedKeySender, sender), + sdk.NewAttribute(MicrotxFeeCollectedKeyFee, fee.String()), ) } diff --git a/x/microtx/types/msgs.pb.go b/x/microtx/types/msgs.pb.go index a272d6aa..b7ea045d 100644 --- a/x/microtx/types/msgs.pb.go +++ b/x/microtx/types/msgs.pb.go @@ -140,7 +140,6 @@ type EventMicrotx struct { Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` Receiver string `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` Amounts []types.Coin `protobuf:"bytes,3,rep,name=amounts,proto3" json:"amounts"` - Fee []types.Coin `protobuf:"bytes,4,rep,name=fee,proto3" json:"fee"` } func (m *EventMicrotx) Reset() { *m = EventMicrotx{} } @@ -197,7 +196,54 @@ func (m *EventMicrotx) GetAmounts() []types.Coin { return nil } -func (m *EventMicrotx) GetFee() []types.Coin { +// A type for the block's event log, Microtx fees may be collected out-of-band and so +// this event may be emitted in an AnteHandler or during the Msg handler +type EventMicrotxFeeCollected struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + Fee []types.Coin `protobuf:"bytes,2,rep,name=fee,proto3" json:"fee"` +} + +func (m *EventMicrotxFeeCollected) Reset() { *m = EventMicrotxFeeCollected{} } +func (m *EventMicrotxFeeCollected) String() string { return proto.CompactTextString(m) } +func (*EventMicrotxFeeCollected) ProtoMessage() {} +func (*EventMicrotxFeeCollected) Descriptor() ([]byte, []int) { + return fileDescriptor_07584d3779b1e62f, []int{3} +} +func (m *EventMicrotxFeeCollected) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventMicrotxFeeCollected) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventMicrotxFeeCollected.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventMicrotxFeeCollected) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventMicrotxFeeCollected.Merge(m, src) +} +func (m *EventMicrotxFeeCollected) XXX_Size() int { + return m.Size() +} +func (m *EventMicrotxFeeCollected) XXX_DiscardUnknown() { + xxx_messageInfo_EventMicrotxFeeCollected.DiscardUnknown(m) +} + +var xxx_messageInfo_EventMicrotxFeeCollected proto.InternalMessageInfo + +func (m *EventMicrotxFeeCollected) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *EventMicrotxFeeCollected) GetFee() []types.Coin { if m != nil { return m.Fee } @@ -215,7 +261,7 @@ func (m *EventBalanceRedirect) Reset() { *m = EventBalanceRedirect{} } func (m *EventBalanceRedirect) String() string { return proto.CompactTextString(m) } func (*EventBalanceRedirect) ProtoMessage() {} func (*EventBalanceRedirect) Descriptor() ([]byte, []int) { - return fileDescriptor_07584d3779b1e62f, []int{3} + return fileDescriptor_07584d3779b1e62f, []int{4} } func (m *EventBalanceRedirect) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -272,7 +318,7 @@ func (m *LiquidInfrastructureAccount) Reset() { *m = LiquidInfrastructur func (m *LiquidInfrastructureAccount) String() string { return proto.CompactTextString(m) } func (*LiquidInfrastructureAccount) ProtoMessage() {} func (*LiquidInfrastructureAccount) Descriptor() ([]byte, []int) { - return fileDescriptor_07584d3779b1e62f, []int{4} + return fileDescriptor_07584d3779b1e62f, []int{5} } func (m *LiquidInfrastructureAccount) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -336,7 +382,7 @@ func (m *MsgLiquify) Reset() { *m = MsgLiquify{} } func (m *MsgLiquify) String() string { return proto.CompactTextString(m) } func (*MsgLiquify) ProtoMessage() {} func (*MsgLiquify) Descriptor() ([]byte, []int) { - return fileDescriptor_07584d3779b1e62f, []int{5} + return fileDescriptor_07584d3779b1e62f, []int{6} } func (m *MsgLiquify) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -381,7 +427,7 @@ func (m *MsgLiquifyResponse) Reset() { *m = MsgLiquifyResponse{} } func (m *MsgLiquifyResponse) String() string { return proto.CompactTextString(m) } func (*MsgLiquifyResponse) ProtoMessage() {} func (*MsgLiquifyResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_07584d3779b1e62f, []int{6} + return fileDescriptor_07584d3779b1e62f, []int{7} } func (m *MsgLiquifyResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -428,7 +474,7 @@ func (m *EventAccountLiquified) Reset() { *m = EventAccountLiquified{} } func (m *EventAccountLiquified) String() string { return proto.CompactTextString(m) } func (*EventAccountLiquified) ProtoMessage() {} func (*EventAccountLiquified) Descriptor() ([]byte, []int) { - return fileDescriptor_07584d3779b1e62f, []int{7} + return fileDescriptor_07584d3779b1e62f, []int{8} } func (m *EventAccountLiquified) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -475,6 +521,7 @@ func init() { proto.RegisterType((*MsgMicrotx)(nil), "althea.microtx.v1.MsgMicrotx") proto.RegisterType((*MsgMicrotxResponse)(nil), "althea.microtx.v1.MsgMicrotxResponse") proto.RegisterType((*EventMicrotx)(nil), "althea.microtx.v1.EventMicrotx") + proto.RegisterType((*EventMicrotxFeeCollected)(nil), "althea.microtx.v1.EventMicrotxFeeCollected") proto.RegisterType((*EventBalanceRedirect)(nil), "althea.microtx.v1.EventBalanceRedirect") proto.RegisterType((*LiquidInfrastructureAccount)(nil), "althea.microtx.v1.LiquidInfrastructureAccount") proto.RegisterType((*MsgLiquify)(nil), "althea.microtx.v1.MsgLiquify") @@ -485,41 +532,42 @@ func init() { func init() { proto.RegisterFile("althea/microtx/v1/msgs.proto", fileDescriptor_07584d3779b1e62f) } var fileDescriptor_07584d3779b1e62f = []byte{ - // 536 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x6e, 0xd3, 0x4c, - 0x10, 0x8f, 0x93, 0x7e, 0xc9, 0xd7, 0x09, 0x17, 0x4c, 0x8a, 0x4c, 0x5a, 0xdc, 0xc8, 0x02, 0xa9, - 0x17, 0x76, 0xe5, 0x70, 0x40, 0x1c, 0x1b, 0x84, 0x44, 0xa5, 0x86, 0x43, 0x8e, 0x1c, 0x40, 0x1b, - 0x7b, 0xe2, 0xae, 0xe4, 0xec, 0x06, 0xef, 0x26, 0x34, 0x57, 0x9e, 0x00, 0x89, 0x17, 0xe1, 0x31, - 0x7a, 0xac, 0xc4, 0x85, 0x13, 0x82, 0x84, 0x07, 0x41, 0xf1, 0xae, 0xd3, 0x24, 0x34, 0x55, 0x11, - 0xb7, 0x99, 0x9d, 0xd9, 0xfd, 0xfd, 0x99, 0xb1, 0xe1, 0x80, 0xa5, 0xfa, 0x0c, 0x19, 0x1d, 0xf2, - 0x28, 0x93, 0xfa, 0x9c, 0x4e, 0x42, 0x3a, 0x54, 0x89, 0x22, 0xa3, 0x4c, 0x6a, 0xe9, 0xde, 0x35, - 0x55, 0x62, 0xab, 0x64, 0x12, 0x36, 0xfd, 0x48, 0xaa, 0xa1, 0x54, 0xb4, 0xcf, 0x14, 0xd2, 0x49, - 0xd8, 0x47, 0xcd, 0x42, 0x1a, 0x49, 0x2e, 0xcc, 0x95, 0x66, 0x23, 0x91, 0x89, 0xcc, 0x43, 0xba, - 0x88, 0xec, 0xe9, 0x41, 0x22, 0x65, 0x92, 0x22, 0x65, 0x23, 0x4e, 0x99, 0x10, 0x52, 0x33, 0xcd, - 0xa5, 0xb0, 0x30, 0xc1, 0x14, 0xa0, 0xab, 0x92, 0xae, 0x01, 0x71, 0xef, 0x43, 0x55, 0xa1, 0x88, - 0x31, 0xf3, 0x9c, 0x96, 0x73, 0xb4, 0xdb, 0xb3, 0x99, 0xdb, 0x84, 0xff, 0x33, 0x8c, 0x90, 0x4f, - 0x30, 0xf3, 0xca, 0x79, 0x65, 0x99, 0xbb, 0xcf, 0xa0, 0xca, 0x86, 0x72, 0x2c, 0xb4, 0x57, 0x69, - 0x39, 0x47, 0xf5, 0xf6, 0x03, 0x62, 0x68, 0x92, 0x05, 0x4d, 0x62, 0x69, 0x92, 0x17, 0x92, 0x8b, - 0xce, 0xce, 0xc5, 0xf7, 0xc3, 0x52, 0xcf, 0xb6, 0x07, 0x0d, 0x70, 0xaf, 0xa0, 0x7b, 0xa8, 0x46, - 0x52, 0x28, 0x0c, 0xbe, 0x38, 0x70, 0xe7, 0xe5, 0x04, 0x85, 0xfe, 0x17, 0x4e, 0xcf, 0xa1, 0x66, - 0x40, 0x94, 0x57, 0x69, 0x55, 0x6e, 0x43, 0xaa, 0xe8, 0x77, 0x43, 0xa8, 0x0c, 0x10, 0xbd, 0x9d, - 0xdb, 0x5d, 0x5b, 0xf4, 0x06, 0x1c, 0x1a, 0x39, 0xe3, 0x0e, 0x4b, 0x99, 0x88, 0xb0, 0x87, 0x31, - 0xcf, 0x30, 0xd2, 0xae, 0x07, 0x35, 0x16, 0x45, 0xb9, 0x35, 0x86, 0x7a, 0x91, 0xae, 0x78, 0x56, - 0xfe, 0x3b, 0xcf, 0x04, 0xec, 0x9f, 0xf2, 0xf7, 0x63, 0x1e, 0x9f, 0x88, 0x41, 0xc6, 0x94, 0xce, - 0xc6, 0x91, 0x1e, 0x67, 0x78, 0x6c, 0xdf, 0x6d, 0xc0, 0x7f, 0xf2, 0x83, 0x58, 0x5a, 0x65, 0x92, - 0x55, 0x1e, 0xe5, 0x75, 0x1e, 0x87, 0x50, 0x17, 0x03, 0xfd, 0x8e, 0xc5, 0x71, 0x86, 0x4a, 0xe5, - 0x03, 0xdc, 0xed, 0x81, 0x18, 0xe8, 0x63, 0x73, 0x12, 0x3c, 0xca, 0xd7, 0x23, 0x87, 0x1c, 0x4c, - 0xb7, 0x8d, 0x22, 0x78, 0x9b, 0x4f, 0xd2, 0x76, 0x15, 0x93, 0x74, 0x5f, 0xad, 0xcb, 0xaf, 0xb7, - 0x09, 0xf9, 0x63, 0xa7, 0xc9, 0x0d, 0x6a, 0x96, 0x34, 0x83, 0xd7, 0xb0, 0x97, 0x1b, 0x6c, 0x0b, - 0x06, 0x88, 0x63, 0x5c, 0xe8, 0x8d, 0x57, 0xf5, 0xc6, 0x9b, 0xaa, 0xca, 0x9b, 0xaa, 0xda, 0x3f, - 0x1d, 0xa8, 0x74, 0x55, 0xe2, 0xa6, 0x50, 0x2b, 0xb6, 0xec, 0xe1, 0x35, 0xdc, 0xae, 0xb6, 0xb3, - 0xf9, 0xf8, 0xc6, 0xf2, 0x72, 0x79, 0xf7, 0x3f, 0x7e, 0xfd, 0xf5, 0xb9, 0xbc, 0x17, 0xdc, 0x5b, - 0xfb, 0xa8, 0x2d, 0x44, 0x0a, 0xb5, 0xc2, 0xc8, 0x2d, 0x68, 0xb6, 0xbc, 0x0d, 0x6d, 0xc3, 0xe0, - 0xeb, 0xd1, 0x52, 0xd3, 0xd4, 0x39, 0xb9, 0x98, 0xf9, 0xce, 0xe5, 0xcc, 0x77, 0x7e, 0xcc, 0x7c, - 0xe7, 0xd3, 0xdc, 0x2f, 0x5d, 0xce, 0xfd, 0xd2, 0xb7, 0xb9, 0x5f, 0x7a, 0x43, 0x13, 0xae, 0xcf, - 0xc6, 0x7d, 0x12, 0xc9, 0x21, 0x35, 0x38, 0x4f, 0x04, 0xea, 0x22, 0x3c, 0x0d, 0xe9, 0xf9, 0xf2, - 0x3d, 0x3d, 0x1d, 0xa1, 0xea, 0x57, 0xf3, 0x5f, 0xc5, 0xd3, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, - 0xf8, 0x19, 0x18, 0x7b, 0xb1, 0x04, 0x00, 0x00, + // 548 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x41, 0x6f, 0xd3, 0x30, + 0x14, 0xc7, 0x9b, 0x14, 0x56, 0xe6, 0x72, 0x21, 0x74, 0x28, 0x74, 0x23, 0xab, 0x22, 0x90, 0x76, + 0xc1, 0x56, 0xca, 0x01, 0x71, 0x5c, 0x27, 0x10, 0x93, 0x56, 0x0e, 0x3d, 0x72, 0x00, 0xb9, 0xce, + 0x6b, 0x66, 0x29, 0xb5, 0x4b, 0xec, 0x96, 0xf5, 0xc0, 0x85, 0x4f, 0x80, 0xc4, 0x97, 0xda, 0x71, + 0x12, 0x17, 0x4e, 0x08, 0x5a, 0x3e, 0x08, 0x6a, 0xec, 0x64, 0x6d, 0x59, 0xa7, 0x21, 0x6e, 0x7e, + 0x7e, 0x2f, 0xfe, 0xfd, 0xdf, 0xdf, 0x2f, 0x46, 0x7b, 0x34, 0xd5, 0xa7, 0x40, 0xc9, 0x90, 0xb3, + 0x4c, 0xea, 0x33, 0x32, 0x89, 0xc8, 0x50, 0x25, 0x0a, 0x8f, 0x32, 0xa9, 0xa5, 0x77, 0xcf, 0x64, + 0xb1, 0xcd, 0xe2, 0x49, 0xd4, 0x0c, 0x98, 0x54, 0x43, 0xa9, 0x48, 0x9f, 0x2a, 0x20, 0x93, 0xa8, + 0x0f, 0x9a, 0x46, 0x84, 0x49, 0x2e, 0xcc, 0x27, 0xcd, 0x46, 0x22, 0x13, 0x99, 0x2f, 0xc9, 0x62, + 0x65, 0x77, 0xf7, 0x12, 0x29, 0x93, 0x14, 0x08, 0x1d, 0x71, 0x42, 0x85, 0x90, 0x9a, 0x6a, 0x2e, + 0x85, 0xc5, 0x84, 0x53, 0x84, 0xba, 0x2a, 0xe9, 0x1a, 0x88, 0xf7, 0x00, 0x6d, 0x29, 0x10, 0x31, + 0x64, 0xbe, 0xd3, 0x72, 0x0e, 0xb6, 0x7b, 0x36, 0xf2, 0x9a, 0xe8, 0x4e, 0x06, 0x0c, 0xf8, 0x04, + 0x32, 0xdf, 0xcd, 0x33, 0x65, 0xec, 0x3d, 0x47, 0x5b, 0x74, 0x28, 0xc7, 0x42, 0xfb, 0xd5, 0x96, + 0x73, 0x50, 0x6f, 0x3f, 0xc4, 0x46, 0x26, 0x5e, 0xc8, 0xc4, 0x56, 0x26, 0x3e, 0x92, 0x5c, 0x74, + 0x6e, 0x9d, 0xff, 0xd8, 0xaf, 0xf4, 0x6c, 0x79, 0xd8, 0x40, 0xde, 0x25, 0xba, 0x07, 0x6a, 0x24, + 0x85, 0x82, 0xf0, 0x13, 0xba, 0xfb, 0x72, 0x02, 0x42, 0xff, 0x8f, 0xa4, 0x17, 0xa8, 0x66, 0x18, + 0xca, 0xaf, 0xb6, 0xaa, 0x37, 0xd1, 0x54, 0xd4, 0x87, 0x80, 0xfc, 0x65, 0xfc, 0x2b, 0x80, 0x23, + 0x99, 0xa6, 0xc0, 0x34, 0xc4, 0x1b, 0xa5, 0x44, 0xa8, 0x3a, 0x00, 0xf0, 0xdd, 0x9b, 0xa1, 0x16, + 0xb5, 0x21, 0x47, 0x8d, 0x1c, 0xd3, 0xa1, 0x29, 0x15, 0x0c, 0x7a, 0x10, 0xf3, 0x0c, 0x98, 0xf6, + 0x7c, 0x54, 0xa3, 0x8c, 0xe5, 0x6e, 0x1a, 0x46, 0x11, 0x2e, 0xd9, 0xec, 0xfe, 0x9b, 0xcd, 0x02, + 0xed, 0x9e, 0xf0, 0x0f, 0x63, 0x1e, 0x1f, 0x8b, 0x41, 0x46, 0x95, 0xce, 0xc6, 0x4c, 0x8f, 0x33, + 0x38, 0xb4, 0xe7, 0x36, 0xd0, 0x6d, 0xf9, 0x51, 0x94, 0x3d, 0x99, 0x60, 0x59, 0x87, 0xbb, 0xaa, + 0x63, 0x1f, 0xd5, 0xc5, 0x40, 0xbf, 0xa7, 0x71, 0x9c, 0x81, 0x52, 0xf9, 0x9d, 0x6f, 0xf7, 0x90, + 0x18, 0xe8, 0x43, 0xb3, 0x13, 0x3e, 0xce, 0x27, 0x2a, 0x47, 0x0e, 0xa6, 0x9b, 0x3c, 0x0b, 0xdf, + 0xe5, 0x97, 0x6f, 0xab, 0x8a, 0xcb, 0xf7, 0x5e, 0xaf, 0xb6, 0x5f, 0x6f, 0x63, 0xfc, 0xd7, 0x6f, + 0x80, 0xaf, 0xe9, 0xa6, 0x94, 0x19, 0xbe, 0x41, 0x3b, 0xb9, 0xc1, 0x36, 0x61, 0x40, 0x1c, 0xe2, + 0xa2, 0xdf, 0x78, 0xb9, 0xdf, 0x78, 0xbd, 0x2b, 0x77, 0xbd, 0xab, 0xf6, 0x2f, 0x07, 0x55, 0xbb, + 0x2a, 0xf1, 0x52, 0x54, 0x2b, 0x26, 0xf3, 0xd1, 0x15, 0xda, 0x2e, 0x07, 0xba, 0xf9, 0xe4, 0xda, + 0x74, 0x39, 0xef, 0xbb, 0x9f, 0xbf, 0xfd, 0xfe, 0xea, 0xee, 0x84, 0xf7, 0x57, 0xde, 0x01, 0x8b, + 0x48, 0x51, 0xad, 0x30, 0x72, 0x03, 0xcd, 0xa6, 0x37, 0xd1, 0xd6, 0x0c, 0xbe, 0x9a, 0x96, 0x9a, + 0xa2, 0xce, 0xf1, 0xf9, 0x2c, 0x70, 0x2e, 0x66, 0x81, 0xf3, 0x73, 0x16, 0x38, 0x5f, 0xe6, 0x41, + 0xe5, 0x62, 0x1e, 0x54, 0xbe, 0xcf, 0x83, 0xca, 0x5b, 0x92, 0x70, 0x7d, 0x3a, 0xee, 0x63, 0x26, + 0x87, 0xc4, 0x70, 0x9e, 0x0a, 0xd0, 0xc5, 0xf2, 0x24, 0x22, 0x67, 0xe5, 0x79, 0x7a, 0x3a, 0x02, + 0xd5, 0xdf, 0xca, 0x5f, 0x97, 0x67, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x45, 0x58, 0x66, 0xa7, + 0xe4, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -732,20 +780,6 @@ func (m *EventMicrotx) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Fee) > 0 { - for iNdEx := len(m.Fee) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Fee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintMsgs(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - } if len(m.Amounts) > 0 { for iNdEx := len(m.Amounts) - 1; iNdEx >= 0; iNdEx-- { { @@ -777,6 +811,50 @@ func (m *EventMicrotx) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *EventMicrotxFeeCollected) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventMicrotxFeeCollected) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventMicrotxFeeCollected) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Fee) > 0 { + for iNdEx := len(m.Fee) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Fee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *EventBalanceRedirect) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1022,6 +1100,19 @@ func (m *EventMicrotx) Size() (n int) { n += 1 + l + sovMsgs(uint64(l)) } } + return n +} + +func (m *EventMicrotxFeeCollected) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } if len(m.Fee) > 0 { for _, e := range m.Fee { l = e.Size() @@ -1440,7 +1531,89 @@ func (m *EventMicrotx) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventMicrotxFeeCollected) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventMicrotxFeeCollected: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventMicrotxFeeCollected: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Fee", wireType) }