diff --git a/changelog.md b/changelog.md index 595b8adc4c..c11a7afa09 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,56 @@ ### Features - [#4457](https://github.com/ignite/cli/pull/4457) Add `skip-build` flag to `chain serve` command to avoid (re)building the chain +- [#3707](https://github.com/ignite/cli/pull/3707) and [#4094](https://github.com/ignite/cli/pull/4094) Add collections support. +- [#3977](https://github.com/ignite/cli/pull/3977) Add `chain lint` command to lint the chain's codebase using `golangci-lint` +- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands +- [#4001](https://github.com/ignite/cli/pull/4001) Improve `xgenny` dry run +- [#3967](https://github.com/ignite/cli/issues/3967) Add HD wallet parameters `address index` and `account number` to the chain account config +- [#4004](https://github.com/ignite/cli/pull/4004) Remove all import placeholders using the `xast` pkg +- [#4071](https://github.com/ignite/cli/pull/4071) Support custom proto path +- [#3718](https://github.com/ignite/cli/pull/3718) Add `gen-mig-diffs` tool app to compare scaffold output of two versions of ignite +- [#4100](https://github.com/ignite/cli/pull/4100) Set the `proto-dir` flag only for the `scaffold chain` command and use the proto path from the config +- [#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 +- [#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) Add `buf.build` version to `ignite version` command +- [#4436](https://github.com/ignite/cli/pull/4436) Return tx hash to the faucet API +- [#4437](https://github.com/ignite/cli/pull/4437) Remove module placeholders +- [#4289](https://github.com/ignite/cli/pull/4289), [#4423](https://github.com/ignite/cli/pull/4423), [#4432](https://github.com/ignite/cli/pull/4432) Cosmos SDK v0.52 support +- [#4413](https://github.com/ignite/cli/pull/4413) Add `ignite s chain-registry` command + +### Changes + +- [#4457](https://github.com/ignite/cli/pull/4457) Add `ski-build` flag to `chain serve` command to avoid building the chain +- [#4094](https://github.com/ignite/cli/pull/4094) Scaffolding a multi-index map using `ignite s map foo bar baz --index foobar,foobaz` is no longer supported. Use one index instead of use `collections.IndexedMap`. +- [#4058](https://github.com/ignite/cli/pull/4058) Simplify scaffolded modules by including `ValidateBasic()` logic in message handler. +- [#4058](https://github.com/ignite/cli/pull/4058) Use `address.Codec` instead of `AccAddressFromBech32`. +- [#3993](https://github.com/ignite/cli/pull/3993) Oracle scaffolding was deprecated and has been removed +- [#3962](https://github.com/ignite/cli/pull/3962) Rename all RPC endpoints and autocli commands generated for `map`/`list`/`single` types +- [#3976](https://github.com/ignite/cli/pull/3976) Remove error checks for Cobra command value get calls +- [#4002](https://github.com/ignite/cli/pull/4002) Bump buf build +- [#4008](https://github.com/ignite/cli/pull/4008) Rename `pkg/yaml` to `pkg/xyaml` +- [#4075](https://github.com/ignite/cli/pull/4075) Use `gopkg.in/yaml.v3` instead `gopkg.in/yaml.v2` +- [#4118](https://github.com/ignite/cli/pull/4118) Version scaffolded protos as `v1` to follow SDK structure. +- [#4167](https://github.com/ignite/cli/pull/4167) Scaffold `int64` instead of `int32` when a field type is `int` +- [#4159](https://github.com/ignite/cli/pull/4159) Enable gci linter +- [#4160](https://github.com/ignite/cli/pull/4160) Enable copyloopvar linter +- [#4162](https://github.com/ignite/cli/pull/4162) Enable errcheck linter +- [#4189](https://github.com/ignite/cli/pull/4189) Deprecate `ignite node` for `ignite connect` app +- [#4290](https://github.com/ignite/cli/pull/4290) Remove ignite ics logic from ignite cli (this functionality will be in the `consumer` app) +- [#4295](https://github.com/ignite/cli/pull/4295) Stop scaffolding `pulsar` files +- [#4317](https://github.com/ignite/cli/pull/4317) Remove xchisel dependency +- [#4361](https://github.com/ignite/cli/pull/4361) Remove unused `KeyPrefix` method +- [#4384](https://github.com/ignite/cli/pull/4384) Compare genesis params into chain genesis tests + +### Fixes + +- [#4000](https://github.com/ignite/cli/pull/4000) Run all dry runners before the wet run in the `xgenny` pkg +- [#4091](https://github.com/ignite/cli/pull/4091) Fix race conditions in the plugin logic +- [#4128](https://github.com/ignite/cli/pull/4128) Check for duplicate proto fields in config +- [#4402](https://github.com/ignite/cli/pull/4402) Fix gentx parser into the cosmosutil package ## [`v28.6.1`](https://github.com/ignite/cli/releases/tag/v28.6.1) diff --git a/ignite/cmd/scaffold.go b/ignite/cmd/scaffold.go index 61e1ffc465..1058967405 100644 --- a/ignite/cmd/scaffold.go +++ b/ignite/cmd/scaffold.go @@ -130,6 +130,7 @@ with an "--ibc" flag. Note that the default module is not IBC-enabled. NewScaffoldBandchain(), NewScaffoldVue(), NewScaffoldReact(), + NewScaffoldChainRegistry(), ) return c diff --git a/ignite/cmd/scaffold_chain_registry.go b/ignite/cmd/scaffold_chain_registry.go new file mode 100644 index 0000000000..ba080a0952 --- /dev/null +++ b/ignite/cmd/scaffold_chain_registry.go @@ -0,0 +1,68 @@ +package ignitecmd + +import ( + "github.com/spf13/cobra" + + "github.com/ignite/cli/v29/ignite/pkg/cliui" + "github.com/ignite/cli/v29/ignite/services/chain" + "github.com/ignite/cli/v29/ignite/services/scaffolder" +) + +// NewScaffoldChainRegistry returns the command to scaffold the chain registry chain.json and assets.json files. +func NewScaffoldChainRegistry() *cobra.Command { + c := &cobra.Command{ + Use: "chain-registry", + Short: "Configs for the chain registry", + Long: `Scaffold the chain registry chain.json and assets.json files. + +The chain registry is a GitHub repo, hosted at https://github.com/cosmos/cosmos-registry, that +contains the chain.json and assets.json files of most of chains in the Cosmos ecosystem. +It is good practices, when creating a new chain, and about to launch a testnet or mainnet, to +publish the chain's metadata in the chain registry. + +Read more about the chain.json at https://github.com/cosmos/chain-registry?tab=readme-ov-file#chainjson +Read more about the assets.json at https://github.com/cosmos/chain-registry?tab=readme-ov-file#assetlists`, + Args: cobra.NoArgs, + PreRunE: migrationPreRunHandler, + RunE: scaffoldChainRegistryFiles, + } + + flagSetPath(c) + flagSetClearCache(c) + + c.Flags().AddFlagSet(flagSetYes()) + + return c +} + +func scaffoldChainRegistryFiles(cmd *cobra.Command, _ []string) error { + session := cliui.New(cliui.StartSpinnerWithText(statusScaffolding)) + defer session.End() + + cfg, _, err := getChainConfig(cmd) + if err != nil { + return err + } + + c, err := chain.NewWithHomeFlags(cmd) + if err != nil { + return err + } + + appPath := flagGetPath(cmd) + sc, err := scaffolder.New(cmd.Context(), appPath, cfg.Build.Proto.Path) + if err != nil { + return err + } + + if err = sc.AddChainRegistryFiles(c, cfg); err != nil { + return err + } + + // no need for post scaffolding, as we are just creating two files + // that are not part of the build process + + session.Printf("🎉 chain-registry files successfully scaffolded\n") + + return nil +} diff --git a/ignite/pkg/xgit/xgit.go b/ignite/pkg/xgit/xgit.go index e2953063ff..5572c55820 100644 --- a/ignite/pkg/xgit/xgit.go +++ b/ignite/pkg/xgit/xgit.go @@ -159,3 +159,23 @@ func IsRepository(path string) (bool, error) { } return true, nil } + +// RepositoryURL returns the URL of the origin remote of a Git repository. +func RepositoryURL(path string) (string, error) { + repo, err := git.PlainOpenWithOptions(path, &defaultOpenOpts) + if err != nil { + return "", err + } + + cfg, err := repo.Config() + if err != nil { + return "", err + } + + origin, ok := cfg.Remotes["origin"] + if !ok { + return "", errors.Errorf("no origin remote found in %s", path) + } + + return origin.URLs[0], nil +} diff --git a/ignite/services/scaffolder/chain_registry.go b/ignite/services/scaffolder/chain_registry.go new file mode 100644 index 0000000000..bd1643aef0 --- /dev/null +++ b/ignite/services/scaffolder/chain_registry.go @@ -0,0 +1,326 @@ +package scaffolder + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + chainconfig "github.com/ignite/cli/v29/ignite/config/chain" + "github.com/ignite/cli/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/ignite/pkg/xgit" + "github.com/ignite/cli/v29/ignite/services/chain" +) + +const ( + // DefaultChainType is the default chain type for the chain.json + // More value are allowed by the chain registry schema, but Ignite only scaffolds Cosmos chains. + DefaultChainType = "cosmos" + + // DefaultChainStatus is the default chain status for the chain.json + // More value are allowed by the chain registry schema, but Ignite only scaffolds upcoming chains. + DefaultChainStatus = "upcoming" + + // DefaultNetworkType is the default network type for the chain.json + // More value are allowed by the chain registry schema, but Ignite only scaffolds devnet chains. + DefaultNetworkType = "devnet" + + chainFilename = "chain.json" + assetListFilename = "assetlist.json" +) + +// https://raw.githubusercontent.com/cosmos/chain-registry/master/chain.schema.json +type chainJSON struct { + ChainName string `json:"chain_name"` + Status string `json:"status"` + NetworkType string `json:"network_type"` + Website string `json:"website"` + PrettyName string `json:"pretty_name"` + ChainType string `json:"chain_type"` + ChainID string `json:"chain_id"` + Bech32Prefix string `json:"bech32_prefix"` + DaemonName string `json:"daemon_name"` + NodeHome string `json:"node_home"` + KeyAlgos []string `json:"key_algos"` + Slip44 int `json:"slip44"` + Fees struct { + FeeTokens []feeToken `json:"fee_tokens"` + } `json:"fees"` + Staking staking `json:"staking"` + Codebase codebase `json:"codebase"` + Description string `json:"description"` +} + +type staking struct { + StakingTokens []stakingToken `json:"staking_tokens"` +} + +type stakingToken struct { + Denom string `json:"denom"` +} + +type codebase struct { + GitRepo string `json:"git_repo"` + Genesis codebaseGenesis `json:"genesis"` + RecommendedVersion string `json:"recommended_version"` + CompatibleVersions []string `json:"compatible_versions"` + Consensus codebaseConsensus `json:"consensus"` + Sdk codebaseSDK `json:"sdk"` + Ibc codebaseIBC `json:"ibc,omitempty"` + Cosmwasm codebaseCosmwasm `json:"cosmwasm,omitempty"` +} + +type codebaseGenesis struct { + GenesisURL string `json:"genesis_url"` +} + +type codebaseConsensus struct { + Type string `json:"type"` + Version string `json:"version"` +} + +type codebaseSDK struct { + Type string `json:"type"` + Version string `json:"version"` +} + +type codebaseIBC struct { + Type string `json:"type"` + Version string `json:"version"` +} + +type codebaseCosmwasm struct { + Version string `json:"version,omitempty"` + Enabled bool `json:"enabled"` +} + +type fees struct { + FeeTokens []feeToken `json:"fee_tokens"` +} + +type feeToken struct { + Denom string `json:"denom"` + FixedMinGasPrice float64 `json:"fixed_min_gas_price"` + LowGasPrice float64 `json:"low_gas_price"` + AverageGasPrice float64 `json:"average_gas_price"` + HighGasPrice float64 `json:"high_gas_price"` +} + +// SaveJSON saves the chainJSON to the given out directory. +func (c chainJSON) SaveJSON(out string) error { + bz, err := json.MarshalIndent(c, "", " ") + if err != nil { + return err + } + + return os.WriteFile(out, bz, 0o600) +} + +// https://raw.githubusercontent.com/cosmos/chain-registry/master/assetlist.schema.json +// https://github.com/cosmos/chain-registry?tab=readme-ov-file#assetlists +type assetListJSON struct { + ChainName string `json:"chain_name"` + Assets []asset `json:"assets"` +} + +type asset struct { + Description string `json:"description"` + DenomUnits []denomUnit `json:"denom_units"` + Base string `json:"base"` + Name string `json:"name"` + Display string `json:"display"` + Symbol string `json:"symbol"` + CoingeckoID string `json:"coingecko_id,omitempty"` + Socials socials `json:"socials,omitempty"` + TypeAsset string `json:"type_asset"` +} + +type denomUnit struct { + Denom string `json:"denom"` + Exponent int `json:"exponent"` +} + +type socials struct { + Website string `json:"website"` + Twitter string `json:"twitter"` +} + +// SaveJSON saves the assetList to the given out directory. +func (c assetListJSON) SaveJSON(out string) error { + bz, err := json.MarshalIndent(c, "", " ") + if err != nil { + return err + } + + return os.WriteFile(out, bz, 0o600) +} + +// AddChainRegistryFiles generates the chain registry files in the scaffolded chains. +func (s Scaffolder) AddChainRegistryFiles(chain *chain.Chain, cfg *chainconfig.Config) error { + binaryName, err := chain.Binary() + if err != nil { + return errors.Wrap(err, "failed to get binary name") + } + + chainHome, err := chain.DefaultHome() + if err != nil { + return errors.Wrap(err, "failed to get default home directory") + } + + chainID, err := chain.ID() + if err != nil { + return errors.Wrap(err, "failed to get chain ID") + } + + chainGitURL, _ /* do not fail on non-existing git repo */ := xgit.RepositoryURL(chain.AppPath()) + + var ( + consensus codebaseConsensus + cosmwasm codebaseCosmwasm + ibc codebaseIBC + ) + + consensusVersion, err := getVersionOfFromGoMod(chain, "github.com/cometbft/cometbft") + if err == nil { + consensus = codebaseConsensus{ + Type: "cometbft", + Version: consensusVersion, + } + } + + cosmwasmVersion, err := getVersionOfFromGoMod(chain, "github.com/CosmWasm/wasmd") + if err == nil { + cosmwasm = codebaseCosmwasm{ + Version: cosmwasmVersion, + Enabled: true, + } + } + + ibcVersion, err := getVersionOfFromGoMod(chain, "github.com/cosmos/ibc-go") + if err == nil { + ibc = codebaseIBC{ + Type: "go", + Version: ibcVersion, + } + } + + // get validators from config and parse their coins + // we can assume it holds the base denom + defaultDenom := "stake" + if len(cfg.Validators) > 0 { + coin, err := sdk.ParseCoinNormalized(cfg.Validators[0].Bonded) + if err == nil { + defaultDenom = coin.Denom + } + } + + chainData := chainJSON{ + ChainName: chain.Name(), + PrettyName: chain.Name(), + ChainType: DefaultChainType, + Status: DefaultChainStatus, + NetworkType: DefaultNetworkType, + Website: "https://example.com", + ChainID: chainID, + Bech32Prefix: "", + DaemonName: binaryName, + NodeHome: chainHome, + KeyAlgos: []string{"secp256k1"}, + Slip44: 118, + Fees: fees{ + FeeTokens: []feeToken{ + { + Denom: defaultDenom, + FixedMinGasPrice: 0.025, + LowGasPrice: 0.01, + AverageGasPrice: 0.025, + HighGasPrice: 0.03, + }, + }, + }, + Staking: staking{ + StakingTokens: []stakingToken{ + { + Denom: defaultDenom, + }, + }, + }, + Codebase: codebase{ + GitRepo: chainGitURL, + RecommendedVersion: "v1.0.0", + CompatibleVersions: []string{"v1.0.0"}, + Sdk: codebaseSDK{ + Type: "cosmos", + Version: chain.Version.String(), + }, + Consensus: consensus, + Ibc: ibc, + Cosmwasm: cosmwasm, + }, + } + + assetListData := assetListJSON{ + ChainName: chainData.ChainName, + Assets: []asset{ + { + Description: fmt.Sprintf("The native token of the %s chain", chainData.ChainName), + DenomUnits: []denomUnit{ + { + Denom: defaultDenom, + Exponent: 0, + }, + }, + Base: defaultDenom, + Name: chainData.ChainName, + Symbol: strings.ToUpper(defaultDenom), + TypeAsset: "sdk.coin", + Socials: socials{ + Website: "https://example.com", + Twitter: "https://x.com/ignite", + }, + }, + }, + } + + if err := chainData.SaveJSON(chainFilename); err != nil { + return err + } + + if err := assetListData.SaveJSON(assetListFilename); err != nil { + return err + } + + return nil +} + +func getVersionOfFromGoMod(chain *chain.Chain, pkg string) (string, error) { + chainPath := chain.AppPath() + + // get the version from the go.mod file + file, err := os.Open(filepath.Join(chainPath, "go.mod")) + if err != nil { + return "", err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, pkg) { + parts := strings.Fields(line) + if len(parts) > 1 { + return parts[len(parts)-1], nil + } + } + } + + if err := scanner.Err(); err != nil { + return "", err + } + + return "", errors.New("consensus version not found in go.mod") +} diff --git a/integration/other_components/cmd_chain_registry_test.go b/integration/other_components/cmd_chain_registry_test.go new file mode 100644 index 0000000000..0d7ae667a2 --- /dev/null +++ b/integration/other_components/cmd_chain_registry_test.go @@ -0,0 +1,38 @@ +//go:build !relayer + +package other_components_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ignite/cli/v29/ignite/pkg/cmdrunner/step" + envtest "github.com/ignite/cli/v29/integration" + "github.com/stretchr/testify/require" +) + +func TestCreateChainRegistry(t *testing.T) { + var ( + env = envtest.New(t) + app = env.Scaffold("github.com/test/mars") + ) + + env.Must(env.Exec("create chain-registry files", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "chain-registry", + ), + step.Workdir(app.SourcePath()), + )), + )) + + _, statErr := os.Stat(filepath.Join(app.SourcePath(), "chain.json")) + require.False(t, os.IsNotExist(statErr), "chain.json cannot be found") + + _, statErr = os.Stat(filepath.Join(app.SourcePath(), "assetlist.json")) + require.False(t, os.IsNotExist(statErr), "assetlist.json cannot be found") + + app.EnsureSteady() +}