diff --git a/custom/auth/ante/fee_test.go b/custom/auth/ante/fee_test.go index bb094edb..a4c63330 100644 --- a/custom/auth/ante/fee_test.go +++ b/custom/auth/ante/fee_test.go @@ -2,7 +2,6 @@ package ante_test import ( "encoding/json" - "fmt" "os" "time" @@ -558,7 +557,8 @@ func (s *AnteTestSuite) TestTaxExemption() { minFeeAmount: 0, expectProceeds: 0, expectReverseCharge: false, - }, { + }, + { name: "MsgSend(normal -> normal)", msgSigner: privs[2], msgCreator: func() []sdk.Msg { @@ -573,7 +573,24 @@ func (s *AnteTestSuite) TestTaxExemption() { minFeeAmount: feeAmt, expectProceeds: feeAmt, expectReverseCharge: false, - }, { + }, + { + name: "MsgSend(normal -> normal), non-zero not enough", + 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 + }, + // don't tax this because we are not sending enough + minFeeAmount: feeAmt / 2, + expectProceeds: 0, + expectReverseCharge: true, + }, + { name: "MsgSend(normal -> normal, reverse charge)", msgSigner: privs[2], msgCreator: func() []sdk.Msg { @@ -585,10 +602,11 @@ func (s *AnteTestSuite) TestTaxExemption() { return msgs }, // tax this one hence burn amount is fee amount - minFeeAmount: 0, + minFeeAmount: 1000, expectProceeds: 0, expectReverseCharge: true, - }, { + }, + { name: "MsgExec(MsgSend(normal -> normal))", msgSigner: privs[2], msgCreator: func() []sdk.Msg { @@ -603,7 +621,24 @@ func (s *AnteTestSuite) TestTaxExemption() { minFeeAmount: feeAmt, expectProceeds: feeAmt, expectReverseCharge: false, - }, { + }, + { + name: "MsgExec(MsgSend(normal -> normal))", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + + msg1 := authz.NewMsgExec(addrs[1], []sdk.Msg{banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin))}) + msgs = append(msgs, &msg1) + + return msgs + }, + // tax this one hence burn amount is fee amount + minFeeAmount: feeAmt, + expectProceeds: feeAmt, + expectReverseCharge: false, + }, + { name: "MsgSend(exemption -> normal), MsgSend(exemption -> exemption)", msgSigner: privs[0], msgCreator: func() []sdk.Msg { @@ -620,7 +655,8 @@ func (s *AnteTestSuite) TestTaxExemption() { minFeeAmount: feeAmt, expectProceeds: feeAmt, expectReverseCharge: false, - }, { + }, + { name: "MsgSend(exemption -> exemption), MsgMultiSend(exemption -> normal, exemption -> exemption)", msgSigner: privs[0], msgCreator: func() []sdk.Msg { @@ -657,7 +693,8 @@ func (s *AnteTestSuite) TestTaxExemption() { minFeeAmount: feeAmt * 2, expectProceeds: feeAmt * 2, expectReverseCharge: false, - }, { + }, + { name: "MsgExecuteContract(exemption), MsgExecuteContract(normal)", msgSigner: privs[3], msgCreator: func() []sdk.Msg { @@ -709,75 +746,482 @@ func (s *AnteTestSuite) TestTaxExemption() { expectProceeds: 0, expectReverseCharge: false, }, + + { + name: "MsgExecuteContract(exemption), MsgExecuteContract(normal)", + msgSigner: privs[3], + msgCreator: func() []sdk.Msg { + sendAmount := int64(1000000) + sendCoins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, sendAmount)) + // get wasm code for wasm contract create and instantiate + wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") + s.Require().NoError(err) + per := wasmkeeper.NewDefaultPermissionKeeper(s.app.WasmKeeper) + // set wasm default params + s.app.WasmKeeper.SetParams(s.ctx, wasmtypes.DefaultParams()) + // wasm create + CodeID, _, err := per.Create(s.ctx, addrs[0], wasmCode, nil) + s.Require().NoError(err) + // params for contract init + r := wasmkeeper.HackatomExampleInitMsg{Verifier: addrs[0], Beneficiary: addrs[0]} + bz, err := json.Marshal(r) + s.Require().NoError(err) + // change block time for contract instantiate + s.ctx = s.ctx.WithBlockTime(time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC)) + // instantiate contract then not set to tax exemption + addr, _, err := per.Instantiate(s.ctx, CodeID, addrs[0], nil, bz, "my label", nil) + s.Require().NoError(err) + + var msgs []sdk.Msg + // msg and signatures + msg1 := &wasmtypes.MsgExecuteContract{ + Sender: addrs[0].String(), + Contract: addr.String(), + Msg: []byte{}, + Funds: sendCoins, + } + msgs = append(msgs, msg1) + + msg2 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin)) + + msgs = append(msgs, msg2) + return msgs + }, + minFeeAmount: feeAmt, + expectProceeds: feeAmt, + expectReverseCharge: false, + }, } // there should be no coin in burn module // run once with reverse charge and once without for _, c := range cases { - s.SetupTest(true) // setup - require := s.Require() - tk := s.app.TreasuryKeeper - ak := s.app.AccountKeeper - bk := s.app.BankKeeper - burnTaxRate := sdk.NewDecWithPrec(5, 3) - burnSplitRate := sdk.NewDecWithPrec(5, 1) - oracleSplitRate := sdk.ZeroDec() - - // Set burn split rate to 50% - // oracle split to 0% (oracle split is covered in another test) - tk.SetBurnSplitRate(s.ctx, burnSplitRate) - tk.SetOracleSplitRate(s.ctx, oracleSplitRate) - - fmt.Printf("CASE = %s \n", c.name) - s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() - - tk.AddBurnTaxExemptionAddress(s.ctx, addrs[0].String()) - tk.AddBurnTaxExemptionAddress(s.ctx, addrs[1].String()) - - mfd := ante.NewFeeDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.DistrKeeper, s.app.TaxKeeper) - antehandler := sdk.ChainAnteDecorators(mfd) - pd := post.NewTaxDecorator(s.app.TaxKeeper, bk, ak, tk) - posthandler := sdk.ChainPostDecorators(pd) - - for i := 0; i < 4; i++ { - coins := sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(10000000))) - testutil.FundAccount(s.app.BankKeeper, s.ctx, addrs[i], coins) - } - - // msg and signatures - feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.minFeeAmount)) - gasLimit := testdata.NewTestGasLimit() - require.NoError(s.txBuilder.SetMsgs(c.msgCreator()...)) - s.txBuilder.SetFeeAmount(feeAmount) - s.txBuilder.SetGasLimit(gasLimit) - - privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} - tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) - require.NoError(err) - - newCtx, err := antehandler(s.ctx, tx, false) - require.NoError(err) - newCtx, err = posthandler(newCtx, tx, false, true) - require.NoError(err) - - actualTaxRate := s.app.TaxKeeper.GetBurnTaxRate(s.ctx) - require.Equal(burnTaxRate, actualTaxRate) - - require.Equal(c.expectReverseCharge, newCtx.Value(taxtypes.ContextKeyTaxReverseCharge)) - - // check fee collector - feeCollector := ak.GetModuleAccount(s.ctx, authtypes.FeeCollectorName) - amountFee := bk.GetBalance(s.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) - if c.expectReverseCharge { - // tax is NOT split in this case in the ante handler - require.Equal(amountFee, sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(c.minFeeAmount))) - } else { - require.Equal(amountFee, sdk.NewCoin(core.MicroSDRDenom, sdk.NewDec(c.minFeeAmount).Mul(burnSplitRate).TruncateInt())) - } - - // check tax proceeds - taxProceeds := s.app.TreasuryKeeper.PeekEpochTaxProceeds(s.ctx) - require.Equal(taxProceeds, sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(c.expectProceeds)))) + s.Run(c.name, func() { + s.SetupTest(true) // setup + require := s.Require() + tk := s.app.TreasuryKeeper + ak := s.app.AccountKeeper + bk := s.app.BankKeeper + burnTaxRate := sdk.NewDecWithPrec(5, 3) + burnSplitRate := sdk.NewDecWithPrec(5, 1) + oracleSplitRate := sdk.ZeroDec() + + // Set burn split rate to 50% + // oracle split to 0% (oracle split is covered in another test) + tk.SetBurnSplitRate(s.ctx, burnSplitRate) + tk.SetOracleSplitRate(s.ctx, oracleSplitRate) + + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[0].String()) + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[1].String()) + + mfd := ante.NewFeeDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.DistrKeeper, s.app.TaxKeeper) + antehandler := sdk.ChainAnteDecorators(mfd) + pd := post.NewTaxDecorator(s.app.TaxKeeper, bk, ak, tk) + posthandler := sdk.ChainPostDecorators(pd) + + for i := 0; i < 4; i++ { + coins := sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(10000000))) + testutil.FundAccount(s.app.BankKeeper, s.ctx, addrs[i], coins) + } + + // msg and signatures + feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.minFeeAmount)) + gasLimit := testdata.NewTestGasLimit() + require.NoError(s.txBuilder.SetMsgs(c.msgCreator()...)) + s.txBuilder.SetFeeAmount(feeAmount) + s.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(err) + + newCtx, err := antehandler(s.ctx, tx, false) + require.NoError(err) + newCtx, err = posthandler(newCtx, tx, false, true) + require.NoError(err) + + actualTaxRate := s.app.TaxKeeper.GetBurnTaxRate(s.ctx) + require.Equal(burnTaxRate, actualTaxRate) + + require.Equal(c.expectReverseCharge, newCtx.Value(taxtypes.ContextKeyTaxReverseCharge)) + + // check fee collector + feeCollector := ak.GetModuleAccount(s.ctx, authtypes.FeeCollectorName) + amountFee := bk.GetBalance(s.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) + if c.expectReverseCharge { + // tax is NOT split in this case in the ante handler + require.Equal(amountFee, sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(c.minFeeAmount))) + } else { + require.Equal(amountFee, sdk.NewCoin(core.MicroSDRDenom, sdk.NewDec(c.minFeeAmount).Mul(burnSplitRate).TruncateInt())) + } + + // check tax proceeds + taxProceeds := s.app.TreasuryKeeper.PeekEpochTaxProceeds(s.ctx) + require.Equal(sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, sdk.NewInt(c.expectProceeds))), taxProceeds) + }) + } +} + +func (s *AnteTestSuite) TestTaxExemptionWithMultipleDenoms() { + // 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) + } + + // use two different denoms but with same gas price + denom1 := "uaad" + denom2 := "ucud" + + // set send amount + sendAmt := int64(1000000) + sendCoin := sdk.NewInt64Coin(denom1, sendAmt) + anotherSendCoin := sdk.NewInt64Coin(denom2, sendAmt) + + cases := []struct { + name string + msgSigner cryptotypes.PrivKey + msgCreator func() []sdk.Msg + minFeeAmounts []sdk.Coin + expectProceeds sdk.Coins + expectReverseCharge bool + }{ + { + name: "MsgSend(exemption -> exemption) with multiple fee denoms", + 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 + }, + minFeeAmounts: []sdk.Coin{ + sdk.NewInt64Coin(denom1, 0), + sdk.NewInt64Coin(denom2, 0), + }, + expectProceeds: sdk.NewCoins(), + expectReverseCharge: false, + }, + { + name: "MsgSend(normal -> normal) with multiple fee denoms", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: []sdk.Coin{ + sdk.NewInt64Coin(denom1, 5000), + sdk.NewInt64Coin(denom2, 5000), + }, + expectProceeds: sdk.NewCoins( + sdk.NewInt64Coin(denom1, 5000), + sdk.NewInt64Coin(denom2, 5000), + ), + expectReverseCharge: false, + }, + { + name: "MsgSend(normal -> normal), enough taxes for both denoms", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: []sdk.Coin{ + sdk.NewInt64Coin(denom1, 5000), + sdk.NewInt64Coin(denom2, 5000), + }, + expectProceeds: []sdk.Coin{ + sdk.NewInt64Coin(denom1, 5000), + sdk.NewInt64Coin(denom2, 5000), + }, + expectReverseCharge: false, + }, + { + name: "MsgSend(normal -> normal), one denom not enough tax", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: []sdk.Coin{ + sdk.NewInt64Coin(denom1, 5000), + sdk.NewInt64Coin(denom2, 2500), + }, + expectProceeds: []sdk.Coin{}, + expectReverseCharge: true, + }, + } + + for _, c := range cases { + s.Run(c.name, func() { + s.SetupTest(true) // setup + require := s.Require() + tk := s.app.TreasuryKeeper + ak := s.app.AccountKeeper + bk := s.app.BankKeeper + + burnTaxRate := sdk.NewDecWithPrec(5, 3) + burnSplitRate := sdk.NewDecWithPrec(5, 1) + oracleSplitRate := sdk.ZeroDec() + + // Set burn split rate to 50% + tk.SetBurnSplitRate(s.ctx, burnSplitRate) + tk.SetOracleSplitRate(s.ctx, oracleSplitRate) + + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[0].String()) + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[1].String()) + + mfd := ante.NewFeeDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.DistrKeeper, s.app.TaxKeeper) + antehandler := sdk.ChainAnteDecorators(mfd) + pd := post.NewTaxDecorator(s.app.TaxKeeper, bk, ak, tk) + posthandler := sdk.ChainPostDecorators(pd) + + // Fund accounts with both denoms + for i := 0; i < 4; i++ { + coins := sdk.NewCoins( + sdk.NewCoin(denom1, sdk.NewInt(10000000)), + sdk.NewCoin(denom2, sdk.NewInt(10000000)), + ) + testutil.FundAccount(s.app.BankKeeper, s.ctx, addrs[i], coins) + } + + // Set up transaction with multiple fee denoms + feeAmount := sdk.NewCoins(c.minFeeAmounts...) + gasLimit := testdata.NewTestGasLimit() + require.NoError(s.txBuilder.SetMsgs(c.msgCreator()...)) + s.txBuilder.SetFeeAmount(feeAmount) + s.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(err) + + newCtx, err := antehandler(s.ctx, tx, false) + require.NoError(err) + newCtx, err = posthandler(newCtx, tx, false, true) + require.NoError(err) + + actualTaxRate := s.app.TaxKeeper.GetBurnTaxRate(s.ctx) + require.Equal(burnTaxRate, actualTaxRate) + + require.Equal(c.expectReverseCharge, newCtx.Value(taxtypes.ContextKeyTaxReverseCharge)) + + // Check fee collector for each denom + feeCollector := ak.GetModuleAccount(s.ctx, authtypes.FeeCollectorName) + for _, feeCoin := range c.minFeeAmounts { + amountFee := bk.GetBalance(s.ctx, feeCollector.GetAddress(), feeCoin.Denom) + if c.expectReverseCharge { + require.Equal(amountFee, feeCoin) + } else { + expectedFee := sdk.NewCoin( + feeCoin.Denom, + sdk.NewDec(feeCoin.Amount.Int64()).Mul(burnSplitRate).TruncateInt(), + ) + require.Equal(expectedFee, amountFee) + } + } + + // Check tax proceeds + taxProceeds := s.app.TreasuryKeeper.PeekEpochTaxProceeds(s.ctx) + require.Equal(c.expectProceeds, taxProceeds) + }) + } +} + +func (s *AnteTestSuite) TestTaxExemptionWithGasPriceEnabled() { + // 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) + } + + // use two different denoms but with same gas price + denom1 := "uaad" + denom2 := "ucud" + + // set send amount + sendAmt := int64(1000000) + sendCoin := sdk.NewInt64Coin(denom1, sendAmt) + anotherSendCoin := sdk.NewInt64Coin(denom2, sendAmt) + denom1Price := sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(10, 1)) + denom2Price := sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(10, 1)) + customGasPrices := []sdk.DecCoin{denom1Price, denom2Price} + + requiredFees := []sdk.Coin{sdk.NewInt64Coin(denom1, 0), sdk.NewInt64Coin(denom2, 200000)} + requiredTaxes := sdk.NewCoins(sdk.NewInt64Coin(denom1, 5000), sdk.NewInt64Coin(denom2, 5000)) + cases := []struct { + name string + msgSigner cryptotypes.PrivKey + msgCreator func() []sdk.Msg + minFeeAmounts sdk.Coins + taxAmounts sdk.Coins + expectProceeds sdk.Coins + expectAnteError bool + expectReverseCharge bool + }{ + { + name: "MsgSend(exemption -> exemption) with multiple fee denoms, two denom not enough gases", + 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 + }, + minFeeAmounts: sdk.NewCoins(), + taxAmounts: sdk.NewCoins(), + expectProceeds: sdk.NewCoins(), + expectAnteError: true, + expectReverseCharge: false, + }, + { + name: "MsgSend(normal -> normal) with multiple fee denoms, with enough gas fees but not enough tax", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: requiredFees, + taxAmounts: []sdk.Coin{sdk.NewInt64Coin(denom1, 0), sdk.NewInt64Coin(denom2, 0)}, + expectProceeds: sdk.NewCoins(), + expectReverseCharge: true, + }, + { + name: "MsgSend(normal -> normal), one denom not enough tax", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: requiredFees, + taxAmounts: requiredTaxes.Sub(sdk.NewInt64Coin(denom2, 1)), + expectProceeds: sdk.NewCoins(), + expectReverseCharge: true, + }, + { + name: "MsgSend(normal -> normal), enough taxes + gases", + msgSigner: privs[2], + msgCreator: func() []sdk.Msg { + var msgs []sdk.Msg + msg1 := banktypes.NewMsgSend(addrs[2], addrs[3], sdk.NewCoins(sendCoin, anotherSendCoin)) + msgs = append(msgs, msg1) + return msgs + }, + minFeeAmounts: requiredFees, + taxAmounts: requiredTaxes, + expectProceeds: requiredTaxes, + expectReverseCharge: false, + }, + } + + for _, c := range cases { + s.Run(c.name, func() { + s.SetupTest(true) // setup + s.ctx = s.ctx.WithMinGasPrices(customGasPrices) + + require := s.Require() + tk := s.app.TreasuryKeeper + ak := s.app.AccountKeeper + bk := s.app.BankKeeper + + burnTaxRate := sdk.NewDecWithPrec(5, 3) + burnSplitRate := sdk.NewDecWithPrec(5, 1) + oracleSplitRate := sdk.ZeroDec() + + // Set burn split rate to 50% + tk.SetBurnSplitRate(s.ctx, burnSplitRate) + tk.SetOracleSplitRate(s.ctx, oracleSplitRate) + + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[0].String()) + tk.AddBurnTaxExemptionAddress(s.ctx, addrs[1].String()) + + mfd := ante.NewFeeDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.DistrKeeper, s.app.TaxKeeper) + antehandler := sdk.ChainAnteDecorators(mfd) + pd := post.NewTaxDecorator(s.app.TaxKeeper, bk, ak, tk) + posthandler := sdk.ChainPostDecorators(pd) + + // Fund accounts with both denoms + for i := 0; i < 4; i++ { + coins := sdk.NewCoins( + sdk.NewCoin(denom1, sdk.NewInt(10000000)), + sdk.NewCoin(denom2, sdk.NewInt(10000000)), + ) + testutil.FundAccount(s.app.BankKeeper, s.ctx, addrs[i], coins) + } + + // Set up transaction with multiple fee denoms + feeAmount := sdk.NewCoins(c.minFeeAmounts...).Add(c.taxAmounts...) + gasLimit := testdata.NewTestGasLimit() + require.NoError(s.txBuilder.SetMsgs(c.msgCreator()...)) + s.txBuilder.SetFeeAmount(feeAmount) + s.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{c.msgSigner}, []uint64{0}, []uint64{0} + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + require.NoError(err) + + newCtx, err := antehandler(s.ctx, tx, false) + if c.expectAnteError { + require.Error(err) + return + } + require.NoError(err) + newCtx, err = posthandler(newCtx, tx, false, true) + require.NoError(err) + + actualTaxRate := s.app.TaxKeeper.GetBurnTaxRate(s.ctx) + require.Equal(burnTaxRate, actualTaxRate) + + require.Equal(c.expectReverseCharge, newCtx.Value(taxtypes.ContextKeyTaxReverseCharge)) + + // Check fee collector for each denom + feeCollector := ak.GetModuleAccount(s.ctx, authtypes.FeeCollectorName) + for i, feeCoin := range c.minFeeAmounts { + taxCoin := c.taxAmounts[i] + amountFee := bk.GetBalance(s.ctx, feeCollector.GetAddress(), feeCoin.Denom) + if c.expectReverseCharge { + require.Equal(amountFee, feeCoin.Add(taxCoin)) // tax that isn't paid enough will not be refunded. + } else { + expectedFee := sdk.NewCoin( + feeCoin.Denom, + sdk.NewDec(taxCoin.Amount.Int64()).Mul(burnSplitRate).TruncateInt(), + ).Add(feeCoin) + require.Equal(expectedFee, amountFee) + } + } + + // Check tax proceeds + taxProceeds := s.app.TreasuryKeeper.PeekEpochTaxProceeds(s.ctx) + require.Equal(c.expectProceeds, taxProceeds) + }) } } diff --git a/custom/auth/ante/integration_test.go b/custom/auth/ante/integration_test.go deleted file mode 100644 index dc02a5a5..00000000 --- a/custom/auth/ante/integration_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package ante_test - -import ( - 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/ante" - "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - - customante "github.com/classic-terra/core/v3/custom/auth/ante" - core "github.com/classic-terra/core/v3/types" - taxtypes "github.com/classic-terra/core/v3/x/tax/types" - treasurytypes "github.com/classic-terra/core/v3/x/treasury/types" -) - -// go test -v -run ^TestAnteTestSuite/TestIntegrationTaxExemption$ github.com/classic-terra/core/v3/custom/auth/ante -func (s *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(1_000_000) - sendCoin := sdk.NewInt64Coin(core.MicroSDRDenom, sendAmt) - feeAmt := int64(1000) - - cases := []struct { - name string - msgSigner int64 - msgCreator func() []sdk.Msg - expectedFeeAmount int64 - expectedReverseCharge bool - }{ - { - 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, - expectedReverseCharge: false, - }, { - 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, - expectedReverseCharge: false, - }, { - 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, - expectedReverseCharge: true, - }, { - 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, - expectedReverseCharge: false, - }, - } - - for _, c := range cases { - s.SetupTest(true) // setup - tk := s.app.TreasuryKeeper - ak := s.app.AccountKeeper - bk := s.app.BankKeeper - dk := s.app.DistrKeeper - wk := s.app.WasmKeeper - - // Set burn split rate to 50% - // fee amount should be 500, 50% of 10000 - burnSplitRate := sdk.NewDecWithPrec(5, 1) - tk.SetBurnSplitRate(s.ctx, burnSplitRate) // 50% - - feeCollector := ak.GetModuleAccount(s.ctx, types.FeeCollectorName) - burnModule := ak.GetModuleAccount(s.ctx, treasurytypes.BurnModuleName) - - encodingConfig := s.SetupEncoding() - wasmConfig := wasmtypes.DefaultWasmConfig() - antehandler, err := customante.NewAnteHandler( - customante.HandlerOptions{ - AccountKeeper: ak, - BankKeeper: bk, - WasmKeeper: &wk, - FeegrantKeeper: s.app.FeeGrantKeeper, - OracleKeeper: s.app.OracleKeeper, - TreasuryKeeper: s.app.TreasuryKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - IBCKeeper: *s.app.IBCKeeper, - DistributionKeeper: dk, - WasmConfig: &wasmConfig, - TXCounterStoreKey: s.app.GetKey(wasmtypes.StoreKey), - TaxKeeper: &s.app.TaxKeeper, - }, - ) - s.Require().NoError(err) - - for i := 0; i < 4; i++ { - coins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, 1_000_000)) - testutil.FundAccount(s.app.BankKeeper, s.ctx, addrs[i], coins) - } - - accNums := make([]uint64, len(privs)) - for i, addr := range addrs { - acc := s.app.AccountKeeper.GetAccount(s.ctx, addr) - accNums[i] = acc.GetAccountNumber() - } - - s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() - - tk.AddBurnTaxExemptionAddress(s.ctx, addrs[0].String()) - tk.AddBurnTaxExemptionAddress(s.ctx, addrs[1].String()) - - s.Run(c.name, func() { - // 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() - s.Require().NoError(s.txBuilder.SetMsgs(c.msgCreator()...)) - s.txBuilder.SetFeeAmount(feeAmount) - s.txBuilder.SetGasLimit(gasLimit) - - accNums := make([]uint64, len(privs)) - accSeqs := make([]uint64, len(privs)) - for i, addr := range addrs { - acc := ak.GetAccount(s.ctx, addr) - accNums[i] = acc.GetAccountNumber() - accSeqs[i] = acc.GetSequence() - } - - privs, accNums, accSeqs := []cryptotypes.PrivKey{privs[c.msgSigner]}, []uint64{accNums[c.msgSigner]}, []uint64{accSeqs[c.msgSigner]} - tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) - s.Require().NoError(err) - - // set zero gas prices - s.ctx = s.ctx.WithMinGasPrices(sdk.NewDecCoins()) - - feeCollectorBefore := bk.GetBalance(s.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) - burnBefore := bk.GetBalance(s.ctx, burnModule.GetAddress(), core.MicroSDRDenom) - communityBefore := dk.GetFeePool(s.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) - supplyBefore := bk.GetSupply(s.ctx, core.MicroSDRDenom) - - newCtx, err := antehandler(s.ctx, tx, false) - if i == 0 && c.expectedFeeAmount != 0 { - /*s.Require().EqualError(err, fmt.Sprintf( - "insufficient fees; got: \"\", required: \"%dusdr\" = \"\"(gas) + \"%dusdr\"(stability): insufficient fee", - c.expectedFeeAmount, c.expectedFeeAmount))*/ - s.Require().NoError(err) - s.Require().Equal(newCtx.Value(taxtypes.ContextKeyTaxReverseCharge), true) // reverse charge due to lack of fee - } else { - s.Require().NoError(err) - } - - feeCollectorAfter := bk.GetBalance(s.ctx, feeCollector.GetAddress(), core.MicroSDRDenom) - burnAfter := bk.GetBalance(s.ctx, burnModule.GetAddress(), core.MicroSDRDenom) - communityAfter := dk.GetFeePool(s.ctx).CommunityPool.AmountOf(core.MicroSDRDenom) - supplyAfter := bk.GetSupply(s.ctx, core.MicroSDRDenom) - - if i == 0 { - s.Require().Equal(feeCollectorBefore, feeCollectorAfter) - s.Require().Equal(burnBefore, burnAfter) - s.Require().Equal(communityBefore, communityAfter) - s.Require().Equal(supplyBefore, supplyAfter) - } - - if i == 1 { - s.Require().Equal(feeCollectorBefore, feeCollectorAfter) - splitAmount := burnSplitRate.MulInt64(c.expectedFeeAmount).TruncateInt() - s.Require().Equal(burnBefore, burnAfter.AddAmount(splitAmount)) - s.Require().Equal(communityBefore, communityAfter.Add(sdk.NewDecFromInt(splitAmount))) - s.Require().Equal(supplyBefore, supplyAfter.SubAmount(splitAmount)) - } - } - }) - } -} diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index c1e96bac..7cd48d7a 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -259,6 +259,14 @@ func (n *NodeConfig) GrantAddress(granter, gratee string, spendLimit string, wal n.LogActionF("successfully granted for address %s", gratee) } +func (n *NodeConfig) GrantBankSend(gratee string, spendLimit string, walletName string) { + n.LogActionF("granting for address %s", gratee) + cmd := []string{"terrad", "tx", "authz", "grant", gratee, "send", fmt.Sprintf("--from=%s", walletName), fmt.Sprintf("--spend-limit=%s", spendLimit)} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainID, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully granted bank send for address %s", gratee) +} + func (n *NodeConfig) CreateWallet(walletName string) string { n.LogActionF("creating wallet %s", walletName) cmd := []string{"terrad", "keys", "add", walletName, "--keyring-backend=test"} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 27fe83e4..7b50d36a 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -119,6 +119,7 @@ func (s *IntegrationTestSuite) TestFeeTax() { s.Require().NoError(err) test1Addr := node.CreateWallet("test1") + s.Require().NotEqual(test1Addr, "") // Test 1: banktypes.MsgSend // burn tax with bank send @@ -138,6 +139,7 @@ func (s *IntegrationTestSuite) TestFeeTax() { // Test 2: try bank send with grant test2Addr := node.CreateWallet("test2") + s.Require().NotEqual(test2Addr, "") transferAmount2 := sdkmath.NewInt(10000000) transferCoin2 := sdk.NewCoin(initialization.TerraDenom, transferAmount2) @@ -176,6 +178,35 @@ func (s *IntegrationTestSuite) TestFeeTax() { s.Require().Equal(newValidatorBalance, validatorBalance.Sub(sdk.NewCoin(initialization.TerraDenom, subAmount))) } +func (s *IntegrationTestSuite) TestAuthz() { + chain := s.configurer.GetChainConfig(0) + node, err := chain.GetDefaultNode() + s.Require().NoError(err) + + transferAmount1 := sdkmath.NewInt(20000000) + transferCoin1 := sdk.NewCoin(initialization.TerraDenom, transferAmount1) + test1WalletName := "authz1" + test2WalletName := "authz2" + test1Addr := node.CreateWallet(test1WalletName) + test2Addr := node.CreateWallet(test2WalletName) + validatorAddr := node.GetWallet(initialization.ValidatorWalletName) + s.Require().NotEqual(validatorAddr, "") + validatorBalance, err := node.QuerySpecificBalance(validatorAddr, initialization.TerraDenom) + s.Require().NoError(err) + + node.GrantBankSend(test1Addr, transferCoin1.String(), "val") + node.BankSendWithWallet(transferCoin1.String(), validatorAddr, test2Addr, test1WalletName) + + newValidatorBalance, err := node.QuerySpecificBalance(validatorAddr, initialization.TerraDenom) + s.Require().NoError(err) + + balanceTest2, err := node.QuerySpecificBalance(test2Addr, initialization.TerraDenom) + s.Require().NoError(err) + + s.Require().Equal(transferAmount1, balanceTest2.Amount) + s.Require().Equal(validatorBalance.Amount.Sub(transferAmount1).Sub(initialization.BurnTaxRate.MulInt(transferAmount1).TruncateInt()), newValidatorBalance.Amount) +} + func (s *IntegrationTestSuite) TestFeeTaxWasm() { chain := s.configurer.GetChainConfig(0) node, err := chain.GetDefaultNode()