diff --git a/changelog.md b/changelog.md index 024110c8f9..de749269dd 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ - [#4111](https://github.com/ignite/cli/pull/4111) Remove vuex generation - [#4113](https://github.com/ignite/cli/pull/4113) Generate chain config documentation automatically - [#4131](https://github.com/ignite/cli/pull/4131) Support `bytes` as data type in the `scaffold` commands +- [#4297](https://github.com/ignite/cli/pull/4297) Add in-place testnet creation command for apps. - [#4300](https://github.com/ignite/cli/pull/4300) Only panics the module in the most top function level - [#4327](https://github.com/ignite/cli/pull/4327) Use the TxConfig from simState instead create a new one - [#4326](https://github.com/ignite/cli/pull/4326) fAdd `buf.build` version to `ignite version` command diff --git a/docs/docs/03-CLI-Commands/01-cli-commands.md b/docs/docs/03-CLI-Commands/01-cli-commands.md index e15f6b37a1..f029a32fad 100644 --- a/docs/docs/03-CLI-Commands/01-cli-commands.md +++ b/docs/docs/03-CLI-Commands/01-cli-commands.md @@ -39,6 +39,7 @@ To get started, create a blockchain: * [ignite relayer](#ignite-relayer) - Connect blockchains with an IBC relayer * [ignite scaffold](#ignite-scaffold) - Create a new blockchain, module, message, query, and more * [ignite version](#ignite-version) - Print the current build information +* [ignite testnet](#ignite-testnet) - Start a testnet local ## ignite account @@ -3658,3 +3659,72 @@ ignite version [flags] * [ignite](#ignite) - Ignite CLI offers everything you need to scaffold, test, build, and launch your blockchain + +## ignite testnet + +Start a testnet local + +**Synopsis** + +The commands in this namespace allow you to start your local testnet for development purposes. Currently there is only one feature to create a testnet from any state network (including mainnet). + + +The "in-place" command is used to create and start a testnet from current local net state(including mainnet). +After using this command in the repo containing the config.yml file, the network will start. +We can create a testnet from the local network state and mint additional coins for the desired accounts from the config.yml file. + +During development, in-place allows you to quickly reboot the chain from a multi-node network state to a node you have full control over. + +**SEE ALSO** + +* [ignite testnet in-place](#ignite-testnet-in-place) - Create and start a testnet from current local net state + + +## ignite testnet in-place + +Create and start a testnet from current local net state + +**Synopsis** + +The "in-place" command is used to create and start a testnet from current local net state(including mainnet). + +We can create a testnet from the local network state and mint additional coins for the desired accounts from the config.yml file. + +During development, in-place allows you to quickly reboot the chain from a multi-node network state to a node you have full control over. + +By default, the data directory will be initialized in $HOME/.mychain, where "mychain" is the name of the project. To set a custom data directory use the --home flag or set the value in config.yml: + + validators: + - name: alice + bonded: '100000000stake' + home: "~/.customdir" + +Get mint coin just add account in config.yml file: + + accounts: + - name: charlie + coins: + - 20000token + - 200000000stake + + +``` +ignite chain debug [flags] +``` + +**Options** + +``` + -h, --help help for debug + -p, --path string path of the app (default ".") +``` + +**Options inherited from parent commands** + +``` + -c, --config string path to Ignite config file (default: ./config.yml) +``` + +**SEE ALSO** + +* [ignite](#ignite) - Ignite CLI offers everything you need to scaffold, test, build, start testnet and launch your blockchain \ No newline at end of file diff --git a/ignite/cmd/cmd.go b/ignite/cmd/cmd.go index 2ebb8b06ac..e2ab8930b8 100644 --- a/ignite/cmd/cmd.go +++ b/ignite/cmd/cmd.go @@ -89,6 +89,7 @@ To get started, create a blockchain: NewApp(), NewDoctor(), NewCompletionCmd(), + NewTestnet(), ) c.AddCommand(deprecated()...) c.SetContext(ctx) diff --git a/ignite/cmd/testnet.go b/ignite/cmd/testnet.go new file mode 100644 index 0000000000..03e0068c06 --- /dev/null +++ b/ignite/cmd/testnet.go @@ -0,0 +1,24 @@ +package ignitecmd + +import ( + "github.com/spf13/cobra" +) + +// NewTestnet returns a command that groups scaffolding related sub commands. +func NewTestnet() *cobra.Command { + c := &cobra.Command{ + Use: "testnet [command]", + Short: "Start a testnet local", + Long: `Start a testnet local + +`, + Aliases: []string{"s"}, + Args: cobra.ExactArgs(1), + } + + c.AddCommand( + NewTestnetInPlace(), + ) + + return c +} diff --git a/ignite/cmd/testnet_inplace.go b/ignite/cmd/testnet_inplace.go new file mode 100644 index 0000000000..17afb21f2a --- /dev/null +++ b/ignite/cmd/testnet_inplace.go @@ -0,0 +1,129 @@ +package ignitecmd + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/spf13/cobra" + + "github.com/ignite/cli/v29/ignite/pkg/cliui" + "github.com/ignite/cli/v29/ignite/pkg/cosmosaccount" + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/services/chain" +) + +func NewTestnetInPlace() *cobra.Command { + c := &cobra.Command{ + Use: "in-place", + Short: "Create and start a testnet from current local net state", + Long: `Testnet in-place command is used to create and start a testnet from current local net state(including mainnet). +After using this command in the repo containing the config.yml file, the network will start. +We can create a testnet from the local network state and mint additional coins for the desired accounts from the config.yml file.`, + Args: cobra.NoArgs, + RunE: testnetInPlaceHandler, + } + flagSetPath(c) + flagSetClearCache(c) + c.Flags().AddFlagSet(flagSetHome()) + c.Flags().AddFlagSet(flagSetCheckDependencies()) + c.Flags().AddFlagSet(flagSetSkipProto()) + c.Flags().AddFlagSet(flagSetVerbose()) + + c.Flags().Bool(flagQuitOnFail, false, "quit program if the app fails to start") + return c +} + +func testnetInPlaceHandler(cmd *cobra.Command, _ []string) error { + session := cliui.New( + cliui.WithVerbosity(getVerbosity(cmd)), + ) + defer session.End() + + // Otherwise run the serve command directly + return testnetInplace(cmd, session) +} + +func testnetInplace(cmd *cobra.Command, session *cliui.Session) error { + chainOption := []chain.Option{ + chain.WithOutputer(session), + chain.CollectEvents(session.EventBus()), + chain.CheckCosmosSDKVersion(), + } + + if flagGetCheckDependencies(cmd) { + chainOption = append(chainOption, chain.CheckDependencies()) + } + + // check if custom config is defined + config, _ := cmd.Flags().GetString(flagConfig) + if config != "" { + chainOption = append(chainOption, chain.ConfigFile(config)) + } + + c, err := chain.NewWithHomeFlags(cmd, chainOption...) + if err != nil { + return err + } + + cfg, err := c.Config() + if err != nil { + return err + } + home, err := c.Home() + if err != nil { + return err + } + keyringBackend, err := c.KeyringBackend() + if err != nil { + return err + } + ca, err := cosmosaccount.New( + cosmosaccount.WithKeyringBackend(cosmosaccount.KeyringBackend(keyringBackend)), + cosmosaccount.WithHome(home), + ) + if err != nil { + return err + } + + var ( + operatorAddress sdk.ValAddress + accounts string + accErr *cosmosaccount.AccountDoesNotExistError + ) + for _, acc := range cfg.Accounts { + sdkAcc, err := ca.GetByName(acc.Name) + if errors.As(err, &accErr) { + sdkAcc, _, err = ca.Create(acc.Name) + } + if err != nil { + return err + } + + sdkAddr, err := sdkAcc.Address(getAddressPrefix(cmd)) + if err != nil { + return err + } + if len(cfg.Validators) == 0 { + return errors.Errorf("no validators found for account %s", sdkAcc.Name) + } + if cfg.Validators[0].Name == acc.Name { + accAddr, err := sdk.AccAddressFromBech32(sdkAddr) + if err != nil { + return err + } + operatorAddress = sdk.ValAddress(accAddr) + } + accounts = accounts + "," + sdkAddr + } + + chainID, err := c.ID() + if err != nil { + return err + } + + args := chain.InPlaceArgs{ + NewChainID: chainID, + NewOperatorAddress: operatorAddress.String(), + AccountsToFund: accounts, + } + return c.TestnetInPlace(cmd.Context(), args) +} diff --git a/ignite/pkg/chaincmd/chaincmd.go b/ignite/pkg/chaincmd/chaincmd.go index 7034b789e5..c45baf53fc 100644 --- a/ignite/pkg/chaincmd/chaincmd.go +++ b/ignite/pkg/chaincmd/chaincmd.go @@ -27,6 +27,7 @@ const ( commandUnsafeReset = "unsafe-reset-all" commandExport = "export" commandTendermint = "tendermint" + commandTestnetInPlace = "in-place-testnet" optionHome = "--home" optionNode = "--node" @@ -55,6 +56,9 @@ const ( optionBroadcastMode = "--broadcast-mode" optionAccount = "--account" optionIndex = "--index" + optionValidatorPrivateKey = "--validator-privkey" + optionAccountToFund = "--accounts-to-fund" + optionSkipConfirmation = "--skip-confirmation" constTendermint = "tendermint" constJSON = "json" diff --git a/ignite/pkg/chaincmd/in-place-testnet.go b/ignite/pkg/chaincmd/in-place-testnet.go new file mode 100644 index 0000000000..84a3b38465 --- /dev/null +++ b/ignite/pkg/chaincmd/in-place-testnet.go @@ -0,0 +1,47 @@ +package chaincmd + +import ( + "github.com/ignite/cli/v29/ignite/pkg/cmdrunner/step" +) + +type InPlaceOption func([]string) []string + +func InPlaceWithPrvKey(prvKey string) InPlaceOption { + return func(s []string) []string { + if len(prvKey) > 0 { + return append(s, optionValidatorPrivateKey, prvKey) + } + return s + } +} + +func InPlaceWithAccountToFund(accounts string) InPlaceOption { + return func(s []string) []string { + if len(accounts) > 0 { + return append(s, optionAccountToFund, accounts) + } + return s + } +} + +func InPlaceWithSkipConfirmation() InPlaceOption { + return func(s []string) []string { + return append(s, optionSkipConfirmation) + } +} + +// TestnetInPlaceCommand return command to start testnet in-place. +func (c ChainCmd) TestnetInPlaceCommand(newChainID, newOperatorAddress string, options ...InPlaceOption) step.Option { + command := []string{ + commandTestnetInPlace, + newChainID, + newOperatorAddress, + } + + // Apply the options provided by the user + for _, apply := range options { + command = apply(command) + } + + return c.daemonCommand(command) +} diff --git a/ignite/pkg/chaincmd/runner/chain.go b/ignite/pkg/chaincmd/runner/chain.go index 7e60719713..0b133a47d2 100644 --- a/ignite/pkg/chaincmd/runner/chain.go +++ b/ignite/pkg/chaincmd/runner/chain.go @@ -45,6 +45,18 @@ func NewKV(key, value string) KV { var gentxRe = regexp.MustCompile(`(?m)"(.+?)"`) +func (r Runner) InPlace(ctx context.Context, newChainID, newOperatorAddress string, options ...chaincmd.InPlaceOption) error { + runOptions := runOptions{ + stdout: os.Stdout, + stderr: os.Stderr, + } + return r.run( + ctx, + runOptions, + r.chainCmd.TestnetInPlaceCommand(newChainID, newOperatorAddress, options...), + ) +} + // Gentx generates a genesis tx carrying a self delegation. func (r Runner) Gentx( ctx context.Context, diff --git a/ignite/services/chain/runtime.go b/ignite/services/chain/runtime.go index ac18fb23f8..a0eb21e1df 100644 --- a/ignite/services/chain/runtime.go +++ b/ignite/services/chain/runtime.go @@ -37,6 +37,17 @@ func (c Chain) Gentx(ctx context.Context, runner chaincmdrunner.Runner, v Valida ) } +func (c Chain) InPlace(ctx context.Context, runner chaincmdrunner.Runner, args InPlaceArgs) error { + err := runner.InPlace(ctx, + args.NewChainID, + args.NewOperatorAddress, + chaincmd.InPlaceWithPrvKey(args.PrvKeyValidator), + chaincmd.InPlaceWithAccountToFund(args.AccountsToFund), + chaincmd.InPlaceWithSkipConfirmation(), + ) + return err +} + // Start wraps the "appd start" command to begin running a chain from the daemon. func (c Chain) Start(ctx context.Context, runner chaincmdrunner.Runner, cfg *chainconfig.Config) error { validator, err := chainconfig.FirstValidator(cfg) diff --git a/ignite/services/chain/testnet.go b/ignite/services/chain/testnet.go new file mode 100644 index 0000000000..5a3b85d77b --- /dev/null +++ b/ignite/services/chain/testnet.go @@ -0,0 +1,37 @@ +package chain + +import ( + "context" + "os" + + chainconfig "github.com/ignite/cli/v29/ignite/config/chain" +) + +type InPlaceArgs struct { + NewChainID string + NewOperatorAddress string + PrvKeyValidator string + AccountsToFund string +} + +func (c Chain) TestnetInPlace(ctx context.Context, args InPlaceArgs) error { + commands, err := c.Commands(ctx) + if err != nil { + return err + } + + // make sure that config.yml exists + if c.options.ConfigFile != "" { + if _, err := os.Stat(c.options.ConfigFile); err != nil { + return err + } + } else if _, err := chainconfig.LocateDefault(c.app.Path); err != nil { + return err + } + + err = c.InPlace(ctx, commands, args) + if err != nil { + return err + } + return nil +} diff --git a/ignite/templates/app/files/app/app.go.plush b/ignite/templates/app/files/app/app.go.plush index 504a3b392f..5d3293fc45 100644 --- a/ignite/templates/app/files/app/app.go.plush +++ b/ignite/templates/app/files/app/app.go.plush @@ -402,4 +402,4 @@ func BlockedAddresses() map[string]bool { } } return result -} +} \ No newline at end of file diff --git a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush index 685899a07d..5388a9d8d7 100644 --- a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush +++ b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/commands.go.plush @@ -35,6 +35,7 @@ func initRootCmd( ) { rootCmd.AddCommand( genutilcli.InitCmd(basicManager, app.DefaultNodeHome), + NewTestnetCmd(addModuleInitFlags), debug.Cmd(), confixcmd.ConfigCommand(), pruning.Cmd(newApp, app.DefaultNodeHome), @@ -186,4 +187,4 @@ func appExport( } return bApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) -} \ No newline at end of file +} diff --git a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush new file mode 100644 index 0000000000..0a02a3bdd7 --- /dev/null +++ b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush @@ -0,0 +1,264 @@ +package cmd + +import ( + "fmt" + "io" + "strings" + + "cosmossdk.io/log" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/libs/bytes" + tmos "github.com/cometbft/cometbft/libs/os" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/client/flags" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/spf13/cast" + "github.com/spf13/cobra" + + "<%= ModulePath %>/app" +) + +const ( + valVotingPower int64 = 900000000000000 +) + +var ( + flagAccountsToFund = "accounts-to-fund" +) + +type valArgs struct { + newValAddr bytes.HexBytes + newOperatorAddress string + newValPubKey crypto.PubKey + accountsToFund []sdk.AccAddress + upgradeToTrigger string + homeDir string +} + +func NewTestnetCmd(addStartFlags servertypes.ModuleInitFlags) *cobra.Command { + cmd := server.InPlaceTestnetCreator(newTestnetApp) + addStartFlags(cmd) + cmd.Short = "Updates chain's application and consensus state with provided validator info and starts the node" + cmd.Long = `The test command modifies both application and consensus stores within a local mainnet node and starts the node, +with the aim of facilitating testing procedures. This command replaces existing validator data with updated information, +thereby removing the old validator set and introducing a new set suitable for local testing purposes. By altering the state extracted from the mainnet node, +it enables developers to configure their local environments to reflect mainnet conditions more accurately.` + + cmd.Example = fmt.Sprintf(`%sd in-place-testnet testing-1 cosmosvaloper1w7f3xx7e75p4l7qdym5msqem9rd4dyc4mq79dm --home $HOME/.%sd/validator1 --validator-privkey=6dq+/KHNvyiw2TToCgOpUpQKIzrLs69Rb8Az39xvmxPHNoPxY1Cil8FY+4DhT9YwD6s0tFABMlLcpaylzKKBOg== --accounts-to-fund="cosmos1f7twgcq4ypzg7y24wuywy06xmdet8pc4473tnq,cosmos1qvuhm5m644660nd8377d6l7yz9e9hhm9evmx3x"`, "<%= ModulePath %>", "<%= ModulePath %>") + + cmd.Flags().String(flagAccountsToFund, "", "Comma-separated list of account addresses that will be funded for testing purposes") + return cmd +} + +// newTestnetApp starts by running the normal newApp method. From there, the app interface returned is modified in order +// for a testnet to be created from the provided app. +func newTestnetApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + // Create an app and type cast to an App + newApp := newApp(logger, db, traceStore, appOpts) + testApp, ok := newApp.(*app.App) + if !ok { + panic("app created from newApp is not of type App") + } + + // Get command args + args, err := getCommandArgs(appOpts) + if err != nil { + panic(err) + } + + return initAppForTestnet(testApp, args) +} + +func initAppForTestnet(app *app.App, args valArgs) *app.App { + // Required Changes: + // + ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{}) + + pubkey := &ed25519.PubKey{Key: args.newValPubKey.Bytes()} + pubkeyAny, err := codectypes.NewAnyWithValue(pubkey) + if err != nil { + tmos.Exit(err.Error()) + } + + // STAKING + // + + // Create Validator struct for our new validator. + newVal := stakingtypes.Validator{ + OperatorAddress: args.newOperatorAddress, + ConsensusPubkey: pubkeyAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: math.NewInt(valVotingPower), + DelegatorShares: math.LegacyMustNewDecFromStr("10000000"), + Description: stakingtypes.Description{ + Moniker: "Testnet Validator", + }, + Commission: stakingtypes.Commission{ + CommissionRates: stakingtypes.CommissionRates{ + Rate: math.LegacyMustNewDecFromStr("0.05"), + MaxRate: math.LegacyMustNewDecFromStr("0.1"), + MaxChangeRate: math.LegacyMustNewDecFromStr("0.05"), + }, + }, + MinSelfDelegation: math.OneInt(), + } + + validator, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(newVal.GetOperator()) + if err != nil { + tmos.Exit(err.Error()) + } + + // Remove all validators from power store + stakingKey := app.GetKey(stakingtypes.ModuleName) + stakingStore := ctx.KVStore(stakingKey) + iterator, err := app.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + if err != nil { + tmos.Exit(err.Error()) + } + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + iterator.Close() + + // Remove all valdiators from last validators store + iterator, err = app.StakingKeeper.LastValidatorsIterator(ctx) + if err != nil { + tmos.Exit(err.Error()) + } + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + iterator.Close() + + // Remove all validators from validators store + iterator = stakingStore.Iterator(stakingtypes.ValidatorsKey, storetypes.PrefixEndBytes(stakingtypes.ValidatorsKey)) + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + iterator.Close() + + // Remove all validators from unbonding queue + iterator = stakingStore.Iterator(stakingtypes.ValidatorQueueKey, storetypes.PrefixEndBytes(stakingtypes.ValidatorQueueKey)) + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + iterator.Close() + + // Add our validator to power and last validators store + app.StakingKeeper.SetValidator(ctx, newVal) + err = app.StakingKeeper.SetValidatorByConsAddr(ctx, newVal) + if err != nil { + tmos.Exit(err.Error()) + } + app.StakingKeeper.SetValidatorByPowerIndex(ctx, newVal) + app.StakingKeeper.SetLastValidatorPower(ctx, validator, 0) + if err := app.StakingKeeper.Hooks().AfterValidatorCreated(ctx, validator); err != nil { + tmos.Exit(err.Error()) + } + + // DISTRIBUTION + // + + // Initialize records for this validator across all distribution stores + app.DistrKeeper.SetValidatorHistoricalRewards(ctx, validator, 0, distrtypes.NewValidatorHistoricalRewards(sdk.DecCoins{}, 1)) + app.DistrKeeper.SetValidatorCurrentRewards(ctx, validator, distrtypes.NewValidatorCurrentRewards(sdk.DecCoins{}, 1)) + app.DistrKeeper.SetValidatorAccumulatedCommission(ctx, validator, distrtypes.InitialValidatorAccumulatedCommission()) + app.DistrKeeper.SetValidatorOutstandingRewards(ctx, validator, distrtypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}}) + +<%= if (!IsChainMinimal) { %> + // SLASHING + // + + // Set validator signing info for our new validator. + newConsAddr := sdk.ConsAddress(args.newValAddr.Bytes()) + newValidatorSigningInfo := slashingtypes.ValidatorSigningInfo{ + Address: newConsAddr.String(), + StartHeight: app.LastBlockHeight() - 1, + Tombstoned: false, + } + app.SlashingKeeper.SetValidatorSigningInfo(ctx, newConsAddr, newValidatorSigningInfo) +<% } %> + + // BANK + // + bondDenom, err := app.StakingKeeper.BondDenom(ctx) + if err != nil { + tmos.Exit(err.Error()) + } + + defaultCoins := sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 1000000000)) + + // Fund local accounts + for _, account := range args.accountsToFund { + err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, defaultCoins) + if err != nil { + tmos.Exit(err.Error()) + } + err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, account, defaultCoins) + if err != nil { + tmos.Exit(err.Error()) + } + } + + return app +} + +// parse the input flags and returns valArgs +func getCommandArgs(appOpts servertypes.AppOptions) (valArgs, error) { + args := valArgs{} + + newValAddr, ok := appOpts.Get(server.KeyNewValAddr).(bytes.HexBytes) + if !ok { + panic("newValAddr is not of type bytes.HexBytes") + } + args.newValAddr = newValAddr + newValPubKey, ok := appOpts.Get(server.KeyUserPubKey).(crypto.PubKey) + if !ok { + panic("newValPubKey is not of type crypto.PubKey") + } + args.newValPubKey = newValPubKey + newOperatorAddress, ok := appOpts.Get(server.KeyNewOpAddr).(string) + if !ok { + panic("newOperatorAddress is not of type string") + } + args.newOperatorAddress = newOperatorAddress + upgradeToTrigger, ok := appOpts.Get(server.KeyTriggerTestnetUpgrade).(string) + if !ok { + panic("upgradeToTrigger is not of type string") + } + args.upgradeToTrigger = upgradeToTrigger + + // validate and set accounts to fund + accountsString := cast.ToString(appOpts.Get(flagAccountsToFund)) + + for _, account := range strings.Split(accountsString, ",") { + if account != "" { + addr, err := sdk.AccAddressFromBech32(account) + if err != nil { + return args, fmt.Errorf("invalid bech32 address format %w", err) + } + args.accountsToFund = append(args.accountsToFund, addr) + } + } + + // home dir + homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) + if homeDir == "" { + return args, fmt.Errorf("invalid home dir") + } + args.homeDir = homeDir + + return args, nil +} diff --git a/ignite/templates/app/files/go.mod.plush b/ignite/templates/app/files/go.mod.plush index 5c3d23ad7e..0f0e4cf1c8 100644 --- a/ignite/templates/app/files/go.mod.plush +++ b/ignite/templates/app/files/go.mod.plush @@ -17,6 +17,7 @@ require ( cosmossdk.io/depinject v1.0.0 cosmossdk.io/errors v1.0.1 cosmossdk.io/log v1.3.1 + cosmossdk.io/math v1.3.0 cosmossdk.io/store v1.1.0 cosmossdk.io/tools/confix v0.1.1 cosmossdk.io/x/circuit v0.1.0 @@ -36,6 +37,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 + github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0