diff --git a/app/encoding.go b/app/encoding.go index d19510959..b2912408a 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -1,8 +1,6 @@ package app import ( - "fmt" - "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/std" @@ -13,7 +11,6 @@ var legacyCodecRegistered = false // MakeEncodingConfig creates an EncodingConfig for testing func MakeEncodingConfig() params.EncodingConfig { - fmt.Println("Invoke codeql") encodingConfig := params.MakeEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) diff --git a/custom/auth/ante/ante.go b/custom/auth/ante/ante.go index 4d3516d42..38b7dceeb 100644 --- a/custom/auth/ante/ante.go +++ b/custom/auth/ante/ante.go @@ -57,14 +57,14 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { cosmosante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first cosmosante.NewRejectExtensionOptionsDecorator(), NewSpammingPreventionDecorator(options.OracleKeeper), // spamming prevention - NewTaxFeeDecorator(options.TreasuryKeeper), // mempool gas fee validation & record tax proceeds cosmosante.NewValidateBasicDecorator(), + NewTaxFeeDecorator(options.TreasuryKeeper), // mempool gas fee validation & record tax proceeds cosmosante.NewTxTimeoutHeightDecorator(), cosmosante.NewValidateMemoDecorator(options.AccountKeeper), cosmosante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), cosmosante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), NewBurnTaxFeeDecorator(options.AccountKeeper, options.TreasuryKeeper, options.BankKeeper, options.DistributionKeeper), // burn tax proceeds - cosmosante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + cosmosante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators cosmosante.NewValidateSigCountDecorator(options.AccountKeeper), cosmosante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), diff --git a/custom/auth/ante/ante_test.go b/custom/auth/ante/ante_test.go index 74ab8fb48..9ccb891a9 100644 --- a/custom/auth/ante/ante_test.go +++ b/custom/auth/ante/ante_test.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -63,13 +64,19 @@ func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { suite.ctx = suite.ctx.WithBlockHeight(1) // Set up TxConfig. + encodingConfig := suite.SetupEncoding() + + suite.clientCtx = client.Context{}. + WithTxConfig(encodingConfig.TxConfig) +} + +func (suite *AnteTestSuite) SetupEncoding() simappparams.EncodingConfig { encodingConfig := simapp.MakeTestEncodingConfig() // We're using TestMsg encoding in some tests, so register it here. encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) - suite.clientCtx = client.Context{}. - WithTxConfig(encodingConfig.TxConfig) + return encodingConfig } // CreateTestTx is a helper function to create a tx given multiple inputs. diff --git a/custom/auth/ante/burntax.go b/custom/auth/ante/burntax.go index 87a55c866..869541790 100644 --- a/custom/auth/ante/burntax.go +++ b/custom/auth/ante/burntax.go @@ -6,7 +6,6 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" cosmosante "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) // TaxPowerUpgradeHeight is when taxes are allowed to go into effect @@ -55,66 +54,6 @@ func (btfd BurnTaxFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // Record tax proceeds if !taxes.IsZero() { - tainted := false - - // Iterate over messages - for _, msg := range msgs { - var recipients []string - var senders []string - - // Fetch recipients - switch v := msg.(type) { - case *banktypes.MsgSend: - recipients = append(recipients, v.ToAddress) - senders = append(senders, v.FromAddress) - case *banktypes.MsgMultiSend: - for _, output := range v.Outputs { - recipients = append(recipients, output.Address) - } - - for _, input := range v.Inputs { - senders = append(senders, input.Address) - } - default: - // TODO: We might want to return an error if we cannot match the msg types, but as such I think that means we also need to cover MsgSetSendEnabled & MsgUpdateParams - // return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidType, "Unsupported message type") - } - - // Match senders vs. burn tax exemption list - exemptionCount := 0 - - for _, sender := range senders { - if btfd.treasuryKeeper.HasBurnTaxExemptionAddress(ctx, sender) { - exemptionCount++ - } - } - - // If all signers are not matched apply burn tax - if len(senders) > exemptionCount { - tainted = true - break - } - - // Check recipients - exemptionCount = 0 - - for _, recipient := range recipients { - if btfd.treasuryKeeper.HasBurnTaxExemptionAddress(ctx, recipient) { - exemptionCount++ - } - } - - // If all recipients are not matched apply burn tax - if len(recipients) > exemptionCount { - tainted = true - break - } - } - - if !tainted { - return next(ctx, tx, simulate) - } - burnSplitRate := btfd.treasuryKeeper.GetBurnSplitRate(ctx) if burnSplitRate.IsPositive() { diff --git a/custom/auth/ante/burntax_test.go b/custom/auth/ante/burntax_test.go index fe0ba75f6..58c8a8efb 100644 --- a/custom/auth/ante/burntax_test.go +++ b/custom/auth/ante/burntax_test.go @@ -10,9 +10,7 @@ import ( "github.com/classic-terra/core/custom/auth/ante" core "github.com/classic-terra/core/types" - treasury "github.com/classic-terra/core/x/treasury/types" "github.com/cosmos/cosmos-sdk/types/query" - cosmosante "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" ) @@ -133,186 +131,3 @@ func (suite *AnteTestSuite) DeductFees(sendAmount int64) sdk.Coins { return taxes } - -// the following binance addresses should not be applied tax -// go test -v -run ^TestAnteTestSuite/TestFilterRecipient$ github.com/classic-terra/core/custom/auth/ante -func (suite *AnteTestSuite) TestFilterRecipient() { - // keys and addresses - var privs []cryptotypes.PrivKey - var addrs []sdk.AccAddress - - // 0, 1: binance - // 2, 3: normal - for i := 0; i < 4; i++ { - priv, _, addr := testdata.KeyTestPubAddr() - privs = append(privs, priv) - addrs = append(addrs, addr) - } - - // set send amount - sendAmt := int64(1000000) - sendCoin := sdk.NewInt64Coin(core.MicroSDRDenom, sendAmt) - feeAmt := int64(1000) - - cases := []struct { - name string - msgSigner cryptotypes.PrivKey - msgCreator func() []sdk.Msg - burnAmount int64 - feeAmount int64 - }{ - { - name: "MsgSend(binance -> binance)", - msgSigner: privs[0], - msgCreator: func() []sdk.Msg { - var msgs []sdk.Msg - - msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) - msgs = append(msgs, msg1) - - return msgs - }, - // skip this one hence burn amount is 0 - burnAmount: 0, - feeAmount: feeAmt, - }, { - name: "MsgSend(normal -> normal)", - msgSigner: privs[2], - msgCreator: func() []sdk.Msg { - var msgs []sdk.Msg - - msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin)) - msgs = append(msgs, msg1) - - return msgs - }, - // tax this one hence burn amount is fee amount - burnAmount: feeAmt / 2, - feeAmount: feeAmt, - }, { - name: "MsgSend(binance -> normal), MsgSend(binance -> binance)", - msgSigner: privs[0], - msgCreator: func() []sdk.Msg { - var msgs []sdk.Msg - - msg1 := banktypes.NewMsgSend(addrs[0], addrs[2], sdk.NewCoins(sendCoin)) - msgs = append(msgs, msg1) - msg2 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) - msgs = append(msgs, msg2) - - return msgs - }, - // tax this one hence burn amount is fee amount - burnAmount: (feeAmt * 2) / 2, - feeAmount: feeAmt * 2, - }, { - name: "MsgSend(binance -> binance), MsgMultiSend(binance -> normal, binance -> binance)", - msgSigner: privs[0], - msgCreator: func() []sdk.Msg { - var msgs []sdk.Msg - - msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) - msgs = append(msgs, msg1) - msg2 := banktypes.NewMsgMultiSend( - []banktypes.Input{ - { - Address: addrs[0].String(), - Coins: sdk.NewCoins(sendCoin), - }, - { - Address: addrs[0].String(), - Coins: sdk.NewCoins(sendCoin), - }, - }, - []banktypes.Output{ - { - Address: addrs[2].String(), - Coins: sdk.NewCoins(sendCoin), - }, - { - Address: addrs[1].String(), - Coins: sdk.NewCoins(sendCoin), - }, - }, - ) - msgs = append(msgs, msg2) - - return msgs - }, - // tax this one hence burn amount is fee amount - burnAmount: (feeAmt * 3) / 2, - feeAmount: feeAmt * 3, - }, - } - - // there should be no coin in burn module - for _, c := range cases { - suite.SetupTest(true) // setup - require := suite.Require() - tk := suite.app.TreasuryKeeper - ak := suite.app.AccountKeeper - bk := suite.app.BankKeeper - - // Set burn split rate to 50% - tk.SetBurnSplitRate(suite.ctx, sdk.NewDecWithPrec(5, 1)) - - fmt.Printf("CASE = %s \n", c.name) - suite.ctx = suite.ctx.WithBlockHeight(ante.TaxPowerUpgradeHeight) - suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() - - tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[0].String()) - tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[1].String()) - - mfd := ante.NewBurnTaxFeeDecorator(ak, tk, bk, suite.app.DistrKeeper) - antehandler := sdk.ChainAnteDecorators( - cosmosante.NewDeductFeeDecorator(ak, bk, suite.app.FeeGrantKeeper), - mfd, - ) - - for i := 0; i < 4; i++ { - fundCoins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, 1000000000)) - acc := ak.NewAccountWithAddress(suite.ctx, addrs[i]) - ak.SetAccount(suite.ctx, acc) - bk.MintCoins(suite.ctx, minttypes.ModuleName, fundCoins) - bk.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addrs[i], fundCoins) - } - - // msg and signatures - feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.feeAmount)) - gasLimit := testdata.NewTestGasLimit() - require.NoError(suite.txBuilder.SetMsgs(c.msgCreator()...)) - suite.txBuilder.SetFeeAmount(feeAmount) - suite.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} - tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) - require.NoError(err) - - // check fee decorator and burn module amount before ante handler - feeCollector := ak.GetModuleAccount(suite.ctx, types.FeeCollectorName) - burnModule := ak.GetModuleAccount(suite.ctx, treasury.BurnModuleName) - - amountFeeBefore := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) - amountBurnBefore := bk.GetBalance(suite.ctx, burnModule.GetAddress(), core.MicroSDRDenom) - amountCommunityBefore := suite.app.DistrKeeper.GetFeePool(suite.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) - fmt.Printf("before: fee = %v, burn = %v, community = %v\n", amountFeeBefore, amountFeeBefore, amountCommunityBefore) - - _, err = antehandler(suite.ctx, tx, false) - require.NoError(err) - - // check fee decorator - amountFee := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) - amountBurn := bk.GetBalance(suite.ctx, burnModule.GetAddress(), core.MicroSDRDenom) - amountCommunity := suite.app.DistrKeeper.GetFeePool(suite.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) - fmt.Printf("after : fee = %v, burn = %v, community = %v\n", amountFee, amountBurn, amountCommunity) - - if c.burnAmount > 0 { - require.Equal(amountBurnBefore.Amount.Add(sdk.NewInt(c.burnAmount)), amountBurn.Amount) - require.Equal(amountFeeBefore, amountFee) - require.Equal(amountCommunity, amountBurn.Amount.ToDec()) - } else { - require.Equal(amountBurnBefore, amountBurn) - require.Equal(amountFeeBefore.Amount.Add(sdk.NewInt(c.feeAmount)), amountFee.Amount) - } - } -} diff --git a/custom/auth/ante/expected_keeper.go b/custom/auth/ante/expected_keeper.go index 87e3c46a4..c5b75f09b 100644 --- a/custom/auth/ante/expected_keeper.go +++ b/custom/auth/ante/expected_keeper.go @@ -11,7 +11,7 @@ type TreasuryKeeper interface { GetTaxRate(ctx sdk.Context) (taxRate sdk.Dec) GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int) GetBurnSplitRate(ctx sdk.Context) sdk.Dec - HasBurnTaxExemptionAddress(ctx sdk.Context, address string) bool + HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string) bool } // OracleKeeper for feeder validation diff --git a/custom/auth/ante/integration_test.go b/custom/auth/ante/integration_test.go new file mode 100644 index 000000000..680a6ab8d --- /dev/null +++ b/custom/auth/ante/integration_test.go @@ -0,0 +1,210 @@ +package ante_test + +import ( + "fmt" + + "github.com/classic-terra/core/custom/auth/ante" + customante "github.com/classic-terra/core/custom/auth/ante" + core "github.com/classic-terra/core/types" + treasurytypes "github.com/classic-terra/core/x/treasury/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" +) + +// go test -v -run ^TestAnteTestSuite/TestIntegrationTaxExemption$ github.com/classic-terra/core/custom/auth/ante +func (suite *AnteTestSuite) TestIntegrationTaxExemption() { + // keys and addresses + var privs []cryptotypes.PrivKey + var addrs []sdk.AccAddress + + // 0, 1: exemption + // 2, 3: normal + for i := 0; i < 4; i++ { + priv, _, addr := testdata.KeyTestPubAddr() + privs = append(privs, priv) + addrs = append(addrs, addr) + } + + // set send amount + sendAmt := int64(1000000) + sendCoin := sdk.NewInt64Coin(core.MicroSDRDenom, sendAmt) + feeAmt := int64(1000) + + cases := []struct { + name string + msgSigner int + msgCreator func() []sdk.Msg + expectedFeeAmount int64 + }{ + { + name: "MsgSend(exemption -> exemption)", + msgSigner: 0, + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + + return msgs + }, + expectedFeeAmount: 0, + }, { + name: "MsgSend(normal -> normal)", + msgSigner: 2, + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + + return msgs + }, + // tax this one hence burn amount is fee amount + expectedFeeAmount: feeAmt, + }, { + name: "MsgSend(exemption -> normal), MsgSend(exemption -> exemption)", + msgSigner: 0, + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[2], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + msg2 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg2) + + return msgs + }, + // tax this one hence burn amount is fee amount + expectedFeeAmount: feeAmt, + }, { + name: "MsgSend(exemption -> exemption), MsgMultiSend(exemption -> normal, exemption)", + msgSigner: 0, + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + msg2 := banktypes.NewMsgMultiSend( + []banktypes.Input{ + { + Address: addrs[0].String(), + Coins: sdk.NewCoins(sendCoin.Add(sendCoin)), + }, + }, + []banktypes.Output{ + { + Address: addrs[2].String(), + Coins: sdk.NewCoins(sendCoin), + }, + { + Address: addrs[1].String(), + Coins: sdk.NewCoins(sendCoin), + }, + }, + ) + msgs = append(msgs, msg2) + + return msgs + }, + expectedFeeAmount: feeAmt * 2, + }, + } + + for _, c := range cases { + suite.SetupTest(true) // setup + tk := suite.app.TreasuryKeeper + ak := suite.app.AccountKeeper + bk := suite.app.BankKeeper + dk := suite.app.DistrKeeper + + // Set burn split rate to 50% + // fee amount should be 500, 50% of 10000 + tk.SetBurnSplitRate(suite.ctx, sdk.NewDecWithPrec(5, 1)) //50% + + feeCollector := ak.GetModuleAccount(suite.ctx, types.FeeCollectorName) + burnModule := ak.GetModuleAccount(suite.ctx, treasurytypes.BurnModuleName) + + encodingConfig := suite.SetupEncoding() + antehandler, err := customante.NewAnteHandler( + customante.HandlerOptions{ + AccountKeeper: ak, + BankKeeper: bk, + FeegrantKeeper: suite.app.FeeGrantKeeper, + OracleKeeper: suite.app.OracleKeeper, + TreasuryKeeper: suite.app.TreasuryKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + IBCChannelKeeper: suite.app.IBCKeeper.ChannelKeeper, + DistributionKeeper: dk, + }, + ) + suite.Require().NoError(err) + + fmt.Printf("CASE = %s \n", c.name) + suite.ctx = suite.ctx.WithBlockHeight(ante.TaxPowerUpgradeHeight) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[0].String()) + tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[1].String()) + + for i := 0; i < 4; i++ { + fundCoins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, 1_000_000_000_000)) + acc := ak.NewAccountWithAddress(suite.ctx, addrs[i]) + suite.Require().NoError(acc.SetAccountNumber(uint64(i))) + ak.SetAccount(suite.ctx, acc) + bk.MintCoins(suite.ctx, minttypes.ModuleName, fundCoins) + bk.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addrs[i], fundCoins) + } + + // case 1 provides zero fee so not enough fee + // case 2 provides enough fee + feeCases := []int64{0, feeAmt} + for i := 0; i < 1; i++ { + feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, feeCases[i])) + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(c.msgCreator()...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{privs[c.msgSigner]}, []uint64{uint64(c.msgSigner)}, []uint64{uint64(i)} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + suite.Require().NoError(err) + + feeCollectorBefore := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) + burnBefore := bk.GetBalance(suite.ctx, burnModule.GetAddress(), core.MicroSDRDenom) + communityBefore := dk.GetFeePool(suite.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) + supplyBefore := bk.GetSupply(suite.ctx, core.MicroSDRDenom) + + _, err = antehandler(suite.ctx, tx, false) + if i == 0 && c.expectedFeeAmount != 0 { + suite.Require().EqualError(err, fmt.Sprintf("insufficient fees; got: \"\", required: \"%dusdr\" = \"\"(gas) +\"%dusdr\"(stability): insufficient fee", c.expectedFeeAmount, c.expectedFeeAmount)) + } else { + suite.Require().NoError(err) + } + + feeCollectorAfter := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) + burnAfter := bk.GetBalance(suite.ctx, burnModule.GetAddress(), core.MicroSDRDenom) + communityAfter := dk.GetFeePool(suite.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) + supplyAfter := bk.GetSupply(suite.ctx, core.MicroSDRDenom) + + if i == 0 { + suite.Require().Equal(feeCollectorBefore, feeCollectorAfter) + suite.Require().Equal(burnBefore, burnAfter) + suite.Require().Equal(communityBefore, communityAfter) + suite.Require().Equal(supplyBefore, supplyAfter) + } + + if i == 1 { + suite.Require().Equal(feeCollectorBefore, feeCollectorAfter) + splitAmount := sdk.NewInt(int64(float64(c.expectedFeeAmount) * 0.5)) + suite.Require().Equal(burnBefore, burnAfter.AddAmount(splitAmount)) + suite.Require().Equal(communityBefore, communityAfter.Add(sdk.NewDecFromInt(splitAmount))) + suite.Require().Equal(supplyBefore, supplyAfter.SubAmount(splitAmount)) + } + } + } +} diff --git a/custom/auth/ante/tax.go b/custom/auth/ante/tax.go index 3986038a7..b14d8e2da 100644 --- a/custom/auth/ante/tax.go +++ b/custom/auth/ante/tax.go @@ -110,14 +110,33 @@ func EnsureSufficientMempoolFees(ctx sdk.Context, gas uint64, feeCoins sdk.Coins // FilterMsgAndComputeTax computes the stability tax on MsgSend and MsgMultiSend. func FilterMsgAndComputeTax(ctx sdk.Context, tk TreasuryKeeper, msgs ...sdk.Msg) sdk.Coins { taxes := sdk.Coins{} + for _, msg := range msgs { switch msg := msg.(type) { case *banktypes.MsgSend: - taxes = taxes.Add(computeTax(ctx, tk, msg.Amount)...) + if !tk.HasBurnTaxExemptionAddress(ctx, msg.FromAddress, msg.ToAddress) { + taxes = taxes.Add(computeTax(ctx, tk, msg.Amount)...) + } case *banktypes.MsgMultiSend: + tainted := 0 + for _, input := range msg.Inputs { - taxes = taxes.Add(computeTax(ctx, tk, input.Coins)...) + if tk.HasBurnTaxExemptionAddress(ctx, input.Address) { + tainted += 1 + } + } + + for _, output := range msg.Outputs { + if tk.HasBurnTaxExemptionAddress(ctx, output.Address) { + tainted += 1 + } + } + + if tainted != len(msg.Inputs)+len(msg.Outputs) { + for _, input := range msg.Inputs { + taxes = taxes.Add(computeTax(ctx, tk, input.Coins)...) + } } case *marketexported.MsgSwapSend: diff --git a/custom/auth/ante/tax_test.go b/custom/auth/ante/tax_test.go index ca806c164..548474c70 100644 --- a/custom/auth/ante/tax_test.go +++ b/custom/auth/ante/tax_test.go @@ -1,11 +1,16 @@ package ante_test import ( + "fmt" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + cosmosante "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/types" authz "github.com/cosmos/cosmos-sdk/x/authz" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/classic-terra/core/custom/auth/ante" core "github.com/classic-terra/core/types" @@ -186,7 +191,8 @@ func (suite *AnteTestSuite) TestEnsureMempoolFeesMultiSend() { banktypes.NewInput(addr1, sendCoins), }, []banktypes.Output{ - banktypes.NewOutput(addr1, sendCoins.Add(sendCoins...)), + banktypes.NewOutput(addr1, sendCoins), + banktypes.NewOutput(addr1, sendCoins), }, ) @@ -518,7 +524,8 @@ func (suite *AnteTestSuite) TestEnsureMempoolFeesMultiSendLunaTax() { banktypes.NewInput(addr1, sendCoins), }, []banktypes.Output{ - banktypes.NewOutput(addr1, sendCoins.Add(sendCoins...)), + banktypes.NewOutput(addr1, sendCoins), + banktypes.NewOutput(addr1, sendCoins), }, ) @@ -740,3 +747,160 @@ func (suite *AnteTestSuite) TestEnsureMempoolFeesExecLunaTax() { _, err = antehandler(suite.ctx, tx, false) suite.Require().NoError(err, "Decorator should not have errored on fee higher than local gasPrice") } + +// go test -v -run ^TestAnteTestSuite/TestTaxExemption$ github.com/classic-terra/core/custom/auth/ante +func (suite *AnteTestSuite) TestTaxExemption() { + // keys and addresses + var privs []cryptotypes.PrivKey + var addrs []sdk.AccAddress + + // 0, 1: exemption + // 2, 3: normal + for i := 0; i < 4; i++ { + priv, _, addr := testdata.KeyTestPubAddr() + privs = append(privs, priv) + addrs = append(addrs, addr) + } + + // set send amount + sendAmt := int64(1000000) + sendCoin := sdk.NewInt64Coin(core.MicroSDRDenom, sendAmt) + feeAmt := int64(1000) + + cases := []struct { + name string + msgSigner cryptotypes.PrivKey + msgCreator func() []sdk.Msg + expectedFeeAmount int64 + }{ + { + name: "MsgSend(exemption -> exemption)", + msgSigner: privs[0], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + + return msgs + }, + expectedFeeAmount: 0, + }, { + name: "MsgSend(normal -> normal)", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + + return msgs + }, + // tax this one hence burn amount is fee amount + expectedFeeAmount: feeAmt, + }, { + name: "MsgSend(exemption -> normal), MsgSend(exemption -> exemption)", + msgSigner: privs[0], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[2], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + msg2 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg2) + + return msgs + }, + // tax this one hence burn amount is fee amount + expectedFeeAmount: feeAmt, + }, { + name: "MsgSend(exemption -> exemption), MsgMultiSend(exemption -> normal, exemption -> exemption)", + msgSigner: privs[0], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := banktypes.NewMsgSend(addrs[0], addrs[1], sdk.NewCoins(sendCoin)) + msgs = append(msgs, msg1) + msg2 := banktypes.NewMsgMultiSend( + []banktypes.Input{ + { + Address: addrs[0].String(), + Coins: sdk.NewCoins(sendCoin), + }, + { + Address: addrs[0].String(), + Coins: sdk.NewCoins(sendCoin), + }, + }, + []banktypes.Output{ + { + Address: addrs[2].String(), + Coins: sdk.NewCoins(sendCoin), + }, + { + Address: addrs[1].String(), + Coins: sdk.NewCoins(sendCoin), + }, + }, + ) + msgs = append(msgs, msg2) + + return msgs + }, + expectedFeeAmount: feeAmt * 2, + }, + } + + // there should be no coin in burn module + for _, c := range cases { + suite.SetupTest(true) // setup + require := suite.Require() + tk := suite.app.TreasuryKeeper + ak := suite.app.AccountKeeper + bk := suite.app.BankKeeper + + // Set burn split rate to 50% + tk.SetBurnSplitRate(suite.ctx, sdk.NewDecWithPrec(5, 1)) + + fmt.Printf("CASE = %s \n", c.name) + suite.ctx = suite.ctx.WithBlockHeight(ante.TaxPowerUpgradeHeight) + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[0].String()) + tk.AddBurnTaxExemptionAddress(suite.ctx, addrs[1].String()) + + mfd := ante.NewTaxFeeDecorator(suite.app.TreasuryKeeper) + antehandler := sdk.ChainAnteDecorators( + mfd, + cosmosante.NewDeductFeeDecorator(ak, bk, suite.app.FeeGrantKeeper), + ) + + for i := 0; i < 4; i++ { + fundCoins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, 1000000000)) + acc := ak.NewAccountWithAddress(suite.ctx, addrs[i]) + ak.SetAccount(suite.ctx, acc) + bk.MintCoins(suite.ctx, minttypes.ModuleName, fundCoins) + bk.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addrs[i], fundCoins) + } + + // msg and signatures + feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.expectedFeeAmount)) + gasLimit := testdata.NewTestGasLimit() + require.NoError(suite.txBuilder.SetMsgs(c.msgCreator()...)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} + tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID()) + require.NoError(err) + + _, err = antehandler(suite.ctx, tx, false) + require.NoError(err) + + // check fee collector + feeCollector := ak.GetModuleAccount(suite.ctx, types.FeeCollectorName) + amountFee := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) + + require.Equal(amountFee, sdk.NewCoin("usdr", sdk.NewInt(c.expectedFeeAmount))) + } +} diff --git a/x/treasury/keeper/keeper.go b/x/treasury/keeper/keeper.go index 2a53b6bab..9308fdedd 100644 --- a/x/treasury/keeper/keeper.go +++ b/x/treasury/keeper/keeper.go @@ -378,7 +378,14 @@ func (k Keeper) RemoveBurnTaxExemptionAddress(ctx sdk.Context, address string) e return nil } -func (k Keeper) HasBurnTaxExemptionAddress(ctx sdk.Context, address string) bool { +func (k Keeper) HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string) bool { sub := prefix.NewStore(ctx.KVStore(k.storeKey), types.BurnTaxExemptionListPrefix) - return sub.Has([]byte(address)) + + for _, address := range addresses { + if !sub.Has([]byte(address)) { + return false + } + } + + return true } diff --git a/x/wasm/keeper/submsg_test.go b/x/wasm/keeper/submsg_test.go index 89cc6fec5..54c302753 100644 --- a/x/wasm/keeper/submsg_test.go +++ b/x/wasm/keeper/submsg_test.go @@ -275,14 +275,14 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { submsgID: 5, msg: validBankSend, // note we charge another 40k for the reply call - resultAssertions: []assertion{assertReturnedEvents(5), assertGasUsed(134000, 136000)}, + resultAssertions: []assertion{assertReturnedEvents(5), assertGasUsed(134000, 137000)}, }, "not enough tokens": { submsgID: 6, msg: invalidBankSend, subMsgError: true, // uses less gas than the send tokens (cost of bank transfer) - resultAssertions: []assertion{assertGasUsed(100000, 101000), assertErrorString("insufficient funds")}, + resultAssertions: []assertion{assertGasUsed(100000, 101500), assertErrorString("insufficient funds")}, }, "out of gas panic with no gas limit": { submsgID: 7, @@ -295,7 +295,7 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { msg: validBankSend, gasLimit: &subGasLimit, // uses same gas as call without limit - resultAssertions: []assertion{assertReturnedEvents(5), assertGasUsed(134000, 136000)}, + resultAssertions: []assertion{assertReturnedEvents(5), assertGasUsed(134000, 137000)}, }, "not enough tokens with limit": { submsgID: 16, @@ -303,7 +303,7 @@ func TestDispatchSubMsgErrorHandling(t *testing.T) { subMsgError: true, gasLimit: &subGasLimit, // uses same gas as call without limit - resultAssertions: []assertion{assertGasUsed(100000, 101000), assertErrorString("insufficient funds")}, + resultAssertions: []assertion{assertGasUsed(100000, 101500), assertErrorString("insufficient funds")}, }, "out of gas caught with gas limit": { submsgID: 17, diff --git a/x/wasm/types/expected_keeper.go b/x/wasm/types/expected_keeper.go index 357056f84..a30c58697 100644 --- a/x/wasm/types/expected_keeper.go +++ b/x/wasm/types/expected_keeper.go @@ -40,7 +40,7 @@ type TreasuryKeeper interface { GetTaxRate(ctx sdk.Context) (taxRate sdk.Dec) GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int) GetBurnSplitRate(ctx sdk.Context) sdk.Dec - HasBurnTaxExemptionAddress(ctx sdk.Context, address string) bool + HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string) bool } // GRPCQueryHandler defines a function type which handles ABCI Query requests