From 4563e66a5ae96f6ed64c430c60aed9a9c9b7bfd8 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Thu, 28 Nov 2024 16:04:08 +0100 Subject: [PATCH] feat(x/photon): validate fee ante decorator (#54) Relates to #44 This PR adds a `sdk.AnteDecorator` implementation in `x/photon/ante` and adds it to the list of the app ante decorators. This new ante decorator ensures that photon is the fee token or returns an error. Exceptions are possible like if the transaction's messages type URL are present in a new parameter of the photon module `txFeeExceptions`. Some significant changes have been made to the e2e tests to make them compatible with this conditions. `uatone` has also been put in a constant to avoid repetition (in `app/params` package) --- Makefile | 2 +- ante/ante.go | 8 +- app/app.go | 4 +- app/params/config.go | 20 ++ app/params/params.go | 7 - app/upgrades/v2/constants.go | 23 ++ app/upgrades/v2/upgrades.go | 66 ++++++ cmd/atomoned/cmd/bech32_convert.go | 3 +- cmd/atomoned/cmd/config.go | 23 +- proto/atomone/photon/v1/photon.proto | 6 +- tests/e2e/e2e_bank_test.go | 23 +- tests/e2e/e2e_distribution_test.go | 5 +- tests/e2e/e2e_exec_test.go | 55 +++-- tests/e2e/e2e_feegrant_test.go | 3 - tests/e2e/e2e_gov_test.go | 2 +- tests/e2e/e2e_ibc_test.go | 10 +- tests/e2e/e2e_photon_test.go | 87 ++++---- tests/e2e/e2e_setup_test.go | 67 +++--- tests/e2e/e2e_staking_test.go | 9 +- tests/e2e/e2e_vesting_test.go | 45 ++-- tests/e2e/scripts/hermes_bootstrap.sh | 8 +- x/photon/ante/ante.go | 77 +++++++ x/photon/ante/ante_test.go | 293 ++++++++++++++++++++++++++ x/photon/keeper/grpc_query_test.go | 9 +- x/photon/keeper/msg_server.go | 8 +- x/photon/keeper/msg_server_test.go | 53 +++-- x/photon/simulation/genesis.go | 18 +- x/photon/simulation/operations.go | 8 +- x/photon/types/errors.go | 11 +- x/photon/types/msgs_test.go | 8 +- x/photon/types/params.go | 11 +- x/photon/types/photon.pb.go | 77 ++++++- 32 files changed, 822 insertions(+), 227 deletions(-) create mode 100644 app/params/config.go delete mode 100644 app/params/params.go create mode 100644 app/upgrades/v2/constants.go create mode 100644 app/upgrades/v2/upgrades.go create mode 100644 x/photon/ante/ante.go create mode 100644 x/photon/ante/ante_test.go diff --git a/Makefile b/Makefile index c0939163..9ba92cdc 100644 --- a/Makefile +++ b/Makefile @@ -273,7 +273,7 @@ start-localnet-ci: build ./build/atomoned genesis add-genesis-account user 1000000000uatone --home ~/.atomoned-liveness --keyring-backend test ./build/atomoned genesis gentx val 1000000000uatone --home ~/.atomoned-liveness --chain-id liveness ./build/atomoned genesis collect-gentxs --home ~/.atomoned-liveness - sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0uatone"/' ~/.atomoned-liveness/config/app.toml + sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0.001uatone,0.001uphoton"/' ~/.atomoned-liveness/config/app.toml ./build/atomoned start --home ~/.atomoned-liveness --x-crisis-skip-assert-invariants .PHONY: start-localnet-ci diff --git a/ante/ante.go b/ante/ante.go index 5696b989..48ff0980 100644 --- a/ante/ante.go +++ b/ante/ante.go @@ -12,6 +12,8 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" atomoneerrors "github.com/atomone-hub/atomone/types/errors" + photonante "github.com/atomone-hub/atomone/x/photon/ante" + photonkeeper "github.com/atomone-hub/atomone/x/photon/keeper" ) // HandlerOptions extend the SDK's AnteHandler options by requiring the IBC @@ -21,6 +23,7 @@ type HandlerOptions struct { Codec codec.BinaryCodec IBCkeeper *ibckeeper.Keeper StakingKeeper *stakingkeeper.Keeper + PhotonKeeper *photonkeeper.Keeper TxFeeChecker ante.TxFeeChecker } @@ -37,10 +40,12 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) { if opts.IBCkeeper == nil { return nil, errorsmod.Wrap(atomoneerrors.ErrLogic, "IBC keeper is required for AnteHandler") } - if opts.StakingKeeper == nil { return nil, errorsmod.Wrap(atomoneerrors.ErrNotFound, "staking param store is required for AnteHandler") } + if opts.PhotonKeeper == nil { + return nil, errorsmod.Wrap(atomoneerrors.ErrNotFound, "photon keeper is required for AnteHandler") + } sigGasConsumer := opts.SigGasConsumer if sigGasConsumer == nil { @@ -54,6 +59,7 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) { ante.NewValidateMemoDecorator(opts.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper), NewGovVoteDecorator(opts.Codec, opts.StakingKeeper), + photonante.NewValidateFeeDecorator(opts.PhotonKeeper), ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper, opts.TxFeeChecker), ante.NewSetPubKeyDecorator(opts.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewValidateSigCountDecorator(opts.AccountKeeper), diff --git a/app/app.go b/app/app.go index 7f72e80a..33439649 100644 --- a/app/app.go +++ b/app/app.go @@ -51,6 +51,7 @@ import ( "github.com/atomone-hub/atomone/app/keepers" "github.com/atomone-hub/atomone/app/params" "github.com/atomone-hub/atomone/app/upgrades" + v2 "github.com/atomone-hub/atomone/app/upgrades/v2" govtypes "github.com/atomone-hub/atomone/x/gov/types" ) @@ -58,7 +59,7 @@ var ( // DefaultNodeHome default home directories for the application daemon DefaultNodeHome string - Upgrades = []upgrades.Upgrade{} + Upgrades = []upgrades.Upgrade{v2.Upgrade} ) var ( @@ -221,6 +222,7 @@ func NewAtomOneApp( Codec: appCodec, IBCkeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, + PhotonKeeper: app.PhotonKeeper, // If TxFeeChecker is nil the default ante TxFeeChecker is used TxFeeChecker: nil, }, diff --git a/app/params/config.go b/app/params/config.go new file mode 100644 index 00000000..f12e7b0f --- /dev/null +++ b/app/params/config.go @@ -0,0 +1,20 @@ +package params + +const ( + BondDenom = "uatone" + + Bech32PrefixAccAddr = "atone" +) + +var ( + // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. + Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" + // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. + Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" + // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. + Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" + // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. + Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" + // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. + Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" +) diff --git a/app/params/params.go b/app/params/params.go deleted file mode 100644 index b6aa5fb5..00000000 --- a/app/params/params.go +++ /dev/null @@ -1,7 +0,0 @@ -package params - -// Simulation parameter constants -const ( - StakePerAccount = "stake_per_account" - InitiallyBondedValidators = "initially_bonded_validators" -) diff --git a/app/upgrades/v2/constants.go b/app/upgrades/v2/constants.go new file mode 100644 index 00000000..2b184a3d --- /dev/null +++ b/app/upgrades/v2/constants.go @@ -0,0 +1,23 @@ +package v2 + +import ( + store "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/atomone-hub/atomone/app/upgrades" + photontypes "github.com/atomone-hub/atomone/x/photon/types" +) + +const ( + UpgradeName = "v2" +) + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: store.StoreUpgrades{ + Added: []string{ + // new module added in v2 + photontypes.ModuleName, + }, + }, +} diff --git a/app/upgrades/v2/upgrades.go b/app/upgrades/v2/upgrades.go new file mode 100644 index 00000000..95930246 --- /dev/null +++ b/app/upgrades/v2/upgrades.go @@ -0,0 +1,66 @@ +package v2 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + "github.com/atomone-hub/atomone/app/keepers" +) + +// CreateUpgradeHandler returns a upgrade handler for AtomOne v2 +// which executes the following migrations: +// - add new denom metadata for photon in the bank module store. +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + keepers *keepers.AppKeepers, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx.Logger().Info("Starting module migrations...") + // RunMigrations will detect the add of the photon module, will initiate + // its genesis and will fill the versionMap with its consensus version. + vm, err := mm.RunMigrations(ctx, configurator, vm) + if err != nil { + return vm, err + } + // Add the photon denom metadata to the bank module store + setPhotonDenomMetadata(ctx, keepers.BankKeeper) + ctx.Logger().Info("Upgrade complete") + return vm, nil + } +} + +func setPhotonDenomMetadata(ctx sdk.Context, bk bankkeeper.Keeper) { + ctx.Logger().Info("Adding photon denom metadata...") + bk.SetDenomMetaData(ctx, banktypes.Metadata{ + Base: "uphoton", + Display: "photon", + Name: "AtomOne Photon", + Symbol: "PHOTON", + Description: "The fee token of AtomOne Hub", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "uphoton", + Exponent: 0, + Aliases: []string{ + "microphoton", + }, + }, + { + Denom: "mphoton", + Exponent: 3, + Aliases: []string{ + "milliphoton", + }, + }, + { + Denom: "photon", + Exponent: 6, + }, + }, + }) + ctx.Logger().Info("Photon denom metadata added") +} diff --git a/cmd/atomoned/cmd/bech32_convert.go b/cmd/atomoned/cmd/bech32_convert.go index ba464594..a954173a 100644 --- a/cmd/atomoned/cmd/bech32_convert.go +++ b/cmd/atomoned/cmd/bech32_convert.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/cobra" + appparams "github.com/atomone-hub/atomone/app/params" addressutil "github.com/atomone-hub/atomone/pkg/address" ) @@ -41,7 +42,7 @@ Example: }, } - cmd.Flags().StringP(flagBech32Prefix, "p", "atone", "Bech32 Prefix to encode to") + cmd.Flags().StringP(flagBech32Prefix, "p", appparams.Bech32PrefixAccAddr, "Bech32 Prefix to encode to") return cmd } diff --git a/cmd/atomoned/cmd/config.go b/cmd/atomoned/cmd/config.go index b309abbf..ccde3feb 100644 --- a/cmd/atomoned/cmd/config.go +++ b/cmd/atomoned/cmd/config.go @@ -2,29 +2,14 @@ package cmd import ( sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - Bech32PrefixAccAddr = "atone" -) -var ( - // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. - Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" - // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. - Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" - // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. - Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" - // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. - Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" - // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. - Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" + appparams "github.com/atomone-hub/atomone/app/params" ) func InitSDKConfig() { cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) - cfg.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) + cfg.SetBech32PrefixForAccount(appparams.Bech32PrefixAccAddr, appparams.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(appparams.Bech32PrefixValAddr, appparams.Bech32PrefixValPub) + cfg.SetBech32PrefixForConsensusNode(appparams.Bech32PrefixConsAddr, appparams.Bech32PrefixConsPub) cfg.Seal() } diff --git a/proto/atomone/photon/v1/photon.proto b/proto/atomone/photon/v1/photon.proto index 8fb44e60..159b7d38 100644 --- a/proto/atomone/photon/v1/photon.proto +++ b/proto/atomone/photon/v1/photon.proto @@ -7,6 +7,10 @@ option go_package = "github.com/atomone-hub/atomone/x/photon/types"; // Params defines the parameters for the x/photon module. message Params { - // mint photon or not + // Allow to mint photon or not bool mint_disabled = 1; + // tx_fee_exceptions holds the msg type urls that are allowed to use some + // different tx fee coins than photon. + // A wildcard "*" can be used to allow all transactions to use any fee denom. + repeated string tx_fee_exceptions = 2; } diff --git a/tests/e2e/e2e_bank_test.go b/tests/e2e/e2e_bank_test.go index 3d576c96..05fdf1d0 100644 --- a/tests/e2e/e2e_bank_test.go +++ b/tests/e2e/e2e_bank_test.go @@ -47,7 +47,7 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() { ) // alice sends tokens to bob - s.execBankSend(s.chainA, valIdx, alice.String(), bob.String(), tokenAmount.String(), standardFees.String(), false) + s.execBankSend(s.chainA, valIdx, alice.String(), bob.String(), tokenAmount.String(), false) // check that the transfer was successful s.Require().Eventually( @@ -58,7 +58,7 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() { afterBobUAtoneBalance, err = getSpecificBalance(chainEndpoint, bob.String(), uatoneDenom) s.Require().NoError(err) - decremented := beforeAliceUAtoneBalance.Sub(tokenAmount).Sub(standardFees).IsEqual(afterAliceUAtoneBalance) + decremented := beforeAliceUAtoneBalance.Sub(tokenAmount).IsEqual(afterAliceUAtoneBalance) incremented := beforeBobUAtoneBalance.Add(tokenAmount).IsEqual(afterBobUAtoneBalance) return decremented && incremented @@ -71,7 +71,8 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() { beforeAliceUAtoneBalance, beforeBobUAtoneBalance = afterAliceUAtoneBalance, afterBobUAtoneBalance // alice sends tokens to bob and charlie, at once - s.execBankMultiSend(s.chainA, valIdx, alice.String(), []string{bob.String(), charlie.String()}, tokenAmount.String(), standardFees.String(), false) + s.execBankMultiSend(s.chainA, valIdx, alice.String(), + []string{bob.String(), charlie.String()}, tokenAmount.String(), false) s.Require().Eventually( func() bool { @@ -85,7 +86,7 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() { s.Require().NoError(err) // assert alice's account gets decremented the amount of tokens twice - decremented := beforeAliceUAtoneBalance.Sub(tokenAmount).Sub(tokenAmount).Sub(standardFees).IsEqual(afterAliceUAtoneBalance) + decremented := beforeAliceUAtoneBalance.Sub(tokenAmount).Sub(tokenAmount).IsEqual(afterAliceUAtoneBalance) incremented := beforeBobUAtoneBalance.Add(tokenAmount).IsEqual(afterBobUAtoneBalance) && beforeCharlieUAtoneBalance.Add(tokenAmount).IsEqual(afterCharlieUAtoneBalance) @@ -95,4 +96,18 @@ func (s *IntegrationTestSuite) testBankTokenTransfer() { time.Second, ) }) + + s.Run("send tokens with atone fees", func() { + var ( + valIdx = 0 + c = s.chainA + ) + alice, _ := c.genesisAccounts[1].keyInfo.GetAddress() + bob, _ := c.genesisAccounts[2].keyInfo.GetAddress() + + // alice sends tokens to bob should fail because doesn't use photons for the fees. + atoneFees := sdk.NewCoin(uatoneDenom, standardFees.Amount) + s.execBankSend(s.chainA, valIdx, alice.String(), bob.String(), + tokenAmount.String(), true, withKeyValue(flagFees, atoneFees)) + }) } diff --git a/tests/e2e/e2e_distribution_test.go b/tests/e2e/e2e_distribution_test.go index 0c23a5ec..a6fff97b 100644 --- a/tests/e2e/e2e_distribution_test.go +++ b/tests/e2e/e2e_distribution_test.go @@ -21,7 +21,6 @@ func (s *IntegrationTestSuite) testDistribution() { delegatorAddress, _ := s.chainA.genesisAccounts[2].keyInfo.GetAddress() newWithdrawalAddress, _ := s.chainA.genesisAccounts[3].keyInfo.GetAddress() - fees := sdk.NewCoin(uatoneDenom, sdk.NewInt(1000)) beforeBalance, err := getSpecificBalance(chainEndpoint, newWithdrawalAddress.String(), uatoneDenom) s.Require().NoError(err) @@ -29,7 +28,7 @@ func (s *IntegrationTestSuite) testDistribution() { beforeBalance = sdk.NewCoin(uatoneDenom, sdk.NewInt(0)) } - s.execSetWithdrawAddress(s.chainA, 0, fees.String(), delegatorAddress.String(), newWithdrawalAddress.String(), atomoneHomePath) + s.execSetWithdrawAddress(s.chainA, 0, delegatorAddress.String(), newWithdrawalAddress.String(), atomoneHomePath) // Verify s.Require().Eventually( @@ -76,7 +75,7 @@ func (s *IntegrationTestSuite) fundCommunityPool() { beforeDistUatoneBalance = sdk.NewInt64Coin(uatoneDenom, 0) } - s.execDistributionFundCommunityPool(s.chainA, 0, sender.String(), tokenAmount.String(), standardFees.String()) + s.execDistributionFundCommunityPool(s.chainA, 0, sender.String(), tokenAmount.String()) s.Require().Eventually( func() bool { diff --git a/tests/e2e/e2e_exec_test.go b/tests/e2e/e2e_exec_test.go index bf95e2c4..f712b845 100644 --- a/tests/e2e/e2e_exec_test.go +++ b/tests/e2e/e2e_exec_test.go @@ -160,7 +160,7 @@ func (s *IntegrationTestSuite) execVestingTx( //nolint:unused atomoneCommand = append(atomoneCommand, fmt.Sprintf("--%s=%v", flag, value)) } - s.executeAtomoneTxCommand(ctx, c, atomoneCommand, 0, s.defaultExecValidation(c, 0, nil)) + s.executeAtomoneTxCommand(ctx, c, atomoneCommand, 0, nil) s.T().Logf("successfully %s with %v", method, args) } @@ -184,7 +184,7 @@ func (s *IntegrationTestSuite) execUnjail( ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - s.T().Logf("Executing atomoned slashing unjail %s with options: %v", c.id, opt) + s.T().Logf("Executing atomoned slashing unjail %s with options: %v", c.id, opts) atomoneCommand := []string{ atomonedBinary, txCommand, @@ -197,7 +197,7 @@ func (s *IntegrationTestSuite) execUnjail( atomoneCommand = append(atomoneCommand, fmt.Sprintf("--%s=%v", flag, value)) } - s.executeAtomoneTxCommand(ctx, c, atomoneCommand, 0, s.defaultExecValidation(c, 0, nil)) + s.executeAtomoneTxCommand(ctx, c, atomoneCommand, 0, nil) s.T().Logf("successfully unjail with options %v", opt) } @@ -257,13 +257,11 @@ func (s *IntegrationTestSuite) execBankSend( valIdx int, from, to, - amt, - fees string, + amt string, expectErr bool, opt ...flagOption, ) { // TODO remove the hardcode opt after refactor, all methods should accept custom flags - opt = append(opt, withKeyValue(flagFees, fees)) opt = append(opt, withKeyValue(flagFrom, from)) opts := applyOptions(c.id, opt) @@ -295,12 +293,10 @@ func (s *IntegrationTestSuite) execBankMultiSend( from string, to []string, amt string, - fees string, expectErr bool, opt ...flagOption, ) { // TODO remove the hardcode opt after refactor, all methods should accept custom flags - opt = append(opt, withKeyValue(flagFees, fees)) opt = append(opt, withKeyValue(flagFrom, from)) opts := applyOptions(c.id, opt) @@ -346,7 +342,7 @@ func (s *IntegrationTestSuite) execBankSendBatch( //nolint:unused for i := range txs { s.T().Logf(txs[i].log) - s.execBankSend(c, valIdx, txs[i].from, txs[i].to, txs[i].amt, txs[i].fees, txs[i].expectErr) + s.execBankSend(c, valIdx, txs[i].from, txs[i].to, txs[i].amt, txs[i].expectErr) if !txs[i].expectErr { if !txs[i].expectErr { sucessBankSendCount++ @@ -357,7 +353,7 @@ func (s *IntegrationTestSuite) execBankSendBatch( //nolint:unused return sucessBankSendCount } -func (s *IntegrationTestSuite) execWithdrawAllRewards(c *chain, valIdx int, payee, fees string, expectErr bool) { //nolint:unused +func (s *IntegrationTestSuite) execWithdrawAllRewards(c *chain, valIdx int, payee string, expectErr bool) { //nolint:unused ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -367,7 +363,7 @@ func (s *IntegrationTestSuite) execWithdrawAllRewards(c *chain, valIdx int, paye distributiontypes.ModuleName, "withdraw-all-rewards", fmt.Sprintf("--%s=%s", flags.FlagFrom, payee), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, fees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), "--keyring-backend=test", "--output=json", @@ -377,7 +373,7 @@ func (s *IntegrationTestSuite) execWithdrawAllRewards(c *chain, valIdx int, paye s.executeAtomoneTxCommand(ctx, c, atomoneCommand, valIdx, s.expectErrExecValidation(c, valIdx, expectErr)) } -func (s *IntegrationTestSuite) execDistributionFundCommunityPool(c *chain, valIdx int, from, amt, fees string) { +func (s *IntegrationTestSuite) execDistributionFundCommunityPool(c *chain, valIdx int, from, amt string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -391,7 +387,7 @@ func (s *IntegrationTestSuite) execDistributionFundCommunityPool(c *chain, valId amt, fmt.Sprintf("--%s=%s", flags.FlagFrom, from), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), - fmt.Sprintf("--%s=%s", flags.FlagFees, fees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", "--output=json", "-y", @@ -401,7 +397,7 @@ func (s *IntegrationTestSuite) execDistributionFundCommunityPool(c *chain, valId s.T().Logf("Successfully funded community pool") } -func (s *IntegrationTestSuite) runGovExec(c *chain, valIdx int, submitterAddr, govCommand string, proposalFlags []string, fees string) { +func (s *IntegrationTestSuite) runGovExec(c *chain, valIdx int, submitterAddr, govCommand string, proposalFlags []string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -415,7 +411,7 @@ func (s *IntegrationTestSuite) runGovExec(c *chain, valIdx int, submitterAddr, g generalFlags := []string{ fmt.Sprintf("--%s=%s", flags.FlagFrom, submitterAddr), fmt.Sprintf("--%s=%s", flags.FlagGas, "300000"), // default 200000 isn't enough - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, fees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), "--keyring-backend=test", "--output=json", @@ -473,7 +469,7 @@ func (s *IntegrationTestSuite) runGovExec(c *chain, valIdx int, submitterAddr, g // }) // } -func (s *IntegrationTestSuite) execDelegate(c *chain, valIdx int, amount, valOperAddress, delegatorAddr, home, delegateFees string) { //nolint:unparam +func (s *IntegrationTestSuite) execDelegate(c *chain, valIdx int, amount, valOperAddress, delegatorAddr, home string) { //nolint:unparam ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -489,7 +485,7 @@ func (s *IntegrationTestSuite) execDelegate(c *chain, valIdx int, amount, valOpe amount, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddr), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, delegateFees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", fmt.Sprintf("--%s=%s", flags.FlagHome, home), "--output=json", @@ -500,7 +496,7 @@ func (s *IntegrationTestSuite) execDelegate(c *chain, valIdx int, amount, valOpe s.T().Logf("%s successfully delegated %s to %s", delegatorAddr, amount, valOperAddress) } -func (s *IntegrationTestSuite) execUnbondDelegation(c *chain, valIdx int, amount, valOperAddress, delegatorAddr, home, delegateFees string) { +func (s *IntegrationTestSuite) execUnbondDelegation(c *chain, valIdx int, amount, valOperAddress, delegatorAddr, home string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -515,7 +511,7 @@ func (s *IntegrationTestSuite) execUnbondDelegation(c *chain, valIdx int, amount amount, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddr), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, delegateFees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", fmt.Sprintf("--%s=%s", flags.FlagHome, home), "--output=json", @@ -526,7 +522,7 @@ func (s *IntegrationTestSuite) execUnbondDelegation(c *chain, valIdx int, amount s.T().Logf("%s successfully undelegated %s to %s", delegatorAddr, amount, valOperAddress) } -func (s *IntegrationTestSuite) execCancelUnbondingDelegation(c *chain, valIdx int, amount, valOperAddress, creationHeight, delegatorAddr, home, delegateFees string) { +func (s *IntegrationTestSuite) execCancelUnbondingDelegation(c *chain, valIdx int, amount, valOperAddress, creationHeight, delegatorAddr, home string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -542,7 +538,7 @@ func (s *IntegrationTestSuite) execCancelUnbondingDelegation(c *chain, valIdx in creationHeight, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddr), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, delegateFees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", fmt.Sprintf("--%s=%s", flags.FlagHome, home), "--output=json", @@ -554,7 +550,7 @@ func (s *IntegrationTestSuite) execCancelUnbondingDelegation(c *chain, valIdx in } func (s *IntegrationTestSuite) execRedelegate(c *chain, valIdx int, amount, originalValOperAddress, - newValOperAddress, delegatorAddr, home, delegateFees string, + newValOperAddress, delegatorAddr, home string, ) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -572,7 +568,7 @@ func (s *IntegrationTestSuite) execRedelegate(c *chain, valIdx int, amount, orig fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddr), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), fmt.Sprintf("--%s=%s", flags.FlagGas, "300000"), // default 200000 isn't enough - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, delegateFees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", fmt.Sprintf("--%s=%s", flags.FlagHome, home), "--output=json", @@ -617,7 +613,6 @@ func (s *IntegrationTestSuite) getLatestBlockTime(c *chain, valIdx int) time.Tim func (s *IntegrationTestSuite) execSetWithdrawAddress( c *chain, valIdx int, - fees, delegatorAddress, newWithdrawalAddress, homePath string, @@ -633,7 +628,7 @@ func (s *IntegrationTestSuite) execSetWithdrawAddress( "set-withdraw-addr", newWithdrawalAddress, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddress), - fmt.Sprintf("--%s=%s", flags.FlagFees, fees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), fmt.Sprintf("--%s=%s", flags.FlagHome, homePath), "--keyring-backend=test", @@ -663,7 +658,7 @@ func (s *IntegrationTestSuite) execWithdrawReward( "withdraw-rewards", validatorAddress, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddress), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, "300uatone"), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), fmt.Sprintf("--%s=%s", flags.FlagGas, "auto"), fmt.Sprintf("--%s=%s", flags.FlagGasAdjustment, "1.5"), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), @@ -808,7 +803,7 @@ func (s *IntegrationTestSuite) defaultExecValidation(chain *chain, valIdx int, m } } -func (s *IntegrationTestSuite) executeValidatorBond(c *chain, valIdx int, valOperAddress, delegatorAddr, home, delegateFees string) { //nolint:unused +func (s *IntegrationTestSuite) executeValidatorBond(c *chain, valIdx int, valOperAddress, delegatorAddr, home string) { //nolint:unused ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -822,7 +817,7 @@ func (s *IntegrationTestSuite) executeValidatorBond(c *chain, valIdx int, valOpe valOperAddress, fmt.Sprintf("--%s=%s", flags.FlagFrom, delegatorAddr), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), - fmt.Sprintf("--%s=%s", flags.FlagGasPrices, delegateFees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), "--keyring-backend=test", fmt.Sprintf("--%s=%s", flags.FlagHome, home), "--output=json", @@ -837,11 +832,9 @@ func (s *IntegrationTestSuite) execPhotonMint( c *chain, valIdx int, from, - amt, - fees string, + amt string, opt ...flagOption, ) (resp photontypes.MsgMintPhotonResponse) { - opt = append(opt, withKeyValue(flagFees, fees)) opt = append(opt, withKeyValue(flagFrom, from)) opts := applyOptions(c.id, opt) diff --git a/tests/e2e/e2e_feegrant_test.go b/tests/e2e/e2e_feegrant_test.go index ace8fbd1..9fee26db 100644 --- a/tests/e2e/e2e_feegrant_test.go +++ b/tests/e2e/e2e_feegrant_test.go @@ -49,7 +49,6 @@ func (s *IntegrationTestSuite) testFeeGrant() { bob.String(), Address(), tokenAmount.String(), - standardFees.String(), false, withKeyValue(flagFeeGranter, alice.String()), ) @@ -67,7 +66,6 @@ func (s *IntegrationTestSuite) testFeeGrant() { bob.String(), Address(), tokenAmount.String(), - standardFees.String(), true, withKeyValue(flagFeeGranter, alice.String()), ) @@ -97,7 +95,6 @@ func (s *IntegrationTestSuite) testFeeGrant() { charlie.String(), Address(), tokenAmount.String(), - standardFees.String(), true, withKeyValue(flagFeeGranter, alice.String()), ) diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index 8f9c7e5a..d14096fd 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -313,7 +313,7 @@ func (s *IntegrationTestSuite) verifyChainPassesUpgradeHeight(c *chain, valIdx i } func (s *IntegrationTestSuite) submitGovCommand(chainAAPIEndpoint, sender string, proposalID int, govCommand string, proposalFlags []string, expectedSuccessStatus govtypesv1beta1.ProposalStatus) { - s.runGovExec(s.chainA, 0, sender, govCommand, proposalFlags, standardFees.String()) + s.runGovExec(s.chainA, 0, sender, govCommand, proposalFlags) s.Require().Eventually( func() bool { diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go index d4f9cfa5..1e043b47 100644 --- a/tests/e2e/e2e_ibc_test.go +++ b/tests/e2e/e2e_ibc_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "strconv" "strings" "time" @@ -13,7 +12,7 @@ import ( ) //nolint:unparam -func (s *IntegrationTestSuite) sendIBC(c *chain, valIdx int, sender, recipient, token, fees, note string) { +func (s *IntegrationTestSuite) sendIBC(c *chain, valIdx int, sender, recipient, token, note string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -27,7 +26,7 @@ func (s *IntegrationTestSuite) sendIBC(c *chain, valIdx int, sender, recipient, recipient, token, fmt.Sprintf("--from=%s", sender), - fmt.Sprintf("--%s=%s", flags.FlagFees, fees), + fmt.Sprintf("--%s=%s", flags.FlagFees, standardFees.String()), fmt.Sprintf("--%s=%s", flags.FlagChainID, c.id), // fmt.Sprintf("--%s=%s", flags.FlagNote, note), fmt.Sprintf("--memo=%s", note), @@ -228,8 +227,7 @@ func (s *IntegrationTestSuite) testIBCTokenTransfer() { } } - tokenAmt := 3300000000 - s.sendIBC(s.chainA, 0, sender, recipient, strconv.Itoa(tokenAmt)+uatoneDenom, standardFees.String(), "") + s.sendIBC(s.chainA, 0, sender, recipient, tokenAmount.String(), "") pass := s.hermesClearPacket(hermesConfigWithGasPrices, s.chainA.id, transferChannel) s.Require().True(pass) @@ -246,7 +244,7 @@ func (s *IntegrationTestSuite) testIBCTokenTransfer() { for _, c := range balances { if strings.Contains(c.Denom, "ibc/") { ibcStakeDenom = c.Denom - s.Require().Equal((int64(tokenAmt) + beforeBalance), c.Amount.Int64()) + s.Require().Equal(tokenAmount.Amount.Int64()+beforeBalance, c.Amount.Int64()) break } } diff --git a/tests/e2e/e2e_photon_test.go b/tests/e2e/e2e_photon_test.go index 7a35a57e..bf39dbab 100644 --- a/tests/e2e/e2e_photon_test.go +++ b/tests/e2e/e2e_photon_test.go @@ -7,47 +7,52 @@ import ( ) func (s *IntegrationTestSuite) testMintPhoton() { - s.Run("mint photon", func() { - var ( - c = s.chainA - valIdx = 0 - chainEndpoint = fmt.Sprintf("http://%s", s.valResources[c.id][valIdx].GetHostPort("1317/tcp")) - ) - alice, _ := c.genesisAccounts[1].keyInfo.GetAddress() - beforeBalance, err := queryAtomOneAllBalances(chainEndpoint, alice.String()) - s.Require().NoError(err) - s.Require().True(beforeBalance.AmountOf(uphotonDenom).IsZero(), "expected 0 balance of photon") - beforeSupply := s.queryBankSupply(chainEndpoint) - s.Require().True(beforeSupply.AmountOf(uphotonDenom).IsZero(), "expected 0 balance of photon in total supply") - conversionRate := s.queryPhotonConversionRate(chainEndpoint) - s.Require().Positive(conversionRate.MustFloat64()) + subtest := func(fees sdk.Coin) func() { + return func() { + var ( + c = s.chainA + valIdx = 0 + chainEndpoint = fmt.Sprintf("http://%s", s.valResources[c.id][valIdx].GetHostPort("1317/tcp")) + ) + alice, _ := c.genesisAccounts[1].keyInfo.GetAddress() + beforeBalance, err := queryAtomOneAllBalances(chainEndpoint, alice.String()) + s.Require().NoError(err) + beforeSupply := s.queryBankSupply(chainEndpoint) + conversionRate := s.queryPhotonConversionRate(chainEndpoint) + s.Require().Positive(conversionRate.MustFloat64()) + burnedAtoneAmt := sdk.NewInt64Coin(uatoneDenom, 1_000_000) - burnedAtoneAmt := sdk.NewInt64Coin(uatoneDenom, 1_000_000) - resp := s.execPhotonMint(s.chainA, valIdx, alice.String(), burnedAtoneAmt.String(), standardFees.String()) + resp := s.execPhotonMint(s.chainA, valIdx, alice.String(), burnedAtoneAmt.String(), + withKeyValue(flagFees, fees), + ) - expectedBalance := beforeBalance. - Sub(burnedAtoneAmt). // remove burned atones - Add(resp.Minted). // add minted photons - Sub(standardFees) // remove fees - afterBalance, err := queryAtomOneAllBalances(chainEndpoint, alice.String()) - s.Require().NoError(err) - s.Require().Equal(expectedBalance.String(), afterBalance.String()) - var ( - expectedUphotonSupply = resp.Minted - afterSupply = s.queryBankSupply(chainEndpoint) - _, afterUphotonSupply = afterSupply.Find(uphotonDenom) - ) - s.Require().Equal(expectedUphotonSupply.String(), afterUphotonSupply.String()) - // For atone supply assertion we must take into account inflation and so - // we except the final supply to be greater or equal than the initial - // supply - the burned atones. - var ( - _, beforeUatoneSupply = beforeSupply.Find(uatoneDenom) - _, afterUatoneSupply = afterSupply.Find(uatoneDenom) - ) - fmt.Println("BEFORE", beforeUatoneSupply.Sub(burnedAtoneAmt)) - fmt.Println("AFTER ", afterUatoneSupply) - s.Require().True(afterUatoneSupply.IsGTE(beforeUatoneSupply.Sub(burnedAtoneAmt)), - "after supply should be >= than initial %s supply", uatoneDenom) - }) + expectedBalance := beforeBalance. + Sub(burnedAtoneAmt). // remove burned atones + Add(resp.Minted). // add minted photons + Sub(fees) // remove fees + + afterBalance, err := queryAtomOneAllBalances(chainEndpoint, alice.String()) + s.Require().NoError(err) + s.Require().Equal(expectedBalance.String(), afterBalance.String()) + var ( + _, beforeUphotonSupply = beforeSupply.Find(uphotonDenom) + expectedUphotonSupply = beforeUphotonSupply.Add(resp.Minted) + afterSupply = s.queryBankSupply(chainEndpoint) + _, afterUphotonSupply = afterSupply.Find(uphotonDenom) + ) + s.Require().Equal(expectedUphotonSupply.String(), afterUphotonSupply.String()) + // For atone supply assertion we must take into account inflation and so + // we except the final supply to be greater or equal than the initial + // supply - the burned atones. + var ( + _, beforeUatoneSupply = beforeSupply.Find(uatoneDenom) + _, afterUatoneSupply = afterSupply.Find(uatoneDenom) + ) + s.Require().True(afterUatoneSupply.IsGTE(beforeUatoneSupply.Sub(burnedAtoneAmt)), + "after supply should be >= than initial %s supply", uatoneDenom) + } + } + s.Run("mint photon", subtest(standardFees)) + atoneFees := sdk.NewCoin(uatoneDenom, standardFees.Amount) + s.Run("mint photon with atone fees", subtest(atoneFees)) } diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 24ca348e..302a6601 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -1,7 +1,6 @@ package e2e import ( - "bytes" "context" "encoding/json" "fmt" @@ -42,6 +41,7 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + appparams "github.com/atomone-hub/atomone/app/params" govtypes "github.com/atomone-hub/atomone/x/gov/types" photontypes "github.com/atomone-hub/atomone/x/photon/types" ) @@ -52,8 +52,8 @@ const ( queryCommand = "query" keysCommand = "keys" atomoneHomePath = "/home/nonroot/.atomone" + uatoneDenom = appparams.BondDenom uphotonDenom = photontypes.Denom - uatoneDenom = "uatone" minGasPrice = "0.00001" gas = 200000 govProposalBlockBuffer int64 = 35 @@ -75,14 +75,18 @@ const ( ) var ( - runInCI = os.Getenv("GITHUB_ACTIONS") == "true" - atomoneConfigPath = filepath.Join(atomoneHomePath, "config") - initBalanceStr = sdk.NewInt64Coin(uatoneDenom, 10_000_000_000_000).String() // 10,000,000atone - stakingAmountCoin = sdk.NewInt64Coin(uatoneDenom, 6_000_000_000_000) // 6,000,000atone - tokenAmount = sdk.NewInt64Coin(uatoneDenom, 100_000_000) // 100atone - standardFees = sdk.NewInt64Coin(uatoneDenom, 330_000) // 0.33atone - depositAmount = sdk.NewInt64Coin(uatoneDenom, 1_000_000_000) // 1,000atone - initialDepositAmount = sdk.NewInt64Coin(uatoneDenom, 100_000_000) // 100atone + runInCI = os.Getenv("GITHUB_ACTIONS") == "true" + atomoneConfigPath = filepath.Join(atomoneHomePath, "config") + initBalance = sdk.NewCoins( + sdk.NewInt64Coin(uatoneDenom, 10_000_000_000_000), // 10,000,000atone + sdk.NewInt64Coin(uphotonDenom, 10_000_000_000), // 10,000photon + ) + initBalanceStr = initBalance.String() + stakingAmountCoin = sdk.NewInt64Coin(uatoneDenom, 6_000_000_000_000) // 6,000,000atone + tokenAmount = sdk.NewInt64Coin(uatoneDenom, 100_000_000) // 100atone + standardFees = sdk.NewInt64Coin(uphotonDenom, 330_000) // 0.33photon + depositAmount = sdk.NewInt64Coin(uatoneDenom, 1_000_000_000) // 1,000atone + initialDepositAmount = sdk.NewInt64Coin(uatoneDenom, 100_000_000) // 100atone proposalCounter = 0 ) @@ -113,6 +117,14 @@ func TestIntegrationTestSuite(t *testing.T) { } func (s *IntegrationTestSuite) SetupSuite() { + defer func() { + if s.T().Failed() { + defer s.TearDownSuite() + s.saveChainLogs(s.chainA) + s.saveChainLogs(s.chainB) + } + }() + s.T().Log("setting up e2e integration test suite...") var err error @@ -348,7 +360,7 @@ func (s *IntegrationTestSuite) addGenesisVestingAndJailedAccounts( } jailedValidatorBalances := banktypes.Balance{ Address: jailedValAcc.String(), - Coins: sdk.NewCoins(tokenAmount), + Coins: initBalance, } stakingModuleBalances := banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String(), @@ -516,7 +528,8 @@ func (s *IntegrationTestSuite) initValidatorConfigs(c *chain) { appConfig := srvconfig.DefaultConfig() appConfig.API.Enable = true appConfig.API.Address = "tcp://0.0.0.0:1317" - appConfig.MinGasPrices = fmt.Sprintf("%s%s", minGasPrice, uatoneDenom) + appConfig.MinGasPrices = fmt.Sprintf("%s%s,%s%s", minGasPrice, uatoneDenom, + minGasPrice, uphotonDenom) appConfig.GRPC.Address = "0.0.0.0:9090" srvconfig.SetConfigTemplate(srvconfig.DefaultConfigTemplate) @@ -589,22 +602,28 @@ func (s *IntegrationTestSuite) runValidators(c *chain, portOffset int) { nodeReadyTimeout, time.Second, ) { - // Print first container logs in case no blocks are produced - var b bytes.Buffer - err := s.dkrPool.Client.Logs(docker.LogsOptions{ - Container: s.valResources[c.id][0].Container.ID, - OutputStream: &b, - ErrorStream: &b, - Stdout: true, - Stderr: true, - }) - if err == nil { - s.T().Logf("Node logs: %s", b.String()) - } s.T().Fatalf("AtomOne node failed to produce blocks. Is docker image %q up-to-date?", dockerImage) } } +func (s *IntegrationTestSuite) saveChainLogs(c *chain) { + f, err := os.CreateTemp("", c.id) + if err != nil { + s.T().Fatal(err) + } + defer f.Close() + err = s.dkrPool.Client.Logs(docker.LogsOptions{ + Container: s.valResources[c.id][0].Container.ID, + OutputStream: f, + ErrorStream: f, + Stdout: true, + Stderr: true, + }) + if err == nil { + s.T().Logf("See chain %s log file %s", c.id, f.Name()) + } +} + func noRestart(config *docker.HostConfig) { // in this case we don't want the nodes to restart on failure config.RestartPolicy = docker.RestartPolicy{ diff --git a/tests/e2e/e2e_staking_test.go b/tests/e2e/e2e_staking_test.go index 92599726..ee4766b7 100644 --- a/tests/e2e/e2e_staking_test.go +++ b/tests/e2e/e2e_staking_test.go @@ -25,8 +25,6 @@ func (s *IntegrationTestSuite) testStaking() { delegatorAddress, _ := s.chainA.genesisAccounts[2].keyInfo.GetAddress() - fees := sdk.NewCoin(uatoneDenom, sdk.NewInt(1)) - existingDelegation := sdk.ZeroDec() res, err := queryDelegation(chainEndpoint, validatorAddressA, delegatorAddress.String()) if err == nil { @@ -37,7 +35,7 @@ func (s *IntegrationTestSuite) testStaking() { delegation := sdk.NewCoin(uatoneDenom, delegationAmount) // 500 atom // Alice delegate uatone to Validator A - s.execDelegate(s.chainA, 0, delegation.String(), validatorAddressA, delegatorAddress.String(), atomoneHomePath, fees.String()) + s.execDelegate(s.chainA, 0, delegation.String(), validatorAddressA, delegatorAddress.String(), atomoneHomePath) // Validate delegation successful s.Require().Eventually( @@ -56,7 +54,7 @@ func (s *IntegrationTestSuite) testStaking() { redelegation := sdk.NewCoin(uatoneDenom, redelegationAmount) // 250 atom // Alice re-delegate half of her uatone delegation from Validator A to Validator B - s.execRedelegate(s.chainA, 0, redelegation.String(), validatorAddressA, validatorAddressB, delegatorAddress.String(), atomoneHomePath, fees.String()) + s.execRedelegate(s.chainA, 0, redelegation.String(), validatorAddressA, validatorAddressB, delegatorAddress.String(), atomoneHomePath) // Validate re-delegation successful s.Require().Eventually( @@ -93,7 +91,7 @@ func (s *IntegrationTestSuite) testStaking() { ) // Alice unbonds all her uatone delegation from Validator A - s.execUnbondDelegation(s.chainA, 0, currDelegation.String(), validatorAddressA, delegatorAddress.String(), atomoneHomePath, fees.String()) + s.execUnbondDelegation(s.chainA, 0, currDelegation.String(), validatorAddressA, delegatorAddress.String(), atomoneHomePath) var ubdDelegationEntry types.UnbondingDelegationEntry @@ -121,7 +119,6 @@ func (s *IntegrationTestSuite) testStaking() { strconv.Itoa(int(ubdDelegationEntry.CreationHeight)), delegatorAddress.String(), atomoneHomePath, - fees.String(), ) // validate that unbonding delegation was successfully canceled diff --git a/tests/e2e/e2e_vesting_test.go b/tests/e2e/e2e_vesting_test.go index 01989da0..614e5695 100644 --- a/tests/e2e/e2e_vesting_test.go +++ b/tests/e2e/e2e_vesting_test.go @@ -31,12 +31,14 @@ type ( ) var ( - genesisVestingKeys = []string{continuousVestingKey, delayedVestingKey, lockedVestingKey, periodicVestingKey} - vestingAmountVested = sdk.NewCoin(uatoneDenom, sdk.NewInt(99900000000)) - vestingAmount = sdk.NewCoin(uatoneDenom, sdk.NewInt(350000)) - vestingBalance = sdk.NewCoins(vestingAmountVested).Add(vestingAmount) + genesisVestingKeys = []string{continuousVestingKey, delayedVestingKey, lockedVestingKey, periodicVestingKey} + vestingAmountVested = sdk.NewCoin(uatoneDenom, sdk.NewInt(99900000000)) + vestingAmount = sdk.NewCoins( + sdk.NewInt64Coin(uatoneDenom, 350_000), + sdk.NewInt64Coin(uphotonDenom, 10_000_000_000), + ) + vestingBalance = sdk.NewCoins(vestingAmountVested).Add(vestingAmount...) vestingDelegationAmount = sdk.NewCoin(uatoneDenom, sdk.NewInt(500000000)) - vestingDelegationFees = sdk.NewCoin(uatoneDenom, sdk.NewInt(1)) ) func (s *IntegrationTestSuite) testDelayedVestingAccount(api string) { @@ -60,7 +62,7 @@ func (s *IntegrationTestSuite) testDelayedVestingAccount(api string) { // Delegate coins should succeed s.execDelegate(chain, valIdx, vestingDelegationAmount.String(), valOpAddr, - vestingDelayedAcc.String(), atomoneHomePath, vestingDelegationFees.String()) + vestingDelayedAcc.String(), atomoneHomePath) // Validate delegation successful s.Require().Eventually( @@ -85,8 +87,7 @@ func (s *IntegrationTestSuite) testDelayedVestingAccount(api string) { valIdx, vestingDelayedAcc.String(), Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), true, ) waitTime = acc.EndTime - time.Now().Unix() + vestingTxDelay @@ -101,8 +102,7 @@ func (s *IntegrationTestSuite) testDelayedVestingAccount(api string) { valIdx, vestingDelayedAcc.String(), Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), false, ) }) @@ -129,7 +129,7 @@ func (s *IntegrationTestSuite) testContinuousVestingAccount(api string) { // Delegate coins should succeed s.execDelegate(chain, valIdx, vestingDelegationAmount.String(), - valOpAddr, continuousVestingAcc.String(), atomoneHomePath, vestingDelegationFees.String()) + valOpAddr, continuousVestingAcc.String(), atomoneHomePath) // Validate delegation successful s.Require().Eventually( @@ -154,8 +154,7 @@ func (s *IntegrationTestSuite) testContinuousVestingAccount(api string) { valIdx, continuousVestingAcc.String(), Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), true, ) waitStartTime = acc.StartTime - time.Now().Unix() + vestingTxDelay @@ -172,8 +171,7 @@ func (s *IntegrationTestSuite) testContinuousVestingAccount(api string) { valIdx, continuousVestingAcc.String(), Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), true, ) waitEndTime = acc.EndTime - time.Now().Unix() + vestingTxDelay @@ -188,8 +186,7 @@ func (s *IntegrationTestSuite) testContinuousVestingAccount(api string) { valIdx, continuousVestingAcc.String(), Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), false, ) }) @@ -239,8 +236,7 @@ func (s *IntegrationTestSuite) testPeriodicVestingAccount(api string) { //nolint valIdx, periodicVestingAddr, Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), true, ) waitStartTime = acc.StartTime - time.Now().Unix() + vestingTxDelay @@ -258,8 +254,7 @@ func (s *IntegrationTestSuite) testPeriodicVestingAccount(api string) { //nolint valIdx, periodicVestingAddr, Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), true, ) waitFirstPeriod = firstPeriod - time.Now().Unix() + vestingTxDelay @@ -268,7 +263,7 @@ func (s *IntegrationTestSuite) testPeriodicVestingAccount(api string) { //nolint // Delegate coins should succeed s.execDelegate(chain, valIdx, vestingDelegationAmount.String(), valOpAddr, - periodicVestingAddr, atomoneHomePath, vestingDelegationFees.String()) + periodicVestingAddr, atomoneHomePath) // Validate delegation successful s.Require().Eventually( @@ -291,8 +286,7 @@ func (s *IntegrationTestSuite) testPeriodicVestingAccount(api string) { //nolint valIdx, periodicVestingAddr, Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), false, ) @@ -309,8 +303,7 @@ func (s *IntegrationTestSuite) testPeriodicVestingAccount(api string) { //nolint valIdx, periodicVestingAddr, Address(), - balance.Sub(standardFees).String(), - standardFees.String(), + balance.String(), false, ) } diff --git a/tests/e2e/scripts/hermes_bootstrap.sh b/tests/e2e/scripts/hermes_bootstrap.sh index 5b56fb91..145254f4 100755 --- a/tests/e2e/scripts/hermes_bootstrap.sh +++ b/tests/e2e/scripts/hermes_bootstrap.sh @@ -53,7 +53,7 @@ account_prefix = 'atone' key_name = 'rly01-gaia-a' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0.00001, denom = 'uatone' } +gas_price = { price = 0.00001, denom = 'uphoton' } gas_multiplier = 1.2 clock_drift = '1m' # to accomdate docker containers trusting_period = '14days' @@ -69,7 +69,7 @@ account_prefix = 'atone' key_name = 'rly01-gaia-b' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0.00001, denom = 'uatone' } +gas_price = { price = 0.00001, denom = 'uphoton' } gas_multiplier = 1.2 clock_drift = '1m' # to accomdate docker containers trusting_period = '14days' @@ -120,7 +120,7 @@ account_prefix = 'atone' key_name = 'rly01-gaia-a' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0, denom = 'uatone' } +gas_price = { price = 0, denom = 'uphoton' } gas_multiplier = 1.2 clock_drift = '1m' # to accommodate docker containers trusting_period = '14days' @@ -136,7 +136,7 @@ account_prefix = 'atone' key_name = 'rly01-gaia-b' store_prefix = 'ibc' max_gas = 6000000 -gas_price = { price = 0, denom = 'uatone' } +gas_price = { price = 0, denom = 'uphoton' } gas_multiplier = 1.2 clock_drift = '1m' # to accommodate docker containers trusting_period = '14days' diff --git a/x/photon/ante/ante.go b/x/photon/ante/ante.go new file mode 100644 index 00000000..a04c05d4 --- /dev/null +++ b/x/photon/ante/ante.go @@ -0,0 +1,77 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/atomone-hub/atomone/x/photon/keeper" + "github.com/atomone-hub/atomone/x/photon/types" +) + +var _ sdk.AnteDecorator = ValidateFeeDecorator{} + +type ValidateFeeDecorator struct { + k *keeper.Keeper +} + +func NewValidateFeeDecorator(k *keeper.Keeper) ValidateFeeDecorator { + return ValidateFeeDecorator{k: k} +} + +// AnteHandle implements the sdk.AnteDecorator interface. +// It returns an error if the tx fee denom is not photon, with some exceptions: +// - tx is a gentx +// - tx mode is simulate +// - tx messages' type URLs match the `TxFeeExceptions` field of the +// [types.Params]. +// - tx has no fees or 0 fees. +func (vfd ValidateFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + if ctx.BlockHeight() == 0 || simulate { + // Skip if this is genesis height or simulate mode, because genesis and + // simulated transactions might have no fees. + return next(ctx, tx, simulate) + } + if allowsAnyTxFee(tx, vfd.k.GetParams(ctx).TxFeeExceptions) { + // Skip if tx is declared in TxFeeExceptions (any fee coins are allowed). + return next(ctx, tx, simulate) + } + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") //nolint:staticcheck + } + feeCoins := feeTx.GetFee() + if feeCoins.IsZero() { + // Skip if no fees + return next(ctx, tx, simulate) + } + if len(feeCoins) > 1 { + return ctx, types.ErrTooManyFeeCoins + } + if feeDenom := feeCoins[0].Denom; feeDenom != types.Denom { + // feeDenom not allowed + return ctx, sdkerrors.Wrapf(types.ErrInvalidFeeToken, "fee denom %s not allowed", feeDenom) //nolint:staticcheck + } + // feeDenom photon is allowed + return next(ctx, tx, simulate) +} + +// allowsAnyTxFee returns true if all tx messages type URL are presents in +// txFeeExceptions, or if it starts with a wildcard "*". +func allowsAnyTxFee(tx sdk.Tx, txFeeExceptions []string) bool { + if len(txFeeExceptions) > 0 && txFeeExceptions[0] == "*" { + // wildcard detected, all tx fees are allowed. + return true + } + var anyTxFeeMsgCount int + for _, msg := range tx.GetMsgs() { + msgTypeURL := sdk.MsgTypeURL(msg) + for _, exception := range txFeeExceptions { + if exception == msgTypeURL { + anyTxFeeMsgCount++ + break + } + } + } + return anyTxFeeMsgCount == len(tx.GetMsgs()) +} diff --git a/x/photon/ante/ante_test.go b/x/photon/ante/ante_test.go new file mode 100644 index 00000000..07fbf592 --- /dev/null +++ b/x/photon/ante/ante_test.go @@ -0,0 +1,293 @@ +package ante + +import ( + "testing" + + "gotest.tools/v3/assert" + + appparams "github.com/atomone-hub/atomone/app/params" + "github.com/atomone-hub/atomone/x/photon/testutil" + "github.com/atomone-hub/atomone/x/photon/types" + "github.com/stretchr/testify/require" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" +) + +func TestValidateFeeDecorator(t *testing.T) { + tests := []struct { + name string + tx sdk.Tx + isGenTx bool + simulateMode bool + expectedError string + }{ + { + name: "ok: no fee", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{}, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + }, + { + name: "ok: tx MsgMintPhoton accepts any fee denom bc declared in txFeeExceptions", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins( + sdk.NewInt64Coin(appparams.BondDenom, 1), + sdk.NewInt64Coin("xxx", 1), + ), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + }, + { + name: "ok: MsgUpdateParams fee uphoton", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins(sdk.NewInt64Coin(types.Denom, 1)), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + }, + { + name: "fail: MsgUpdateParams fee uatone", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins(sdk.NewInt64Coin(appparams.BondDenom, 1)), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + expectedError: "fee denom uatone not allowed: invalid fee token", + }, + { + name: "fail: MsgUpdateParams fee xxx", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1)), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + expectedError: "fee denom xxx not allowed: invalid fee token", + }, + { + name: "fail: MsgUpdateParams multiple fee denom", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins( + sdk.NewInt64Coin(appparams.BondDenom, 1), + sdk.NewInt64Coin("xxx", 1), + ), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: false, + simulateMode: false, + expectedError: "too many fee coins, only accepts fees in one denom", + }, + { + name: "ok: MsgUpdateParams fee xxx with simulate", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1)), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: false, + simulateMode: true, + }, + { + name: "ok: MsgUpdateParams fee xxx with gentx", + tx: &tx.Tx{ + AuthInfo: &tx.AuthInfo{ + Fee: &tx.Fee{ + Amount: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1)), + }, + }, + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + isGenTx: true, + simulateMode: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k, _, ctx := testutil.SetupPhotonKeeper(t) + if !tt.isGenTx { + // default block height is 0, if the tx is not a genTx, then it should + // be higher than 0. + ctx = ctx.WithBlockHeight(1) + } + k.SetParams(ctx, types.DefaultParams()) + var ( + nextInvoked bool + next = func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextInvoked = true + return ctx, nil + } + vfd = NewValidateFeeDecorator(k) + ) + + _, err := vfd.AnteHandle(ctx, tt.tx, tt.simulateMode, next) + + if tt.expectedError != "" { + require.EqualError(t, err, tt.expectedError) + return + } + require.NoError(t, err) + require.True(t, nextInvoked, "next is not invoked") + }) + } +} + +func TestAllowsAnyTxFee(t *testing.T) { + tests := []struct { + name string + tx sdk.Tx + txFeeExceptions []string + expectedRes bool + }{ + { + name: "wildcard fee execptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + txFeeExceptions: []string{"*"}, + expectedRes: true, + }, + { + name: "empty fee execptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + txFeeExceptions: nil, + expectedRes: false, + }, + { + name: "one message match txFeeExceptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + txFeeExceptions: []string{sdk.MsgTypeURL(&types.MsgMintPhoton{})}, + expectedRes: true, + }, + { + name: "multiple messages not all match txFeeExceptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + txFeeExceptions: []string{sdk.MsgTypeURL(&types.MsgMintPhoton{})}, + expectedRes: false, + }, + { + name: "multiple same messages match txFeeExceptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + }, + }, + }, + txFeeExceptions: []string{sdk.MsgTypeURL(&types.MsgMintPhoton{})}, + expectedRes: true, + }, + { + name: "multiple different messages match txFeeExceptions", + tx: &tx.Tx{ + Body: &tx.TxBody{ + Messages: []*codectypes.Any{ + codectypes.UnsafePackAny(&types.MsgMintPhoton{}), + codectypes.UnsafePackAny(&types.MsgUpdateParams{}), + }, + }, + }, + txFeeExceptions: []string{ + sdk.MsgTypeURL(&types.MsgMintPhoton{}), + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + }, + expectedRes: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res := allowsAnyTxFee(tt.tx, tt.txFeeExceptions) + + assert.Equal(t, tt.expectedRes, res) + }) + } +} diff --git a/x/photon/keeper/grpc_query_test.go b/x/photon/keeper/grpc_query_test.go index 5dd1be5c..764950bc 100644 --- a/x/photon/keeper/grpc_query_test.go +++ b/x/photon/keeper/grpc_query_test.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + appparams "github.com/atomone-hub/atomone/app/params" "github.com/atomone-hub/atomone/x/photon/testutil" "github.com/atomone-hub/atomone/x/photon/types" "github.com/stretchr/testify/require" @@ -48,11 +49,11 @@ func TestConversionRateQuery(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k, m, ctx := testutil.SetupPhotonKeeper(t) - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") - m.BankKeeper.EXPECT().GetSupply(ctx, "uatone"). - Return(sdk.NewInt64Coin("uatone", tt.uatoneSupply)) + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) + m.BankKeeper.EXPECT().GetSupply(ctx, appparams.BondDenom). + Return(sdk.NewInt64Coin(appparams.BondDenom, tt.uatoneSupply)) m.BankKeeper.EXPECT().GetSupply(ctx, types.Denom). - Return(sdk.NewInt64Coin("uatone", tt.uphotonSupply)) + Return(sdk.NewInt64Coin(appparams.BondDenom, tt.uphotonSupply)) resp, err := k.ConversionRate(ctx, &types.QueryConversionRateRequest{}) diff --git a/x/photon/keeper/msg_server.go b/x/photon/keeper/msg_server.go index 7b90a0dd..40a32cd2 100644 --- a/x/photon/keeper/msg_server.go +++ b/x/photon/keeper/msg_server.go @@ -42,14 +42,14 @@ func (k msgServer) MintPhoton(goCtx context.Context, msg *types.MsgMintPhoton) ( uphotonSupply = k.bankKeeper.GetSupply(ctx, types.Denom).Amount.ToLegacyDec() conversionRate = k.conversionRate(ctx, bondDenomSupply, uphotonSupply) bondDenomToBurn = msg.Amount - uphotonToMint = bondDenomToBurn.Amount.ToLegacyDec().Mul(conversionRate) + uphotonToMint = bondDenomToBurn.Amount.ToLegacyDec().Mul(conversionRate).RoundInt() ) // If no photon to mint, do not burn bondDenomToBurn, returns an error if uphotonToMint.IsZero() { - return nil, types.ErrNoMintablePhotons + return nil, types.ErrZeroMintPhotons } // If photonToMint + photonSupply > photonMaxSupply, returns an error - if uphotonSupply.Add(uphotonToMint).GT(sdk.NewDec(types.MaxSupply)) { + if uphotonSupply.Add(uphotonToMint.ToLegacyDec()).GT(sdk.NewDec(types.MaxSupply)) { return nil, types.ErrNotEnoughPhotons } @@ -60,7 +60,7 @@ func (k msgServer) MintPhoton(goCtx context.Context, msg *types.MsgMintPhoton) ( // 4) move PHOTONs from this module address to msg signer address var ( coinsToBurn = sdk.NewCoins(bondDenomToBurn) - coinsToMint = sdk.NewCoins(sdk.NewCoin(types.Denom, uphotonToMint.RoundInt())) + coinsToMint = sdk.NewCoins(sdk.NewCoin(types.Denom, uphotonToMint)) ) // 1) Send atone to photon module for burn to, err := sdk.AccAddressFromBech32(msg.ToAddress) diff --git a/x/photon/keeper/msg_server_test.go b/x/photon/keeper/msg_server_test.go index 483b5fc8..86415910 100644 --- a/x/photon/keeper/msg_server_test.go +++ b/x/photon/keeper/msg_server_test.go @@ -1,13 +1,16 @@ package keeper_test import ( + "math" "testing" - "github.com/atomone-hub/atomone/x/photon/testutil" - "github.com/atomone-hub/atomone/x/photon/types" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" + + appparams "github.com/atomone-hub/atomone/app/params" + "github.com/atomone-hub/atomone/x/photon/testutil" + "github.com/atomone-hub/atomone/x/photon/types" ) func TestMsgServerMintPhoton(t *testing.T) { @@ -34,7 +37,7 @@ func TestMsgServerMintPhoton(t *testing.T) { params: types.Params{MintDisabled: false}, msg: &types.MsgMintPhoton{}, setup: func(ctx sdk.Context, m testutil.Mocks) { - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) }, expectedErr: "invalid burned amount denom: expected bond denom", }, @@ -45,7 +48,7 @@ func TestMsgServerMintPhoton(t *testing.T) { Amount: sdk.NewInt64Coin("xxx", 42), }, setup: func(ctx sdk.Context, m testutil.Mocks) { - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) }, expectedErr: "invalid burned amount denom: expected bond denom", }, @@ -54,46 +57,64 @@ func TestMsgServerMintPhoton(t *testing.T) { params: types.Params{MintDisabled: false}, msg: &types.MsgMintPhoton{ ToAddress: toAddress.String(), - Amount: sdk.NewInt64Coin("uatone", 1), + Amount: sdk.NewInt64Coin(appparams.BondDenom, 1), }, setup: func(ctx sdk.Context, m testutil.Mocks) { - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") - m.BankKeeper.EXPECT().GetSupply(ctx, "uatone").Return(sdk.NewInt64Coin("uatone", atoneSupply)) + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) + m.BankKeeper.EXPECT().GetSupply(ctx, appparams.BondDenom). + Return(sdk.NewInt64Coin(appparams.BondDenom, atoneSupply)) m.BankKeeper.EXPECT().GetSupply(ctx, types.Denom).Return(sdk.NewInt64Coin(types.Denom, types.MaxSupply)) }, - expectedErr: "no more photon can be minted", + expectedErr: "no mintable photon after rounding, try higher burn", }, { name: "fail: photon_supply+minted>max", params: types.Params{MintDisabled: false}, msg: &types.MsgMintPhoton{ ToAddress: toAddress.String(), - Amount: sdk.NewInt64Coin("uatone", 1_000_000_000_000_000), + Amount: sdk.NewInt64Coin(appparams.BondDenom, 1_000_000_000_000_000), }, setup: func(ctx sdk.Context, m testutil.Mocks) { - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") - m.BankKeeper.EXPECT().GetSupply(ctx, "uatone").Return(sdk.NewInt64Coin("uatone", atoneSupply)) + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) + m.BankKeeper.EXPECT().GetSupply(ctx, appparams.BondDenom). + Return(sdk.NewInt64Coin(appparams.BondDenom, atoneSupply)) m.BankKeeper.EXPECT().GetSupply(ctx, types.Denom).Return(sdk.NewInt64Coin(types.Denom, types.MaxSupply-1_000_000)) }, expectedErr: "not enough photon can be minted", }, + { + name: "fail: atone_supply >> photon_supply", + params: types.Params{MintDisabled: false}, + msg: &types.MsgMintPhoton{ + ToAddress: toAddress.String(), + Amount: sdk.NewInt64Coin(appparams.BondDenom, 1), + }, + setup: func(ctx sdk.Context, m testutil.Mocks) { + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) + m.BankKeeper.EXPECT().GetSupply(ctx, appparams.BondDenom). + Return(sdk.NewInt64Coin(appparams.BondDenom, math.MaxInt)) + m.BankKeeper.EXPECT().GetSupply(ctx, types.Denom).Return(sdk.NewInt64Coin(types.Denom, 0)) + }, + expectedErr: "no mintable photon after rounding, try higher burn", + }, { name: "ok: photon_supply=0", params: types.Params{MintDisabled: false}, msg: &types.MsgMintPhoton{ ToAddress: toAddress.String(), - Amount: sdk.NewInt64Coin("uatone", 1), + Amount: sdk.NewInt64Coin(appparams.BondDenom, 1), }, setup: func(ctx sdk.Context, m testutil.Mocks) { - m.StakingKeeper.EXPECT().BondDenom(ctx).Return("uatone") - m.BankKeeper.EXPECT().GetSupply(ctx, "uatone").Return(sdk.NewInt64Coin("uatone", atoneSupply)) + m.StakingKeeper.EXPECT().BondDenom(ctx).Return(appparams.BondDenom) + m.BankKeeper.EXPECT().GetSupply(ctx, appparams.BondDenom). + Return(sdk.NewInt64Coin(appparams.BondDenom, atoneSupply)) m.BankKeeper.EXPECT().GetSupply(ctx, types.Denom).Return(sdk.NewInt64Coin(types.Denom, 0)) m.BankKeeper.EXPECT().SendCoinsFromAccountToModule( ctx, toAddress, types.ModuleName, - sdk.NewCoins(sdk.NewInt64Coin("uatone", 1)), + sdk.NewCoins(sdk.NewInt64Coin(appparams.BondDenom, 1)), ) m.BankKeeper.EXPECT().BurnCoins(ctx, types.ModuleName, - sdk.NewCoins(sdk.NewInt64Coin("uatone", 1)), + sdk.NewCoins(sdk.NewInt64Coin(appparams.BondDenom, 1)), ) m.BankKeeper.EXPECT().MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(types.Denom, 9)), diff --git a/x/photon/simulation/genesis.go b/x/photon/simulation/genesis.go index 7a63774f..5786fa91 100644 --- a/x/photon/simulation/genesis.go +++ b/x/photon/simulation/genesis.go @@ -9,7 +9,8 @@ import ( ) const ( - MintDisabled = "mint_disabled" + MintDisabled = "mint_disabled" + TxFeeExceptions = "tx_fee_exceptions" ) // GenMintDisabled returns a randomized MintDisabled param. @@ -17,6 +18,14 @@ func GenMintDisabled(r *rand.Rand) bool { return r.Int63n(101) <= 15 // 15% chance of mint being disabled } +// GenTxFeeExceptions returns a wildcard to allow all transactions to use any +// fee denom. +// This is needed because other modules' simulations do not allow the fee coins +// to be changed, so w/o this configuration all transactions would fail. +func GenTxFeeExceptions(r *rand.Rand) []string { + return []string{"*"} +} + // RandomizedGenState generates a random GenesisState for gov func RandomizedGenState(simState *module.SimulationState) { var mintDisabled bool @@ -24,9 +33,14 @@ func RandomizedGenState(simState *module.SimulationState) { simState.Cdc, MintDisabled, &mintDisabled, simState.Rand, func(r *rand.Rand) { mintDisabled = GenMintDisabled(r) }, ) + var txFeeExceptions []string + simState.AppParams.GetOrGenerate( + simState.Cdc, TxFeeExceptions, &txFeeExceptions, simState.Rand, + func(r *rand.Rand) { txFeeExceptions = GenTxFeeExceptions(r) }, + ) photonGenesis := types.NewGenesisState( - types.NewParams(mintDisabled), + types.NewParams(mintDisabled, txFeeExceptions), ) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(photonGenesis) diff --git a/x/photon/simulation/operations.go b/x/photon/simulation/operations.go index e492eaf3..e0986ab9 100644 --- a/x/photon/simulation/operations.go +++ b/x/photon/simulation/operations.go @@ -42,16 +42,20 @@ func WeightedOperations(appParams simtypes.AppParams, cdc codec.JSONCodec, return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgMintPhoton, - SimulateMsgMintPhoton(ak, bk, sk), + SimulateMsgMintPhoton(ak, bk, sk, k), ), } } func SimulateMsgMintPhoton( - ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, + ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, k keeper.Keeper, ) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Check if mint is disabled + if k.GetParams(ctx).MintDisabled { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgMintPhoton, "mint is disabled"), nil, nil + } toAddress, _ := simtypes.RandomAcc(r, accs) acc := ak.GetAccount(ctx, toAddress.Address) spendable := bk.SpendableCoins(ctx, acc.GetAddress()) diff --git a/x/photon/types/errors.go b/x/photon/types/errors.go index 3f671144..031b7eb1 100644 --- a/x/photon/types/errors.go +++ b/x/photon/types/errors.go @@ -6,9 +6,10 @@ import ( // x/photon module sentinel errors var ( - ErrMintDisabled = sdkerrors.Register(ModuleName, 1, "photon mint disabled") //nolint:staticcheck - ErrBurnInvalidDenom = sdkerrors.Register(ModuleName, 2, "invalid burned amount denom: expected bond denom") //nolint:staticcheck - ErrNoMintablePhotons = sdkerrors.Register(ModuleName, 3, "no more photon can be minted") //nolint:staticcheck - ErrNotEnoughPhotons = sdkerrors.Register(ModuleName, 4, "not enough photon can be minted") //nolint:staticcheck - + ErrMintDisabled = sdkerrors.Register(ModuleName, 1, "photon mint disabled") //nolint:staticcheck + ErrBurnInvalidDenom = sdkerrors.Register(ModuleName, 2, "invalid burned amount denom: expected bond denom") //nolint:staticcheck + ErrZeroMintPhotons = sdkerrors.Register(ModuleName, 3, "no mintable photon after rounding, try higher burn") //nolint:staticcheck + ErrNotEnoughPhotons = sdkerrors.Register(ModuleName, 4, "not enough photon can be minted") //nolint:staticcheck + ErrTooManyFeeCoins = sdkerrors.Register(ModuleName, 5, "too many fee coins, only accepts fees in one denom") //nolint:staticcheck + ErrInvalidFeeToken = sdkerrors.Register(ModuleName, 6, "invalid fee token") //nolint:staticcheck ) diff --git a/x/photon/types/msgs_test.go b/x/photon/types/msgs_test.go index 2b9ed2f3..4e73e040 100644 --- a/x/photon/types/msgs_test.go +++ b/x/photon/types/msgs_test.go @@ -7,6 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + appparams "github.com/atomone-hub/atomone/app/params" ) func TestMsgMintPhoton_ValidateBasic(t *testing.T) { @@ -27,7 +29,7 @@ func TestMsgMintPhoton_ValidateBasic(t *testing.T) { msg: MsgMintPhoton{ ToAddress: sdk.AccAddress("test1").String(), Amount: sdk.Coin{ - Denom: "uatone", + Denom: appparams.BondDenom, Amount: sdk.NewInt(-1), }, }, @@ -38,7 +40,7 @@ func TestMsgMintPhoton_ValidateBasic(t *testing.T) { msg: MsgMintPhoton{ ToAddress: sdk.AccAddress("test1").String(), Amount: sdk.Coin{ - Denom: "uatone", + Denom: appparams.BondDenom, Amount: sdk.NewInt(0), }, }, @@ -48,7 +50,7 @@ func TestMsgMintPhoton_ValidateBasic(t *testing.T) { name: "ok", msg: MsgMintPhoton{ ToAddress: sdk.AccAddress("test1").String(), - Amount: sdk.NewInt64Coin("uatone", 1), + Amount: sdk.NewInt64Coin(appparams.BondDenom, 1), }, }, } diff --git a/x/photon/types/params.go b/x/photon/types/params.go index 7511d52a..a4555887 100644 --- a/x/photon/types/params.go +++ b/x/photon/types/params.go @@ -1,9 +1,10 @@ package types // NewParams creates a new Params instance -func NewParams(mintDisabled bool) Params { +func NewParams(mintDisabled bool, txFeeExceptions []string) Params { return Params{ - MintDisabled: mintDisabled, + MintDisabled: mintDisabled, + TxFeeExceptions: txFeeExceptions, } } @@ -11,9 +12,13 @@ const ( defaultMintDisabled = false ) +// NOTE(tb): Not possible to use `sdk.MsgTypeURL(types.MsgMintPhoton{})` +// instead of plain text because at this step the msg is not registered yet. +var defaultTxFeeExceptions = []string{"/atomone.photon.v1.MsgMintPhoton"} + // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams(defaultMintDisabled) + return NewParams(defaultMintDisabled, defaultTxFeeExceptions) } // Validate validates the set of params diff --git a/x/photon/types/photon.pb.go b/x/photon/types/photon.pb.go index 7a76d34d..9853d43b 100644 --- a/x/photon/types/photon.pb.go +++ b/x/photon/types/photon.pb.go @@ -25,8 +25,12 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the x/photon module. type Params struct { - // mint photon or not + // Allow to mint photon or not MintDisabled bool `protobuf:"varint,1,opt,name=mint_disabled,json=mintDisabled,proto3" json:"mint_disabled,omitempty"` + // tx_fee_exceptions holds the msg type urls that are allowed to use some + // different tx fee coins than photon. + // A wildcard "*" can be used to allow all transactions to use any fee denom. + TxFeeExceptions []string `protobuf:"bytes,2,rep,name=tx_fee_exceptions,json=txFeeExceptions,proto3" json:"tx_fee_exceptions,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -69,6 +73,13 @@ func (m *Params) GetMintDisabled() bool { return false } +func (m *Params) GetTxFeeExceptions() []string { + if m != nil { + return m.TxFeeExceptions + } + return nil +} + func init() { proto.RegisterType((*Params)(nil), "atomone.photon.v1.Params") } @@ -76,18 +87,21 @@ func init() { func init() { proto.RegisterFile("atomone/photon/v1/photon.proto", fileDescriptor_37449d2fb4799465) } var fileDescriptor_37449d2fb4799465 = []byte{ - // 175 bytes of a gzipped FileDescriptorProto + // 212 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x2c, 0xc9, 0xcf, 0xcd, 0xcf, 0x4b, 0xd5, 0x2f, 0xc8, 0xc8, 0x2f, 0xc9, 0xcf, 0xd3, 0x2f, 0x33, 0x84, 0xb2, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x04, 0xa1, 0xf2, 0x7a, 0x50, 0xd1, 0x32, 0x43, 0x29, 0x91, - 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0xac, 0x3e, 0x88, 0x05, 0x51, 0xa8, 0xa4, 0xcb, 0xc5, 0x16, 0x90, + 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0xac, 0x3e, 0x88, 0x05, 0x51, 0xa8, 0x14, 0xc9, 0xc5, 0x16, 0x90, 0x58, 0x94, 0x98, 0x5b, 0x2c, 0xa4, 0xcc, 0xc5, 0x9b, 0x9b, 0x99, 0x57, 0x12, 0x9f, 0x92, 0x59, 0x9c, 0x98, 0x94, 0x93, 0x9a, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x11, 0xc4, 0x03, 0x12, 0x74, - 0x81, 0x8a, 0x39, 0xb9, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, - 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x6e, - 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0x72, 0xdd, 0x8c, 0xd2, - 0x24, 0x18, 0x5b, 0xbf, 0x02, 0xe6, 0xd4, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0xf5, - 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xec, 0xa7, 0xa2, 0x97, 0xc9, 0x00, 0x00, 0x00, + 0x81, 0x8a, 0x09, 0x69, 0x71, 0x09, 0x96, 0x54, 0xc4, 0xa7, 0xa5, 0xa6, 0xc6, 0xa7, 0x56, 0x24, + 0xa7, 0x16, 0x94, 0x64, 0xe6, 0xe7, 0x15, 0x4b, 0x30, 0x29, 0x30, 0x6b, 0x70, 0x06, 0xf1, 0x97, + 0x54, 0xb8, 0xa5, 0xa6, 0xba, 0xc2, 0x85, 0x9d, 0xdc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, + 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, + 0x58, 0x8e, 0x21, 0x4a, 0x37, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, + 0xea, 0x50, 0xdd, 0x8c, 0xd2, 0x24, 0x18, 0x5b, 0xbf, 0x02, 0xe6, 0xad, 0x92, 0xca, 0x82, 0xd4, + 0xe2, 0x24, 0x36, 0xb0, 0x53, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd0, 0x3f, 0x46, 0x7e, + 0xf5, 0x00, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -110,6 +124,15 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.TxFeeExceptions) > 0 { + for iNdEx := len(m.TxFeeExceptions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.TxFeeExceptions[iNdEx]) + copy(dAtA[i:], m.TxFeeExceptions[iNdEx]) + i = encodeVarintPhoton(dAtA, i, uint64(len(m.TxFeeExceptions[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } if m.MintDisabled { i-- if m.MintDisabled { @@ -143,6 +166,12 @@ func (m *Params) Size() (n int) { if m.MintDisabled { n += 2 } + if len(m.TxFeeExceptions) > 0 { + for _, s := range m.TxFeeExceptions { + l = len(s) + n += 1 + l + sovPhoton(uint64(l)) + } + } return n } @@ -201,6 +230,38 @@ func (m *Params) Unmarshal(dAtA []byte) error { } } m.MintDisabled = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxFeeExceptions", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPhoton + } + 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 ErrInvalidLengthPhoton + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPhoton + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxFeeExceptions = append(m.TxFeeExceptions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipPhoton(dAtA[iNdEx:])