Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(tax2gas): add test for post handler #523

Merged
merged 9 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions x/tax2gas/post/fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package post_test

import (
oracle "github.com/classic-terra/core/v3/x/oracle/types"
"github.com/classic-terra/core/v3/x/tax2gas/ante"
"github.com/classic-terra/core/v3/x/tax2gas/post"
tax2gastypes "github.com/classic-terra/core/v3/x/tax2gas/types"
"github.com/classic-terra/core/v3/x/tax2gas/utils"
"github.com/classic-terra/core/v3/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/signing"
authtypes "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"
)

func (s *PostTestSuite) TestDeductFeeDecorator() {
anteConsumedFee := int64(207566)
postConsumedFee := int64(1368041)

testCases := []struct {
name string
simulation bool
checkTx bool
setupFunc func(addr sdk.AccAddress, tx signing.Tx)
expectedOracle sdk.Coins
expectedCp sdk.Coins
expectedBurn sdk.Coins
expFail bool
expErrMsg string
}{
{
name: "happy case",
setupFunc: func(addr sdk.AccAddress, tx signing.Tx) {
s.setupTestCase(addr, 100000000, 100000, anteConsumedFee+500022)
},
// amount: 100000000
// tax(0.5%): 500022
// use default value
// burn: tax * 0.9
// distributtion: tax * 0.1 = 500022
// cp: 2% of distribution: 499994 * 0.1 * 0.02 ~ 1000
// oracle: distribution - oracle = 49002
expectedBurn: sdk.NewCoins(sdk.NewInt64Coin("uluna", 450020)),
expectedCp: sdk.NewCoins(sdk.NewInt64Coin("uluna", 1000)),
expectedOracle: sdk.NewCoins(sdk.NewInt64Coin("uluna", 49002)),
},
{
name: "not enough fee",
setupFunc: func(addr sdk.AccAddress, tx signing.Tx) {
s.setupTestCase(addr, 100000000, 100000, 600000)
},
expFail: true,
},
{
name: "combine ante + post, not enough fee",
setupFunc: func(addr sdk.AccAddress, tx signing.Tx) {
s.setupCombineAnteAndPost(addr, tx, 100000, 7328, 707559)
},
expFail: true,
},
{
name: "combine ante + post, enough fee",
setupFunc: func(addr sdk.AccAddress, tx signing.Tx) {
s.setupCombineAnteAndPost(addr, tx, 100000000, 100000, anteConsumedFee+postConsumedFee+500022)
},
expFail: false,
expectedOracle: sdk.NewCoins(sdk.NewInt64Coin("uluna", 49002)),
expectedCp: sdk.NewCoins(sdk.NewInt64Coin("uluna", 1000)),
expectedBurn: sdk.NewCoins(sdk.NewInt64Coin("uluna", 450020)),
},
}

for _, tc := range testCases {
s.Run(tc.name, func() {
// Reset the entire app and context for each test case
s.SetupTest(true)
s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder()
deco := post.NewTax2GasPostDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.DistrKeeper, s.app.Tax2gasKeeper)
posthandler := sdk.ChainPostDecorators(deco)

// Generate a new address for each test case
priv1, _, addr1 := testdata.KeyTestPubAddr()

// Fund the account
coins := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(10000000)))
testutil.FundAccount(s.app.BankKeeper, s.ctx, addr1, coins)

// Create a new test transaction
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID())
s.Require().NoError(err)

tc.setupFunc(addr1, tx)

_, err = posthandler(s.ctx, tx, tc.simulation, true)
s.assertTestCase(tc, err)
})
}
}

func (s *PostTestSuite) setupTestCase(addr sdk.AccAddress, sendAmount, gasLimit, feeAmount int64) {
msg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(sendAmount))))
gm := tax2gastypes.NewTax2GasMeter(s.ctx.GasMeter().Limit(), false)
s.setupTax2GasMeter(gm, msg)
s.ctx = s.ctx.WithGasMeter(gm)
s.ctx = s.ctx.WithValue(tax2gastypes.AnteConsumedGas, uint64(7328))
s.Require().NoError(s.txBuilder.SetMsgs(msg))
s.txBuilder.SetGasLimit(uint64(gasLimit))
s.ctx = s.ctx.WithValue(tax2gastypes.PaidDenom, "uluna")
s.txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewInt64Coin("uluna", feeAmount)))
}

func (s *PostTestSuite) setupCombineAnteAndPost(addr sdk.AccAddress, tx signing.Tx, sendAmount, gasLimit, feeAmount int64) {
msg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(sendAmount))))
gm := tax2gastypes.NewTax2GasMeter(s.ctx.GasMeter().Limit(), false)
// s.setupTax2GasMeter(gm, msg)
mfd := ante.NewFeeDecorator(s.app.AccountKeeper, s.app.BankKeeper, s.app.FeeGrantKeeper, s.app.TreasuryKeeper, s.app.Tax2gasKeeper)
s.txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewInt64Coin("uluna", feeAmount)))
s.txBuilder.SetGasLimit(uint64(gasLimit))
s.Require().NoError(s.txBuilder.SetMsgs(msg))
s.ctx = s.ctx.WithGasMeter(gm)

antehandler := sdk.ChainAnteDecorators(mfd)
newCtx, _ := antehandler(s.ctx, tx, false)
distrBalance := s.app.BankKeeper.GetBalance(s.ctx, s.app.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName), "uluna")

// msg send consume 7328 gas, uluna gas price: 28.325
// expect only this consumed gas is transferred to fee collector account after ante.
s.Require().Equal(sdk.NewDecWithPrec(7328, 0).Mul(sdk.NewDecWithPrec(28325, 3)).RoundInt64(), distrBalance.Amount.Int64())
s.ctx = newCtx
}

func (s *PostTestSuite) setupTax2GasMeter(gm sdk.GasMeter, msg sdk.Msg) {
burnTaxRate := s.app.Tax2gasKeeper.GetBurnTaxRate(s.ctx)
lunaGasPrice := sdk.NewDecCoinFromDec("uluna", sdk.NewDecWithPrec(28325, 3))
taxes := utils.FilterMsgAndComputeTax(s.ctx, s.app.TreasuryKeeper, burnTaxRate, msg)
taxGas, _ := utils.ComputeGas(sdk.DecCoins{lunaGasPrice}, taxes)
gm.(*tax2gastypes.Tax2GasMeter).ConsumeTax(taxGas, "tax")
}

func (s *PostTestSuite) assertTestCase(tc struct {
name string
simulation bool
checkTx bool
setupFunc func(addr sdk.AccAddress, tx signing.Tx)
expectedOracle sdk.Coins
expectedCp sdk.Coins
expectedBurn sdk.Coins
expFail bool
expErrMsg string
}, err error,
) {
currentOracle := s.app.BankKeeper.GetBalance(s.ctx, s.app.AccountKeeper.GetModuleAddress(oracle.ModuleName), "uluna")
currentCp := s.app.DistrKeeper.GetFeePoolCommunityCoins(s.ctx).AmountOf("uluna")
currentBurn := s.app.BankKeeper.GetBalance(s.ctx, s.app.AccountKeeper.GetModuleAddress(types.BurnModuleName), "uluna")
if tc.expFail {
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expErrMsg)
} else {
s.Require().NoError(err)
}
s.Require().Equal(tc.expectedOracle.AmountOf("uluna").Int64(), currentOracle.Amount.Int64())
s.Require().Equal(tc.expectedCp.AmountOf("uluna").Int64(), currentCp.RoundInt64())
s.Require().Equal(tc.expectedBurn.AmountOf("uluna").Int64(), currentBurn.Amount.Int64())
}
6 changes: 4 additions & 2 deletions x/tax2gas/post/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func (tgd Tax2gasPostDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate

if !simulate {
// Deduct feeCoins with paid amount
if paidAmount.Ceil().RoundInt().Int64() > feeCoins.AmountOf(paidDenom).Int64() {
return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fee: %s", feeCoins)
}
feeCoins = feeCoins.Sub(sdk.NewCoin(paidDenom, paidAmount.Ceil().RoundInt()))
}

Expand Down Expand Up @@ -155,12 +158,11 @@ func (tgd Tax2gasPostDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate

// First, we will deduct the fees covered taxGas and handle BurnTaxSplit
taxes, payableFees, gasRemaining := tax2gasutils.CalculateTaxesAndPayableFee(gasPrices, feeCoins, taxGas, totalGasRemaining)
if !simulate && !ctx.IsCheckTx() && gasRemaining.IsPositive() {
if !simulate && gasRemaining.IsPositive() {
gasRemainingFees, err := tax2gasutils.ComputeFeesOnGasConsumed(tx, gasPrices, gasRemaining)
if err != nil {
return ctx, err
}

return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "fees are not enough to pay for gas, need to cover %s gas more, which equal to %q ", gasRemaining.String(), gasRemainingFees)
}
feePayerAccount := tgd.accountKeeper.GetAccount(ctx, feePayer)
Expand Down
134 changes: 134 additions & 0 deletions x/tax2gas/post/post_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package post_test

import (
"testing"

"github.com/stretchr/testify/suite"

dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"

terraapp "github.com/classic-terra/core/v3/app"
tax2gastypes "github.com/classic-terra/core/v3/x/tax2gas/types"
treasurytypes "github.com/classic-terra/core/v3/x/treasury/types"
)

type PostTestSuite struct {
suite.Suite

app *terraapp.TerraApp
// anteHandler sdk.AnteHandler
ctx sdk.Context
clientCtx client.Context
txBuilder client.TxBuilder
}

// returns context and app with params set on account keeper
func createTestApp(isCheckTx bool, tempDir string) (*terraapp.TerraApp, sdk.Context) {
// TODO: we need to feed in custom binding here?
var wasmOpts []wasmkeeper.Option
app := terraapp.NewTerraApp(
log.NewNopLogger(), dbm.NewMemDB(), nil, true, map[int64]bool{},
tempDir, terraapp.MakeEncodingConfig(),
simtestutil.EmptyAppOptions{}, wasmOpts,
)
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{})
app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams())
app.TreasuryKeeper.SetParams(ctx, treasurytypes.DefaultParams())
app.DistrKeeper.SetParams(ctx, distributiontypes.DefaultParams())
app.DistrKeeper.SetFeePool(ctx, distributiontypes.InitialFeePool())
tax2gasParams := tax2gastypes.DefaultParams()
tax2gasParams.Enabled = true
app.Tax2gasKeeper.SetParams(ctx, tax2gasParams)

return app, ctx
}

// SetupTest setups a new test, with new app, context, and anteHandler.
func (suite *PostTestSuite) SetupTest(isCheckTx bool) {
tempDir := suite.T().TempDir()
suite.app, suite.ctx = createTestApp(isCheckTx, tempDir)
suite.ctx = suite.ctx.WithBlockHeight(1)
suite.ctx = suite.ctx.WithGasMeter(tax2gastypes.NewTax2GasMeter(suite.ctx.GasMeter().Limit(), false))

// Set up TxConfig.
encodingConfig := suite.SetupEncoding()

suite.clientCtx = client.Context{}.
WithTxConfig(encodingConfig.TxConfig)
}

func (suite *PostTestSuite) SetupEncoding() testutil.TestEncodingConfig {
encodingConfig := testutil.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)

return encodingConfig
}

// CreateTestTx is a helper function to create a tx given multiple inputs.
func (suite *PostTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) {
// First round: we gather all the signer infos. We use the "set empty
// signature" hack to do that.
var sigsV2 []signing.SignatureV2
for i, priv := range privs {
sigV2 := signing.SignatureV2{
PubKey: priv.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: accSeqs[i],
}

sigsV2 = append(sigsV2, sigV2)
}
err := suite.txBuilder.SetSignatures(sigsV2...)
if err != nil {
return nil, err
}

// Second round: all signer infos are set, so each signer can sign.
sigsV2 = []signing.SignatureV2{}
for i, priv := range privs {
signerData := xauthsigning.SignerData{
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
}
sigV2, err := tx.SignWithPrivKey(
suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData,
suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i])
if err != nil {
return nil, err
}

sigsV2 = append(sigsV2, sigV2)
}
err = suite.txBuilder.SetSignatures(sigsV2...)
if err != nil {
return nil, err
}

return suite.txBuilder.GetTx(), nil
}

func TestPostTestSuite(t *testing.T) {
suite.Run(t, new(PostTestSuite))
}
Loading