From 1d55c022c373aa4d32c27f2e129c429eb953ee4d Mon Sep 17 00:00:00 2001 From: ltacker Date: Mon, 21 Jun 2021 18:34:19 +0200 Subject: [PATCH 01/30] expected keeper template --- .../stargate/x/{{moduleName}}/types/expected_keepers.go.plush | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush new file mode 100644 index 0000000000..eb2cc0120b --- /dev/null +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush @@ -0,0 +1,3 @@ +package types + +// this line is used by starport scaffolding # dep/expected_keeper \ No newline at end of file From 34aaa4909f83c75ee9eee7ab8e0808eac6e17668 Mon Sep 17 00:00:00 2001 From: ltacker Date: Mon, 21 Jun 2021 18:49:12 +0200 Subject: [PATCH 02/30] add dependenciy options --- starport/cmd/module-create.go | 13 +++++++++++++ starport/services/scaffolder/module.go | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index f32e6d127b..b7944e91d6 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "strings" "github.com/spf13/cobra" "github.com/tendermint/starport/starport/pkg/clispinner" @@ -15,6 +16,7 @@ import ( ) const ( + flagDep = "dep" flagIBC = "ibc" flagIBCOrdering = "ordering" flagRequireRegistration = "require-registration" @@ -53,6 +55,7 @@ func NewModuleCreate() *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: createModuleHandler, } + c.Flags().String(flagDep, "", "comma-separated module dependencies (e.g. --dep account,bank)") c.Flags().Bool(flagIBC, false, "scaffold an IBC module") c.Flags().String(flagIBCOrdering, "none", "channel ordering of the IBC module [none|ordered|unordered]") c.Flags().Bool(flagRequireRegistration, false, "if true command will fail if module can't be registered") @@ -86,6 +89,16 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { options = append(options, scaffolder.WithIBCChannelOrdering(ibcOrdering), scaffolder.WithIBC()) } + // Get module dependencies + dep, err := cmd.Flags().GetString(flagDep) + if err != nil { + return err + } + if len(dep) > 0 { + dependencies := strings.Split(dep, ",") + options = append(options, scaffolder.WithDependencies(dependencies)) + } + sc, err := scaffolder.New(appPath) if err != nil { return err diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index f929b85ff4..75873402ed 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -44,6 +44,9 @@ type moduleCreationOptions struct { // homePath of the chain's config dir. ibcChannelOrdering string + + // list of module depencies + dependencies []string } // ModuleCreationOption configures Chain. @@ -70,6 +73,13 @@ func WithIBCChannelOrdering(ordering string) ModuleCreationOption { } } +// WithDependencies specifies the name of the modules that the module depends on +func WithDependencies(dependencies []string) ModuleCreationOption { + return func(m *moduleCreationOptions) { + m.dependencies = dependencies + } +} + // CreateModule creates a new empty module in the scaffolded app func (s *Scaffolder) CreateModule( tracer *placeholder.Tracer, From 565831ceb9c90b37759082e7ebda71039e1c684a Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 06:59:09 +0200 Subject: [PATCH 03/30] Template init --- starport/services/scaffolder/module.go | 39 ++++++++++--------- starport/templates/module/create/options.go | 3 ++ starport/templates/module/create/stargate.go | 1 + .../x/{{moduleName}}/keeper/keeper.go.plush | 4 ++ .../types/expected_keepers.go.plush | 6 ++- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index 75873402ed..7a1ae100e1 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -32,7 +32,7 @@ var ( const ( wasmImport = "github.com/CosmWasm/wasmd" - apppkg = "app" + appPkg = "app" moduleDir = "x" wasmVersion = "v0.16.0" ) @@ -85,25 +85,25 @@ func (s *Scaffolder) CreateModule( tracer *placeholder.Tracer, moduleName string, options ...ModuleCreationOption, -) (sm xgenny.SourceModification, err error) { +) (sourceModification xgenny.SourceModification, err error) { mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber) if err != nil { - return sm, err + return sourceModification, err } moduleName = mfName.Lowercase // Check if the module name is valid if err := checkModuleName(moduleName); err != nil { - return sm, err + return sourceModification, err } // Check if the module already exist ok, err := moduleExists(s.path, moduleName) if err != nil { - return sm, err + return sourceModification, err } if ok { - return sm, fmt.Errorf("the module %v already exists", moduleName) + return sourceModification, fmt.Errorf("the module %v already exists", moduleName) } // Apply the options @@ -113,7 +113,7 @@ func (s *Scaffolder) CreateModule( } path, err := gomodulepath.ParseAt(s.path) if err != nil { - return sm, err + return sourceModification, err } opts := &modulecreate.CreateOptions{ ModuleName: moduleName, @@ -122,21 +122,22 @@ func (s *Scaffolder) CreateModule( OwnerName: owner(path.RawPath), IsIBC: creationOpts.ibc, IBCOrdering: creationOpts.ibcChannelOrdering, + Dependencies: creationOpts.dependencies, } if opts.IsIBC { ibcPlaceholder, err := checkIBCRouterPlaceholder(s.path) if err != nil { - return sm, err + return sourceModification, err } if !ibcPlaceholder { - return sm, ErrNoIBCRouterPlaceholder + return sourceModification, ErrNoIBCRouterPlaceholder } } // Generator from Cosmos SDK version g, err := modulecreate.NewStargate(opts) if err != nil { - return sm, err + return sourceModification, err } gens := []*genny.Generator{g} @@ -144,31 +145,31 @@ func (s *Scaffolder) CreateModule( if opts.IsIBC { g, err = modulecreate.NewIBC(tracer, opts) if err != nil { - return sm, err + return sourceModification, err } gens = append(gens, g) } - sm, err = xgenny.RunWithValidation(tracer, gens...) + sourceModification, err = xgenny.RunWithValidation(tracer, gens...) if err != nil { - return sm, err + return sourceModification, err } newSourceModification, runErr := xgenny.RunWithValidation(tracer, modulecreate.NewStargateAppModify(tracer, opts)) - sm.Merge(newSourceModification) + sourceModification.Merge(newSourceModification) var validationErr validation.Error if runErr != nil && !errors.As(runErr, &validationErr) { - return sm, runErr + return sourceModification, runErr } // Generate proto and format the source pwd, err := os.Getwd() if err != nil { - return sm, err + return sourceModification, err } if err := s.finish(pwd, path.RawPath); err != nil { - return sm, err + return sourceModification, err } - return sm, runErr + return sourceModification, runErr } // ImportModule imports specified module with name to the scaffolded app. @@ -265,7 +266,7 @@ func checkModuleName(moduleName string) error { } func isWasmImported(appPath string) (bool, error) { - abspath, err := filepath.Abs(filepath.Join(appPath, apppkg)) + abspath, err := filepath.Abs(filepath.Join(appPath, appPkg)) if err != nil { return false, err } diff --git a/starport/templates/module/create/options.go b/starport/templates/module/create/options.go index 3403d57601..f726bfe0d7 100644 --- a/starport/templates/module/create/options.go +++ b/starport/templates/module/create/options.go @@ -12,6 +12,9 @@ type CreateOptions struct { // Channel ordering of the IBC module: ordered, unordered or none IBCOrdering string + + // Dependencies of the module + Dependencies []string } // CreateOptions defines options to add MsgServer diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index 64c9bed158..d6a59cd2c5 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -27,6 +27,7 @@ func NewStargate(opts *CreateOptions) (*genny.Generator, error) { ctx.Set("appName", opts.AppName) ctx.Set("ownerName", opts.OwnerName) ctx.Set("title", strings.Title) + ctx.Set("dependencies", opts.Dependencies) // Used for proto package name ctx.Set("formatOwnerName", xstrings.FormatUsername) diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush index 39ec79bade..231a35c79b 100644 --- a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush @@ -17,6 +17,8 @@ type ( storeKey sdk.StoreKey memKey sdk.StoreKey // this line is used by starport scaffolding # ibc/keeper/attribute + <%= for (dependency) in dependencies { %> + <%= dependency %>Keeper types.<%= title(dependency) %>Keeper<% } %> } ) @@ -25,12 +27,14 @@ func NewKeeper( storeKey, memKey sdk.StoreKey, // this line is used by starport scaffolding # ibc/keeper/parameter + <%= for (dependency) in dependencies { %><%= dependency %>Keeper types.<%= title(dependency) %>Keeper,<% } %> ) *Keeper { return &Keeper{ cdc: cdc, storeKey: storeKey, memKey: memKey, // this line is used by starport scaffolding # ibc/keeper/return + <%= for (dependency) in dependencies { %><%= dependency %>Keeper: <%= dependency %>Keeper,<% } %> } } diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush index eb2cc0120b..096e004cc1 100644 --- a/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush @@ -1,3 +1,7 @@ package types -// this line is used by starport scaffolding # dep/expected_keeper \ No newline at end of file +<%= for (dependency) in dependencies { %> +type <%= title(dependency) %>Keeper interface { + // Methods imported by the module should be defined here +} +<% } %> \ No newline at end of file From 8b9eafe78dc45435ba6afc6c93c5bd6115f49a1b Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 09:32:45 +0200 Subject: [PATCH 04/30] Add misc errors to tracer --- starport/pkg/placeholder/tracer.go | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/starport/pkg/placeholder/tracer.go b/starport/pkg/placeholder/tracer.go index 26bee87b2d..43f630f5ed 100644 --- a/starport/pkg/placeholder/tracer.go +++ b/starport/pkg/placeholder/tracer.go @@ -1,6 +1,7 @@ package placeholder import ( + "fmt" "strings" "github.com/tendermint/starport/starport/pkg/validation" @@ -25,8 +26,9 @@ func (set iterableStringSet) Add(item string) { var _ validation.Error = (*MissingPlaceholdersError)(nil) type MissingPlaceholdersError struct { - missing iterableStringSet - additionalInfo string + missing iterableStringSet + additionalInfo string + additionalErrors error } // Is true if both errors have the same list of missing placeholders. @@ -73,6 +75,10 @@ func (e *MissingPlaceholdersError) ValidationInfo() string { b.WriteString("\n\n") b.WriteString(e.additionalInfo) } + if e.additionalErrors != nil { + b.WriteString("\n\nAdditional errors: ") + b.WriteString(e.additionalErrors.Error()) + } return b.String() } @@ -98,11 +104,13 @@ func New(opts ...Option) *Tracer { type Replacer interface { Replace(content, placeholder, replacement string) string ReplaceOnce(content, placeholder, replacement string) string + AppendMiscError(miscError string) } -// Tracer keeps track of missing placeholders. +// Tracer keeps track of missing placeholders or other issues related to file modification. type Tracer struct { missing iterableStringSet + miscErrors []string additionalInfo string } @@ -125,14 +133,31 @@ func (t *Tracer) ReplaceOnce(content, placeholder, replacement string) string { return content } +// AppendMiscError allows to track errors not related to missing placeholders during file modification +func (t *Tracer) AppendMiscError(miscError string) { + t.miscErrors = append(t.miscErrors, miscError) +} + // Err if any of the placeholders were missing during execution. func (t *Tracer) Err() error { + // miscellaneous errors represent errors preventing source modification not related to missing placeholder + var miscErrors error + if len(t.miscErrors) > 0 { + miscErrors = fmt.Errorf("%v", t.miscErrors) + } + if len(t.missing) > 0 { missing := iterableStringSet{} for key := range t.missing { missing.Add(key) } - return &MissingPlaceholdersError{missing: missing, additionalInfo: t.additionalInfo} + return &MissingPlaceholdersError{ + missing: missing, + additionalInfo: t.additionalInfo, + additionalErrors: miscErrors, + } } - return nil + + // if not missing placeholder but still miscellaneous errors, return them + return miscErrors } From 6148c53ae37ec9447040a3f32f3fd877539c9237 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 09:33:21 +0200 Subject: [PATCH 05/30] app.go modification --- starport/cmd/module-create.go | 2 +- starport/services/scaffolder/module.go | 46 ++++++++++---------- starport/templates/module/create/stargate.go | 19 +++++++- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index b7944e91d6..4d88dab9ee 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -16,7 +16,7 @@ import ( ) const ( - flagDep = "dep" + flagDep = "dep" flagIBC = "ibc" flagIBCOrdering = "ordering" flagRequireRegistration = "require-registration" diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index 7a1ae100e1..da8266b74d 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -85,25 +85,25 @@ func (s *Scaffolder) CreateModule( tracer *placeholder.Tracer, moduleName string, options ...ModuleCreationOption, -) (sourceModification xgenny.SourceModification, err error) { +) (sm xgenny.SourceModification, err error) { mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber) if err != nil { - return sourceModification, err + return sm, err } moduleName = mfName.Lowercase // Check if the module name is valid if err := checkModuleName(moduleName); err != nil { - return sourceModification, err + return sm, err } // Check if the module already exist ok, err := moduleExists(s.path, moduleName) if err != nil { - return sourceModification, err + return sm, err } if ok { - return sourceModification, fmt.Errorf("the module %v already exists", moduleName) + return sm, fmt.Errorf("the module %v already exists", moduleName) } // Apply the options @@ -113,31 +113,31 @@ func (s *Scaffolder) CreateModule( } path, err := gomodulepath.ParseAt(s.path) if err != nil { - return sourceModification, err + return sm, err } opts := &modulecreate.CreateOptions{ - ModuleName: moduleName, - ModulePath: path.RawPath, - AppName: path.Package, - OwnerName: owner(path.RawPath), - IsIBC: creationOpts.ibc, - IBCOrdering: creationOpts.ibcChannelOrdering, + ModuleName: moduleName, + ModulePath: path.RawPath, + AppName: path.Package, + OwnerName: owner(path.RawPath), + IsIBC: creationOpts.ibc, + IBCOrdering: creationOpts.ibcChannelOrdering, Dependencies: creationOpts.dependencies, } if opts.IsIBC { ibcPlaceholder, err := checkIBCRouterPlaceholder(s.path) if err != nil { - return sourceModification, err + return sm, err } if !ibcPlaceholder { - return sourceModification, ErrNoIBCRouterPlaceholder + return sm, ErrNoIBCRouterPlaceholder } } // Generator from Cosmos SDK version g, err := modulecreate.NewStargate(opts) if err != nil { - return sourceModification, err + return sm, err } gens := []*genny.Generator{g} @@ -145,31 +145,31 @@ func (s *Scaffolder) CreateModule( if opts.IsIBC { g, err = modulecreate.NewIBC(tracer, opts) if err != nil { - return sourceModification, err + return sm, err } gens = append(gens, g) } - sourceModification, err = xgenny.RunWithValidation(tracer, gens...) + sm, err = xgenny.RunWithValidation(tracer, gens...) if err != nil { - return sourceModification, err + return sm, err } newSourceModification, runErr := xgenny.RunWithValidation(tracer, modulecreate.NewStargateAppModify(tracer, opts)) - sourceModification.Merge(newSourceModification) + sm.Merge(newSourceModification) var validationErr validation.Error if runErr != nil && !errors.As(runErr, &validationErr) { - return sourceModification, runErr + return sm, runErr } // Generate proto and format the source pwd, err := os.Getwd() if err != nil { - return sourceModification, err + return sm, err } if err := s.finish(pwd, path.RawPath); err != nil { - return sourceModification, err + return sm, err } - return sourceModification, runErr + return sm, runErr } // ImportModule imports specified module with name to the scaffolded app. diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index d6a59cd2c5..a491c58f66 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -96,6 +96,22 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny content = replacer.Replace(content, module.PlaceholderSgAppStoreKey, replacement) // Keeper definition + var depArgs string + for _, dep := range opts.Dependencies { + keeperDefinition := fmt.Sprintf("app.%sKeeper", strings.Title(dep)) + + // if module has dependencies, we must verify the keeper of the dependency is defined in app.go + if !strings.Contains(content, keeperDefinition) { + replacer.AppendMiscError(fmt.Sprintf( + "the module cannot have %s as a dependency: %s is not declared in app.go", + dep, + keeperDefinition, + )) + } + + depArgs = fmt.Sprintf("%s%s,\n", depArgs, keeperDefinition) + } + var scopedKeeperDefinition string var ibcKeeperArgument string if opts.IsIBC { @@ -111,7 +127,7 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny keys[%[2]vmoduletypes.StoreKey], keys[%[2]vmoduletypes.MemStoreKey], %[4]v - ) + %[6]v) %[2]vModule := %[2]vmodule.NewAppModule(appCodec, app.%[5]vKeeper)` replacement = fmt.Sprintf( template, @@ -120,6 +136,7 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny scopedKeeperDefinition, ibcKeeperArgument, strings.Title(opts.ModuleName), + depArgs, ) content = replacer.Replace(content, module.PlaceholderSgAppKeeperDefinition, replacement) From c73e72e181971672fcfd1d696c45f7fdac134d7f Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 10:45:54 +0200 Subject: [PATCH 06/30] app.go update gov --- starport/pkg/placeholder/tracer.go | 2 +- starport/templates/app/stargate/app/app.go.plush | 4 ++-- starport/templates/module/create/stargate.go | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/starport/pkg/placeholder/tracer.go b/starport/pkg/placeholder/tracer.go index 43f630f5ed..f8927b03b3 100644 --- a/starport/pkg/placeholder/tracer.go +++ b/starport/pkg/placeholder/tracer.go @@ -143,7 +143,7 @@ func (t *Tracer) Err() error { // miscellaneous errors represent errors preventing source modification not related to missing placeholder var miscErrors error if len(t.miscErrors) > 0 { - miscErrors = fmt.Errorf("%v", t.miscErrors) + miscErrors = fmt.Errorf("%v", strings.Join(t.miscErrors, "\n")) } if len(t.missing) > 0 { diff --git a/starport/templates/app/stargate/app/app.go.plush b/starport/templates/app/stargate/app/app.go.plush index 74329fd1fb..859e95d1a8 100644 --- a/starport/templates/app/stargate/app/app.go.plush +++ b/starport/templates/app/stargate/app/app.go.plush @@ -316,13 +316,13 @@ func New( // If evidence needs to be handled for the app, set routes in router here and seal app.EvidenceKeeper = *evidenceKeeper - // this line is used by starport scaffolding # stargate/app/keeperDefinition - app.GovKeeper = govkeeper.NewKeeper( appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, &stakingKeeper, govRouter, ) + // this line is used by starport scaffolding # stargate/app/keeperDefinition + // Create static IBC router, add transfer route, then set and seal it ibcRouter := porttypes.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index a491c58f66..7dea4a7183 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -120,15 +120,16 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny scopedKeeperDefinition = module.PlaceholderIBCAppScopedKeeperDefinition ibcKeeperArgument = module.PlaceholderIBCAppKeeperArgument } - template = `%[1]v - %[3]v + template = `%[3]v app.%[5]vKeeper = *%[2]vmodulekeeper.NewKeeper( appCodec, keys[%[2]vmoduletypes.StoreKey], keys[%[2]vmoduletypes.MemStoreKey], %[4]v %[6]v) - %[2]vModule := %[2]vmodule.NewAppModule(appCodec, app.%[5]vKeeper)` + %[2]vModule := %[2]vmodule.NewAppModule(appCodec, app.%[5]vKeeper) + + %[1]v` replacement = fmt.Sprintf( template, module.PlaceholderSgAppKeeperDefinition, From aed8bc3e5d37818bc2ed925fa56ad504dffee4e1 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 11:04:26 +0200 Subject: [PATCH 07/30] Add tests --- integration/cmd_app_test.go | 31 ++++++++++++++++++++ integration/cmd_ibc_test.go | 51 ++++++++++++++++++++++++++++++--- integration/cmd_message_test.go | 24 ++++++++++++++-- integration/cmd_query_test.go | 36 +++++++++++++++++++++-- 4 files changed, 133 insertions(+), 9 deletions(-) diff --git a/integration/cmd_app_test.go b/integration/cmd_app_test.go index 0e04952323..272536ab2e 100644 --- a/integration/cmd_app_test.go +++ b/integration/cmd_app_test.go @@ -110,5 +110,36 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { ExecShouldError(), )) + env.Must(env.Exec("create a module with dependencies", + step.NewSteps(step.New( + step.Exec( + "starport", + "module", + "create", + "example_with_dep", + "--dep", + "account,bank,staking,slashing,example", + "--require-registration", + ), + step.Workdir(path), + )), + )) + + env.Must(env.Exec("should prevent registering a module with an non-existent dependency", + step.NewSteps(step.New( + step.Exec( + "starport", + "module", + "create", + "example_with_wrong_dep", + "--dep", + "nonexistent", + "--require-registration", + ), + step.Workdir(path), + )), + ExecShouldError(), + )) + env.EnsureAppIsSteady(path) } diff --git a/integration/cmd_ibc_test.go b/integration/cmd_ibc_test.go index b9a3a0163a..f67fe48b8e 100644 --- a/integration/cmd_ibc_test.go +++ b/integration/cmd_ibc_test.go @@ -31,25 +31,59 @@ func TestCreateModuleWithIBC(t *testing.T) { env.Must(env.Exec("create an IBC module with an ordered channel", step.NewSteps(step.New( - step.Exec("starport", "module", "create", "--ibc", "orderedfoo", "--ordering", "ordered", "--require-registration"), + step.Exec( + "starport", + "module", + "create", + "--ibc", + "orderedfoo", + "--ordering", + "ordered", + "--require-registration", + ), step.Workdir(path), )), )) env.Must(env.Exec("create an IBC module with an unordered channel", step.NewSteps(step.New( - step.Exec("starport", "module", "create", "--ibc", "unorderedfoo", "--ordering", "unordered", "--require-registration"), + step.Exec( + "starport", + "module", + "create", + "--ibc", + "unorderedfoo", + "--ordering", + "unordered", + "--require-registration", + ), step.Workdir(path), )), )) - env.Must(env.Exec("create an non IBC module", + env.Must(env.Exec("create a non IBC module", step.NewSteps(step.New( step.Exec("starport", "module", "create", "foobar", "--require-registration"), step.Workdir(path), )), )) + env.Must(env.Exec("create an IBC module with dependencies", + step.NewSteps(step.New( + step.Exec( + "starport", + "module", + "create", + "example_with_dep", + "--ibc", + "--dep", + "account,bank,staking,slashing", + "--require-registration", + ), + step.Workdir(path), + )), + )) + env.EnsureAppIsSteady(path) } @@ -69,7 +103,16 @@ func TestCreateIBCPacket(t *testing.T) { env.Must(env.Exec("create a packet", step.NewSteps(step.New( - step.Exec("starport", "packet", "bar", "text", "--module", "foo", "--ack", "foo:string,bar:int,foobar:bool"), + step.Exec( + "starport", + "packet", + "bar", + "text", + "--module", + "foo", + "--ack", + "foo:string,bar:int,foobar:bool", + ), step.Workdir(path), )), )) diff --git a/integration/cmd_message_test.go b/integration/cmd_message_test.go index 0d1bd2d5ff..f72419317b 100644 --- a/integration/cmd_message_test.go +++ b/integration/cmd_message_test.go @@ -16,7 +16,16 @@ func TestGenerateAnAppWithMessage(t *testing.T) { env.Must(env.Exec("create a message", step.NewSteps(step.New( - step.Exec("starport", "message", "do-foo", "text", "vote:int", "like:bool", "-r", "foo,bar:int,foobar:bool"), + step.Exec( + "starport", + "message", + "do-foo", + "text", + "vote:int", + "like:bool", + "-r", + "foo,bar:int,foobar:bool", + ), step.Workdir(path), )), )) @@ -45,7 +54,18 @@ func TestGenerateAnAppWithMessage(t *testing.T) { env.Must(env.Exec("create a message in a module", step.NewSteps(step.New( - step.Exec("starport", "message", "do-foo", "text", "--module", "foo", "--desc", "foo bar foobar", "--response", "foo,bar:int,foobar:bool"), + step.Exec( + "starport", + "message", + "do-foo", + "text", + "--module", + "foo", + "--desc", + "foo bar foobar", + "--response", + "foo,bar:int,foobar:bool", + ), step.Workdir(path), )), )) diff --git a/integration/cmd_query_test.go b/integration/cmd_query_test.go index 6b9f9c2ab5..d9dd3cdc5e 100644 --- a/integration/cmd_query_test.go +++ b/integration/cmd_query_test.go @@ -14,14 +14,33 @@ func TestGenerateAnAppWithQuery(t *testing.T) { env.Must(env.Exec("create a query", step.NewSteps(step.New( - step.Exec("starport", "query", "foo", "text", "vote:int", "like:bool", "-r", "foo,bar:int,foobar:bool"), + step.Exec( + "starport", + "query", + "foo", + "text", + "vote:int", + "like:bool", + "-r", + "foo,bar:int,foobar:bool", + ), step.Workdir(path), )), )) env.Must(env.Exec("create a paginated query", step.NewSteps(step.New( - step.Exec("starport", "query", "bar", "text", "vote:int", "like:bool", "-r", "foo,bar:int,foobar:bool", "--paginated"), + step.Exec( + "starport", + "query", + "bar", + "text", + "vote:int", + "like:bool", + "-r", + "foo,bar:int,foobar:bool", + "--paginated", + ), step.Workdir(path), )), )) @@ -50,7 +69,18 @@ func TestGenerateAnAppWithQuery(t *testing.T) { env.Must(env.Exec("create a query in a module", step.NewSteps(step.New( - step.Exec("starport", "query", "foo", "text", "--module", "foo", "--desc", "foo bar foobar", "--response", "foo,bar:int,foobar:bool"), + step.Exec( + "starport", + "query", + "foo", + "text", + "--module", + "foo", + "--desc", + "foo bar foobar", + "--response", + "foo,bar:int,foobar:bool", + ), step.Workdir(path), )), )) From 7099905832ea6c21ba1493e6ac48c4c20b8fb225 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 14:32:01 +0200 Subject: [PATCH 08/30] Fix generated tests --- .../stargate/x/{{moduleName}}/keeper/keeper_test.go.plush | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper_test.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper_test.go.plush index c6fad1b860..026f54a75a 100644 --- a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper_test.go.plush +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper_test.go.plush @@ -26,7 +26,12 @@ func setupKeeper(t testing.TB) (*Keeper, sdk.Context) { require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() - keeper := NewKeeper(codec.NewProtoCodec(registry), storeKey, memStoreKey) + keeper := NewKeeper( + codec.NewProtoCodec(registry), + storeKey, + memStoreKey,<%= for (dependency) in dependencies { %> + nil,<% } %> + ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) return keeper, ctx From c4ba699047a46dbcd2a3e6cbb64ca65e6d177997 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 15:49:55 +0200 Subject: [PATCH 09/30] gov module warning --- starport/cmd/module-create.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 4d88dab9ee..93ebb4babb 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -94,8 +94,9 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { if err != nil { return err } + var dependencies []string if len(dep) > 0 { - dependencies := strings.Split(dep, ",") + dependencies = strings.Split(dep, ",") options = append(options, scaffolder.WithDependencies(dependencies)) } @@ -124,6 +125,30 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { fmt.Println(sourceModificationToString(sm)) } + if len(dependencies) > 0 { + dependencyWarning(dependencies) + } + io.Copy(cmd.OutOrStdout(), &msg) return nil } + +// in previously scaffolded apps gov keeper is defined below the scaffolded module keeper definition +// therefore we must warn the user to manually move the definition if it's the case +// https://github.com/tendermint/starport/issues/818#issuecomment-865736052 +const govWarning = `⚠️ If your app has been scaffolded with Starport 0.16.x or below +Please make sure that your module keeper definition is defined after gov module keeper definition in app/app.go: + +app.GovKeeper = ... +... +[your module keeper definition] +` + +// dependencyWarning is used to print a warning if gov is provided as a dependency +func dependencyWarning(dependencies []string) { + for _, dep := range dependencies { + if dep == "gov" { + fmt.Printf(govWarning) + } + } +} \ No newline at end of file From f909d9622fcd87dc46b412b1bc7a669375eef240 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 15:58:14 +0200 Subject: [PATCH 10/30] Check dependencies --- starport/services/scaffolder/module.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index da8266b74d..dc0d08ed04 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -111,6 +111,12 @@ func (s *Scaffolder) CreateModule( for _, apply := range options { apply(&creationOpts) } + + // Check dependencies + if err := checkDependencies(creationOpts.dependencies); err != nil { + return sm, err + } + path, err := gomodulepath.ParseAt(s.path) if err != nil { return sm, err @@ -324,3 +330,18 @@ func checkIBCRouterPlaceholder(appPath string) (bool, error) { return strings.Contains(string(content), module.PlaceholderIBCAppRouter), nil } + +// checkDependencies perform checks on the dependencies +func checkDependencies(dependencies []string) error { + // check there is no duplicated dependencies + depMap := make(map[string]struct{}) + for _, dep := range dependencies { + _, ok := depMap[dep] + if ok { + return fmt.Errorf("%s is a duplicated dependency", dep) + } + depMap[dep] = struct{}{} + } + + return nil +} \ No newline at end of file From dc64f5d0d4cfcac2af088aa93b53acd5a5d51172 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 16:23:42 +0200 Subject: [PATCH 11/30] Fix test --- integration/cmd_app_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/cmd_app_test.go b/integration/cmd_app_test.go index 272536ab2e..95f1405fd9 100644 --- a/integration/cmd_app_test.go +++ b/integration/cmd_app_test.go @@ -125,7 +125,7 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { )), )) - env.Must(env.Exec("should prevent registering a module with an non-existent dependency", + env.Must(env.Exec("should prevent creating a module with invalid dependencies", step.NewSteps(step.New( step.Exec( "starport", @@ -133,7 +133,7 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { "create", "example_with_wrong_dep", "--dep", - "nonexistent", + "dup,dup", "--require-registration", ), step.Workdir(path), From 7f40b9bcc3701a6c43e48f2814bf43759369bf52 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 16:37:42 +0200 Subject: [PATCH 12/30] bank permissions --- starport/cmd/module-create.go | 4 ++-- starport/services/scaffolder/module.go | 2 +- starport/templates/app/stargate/app/app.go.plush | 1 + starport/templates/module/create/stargate.go | 16 +++++++++++++++- starport/templates/module/placeholders.go | 1 + 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 93ebb4babb..033c7e8414 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -148,7 +148,7 @@ app.GovKeeper = ... func dependencyWarning(dependencies []string) { for _, dep := range dependencies { if dep == "gov" { - fmt.Printf(govWarning) + fmt.Print(govWarning) } } -} \ No newline at end of file +} diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index dc0d08ed04..02521a4a64 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -344,4 +344,4 @@ func checkDependencies(dependencies []string) error { } return nil -} \ No newline at end of file +} diff --git a/starport/templates/app/stargate/app/app.go.plush b/starport/templates/app/stargate/app/app.go.plush index 859e95d1a8..21da847028 100644 --- a/starport/templates/app/stargate/app/app.go.plush +++ b/starport/templates/app/stargate/app/app.go.plush @@ -142,6 +142,7 @@ var ( stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, govtypes.ModuleName: {authtypes.Burner}, ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + // this line is used by starport scaffolding # stargate/app/maccPerms } ) diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index 7dea4a7183..2fe2c64d04 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -95,7 +95,7 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny replacement = fmt.Sprintf(template, module.PlaceholderSgAppStoreKey, opts.ModuleName) content = replacer.Replace(content, module.PlaceholderSgAppStoreKey, replacement) - // Keeper definition + // Module dependencies var depArgs string for _, dep := range opts.Dependencies { keeperDefinition := fmt.Sprintf("app.%sKeeper", strings.Title(dep)) @@ -110,8 +110,22 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny } depArgs = fmt.Sprintf("%s%s,\n", depArgs, keeperDefinition) + + // If bank is a dependency, add account permissions to the module + if dep == "bank" { + template = `%[1]v + %[2]vmoduletypes.ModuleName: {authtypes.Minter, authtypes.Burner, authtypes.Staking},` + + replacement = fmt.Sprintf( + template, + module.PlaceholderSgAppMaccPerms, + opts.ModuleName, + ) + content = replacer.Replace(content, module.PlaceholderSgAppMaccPerms, replacement) + } } + // Keeper definition var scopedKeeperDefinition string var ibcKeeperArgument string if opts.IsIBC { diff --git a/starport/templates/module/placeholders.go b/starport/templates/module/placeholders.go index 7b87829472..6883ffe064 100644 --- a/starport/templates/module/placeholders.go +++ b/starport/templates/module/placeholders.go @@ -29,6 +29,7 @@ const ( PlaceholderSgAppNewArgument = "// this line is used by starport scaffolding # stargate/app/newArgument" PlaceholderSgAppScopedKeeper = "// this line is used by starport scaffolding # stargate/app/scopedKeeper" PlaceholderSgAppBeforeInitReturn = "// this line is used by starport scaffolding # stargate/app/beforeInitReturn" + PlaceholderSgAppMaccPerms = "// this line is used by starport scaffolding # stargate/app/maccPerms" // Placeholders in Stargate app.go for wasm PlaceholderSgWasmAppEnabledProposals = "// this line is used by starport scaffolding # stargate/wasm/app/enabledProposals" From 8661015682a52109f48cffc6bfe2da2d28425bca Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 22 Jun 2021 18:14:05 +0200 Subject: [PATCH 13/30] Fix ibc test --- starport/templates/module/create/ibc.go | 1 + .../ibc/x/{{moduleName}}/keeper/keeper_test.go.plush | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/starport/templates/module/create/ibc.go b/starport/templates/module/create/ibc.go index 658fd0de2a..47a2ed9c71 100644 --- a/starport/templates/module/create/ibc.go +++ b/starport/templates/module/create/ibc.go @@ -34,6 +34,7 @@ func NewIBC(replacer placeholder.Replacer, opts *CreateOptions) (*genny.Generato ctx.Set("ownerName", opts.OwnerName) ctx.Set("ibcOrdering", opts.IBCOrdering) ctx.Set("title", strings.Title) + ctx.Set("dependencies", opts.Dependencies) // Used for proto package name ctx.Set("formatOwnerName", xstrings.FormatUsername) diff --git a/starport/templates/module/create/ibc/x/{{moduleName}}/keeper/keeper_test.go.plush b/starport/templates/module/create/ibc/x/{{moduleName}}/keeper/keeper_test.go.plush index 1dfbb62abb..0b5af28f9a 100644 --- a/starport/templates/module/create/ibc/x/{{moduleName}}/keeper/keeper_test.go.plush +++ b/starport/templates/module/create/ibc/x/{{moduleName}}/keeper/keeper_test.go.plush @@ -27,8 +27,13 @@ func setupKeeper(t testing.TB) (*Keeper, sdk.Context) { registry := codectypes.NewInterfaceRegistry() keeper := NewKeeper( - codec.NewProtoCodec(registry), storeKey, memStoreKey, - nil, nil, nil, + codec.NewProtoCodec(registry), + storeKey, + memStoreKey, + nil, + nil, + nil,<%= for (dependency) in dependencies { %> + nil,<% } %> ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) From 6aef2c3bca1e4991241d3fa3ea2d5a22042139f1 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Fri, 25 Jun 2021 13:21:28 +0200 Subject: [PATCH 14/30] Update starport/cmd/module-create.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- starport/cmd/module-create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 033c7e8414..942f7f76e5 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -55,7 +55,7 @@ func NewModuleCreate() *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: createModuleHandler, } - c.Flags().String(flagDep, "", "comma-separated module dependencies (e.g. --dep account,bank)") + c.Flags().String(flagDep, "", "module dependencies (e.g. --dep account,bank)") c.Flags().Bool(flagIBC, false, "scaffold an IBC module") c.Flags().String(flagIBCOrdering, "none", "channel ordering of the IBC module [none|ordered|unordered]") c.Flags().Bool(flagRequireRegistration, false, "if true command will fail if module can't be registered") From 70af24c54e6c17517c1f9cd1d602693b3f0597cb Mon Sep 17 00:00:00 2001 From: ltacker Date: Fri, 25 Jun 2021 13:27:20 +0200 Subject: [PATCH 15/30] Use string slice --- starport/cmd/module-create.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 033c7e8414..69217ff091 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -4,15 +4,13 @@ import ( "bytes" "errors" "fmt" - "io" - "strings" - "github.com/spf13/cobra" "github.com/tendermint/starport/starport/pkg/clispinner" "github.com/tendermint/starport/starport/pkg/placeholder" "github.com/tendermint/starport/starport/pkg/validation" "github.com/tendermint/starport/starport/services/scaffolder" "github.com/tendermint/starport/starport/templates/module" + "io" ) const ( @@ -55,7 +53,7 @@ func NewModuleCreate() *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: createModuleHandler, } - c.Flags().String(flagDep, "", "comma-separated module dependencies (e.g. --dep account,bank)") + c.Flags().StringSlice(flagDep, []string{}, "module dependencies (e.g. --dep account,bank)") c.Flags().Bool(flagIBC, false, "scaffold an IBC module") c.Flags().String(flagIBCOrdering, "none", "channel ordering of the IBC module [none|ordered|unordered]") c.Flags().Bool(flagRequireRegistration, false, "if true command will fail if module can't be registered") @@ -90,15 +88,10 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { } // Get module dependencies - dep, err := cmd.Flags().GetString(flagDep) + dependencies, err := cmd.Flags().GetStringSlice(flagDep) if err != nil { return err } - var dependencies []string - if len(dep) > 0 { - dependencies = strings.Split(dep, ",") - options = append(options, scaffolder.WithDependencies(dependencies)) - } sc, err := scaffolder.New(appPath) if err != nil { From be076204fa9907c07f773321da7f5adfdf498f98 Mon Sep 17 00:00:00 2001 From: ltacker Date: Fri, 25 Jun 2021 13:31:33 +0200 Subject: [PATCH 16/30] lint --- starport/cmd/module-create.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 69217ff091..9ad48dc9e0 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -4,13 +4,14 @@ import ( "bytes" "errors" "fmt" + "io" + "github.com/spf13/cobra" "github.com/tendermint/starport/starport/pkg/clispinner" "github.com/tendermint/starport/starport/pkg/placeholder" "github.com/tendermint/starport/starport/pkg/validation" "github.com/tendermint/starport/starport/services/scaffolder" "github.com/tendermint/starport/starport/templates/module" - "io" ) const ( From cc449ae702332734d6b125817294559cfb083011 Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 29 Jun 2021 13:54:32 +0200 Subject: [PATCH 17/30] Parse app module --- starport/pkg/cosmosanalysis/app/app.go | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 starport/pkg/cosmosanalysis/app/app.go diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go new file mode 100644 index 0000000000..489e9baadf --- /dev/null +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -0,0 +1,52 @@ +package app + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" +) + +const ( + appTypeName = "App" +) + +// CheckKeeper checks for the existence of the keeper with the provided name in the app structure +func CheckKeeper(appPath, keeperName string) error { + fileSet := token.NewFileSet() + f, err := parser.ParseFile(fileSet, appPath, nil, 0) + if err != nil { + return err + } + + // Inspect the file for app struct + var found bool + ast.Inspect(f, func(n ast.Node) bool { + // look for struct methods. + appType, ok := n.(*ast.TypeSpec) + if !ok || appType.Name.Name != appTypeName { + return true + } + + appStruct, ok := appType.Type.(*ast.StructType) + if !ok { + return true + } + + // Search for the keeper specific field + for _, field := range appStruct.Fields.List { + for _, fieldName := range field.Names { + if fieldName.Name == keeperName { + found = true + } + } + } + + return false + }) + + if !found { + return fmt.Errorf("app doesn't contain %s", keeperName) + } + return nil +} \ No newline at end of file From 5aa900dc15e7ea4ed86255b450377f9d25696a8c Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 29 Jun 2021 13:54:59 +0200 Subject: [PATCH 18/30] Update dependency type --- starport/cmd/module-create.go | 25 +++++++++++++++++++ starport/services/scaffolder/module.go | 10 ++++---- starport/templates/module/create/options.go | 25 ++++++++++++++++++- starport/templates/module/create/stargate.go | 16 ++++++------ .../x/{{moduleName}}/keeper/keeper.go.plush | 6 ++--- .../types/expected_keepers.go.plush | 4 +-- 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 9ad48dc9e0..ed25d7a371 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -4,7 +4,9 @@ import ( "bytes" "errors" "fmt" + modulecreate "github.com/tendermint/starport/starport/templates/module/create" "io" + "strings" "github.com/spf13/cobra" "github.com/tendermint/starport/starport/pkg/clispinner" @@ -93,6 +95,29 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { if err != nil { return err } + if len(dependencies) > 0 { + var formattedDependencies []modulecreate.Dependency + + // Parse the provided dependencies + for _, dependency := range dependencies { + splitted := strings.Split(dependency, ".") + switch { + case len(splitted) == 1: + formattedDependencies = append( + formattedDependencies, + modulecreate.NewDependency(splitted[0], ""), + ) + case len(splitted) == 1: + formattedDependencies = append( + formattedDependencies, + modulecreate.NewDependency(splitted[0], splitted[1]), + ) + default: + return fmt.Errorf("dependency %s is invalid, must have or .") + } + } + options = append(options, scaffolder.WithDependencies(formattedDependencies)) + } sc, err := scaffolder.New(appPath) if err != nil { diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index 02521a4a64..e4a2cb6910 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -46,7 +46,7 @@ type moduleCreationOptions struct { ibcChannelOrdering string // list of module depencies - dependencies []string + dependencies []modulecreate.Dependency } // ModuleCreationOption configures Chain. @@ -74,7 +74,7 @@ func WithIBCChannelOrdering(ordering string) ModuleCreationOption { } // WithDependencies specifies the name of the modules that the module depends on -func WithDependencies(dependencies []string) ModuleCreationOption { +func WithDependencies(dependencies []modulecreate.Dependency) ModuleCreationOption { return func(m *moduleCreationOptions) { m.dependencies = dependencies } @@ -332,15 +332,15 @@ func checkIBCRouterPlaceholder(appPath string) (bool, error) { } // checkDependencies perform checks on the dependencies -func checkDependencies(dependencies []string) error { +func checkDependencies(dependencies []modulecreate.Dependency) error { // check there is no duplicated dependencies depMap := make(map[string]struct{}) for _, dep := range dependencies { - _, ok := depMap[dep] + _, ok := depMap[dep.Name] if ok { return fmt.Errorf("%s is a duplicated dependency", dep) } - depMap[dep] = struct{}{} + depMap[dep.Name] = struct{}{} } return nil diff --git a/starport/templates/module/create/options.go b/starport/templates/module/create/options.go index f726bfe0d7..07a22acc36 100644 --- a/starport/templates/module/create/options.go +++ b/starport/templates/module/create/options.go @@ -1,5 +1,10 @@ package modulecreate +import ( + "fmt" + "strings" +) + // CreateOptions represents the options to scaffold a Cosmos SDK module type CreateOptions struct { ModuleName string @@ -14,7 +19,7 @@ type CreateOptions struct { IBCOrdering string // Dependencies of the module - Dependencies []string + Dependencies []Dependency } // CreateOptions defines options to add MsgServer @@ -29,3 +34,21 @@ type MsgServerOptions struct { func (opts *CreateOptions) Validate() error { return nil } + +// Dependency represents a module dependency of a module +type Dependency struct { + Name string + KeeperName string // KeeperName represents the name of the keeper for the module in app.go +} + +// NewDependency returns a new dependency object +func NewDependency(name, keeperName string) Dependency { + // Default keeper name + if keeperName == "" { + keeperName = fmt.Sprintf("%sKeeper", strings.Title(name)) + } + return Dependency{ + name, + keeperName, + } +} \ No newline at end of file diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index 2fe2c64d04..b0170f4620 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + appanalysis "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "github.com/gobuffalo/genny" "github.com/gobuffalo/plush" "github.com/gobuffalo/plushgen" @@ -98,21 +99,18 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny // Module dependencies var depArgs string for _, dep := range opts.Dependencies { - keeperDefinition := fmt.Sprintf("app.%sKeeper", strings.Title(dep)) - // if module has dependencies, we must verify the keeper of the dependency is defined in app.go - if !strings.Contains(content, keeperDefinition) { + if err := appanalysis.CheckKeeper(path, dep.KeeperName); err != nil { replacer.AppendMiscError(fmt.Sprintf( - "the module cannot have %s as a dependency: %s is not declared in app.go", - dep, - keeperDefinition, + "the module cannot have %s as a dependency: %s", + dep.Name, + err.Error(), )) } - - depArgs = fmt.Sprintf("%s%s,\n", depArgs, keeperDefinition) + depArgs = fmt.Sprintf("%sapp.%s,\n", depArgs, dep.KeeperName) // If bank is a dependency, add account permissions to the module - if dep == "bank" { + if dep.Name == "bank" { template = `%[1]v %[2]vmoduletypes.ModuleName: {authtypes.Minter, authtypes.Burner, authtypes.Staking},` diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush index 231a35c79b..4262629d82 100644 --- a/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/keeper/keeper.go.plush @@ -18,7 +18,7 @@ type ( memKey sdk.StoreKey // this line is used by starport scaffolding # ibc/keeper/attribute <%= for (dependency) in dependencies { %> - <%= dependency %>Keeper types.<%= title(dependency) %>Keeper<% } %> + <%= dependency.Name %>Keeper types.<%= title(dependency.Name) %>Keeper<% } %> } ) @@ -27,14 +27,14 @@ func NewKeeper( storeKey, memKey sdk.StoreKey, // this line is used by starport scaffolding # ibc/keeper/parameter - <%= for (dependency) in dependencies { %><%= dependency %>Keeper types.<%= title(dependency) %>Keeper,<% } %> + <%= for (dependency) in dependencies { %><%= dependency.Name %>Keeper types.<%= title(dependency.Name) %>Keeper,<% } %> ) *Keeper { return &Keeper{ cdc: cdc, storeKey: storeKey, memKey: memKey, // this line is used by starport scaffolding # ibc/keeper/return - <%= for (dependency) in dependencies { %><%= dependency %>Keeper: <%= dependency %>Keeper,<% } %> + <%= for (dependency) in dependencies { %><%= dependency.Name %>Keeper: <%= dependency.Name %>Keeper,<% } %> } } diff --git a/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush index 096e004cc1..74e14e8e3c 100644 --- a/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush +++ b/starport/templates/module/create/stargate/x/{{moduleName}}/types/expected_keepers.go.plush @@ -1,7 +1,7 @@ package types <%= for (dependency) in dependencies { %> -type <%= title(dependency) %>Keeper interface { - // Methods imported by the module should be defined here +type <%= title(dependency.Name) %>Keeper interface { + // Methods imported from <%= dependency.Name %> should be defined here } <% } %> \ No newline at end of file From 5c557fe0a7aa63994c6e88e28ccf57478c306def Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 29 Jun 2021 14:49:26 +0200 Subject: [PATCH 19/30] Fix validation error bug --- starport/cmd/module-create.go | 4 +- starport/pkg/placeholder/error.go | 86 ++++++++++++++++++++++++++++++ starport/pkg/placeholder/tracer.go | 65 ++-------------------- 3 files changed, 91 insertions(+), 64 deletions(-) create mode 100644 starport/pkg/placeholder/error.go diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index ed25d7a371..880936a92e 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -107,13 +107,13 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { formattedDependencies, modulecreate.NewDependency(splitted[0], ""), ) - case len(splitted) == 1: + case len(splitted) == 2: formattedDependencies = append( formattedDependencies, modulecreate.NewDependency(splitted[0], splitted[1]), ) default: - return fmt.Errorf("dependency %s is invalid, must have or .") + return fmt.Errorf("dependency %s is invalid, must have or .", dependency) } } options = append(options, scaffolder.WithDependencies(formattedDependencies)) diff --git a/starport/pkg/placeholder/error.go b/starport/pkg/placeholder/error.go new file mode 100644 index 0000000000..7f8b0d5867 --- /dev/null +++ b/starport/pkg/placeholder/error.go @@ -0,0 +1,86 @@ +package placeholder + +import ( + "fmt" + "github.com/tendermint/starport/starport/pkg/validation" + "strings" +) + +var _ validation.Error = (*MissingPlaceholdersError)(nil) + +// MissingPlaceholdersError is used as an error when a source file is missing placeholder +type MissingPlaceholdersError struct { + missing iterableStringSet + additionalInfo string + additionalErrors error +} + +// Is true if both errors have the same list of missing placeholders. +func (e *MissingPlaceholdersError) Is(err error) bool { + other, ok := err.(*MissingPlaceholdersError) + if !ok { + return false + } + if len(other.missing) != len(e.missing) { + return false + } + for i := range e.missing { + if e.missing[i] != other.missing[i] { + return false + } + } + return true +} + +// Error implements error interface +func (e *MissingPlaceholdersError) Error() string { + var b strings.Builder + b.WriteString("missing placeholders: ") + e.missing.Iterate(func(i int, element string) bool { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(element) + return true + }) + return b.String() +} + +// ValidationInfo implements validation.Error interface +func (e *MissingPlaceholdersError) ValidationInfo() string { + var b strings.Builder + b.WriteString("Missing placeholders:\n\n") + e.missing.Iterate(func(i int, element string) bool { + if i > 0 { + b.WriteString("\n") + } + b.WriteString(element) + return true + }) + if e.additionalInfo != "" { + b.WriteString("\n\n") + b.WriteString(e.additionalInfo) + } + if e.additionalErrors != nil { + b.WriteString("\n\nAdditional errors: ") + b.WriteString(e.additionalErrors.Error()) + } + return b.String() +} + +var _ validation.Error = (*ValidationMiscError)(nil) + +// ValidationMiscError is used as a miscellaneous error related to validation +type ValidationMiscError struct { + errors []string +} + +// Error implements error interface +func (e *ValidationMiscError) Error() string { + return fmt.Sprintf("validation errors: %v", e.errors) +} + +// ValidationInfo implements validation.Error interface +func (e *ValidationMiscError) ValidationInfo() string { + return fmt.Sprintf("Validation errors:\n\n%v", strings.Join(e.errors, "\n")) +} diff --git a/starport/pkg/placeholder/tracer.go b/starport/pkg/placeholder/tracer.go index f8927b03b3..9ded846c1c 100644 --- a/starport/pkg/placeholder/tracer.go +++ b/starport/pkg/placeholder/tracer.go @@ -1,10 +1,7 @@ package placeholder import ( - "fmt" "strings" - - "github.com/tendermint/starport/starport/pkg/validation" ) type iterableStringSet map[string]struct{} @@ -23,64 +20,6 @@ func (set iterableStringSet) Add(item string) { set[item] = struct{}{} } -var _ validation.Error = (*MissingPlaceholdersError)(nil) - -type MissingPlaceholdersError struct { - missing iterableStringSet - additionalInfo string - additionalErrors error -} - -// Is true if both errors have the same list of missing placeholders. -func (e *MissingPlaceholdersError) Is(err error) bool { - other, ok := err.(*MissingPlaceholdersError) - if !ok { - return false - } - if len(other.missing) != len(e.missing) { - return false - } - for i := range e.missing { - if e.missing[i] != other.missing[i] { - return false - } - } - return true -} - -func (e *MissingPlaceholdersError) Error() string { - var b strings.Builder - b.WriteString("missing placeholders: ") - e.missing.Iterate(func(i int, element string) bool { - if i > 0 { - b.WriteString(", ") - } - b.WriteString(element) - return true - }) - return b.String() -} - -func (e *MissingPlaceholdersError) ValidationInfo() string { - var b strings.Builder - b.WriteString("Missing placeholders:\n\n") - e.missing.Iterate(func(i int, element string) bool { - if i > 0 { - b.WriteString("\n") - } - b.WriteString(element) - return true - }) - if e.additionalInfo != "" { - b.WriteString("\n\n") - b.WriteString(e.additionalInfo) - } - if e.additionalErrors != nil { - b.WriteString("\n\nAdditional errors: ") - b.WriteString(e.additionalErrors.Error()) - } - return b.String() -} // Option for configuring session. type Option func(*Tracer) @@ -143,7 +82,9 @@ func (t *Tracer) Err() error { // miscellaneous errors represent errors preventing source modification not related to missing placeholder var miscErrors error if len(t.miscErrors) > 0 { - miscErrors = fmt.Errorf("%v", strings.Join(t.miscErrors, "\n")) + miscErrors = &ValidationMiscError{ + errors: t.miscErrors, + } } if len(t.missing) > 0 { From 49ee9c03c6993505e642f5fae5fa3946246ff4fd Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 29 Jun 2021 15:05:46 +0200 Subject: [PATCH 20/30] Add tests for app analysis --- starport/pkg/cosmosanalysis/app/app_test.go | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 starport/pkg/cosmosanalysis/app/app_test.go diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go new file mode 100644 index 0000000000..488ea2908f --- /dev/null +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -0,0 +1,51 @@ +package app_test + +import ( + "github.com/stretchr/testify/require" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" + "os" + "path/filepath" + "testing" +) + +var ( + source = []byte(` +package foo + +type App struct { + FooKeeper foo.keeper +} +`) + + sourceNoApp = []byte(` +package foo + +type Bar struct { + FooKeeper foo.keeper +} +`) +) + +func TestCheckKeeper(t *testing.T) { + tmpDir := os.TempDir() + + // Test with a source file containing an app + tmpFile := filepath.Join(tmpDir, "source") + err := os.WriteFile(tmpFile, source, 0644) + require.NoError(t, err) + t.Cleanup(func() {os.Remove(tmpFile)}) + + err = app.CheckKeeper(tmpFile, "FooKeeper") + require.NoError(t, err) + err = app.CheckKeeper(tmpFile, "BarKeeper") + require.Error(t, err) + + // No app in source must return an error + tmpFileNoApp := filepath.Join(tmpDir, "source") + err = os.WriteFile(tmpFileNoApp, sourceNoApp, 0644) + require.NoError(t, err) + t.Cleanup(func() {os.Remove(tmpFileNoApp)}) + + err = app.CheckKeeper(tmpFileNoApp, "FooKeeper") + require.Error(t, err) +} \ No newline at end of file From 76399e42d575a34f5cf0eb8afe788897f10e3a0e Mon Sep 17 00:00:00 2001 From: ltacker Date: Tue, 29 Jun 2021 15:08:23 +0200 Subject: [PATCH 21/30] Lint --- starport/cmd/module-create.go | 4 ++-- starport/pkg/cosmosanalysis/app/app.go | 2 +- starport/pkg/cosmosanalysis/app/app_test.go | 11 ++++++----- starport/pkg/placeholder/error.go | 5 +++-- starport/pkg/placeholder/tracer.go | 1 - starport/templates/module/create/options.go | 6 +++--- starport/templates/module/create/stargate.go | 2 +- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index 880936a92e..eb3750623f 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - modulecreate "github.com/tendermint/starport/starport/templates/module/create" "io" "strings" @@ -14,6 +13,7 @@ import ( "github.com/tendermint/starport/starport/pkg/validation" "github.com/tendermint/starport/starport/services/scaffolder" "github.com/tendermint/starport/starport/templates/module" + modulecreate "github.com/tendermint/starport/starport/templates/module/create" ) const ( @@ -106,7 +106,7 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { formattedDependencies = append( formattedDependencies, modulecreate.NewDependency(splitted[0], ""), - ) + ) case len(splitted) == 2: formattedDependencies = append( formattedDependencies, diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index 489e9baadf..2c6c99d5cb 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -49,4 +49,4 @@ func CheckKeeper(appPath, keeperName string) error { return fmt.Errorf("app doesn't contain %s", keeperName) } return nil -} \ No newline at end of file +} diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go index 488ea2908f..05cdd85a34 100644 --- a/starport/pkg/cosmosanalysis/app/app_test.go +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -1,11 +1,12 @@ package app_test import ( - "github.com/stretchr/testify/require" - "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" ) var ( @@ -33,7 +34,7 @@ func TestCheckKeeper(t *testing.T) { tmpFile := filepath.Join(tmpDir, "source") err := os.WriteFile(tmpFile, source, 0644) require.NoError(t, err) - t.Cleanup(func() {os.Remove(tmpFile)}) + t.Cleanup(func() { os.Remove(tmpFile) }) err = app.CheckKeeper(tmpFile, "FooKeeper") require.NoError(t, err) @@ -44,8 +45,8 @@ func TestCheckKeeper(t *testing.T) { tmpFileNoApp := filepath.Join(tmpDir, "source") err = os.WriteFile(tmpFileNoApp, sourceNoApp, 0644) require.NoError(t, err) - t.Cleanup(func() {os.Remove(tmpFileNoApp)}) + t.Cleanup(func() { os.Remove(tmpFileNoApp) }) err = app.CheckKeeper(tmpFileNoApp, "FooKeeper") require.Error(t, err) -} \ No newline at end of file +} diff --git a/starport/pkg/placeholder/error.go b/starport/pkg/placeholder/error.go index 7f8b0d5867..9d056d6e42 100644 --- a/starport/pkg/placeholder/error.go +++ b/starport/pkg/placeholder/error.go @@ -2,8 +2,9 @@ package placeholder import ( "fmt" - "github.com/tendermint/starport/starport/pkg/validation" "strings" + + "github.com/tendermint/starport/starport/pkg/validation" ) var _ validation.Error = (*MissingPlaceholdersError)(nil) @@ -72,7 +73,7 @@ var _ validation.Error = (*ValidationMiscError)(nil) // ValidationMiscError is used as a miscellaneous error related to validation type ValidationMiscError struct { - errors []string + errors []string } // Error implements error interface diff --git a/starport/pkg/placeholder/tracer.go b/starport/pkg/placeholder/tracer.go index 9ded846c1c..641634a2f0 100644 --- a/starport/pkg/placeholder/tracer.go +++ b/starport/pkg/placeholder/tracer.go @@ -20,7 +20,6 @@ func (set iterableStringSet) Add(item string) { set[item] = struct{}{} } - // Option for configuring session. type Option func(*Tracer) diff --git a/starport/templates/module/create/options.go b/starport/templates/module/create/options.go index 07a22acc36..e25d3a234e 100644 --- a/starport/templates/module/create/options.go +++ b/starport/templates/module/create/options.go @@ -37,8 +37,8 @@ func (opts *CreateOptions) Validate() error { // Dependency represents a module dependency of a module type Dependency struct { - Name string - KeeperName string // KeeperName represents the name of the keeper for the module in app.go + Name string + KeeperName string // KeeperName represents the name of the keeper for the module in app.go } // NewDependency returns a new dependency object @@ -51,4 +51,4 @@ func NewDependency(name, keeperName string) Dependency { name, keeperName, } -} \ No newline at end of file +} diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index b0170f4620..45cce27c54 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -4,10 +4,10 @@ import ( "fmt" "strings" - appanalysis "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "github.com/gobuffalo/genny" "github.com/gobuffalo/plush" "github.com/gobuffalo/plushgen" + appanalysis "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "github.com/tendermint/starport/starport/pkg/placeholder" "github.com/tendermint/starport/starport/pkg/xstrings" "github.com/tendermint/starport/starport/templates/module" From f3457417909cd1fc98e26340c58ae7da23ebbedc Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 15:57:50 +0200 Subject: [PATCH 22/30] Refactor cosmosanalysis --- starport/pkg/cosmosanalysis/app/app.go | 22 ++- starport/pkg/cosmosanalysis/app/app_test.go | 8 +- starport/pkg/cosmosanalysis/cosmosanalysis.go | 125 ++++++++++++++++++ .../pkg/cosmosanalysis/cosmosanalysis_test.go | 76 +++++++++++ starport/pkg/cosmosanalysis/docs.go | 3 - starport/pkg/cosmosanalysis/module/message.go | 88 ++---------- starport/pkg/cosmosanalysis/module/module.go | 29 +--- 7 files changed, 242 insertions(+), 109 deletions(-) create mode 100644 starport/pkg/cosmosanalysis/cosmosanalysis.go create mode 100644 starport/pkg/cosmosanalysis/cosmosanalysis_test.go delete mode 100644 starport/pkg/cosmosanalysis/docs.go diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index 2c6c99d5cb..8fc9a5124d 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -1,15 +1,21 @@ package app import ( + "errors" "fmt" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "go/ast" "go/parser" "go/token" ) -const ( - appTypeName = "App" -) +var appImplementation = []string{ + "RegisterAPIRoutes", + "RegisterGRPCServer", + "RegisterTxService", + "RegisterTendermintService", +} + // CheckKeeper checks for the existence of the keeper with the provided name in the app structure func CheckKeeper(appPath, keeperName string) error { @@ -19,6 +25,16 @@ func CheckKeeper(appPath, keeperName string) error { return err } + // find app type + appImpl, err := cosmosanalysis.FindImplementation(appPath, appImplementation) + if err != nil { + return err + } + if len(appImpl) != 1 { + return errors.New("app.go should contain a single app") + } + appTypeName := appImpl[0] + // Inspect the file for app struct var found bool ast.Inspect(f, func(n ast.Node) bool { diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go index 05cdd85a34..a836c8dd69 100644 --- a/starport/pkg/cosmosanalysis/app/app_test.go +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -13,9 +13,14 @@ var ( source = []byte(` package foo -type App struct { +type Foo struct { FooKeeper foo.keeper } + +func (f Foo) RegisterAPIRoutes() {} +func (f Foo) RegisterGRPCServer() {} +func (f Foo) RegisterTxService() {} +func (f Foo) RegisterTendermintService() {} `) sourceNoApp = []byte(` @@ -50,3 +55,4 @@ func TestCheckKeeper(t *testing.T) { err = app.CheckKeeper(tmpFileNoApp, "FooKeeper") require.Error(t, err) } + diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis.go b/starport/pkg/cosmosanalysis/cosmosanalysis.go new file mode 100644 index 0000000000..3e7b7643fc --- /dev/null +++ b/starport/pkg/cosmosanalysis/cosmosanalysis.go @@ -0,0 +1,125 @@ +// Package cosmosanalysis provides a toolset for staticly analysing Cosmos SDK's +// source code and blockchain source codes based on the Cosmos SDK +package cosmosanalysis + +import ( + "os" + "go/ast" + "go/parser" + "go/token" +) + +// implementation tracks the implementation of an interface for a given struct +type implementation map[string]bool + +// FindImplementation finds the name of all types that implement the provided interface +func FindImplementation(path string, interfaceList []string) (found []string, err error) { + // parse go packages/files under path + fset := token.NewFileSet() + + // collect all structs under path to find out the ones that satisfies the implementation + structImplementations := make(map[string]implementation) + dir, err := isDirectory(path) + if err != nil { + return nil, err + } + if dir { + // find in dir + pkgs, err := parser.ParseDir(fset, path, nil, 0) + if err != nil { + return nil, err + } + for _, pkg := range pkgs { + for _, f := range pkg.Files { + findStructImplementationsInFile(f, structImplementations, interfaceList) + } + } + } else { + // find in file + f, err := parser.ParseFile(fset, path, nil, 0) + if err != nil { + return nil, err + } + findStructImplementationsInFile(f, structImplementations, interfaceList) + } + + // append structs that satisfy the implementation + for name, impl := range structImplementations { + if checkImplementation(impl) { + found = append(found, name) + } + } + + return found, nil +} + +func isDirectory(path string) (bool, error) { + fileInfo, err := os.Stat(path) + if err != nil{ + return false, err + } + return fileInfo.IsDir(), nil +} + +// newImplementation returns a new object to parse implementation of an interface +func newImplementation(interfaceList []string) implementation { + impl := make(implementation) + for _, m := range interfaceList { + impl[m] = false + } + return impl +} + +// checkImplementation checks if the entire implementation is satisfied +func checkImplementation(r implementation) bool { + for _, ok := range r { + if !ok { + return false + } + } + return true +} + +// findStructImplementationsInFile append to the provided struct map +// the implementation of the interface found in the struct in the file +func findStructImplementationsInFile( + f ast.Node, + structImplementations map[string]implementation, + interfaceList []string, + ) { + ast.Inspect(f, func(n ast.Node) bool { + // look for struct methods. + methodDecl, ok := n.(*ast.FuncDecl) + if !ok { + return true + } + + // not a method. + if methodDecl.Recv == nil { + return true + } + + methodName := methodDecl.Name.Name + + // find the struct name that method belongs to. + t := methodDecl.Recv.List[0].Type + ident, ok := t.(*ast.Ident) + if !ok { + sexp, ok := t.(*ast.StarExpr) + if !ok { + return true + } + ident = sexp.X.(*ast.Ident) + } + structName := ident.Name + + // mark the implementation that this struct satisfies. + if _, ok := structImplementations[structName]; !ok { + structImplementations[structName] = newImplementation(interfaceList) + } + + structImplementations[structName][methodName] = true + + return true + }) +} diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis_test.go b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go new file mode 100644 index 0000000000..fa7e7a29e5 --- /dev/null +++ b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go @@ -0,0 +1,76 @@ +package cosmosanalysis_test + +import ( + "github.com/stretchr/testify/require" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" + "os" + "path/filepath" + "testing" +) + +var ( + expectedinterface = []string{"foo", "bar", "foobar"} + + file1 = []byte(` +package foo + +type Foo struct {} +func (f Foo) foo() {} +func (f Foo) bar() {} +func (f Foo) foobar() {} + +type Bar struct {} +func (b Bar) foo() {} +func (b Bar) bar() {} +func (b Bar) barfoo() {} +`) + + file2 = []byte(` +package foo + +type Foobar struct {} +func (f Foobar) foo() {} +func (f Foobar) bar() {} +func (f Foobar) foobar() {} +func (f Foobar) barfoo() {} +`) +) + + +func TestFindImplementation(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "cosmosanalysis_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) + + f1 := filepath.Join(tmpDir, "1.go") + err = os.WriteFile(f1, file1, 0644) + require.NoError(t, err) + f2 := filepath.Join(tmpDir, "2.go") + err = os.WriteFile(f2, file2, 0644) + require.NoError(t, err) + + // find in dir + found, err := cosmosanalysis.FindImplementation(tmpDir, expectedinterface) + require.NoError(t, err) + require.Len(t, found, 2) + require.Contains(t, found, "Foo") + require.Contains(t, found, "Foobar") + + // empty directory + emptyDir, err := os.MkdirTemp("", "cosmosanalysis_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(emptyDir) }) + found, err = cosmosanalysis.FindImplementation(emptyDir, expectedinterface) + require.NoError(t, err) + require.Empty(t, found) + + // find in file + found, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "1.go"), expectedinterface) + require.NoError(t, err) + require.Len(t, found, 1) + require.Contains(t, found, "Foo") + + // no file + _, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "3.go"), expectedinterface) + require.Error(t, err) +} \ No newline at end of file diff --git a/starport/pkg/cosmosanalysis/docs.go b/starport/pkg/cosmosanalysis/docs.go deleted file mode 100644 index c59f3f80d1..0000000000 --- a/starport/pkg/cosmosanalysis/docs.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package cosmosanalysis provides a toolset for staticly analysing Cosmos SDK's -// source code and blockchain source codes based on the Cosmos SDK. -package cosmosanalysis diff --git a/starport/pkg/cosmosanalysis/module/message.go b/starport/pkg/cosmosanalysis/module/message.go index 0e071f3ae4..7f58cc362d 100644 --- a/starport/pkg/cosmosanalysis/module/message.go +++ b/starport/pkg/cosmosanalysis/module/message.go @@ -1,84 +1,12 @@ package module -import ( - "go/ast" - "go/parser" - "go/token" -) -// DiscoverMessages discovers sdk messages defined in a module that resides under modulePath. -func DiscoverMessages(modulePath string) (msgs []string, err error) { - // parse go packages/files under modulePath. - fset := token.NewFileSet() - - pkgs, err := parser.ParseDir(fset, modulePath, nil, 0) - if err != nil { - return nil, err - } - - // collect all structs under modulePath to find out the ones that satisfy requirements. - structs := make(map[string]requirements) - - for _, pkg := range pkgs { - for _, f := range pkg.Files { - ast.Inspect(f, func(n ast.Node) bool { - // look for struct methods. - fdecl, ok := n.(*ast.FuncDecl) - if !ok { - return true - } - - // not a method. - if fdecl.Recv == nil { - return true - } - - // fname is the name of method. - fname := fdecl.Name.Name - - // find the struct name that method belongs to. - t := fdecl.Recv.List[0].Type - sident, ok := t.(*ast.Ident) - if !ok { - sexp, ok := t.(*ast.StarExpr) - if !ok { - return true - } - sident = sexp.X.(*ast.Ident) - } - sname := sident.Name - - // mark the requirement that this struct satisfies. - if _, ok := structs[sname]; !ok { - structs[sname] = newRequirements() - } - - structs[sname][fname] = true - - return true - }) - } - } - - // checkRequirements checks if all requirements are satisfied. - checkRequirements := func(r requirements) bool { - for _, ok := range r { - if !ok { - return false - } - } - return true - } - - for name, reqs := range structs { - if checkRequirements(reqs) { - msgs = append(msgs, name) - } - } - - if len(msgs) == 0 { - return nil, ErrModuleNotFound - } - - return msgs, nil +// messageImplementation is the list of methods needed for a sdk.Msg implementation +// TODO(low priority): dynamically get these from the source code of underlying version of the sdk. +var messageImplementation = []string{ + "Route", + "Type", + "GetSigners", + "GetSignBytes", + "ValidateBasic", } diff --git a/starport/pkg/cosmosanalysis/module/module.go b/starport/pkg/cosmosanalysis/module/module.go index 6d80caf459..cdce5a9045 100644 --- a/starport/pkg/cosmosanalysis/module/module.go +++ b/starport/pkg/cosmosanalysis/module/module.go @@ -2,8 +2,8 @@ package module import ( "context" - "errors" "fmt" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "path/filepath" "strings" @@ -11,23 +11,7 @@ import ( "github.com/tendermint/starport/starport/pkg/protoanalysis" ) -// ErrModuleNotFound error returned when an sdk module cannot be found. -var ErrModuleNotFound = errors.New("sdk module not found") - -// requirements holds a list of sdk.Msg's method names. -type requirements map[string]bool - -// newRequirements creates a new list of requirements(method names) that needed by a sdk.Msg impl. -// TODO(low priority): dynamically get these from the source code of underlying version of the sdk. -func newRequirements() requirements { - return requirements{ - "Route": false, - "Type": false, - "GetSigners": false, - "GetSignBytes": false, - "ValidateBasic": false, - } -} + // Msgs is a module import path-sdk msgs pair. type Msgs map[string][]string @@ -136,13 +120,14 @@ func (d *moduleDiscoverer) discover(pkg protoanalysis.Package) (Module, error) { pkgrelpath := strings.TrimPrefix(pkg.GoImportPath(), d.basegopath) pkgpath := filepath.Join(d.sourcePath, pkgrelpath) - msgs, err := DiscoverMessages(pkgpath) - if err == ErrModuleNotFound { - return Module{}, nil - } + msgs, err := cosmosanalysis.FindImplementation(pkgpath, messageImplementation) if err != nil { return Module{}, err } + if len(msgs) == 0 { + // No message means the module has not been found + return Module{}, nil + } namesplit := strings.Split(pkg.Name, ".") m := Module{ From 6a92b6c103c17eccfc41d99496ac3a2e4b09c3eb Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 16:02:50 +0200 Subject: [PATCH 23/30] Lint --- starport/pkg/cosmosanalysis/app/app.go | 4 ++-- starport/pkg/cosmosanalysis/app/app_test.go | 1 - starport/pkg/cosmosanalysis/cosmosanalysis.go | 6 +++--- starport/pkg/cosmosanalysis/cosmosanalysis_test.go | 8 ++++---- starport/pkg/cosmosanalysis/module/message.go | 1 - starport/pkg/cosmosanalysis/module/module.go | 4 +--- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index 8fc9a5124d..666fbaad60 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -3,10 +3,11 @@ package app import ( "errors" "fmt" - "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "go/ast" "go/parser" "go/token" + + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" ) var appImplementation = []string{ @@ -16,7 +17,6 @@ var appImplementation = []string{ "RegisterTendermintService", } - // CheckKeeper checks for the existence of the keeper with the provided name in the app structure func CheckKeeper(appPath, keeperName string) error { fileSet := token.NewFileSet() diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go index a836c8dd69..59e73689a8 100644 --- a/starport/pkg/cosmosanalysis/app/app_test.go +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -55,4 +55,3 @@ func TestCheckKeeper(t *testing.T) { err = app.CheckKeeper(tmpFileNoApp, "FooKeeper") require.Error(t, err) } - diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis.go b/starport/pkg/cosmosanalysis/cosmosanalysis.go index 3e7b7643fc..0f0ce3a205 100644 --- a/starport/pkg/cosmosanalysis/cosmosanalysis.go +++ b/starport/pkg/cosmosanalysis/cosmosanalysis.go @@ -3,10 +3,10 @@ package cosmosanalysis import ( - "os" "go/ast" "go/parser" "go/token" + "os" ) // implementation tracks the implementation of an interface for a given struct @@ -55,7 +55,7 @@ func FindImplementation(path string, interfaceList []string) (found []string, er func isDirectory(path string) (bool, error) { fileInfo, err := os.Stat(path) - if err != nil{ + if err != nil { return false, err } return fileInfo.IsDir(), nil @@ -86,7 +86,7 @@ func findStructImplementationsInFile( f ast.Node, structImplementations map[string]implementation, interfaceList []string, - ) { +) { ast.Inspect(f, func(n ast.Node) bool { // look for struct methods. methodDecl, ok := n.(*ast.FuncDecl) diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis_test.go b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go index fa7e7a29e5..639247cbe3 100644 --- a/starport/pkg/cosmosanalysis/cosmosanalysis_test.go +++ b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go @@ -1,11 +1,12 @@ package cosmosanalysis_test import ( - "github.com/stretchr/testify/require" - "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" ) var ( @@ -36,7 +37,6 @@ func (f Foobar) barfoo() {} `) ) - func TestFindImplementation(t *testing.T) { tmpDir, err := os.MkdirTemp("", "cosmosanalysis_test") require.NoError(t, err) @@ -73,4 +73,4 @@ func TestFindImplementation(t *testing.T) { // no file _, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "3.go"), expectedinterface) require.Error(t, err) -} \ No newline at end of file +} diff --git a/starport/pkg/cosmosanalysis/module/message.go b/starport/pkg/cosmosanalysis/module/message.go index 7f58cc362d..7204016d0b 100644 --- a/starport/pkg/cosmosanalysis/module/message.go +++ b/starport/pkg/cosmosanalysis/module/message.go @@ -1,6 +1,5 @@ package module - // messageImplementation is the list of methods needed for a sdk.Msg implementation // TODO(low priority): dynamically get these from the source code of underlying version of the sdk. var messageImplementation = []string{ diff --git a/starport/pkg/cosmosanalysis/module/module.go b/starport/pkg/cosmosanalysis/module/module.go index cdce5a9045..fffa8c9ffc 100644 --- a/starport/pkg/cosmosanalysis/module/module.go +++ b/starport/pkg/cosmosanalysis/module/module.go @@ -3,16 +3,14 @@ package module import ( "context" "fmt" - "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "path/filepath" "strings" + "github.com/tendermint/starport/starport/pkg/cosmosanalysis" "github.com/tendermint/starport/starport/pkg/gomodule" "github.com/tendermint/starport/starport/pkg/protoanalysis" ) - - // Msgs is a module import path-sdk msgs pair. type Msgs map[string][]string From 9efd5013b4142a63f1d4b6d5c3e45d7b2567e1a5 Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 16:54:22 +0200 Subject: [PATCH 24/30] Interface --- starport/pkg/cosmosanalysis/app/app.go | 1 - starport/pkg/cosmosanalysis/app/app_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index 666fbaad60..f08b97c66a 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -12,7 +12,6 @@ import ( var appImplementation = []string{ "RegisterAPIRoutes", - "RegisterGRPCServer", "RegisterTxService", "RegisterTendermintService", } diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go index 59e73689a8..928145e32c 100644 --- a/starport/pkg/cosmosanalysis/app/app_test.go +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -18,7 +18,6 @@ type Foo struct { } func (f Foo) RegisterAPIRoutes() {} -func (f Foo) RegisterGRPCServer() {} func (f Foo) RegisterTxService() {} func (f Foo) RegisterTendermintService() {} `) From cd1cb63706b82cff6f8f6ebb07b9286a4c7ad931 Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 17:11:34 +0200 Subject: [PATCH 25/30] Parse module instead of files --- starport/pkg/cosmosanalysis/app/app.go | 59 +++++----- starport/pkg/cosmosanalysis/app/app_test.go | 56 +++++++-- starport/pkg/cosmosanalysis/cosmosanalysis.go | 111 ++++++------------ .../pkg/cosmosanalysis/cosmosanalysis_test.go | 10 +- 4 files changed, 116 insertions(+), 120 deletions(-) diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index f08b97c66a..42fb59a72d 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -17,15 +17,9 @@ var appImplementation = []string{ } // CheckKeeper checks for the existence of the keeper with the provided name in the app structure -func CheckKeeper(appPath, keeperName string) error { - fileSet := token.NewFileSet() - f, err := parser.ParseFile(fileSet, appPath, nil, 0) - if err != nil { - return err - } - +func CheckKeeper(path, keeperName string) error { // find app type - appImpl, err := cosmosanalysis.FindImplementation(appPath, appImplementation) + appImpl, err := cosmosanalysis.FindImplementation(path, appImplementation) if err != nil { return err } @@ -34,31 +28,40 @@ func CheckKeeper(appPath, keeperName string) error { } appTypeName := appImpl[0] - // Inspect the file for app struct + // Inspect the module for app struct var found bool - ast.Inspect(f, func(n ast.Node) bool { - // look for struct methods. - appType, ok := n.(*ast.TypeSpec) - if !ok || appType.Name.Name != appTypeName { - return true - } + fileSet := token.NewFileSet() + pkgs, err := parser.ParseDir(fileSet, path, nil, 0) + if err != nil { + return err + } + for _, pkg := range pkgs { + for _, f := range pkg.Files { + ast.Inspect(f, func(n ast.Node) bool { + // look for struct methods. + appType, ok := n.(*ast.TypeSpec) + if !ok || appType.Name.Name != appTypeName { + return true + } - appStruct, ok := appType.Type.(*ast.StructType) - if !ok { - return true - } + appStruct, ok := appType.Type.(*ast.StructType) + if !ok { + return true + } - // Search for the keeper specific field - for _, field := range appStruct.Fields.List { - for _, fieldName := range field.Names { - if fieldName.Name == keeperName { - found = true + // Search for the keeper specific field + for _, field := range appStruct.Fields.List { + for _, fieldName := range field.Names { + if fieldName.Name == keeperName { + found = true + } + } } - } - } - return false - }) + return false + }) + } + } if !found { return fmt.Errorf("app doesn't contain %s", keeperName) diff --git a/starport/pkg/cosmosanalysis/app/app_test.go b/starport/pkg/cosmosanalysis/app/app_test.go index 928145e32c..e312dccbb8 100644 --- a/starport/pkg/cosmosanalysis/app/app_test.go +++ b/starport/pkg/cosmosanalysis/app/app_test.go @@ -10,7 +10,7 @@ import ( ) var ( - source = []byte(` + AppFile = []byte(` package foo type Foo struct { @@ -22,35 +22,67 @@ func (f Foo) RegisterTxService() {} func (f Foo) RegisterTendermintService() {} `) - sourceNoApp = []byte(` + NoAppFile = []byte(` package foo type Bar struct { FooKeeper foo.keeper } +`) + + TwoAppFile = []byte(` +package foo + +type Foo struct { + FooKeeper foo.keeper +} + +func (f Foo) RegisterAPIRoutes() {} +func (f Foo) RegisterTxService() {} +func (f Foo) RegisterTendermintService() {} + +type Bar struct { + FooKeeper foo.keeper +} + +func (f Bar) RegisterAPIRoutes() {} +func (f Bar) RegisterTxService() {} +func (f Bar) RegisterTendermintService() {} `) ) func TestCheckKeeper(t *testing.T) { - tmpDir := os.TempDir() + tmpDir, err := os.MkdirTemp("", "app_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) // Test with a source file containing an app - tmpFile := filepath.Join(tmpDir, "source") - err := os.WriteFile(tmpFile, source, 0644) + tmpFile := filepath.Join(tmpDir, "app.go") + err = os.WriteFile(tmpFile, AppFile, 0644) require.NoError(t, err) - t.Cleanup(func() { os.Remove(tmpFile) }) - err = app.CheckKeeper(tmpFile, "FooKeeper") + err = app.CheckKeeper(tmpDir, "FooKeeper") require.NoError(t, err) - err = app.CheckKeeper(tmpFile, "BarKeeper") + err = app.CheckKeeper(tmpDir, "BarKeeper") require.Error(t, err) // No app in source must return an error - tmpFileNoApp := filepath.Join(tmpDir, "source") - err = os.WriteFile(tmpFileNoApp, sourceNoApp, 0644) + tmpDirNoApp, err := os.MkdirTemp("", "app_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) + tmpFileNoApp := filepath.Join(tmpDirNoApp, "app.go") + err = os.WriteFile(tmpFileNoApp, NoAppFile, 0644) require.NoError(t, err) - t.Cleanup(func() { os.Remove(tmpFileNoApp) }) + err = app.CheckKeeper(tmpDirNoApp, "FooKeeper") + require.Error(t, err) - err = app.CheckKeeper(tmpFileNoApp, "FooKeeper") + // More than one app must return an error + tmpDirTwoApp, err := os.MkdirTemp("", "app_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) + tmpFileTwoApp := filepath.Join(tmpDirTwoApp, "app.go") + err = os.WriteFile(tmpFileTwoApp, TwoAppFile, 0644) + require.NoError(t, err) + err = app.CheckKeeper(tmpDirTwoApp, "FooKeeper") require.Error(t, err) } diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis.go b/starport/pkg/cosmosanalysis/cosmosanalysis.go index 0f0ce3a205..b5f08c9c7d 100644 --- a/starport/pkg/cosmosanalysis/cosmosanalysis.go +++ b/starport/pkg/cosmosanalysis/cosmosanalysis.go @@ -6,41 +6,60 @@ import ( "go/ast" "go/parser" "go/token" - "os" ) // implementation tracks the implementation of an interface for a given struct type implementation map[string]bool // FindImplementation finds the name of all types that implement the provided interface -func FindImplementation(path string, interfaceList []string) (found []string, err error) { +func FindImplementation(modulePath string, interfaceList []string) (found []string, err error) { // parse go packages/files under path fset := token.NewFileSet() // collect all structs under path to find out the ones that satisfies the implementation structImplementations := make(map[string]implementation) - dir, err := isDirectory(path) + pkgs, err := parser.ParseDir(fset, modulePath, nil, 0) if err != nil { return nil, err } - if dir { - // find in dir - pkgs, err := parser.ParseDir(fset, path, nil, 0) - if err != nil { - return nil, err - } - for _, pkg := range pkgs { - for _, f := range pkg.Files { - findStructImplementationsInFile(f, structImplementations, interfaceList) - } - } - } else { - // find in file - f, err := parser.ParseFile(fset, path, nil, 0) - if err != nil { - return nil, err + for _, pkg := range pkgs { + for _, f := range pkg.Files { + ast.Inspect(f, func(n ast.Node) bool { + // look for struct methods. + methodDecl, ok := n.(*ast.FuncDecl) + if !ok { + return true + } + + // not a method. + if methodDecl.Recv == nil { + return true + } + + methodName := methodDecl.Name.Name + + // find the struct name that method belongs to. + t := methodDecl.Recv.List[0].Type + ident, ok := t.(*ast.Ident) + if !ok { + sexp, ok := t.(*ast.StarExpr) + if !ok { + return true + } + ident = sexp.X.(*ast.Ident) + } + structName := ident.Name + + // mark the implementation that this struct satisfies. + if _, ok := structImplementations[structName]; !ok { + structImplementations[structName] = newImplementation(interfaceList) + } + + structImplementations[structName][methodName] = true + + return true + }) } - findStructImplementationsInFile(f, structImplementations, interfaceList) } // append structs that satisfy the implementation @@ -53,14 +72,6 @@ func FindImplementation(path string, interfaceList []string) (found []string, er return found, nil } -func isDirectory(path string) (bool, error) { - fileInfo, err := os.Stat(path) - if err != nil { - return false, err - } - return fileInfo.IsDir(), nil -} - // newImplementation returns a new object to parse implementation of an interface func newImplementation(interfaceList []string) implementation { impl := make(implementation) @@ -79,47 +90,3 @@ func checkImplementation(r implementation) bool { } return true } - -// findStructImplementationsInFile append to the provided struct map -// the implementation of the interface found in the struct in the file -func findStructImplementationsInFile( - f ast.Node, - structImplementations map[string]implementation, - interfaceList []string, -) { - ast.Inspect(f, func(n ast.Node) bool { - // look for struct methods. - methodDecl, ok := n.(*ast.FuncDecl) - if !ok { - return true - } - - // not a method. - if methodDecl.Recv == nil { - return true - } - - methodName := methodDecl.Name.Name - - // find the struct name that method belongs to. - t := methodDecl.Recv.List[0].Type - ident, ok := t.(*ast.Ident) - if !ok { - sexp, ok := t.(*ast.StarExpr) - if !ok { - return true - } - ident = sexp.X.(*ast.Ident) - } - structName := ident.Name - - // mark the implementation that this struct satisfies. - if _, ok := structImplementations[structName]; !ok { - structImplementations[structName] = newImplementation(interfaceList) - } - - structImplementations[structName][methodName] = true - - return true - }) -} diff --git a/starport/pkg/cosmosanalysis/cosmosanalysis_test.go b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go index 639247cbe3..a9d628a583 100644 --- a/starport/pkg/cosmosanalysis/cosmosanalysis_test.go +++ b/starport/pkg/cosmosanalysis/cosmosanalysis_test.go @@ -64,13 +64,7 @@ func TestFindImplementation(t *testing.T) { require.NoError(t, err) require.Empty(t, found) - // find in file - found, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "1.go"), expectedinterface) - require.NoError(t, err) - require.Len(t, found, 1) - require.Contains(t, found, "Foo") - - // no file - _, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "3.go"), expectedinterface) + // can't provide file + _, err = cosmosanalysis.FindImplementation(filepath.Join(tmpDir, "1.go"), expectedinterface) require.Error(t, err) } From f2a8633de28d3310152897b8023c5ed8fb4d64ce Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 17:20:17 +0200 Subject: [PATCH 26/30] Ilker comment --- starport/cmd/module-create.go | 21 +++++++++------------ starport/pkg/cosmosanalysis/app/app.go | 1 + 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/starport/cmd/module-create.go b/starport/cmd/module-create.go index eb3750623f..0b0c0e9ca7 100644 --- a/starport/cmd/module-create.go +++ b/starport/cmd/module-create.go @@ -100,21 +100,18 @@ func createModuleHandler(cmd *cobra.Command, args []string) error { // Parse the provided dependencies for _, dependency := range dependencies { - splitted := strings.Split(dependency, ".") - switch { - case len(splitted) == 1: - formattedDependencies = append( - formattedDependencies, - modulecreate.NewDependency(splitted[0], ""), - ) - case len(splitted) == 2: - formattedDependencies = append( - formattedDependencies, - modulecreate.NewDependency(splitted[0], splitted[1]), - ) + var formattedDependency modulecreate.Dependency + + splitted := strings.Split(dependency, ":") + switch len(splitted) { + case 1: + formattedDependency = modulecreate.NewDependency(splitted[0], "") + case 2: + formattedDependency = modulecreate.NewDependency(splitted[0], splitted[1]) default: return fmt.Errorf("dependency %s is invalid, must have or .", dependency) } + formattedDependencies = append(formattedDependencies, formattedDependency) } options = append(options, scaffolder.WithDependencies(formattedDependencies)) } diff --git a/starport/pkg/cosmosanalysis/app/app.go b/starport/pkg/cosmosanalysis/app/app.go index 42fb59a72d..73951e223c 100644 --- a/starport/pkg/cosmosanalysis/app/app.go +++ b/starport/pkg/cosmosanalysis/app/app.go @@ -54,6 +54,7 @@ func CheckKeeper(path, keeperName string) error { for _, fieldName := range field.Names { if fieldName.Name == keeperName { found = true + return false } } } From 08812cbe14800dfe1567340b7fc5640a5aca4ff7 Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 19:15:34 +0200 Subject: [PATCH 27/30] No non-registered --- starport/services/scaffolder/module.go | 13 ++++++++++++- starport/templates/module/const.go | 4 ++-- starport/templates/module/create/stargate.go | 9 --------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/starport/services/scaffolder/module.go b/starport/services/scaffolder/module.go index e4a2cb6910..b4a3c3b7ff 100644 --- a/starport/services/scaffolder/module.go +++ b/starport/services/scaffolder/module.go @@ -14,6 +14,7 @@ import ( "github.com/gobuffalo/genny" "github.com/tendermint/starport/starport/pkg/cmdrunner" "github.com/tendermint/starport/starport/pkg/cmdrunner/step" + appanalysis "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "github.com/tendermint/starport/starport/pkg/cosmosver" "github.com/tendermint/starport/starport/pkg/gocmd" "github.com/tendermint/starport/starport/pkg/gomodulepath" @@ -160,6 +161,7 @@ func (s *Scaffolder) CreateModule( return sm, err } + // Modify app.go to register the module newSourceModification, runErr := xgenny.RunWithValidation(tracer, modulecreate.NewStargateAppModify(tracer, opts)) sm.Merge(newSourceModification) var validationErr validation.Error @@ -333,9 +335,18 @@ func checkIBCRouterPlaceholder(appPath string) (bool, error) { // checkDependencies perform checks on the dependencies func checkDependencies(dependencies []modulecreate.Dependency) error { - // check there is no duplicated dependencies depMap := make(map[string]struct{}) for _, dep := range dependencies { + // check the dependency has been registered + if err := appanalysis.CheckKeeper(module.PathAppModule, dep.KeeperName); err != nil { + return fmt.Errorf( + "the module cannot have %s as a dependency: %s", + dep.Name, + err.Error(), + ) + } + + // check duplicated _, ok := depMap[dep.Name] if ok { return fmt.Errorf("%s is a duplicated dependency", dep) diff --git a/starport/templates/module/const.go b/starport/templates/module/const.go index e4e144802e..c314a08aa1 100644 --- a/starport/templates/module/const.go +++ b/starport/templates/module/const.go @@ -1,6 +1,6 @@ package module const ( - PathAppGo = "app/app.go" - PathExportGo = "app/export.go" + PathAppModule = "app" + PathAppGo = "app/app.go" ) diff --git a/starport/templates/module/create/stargate.go b/starport/templates/module/create/stargate.go index 45cce27c54..9963392bfc 100644 --- a/starport/templates/module/create/stargate.go +++ b/starport/templates/module/create/stargate.go @@ -7,7 +7,6 @@ import ( "github.com/gobuffalo/genny" "github.com/gobuffalo/plush" "github.com/gobuffalo/plushgen" - appanalysis "github.com/tendermint/starport/starport/pkg/cosmosanalysis/app" "github.com/tendermint/starport/starport/pkg/placeholder" "github.com/tendermint/starport/starport/pkg/xstrings" "github.com/tendermint/starport/starport/templates/module" @@ -99,14 +98,6 @@ func appModifyStargate(replacer placeholder.Replacer, opts *CreateOptions) genny // Module dependencies var depArgs string for _, dep := range opts.Dependencies { - // if module has dependencies, we must verify the keeper of the dependency is defined in app.go - if err := appanalysis.CheckKeeper(path, dep.KeeperName); err != nil { - replacer.AppendMiscError(fmt.Sprintf( - "the module cannot have %s as a dependency: %s", - dep.Name, - err.Error(), - )) - } depArgs = fmt.Sprintf("%sapp.%s,\n", depArgs, dep.KeeperName) // If bank is a dependency, add account permissions to the module From a177ef54aff6a3c650f6419ce9f39ec084e2dee7 Mon Sep 17 00:00:00 2001 From: ltacker Date: Thu, 1 Jul 2021 19:15:44 +0200 Subject: [PATCH 28/30] New test --- integration/cmd_app_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/integration/cmd_app_test.go b/integration/cmd_app_test.go index 95f1405fd9..efaf68e610 100644 --- a/integration/cmd_app_test.go +++ b/integration/cmd_app_test.go @@ -141,5 +141,21 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { ExecShouldError(), )) + env.Must(env.Exec("should prevent creating a module with a non registered dependency", + step.NewSteps(step.New( + step.Exec( + "starport", + "module", + "create", + "example_with_no_dep", + "--dep", + "inexistent", + "--require-registration", + ), + step.Workdir(path), + )), + ExecShouldError(), + )) + env.EnsureAppIsSteady(path) } From 5e50910ef7611551449601bd65debaadb008c895 Mon Sep 17 00:00:00 2001 From: ltacker Date: Mon, 5 Jul 2021 09:52:24 +0200 Subject: [PATCH 29/30] Fix tests --- integration/cmd_ibc_test.go | 4 ++++ integration/cmd_message_test.go | 2 ++ integration/cmd_query_test.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/integration/cmd_ibc_test.go b/integration/cmd_ibc_test.go index b6678b8b11..4ed765ed8d 100644 --- a/integration/cmd_ibc_test.go +++ b/integration/cmd_ibc_test.go @@ -33,6 +33,7 @@ func TestCreateModuleWithIBC(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", "create", "--ibc", @@ -49,6 +50,7 @@ func TestCreateModuleWithIBC(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", "create", "--ibc", @@ -72,6 +74,7 @@ func TestCreateModuleWithIBC(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", "create", "example_with_dep", @@ -105,6 +108,7 @@ func TestCreateIBCPacket(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "packet", "bar", "text", diff --git a/integration/cmd_message_test.go b/integration/cmd_message_test.go index a0fcb46815..e80da5da17 100644 --- a/integration/cmd_message_test.go +++ b/integration/cmd_message_test.go @@ -18,6 +18,7 @@ func TestGenerateAnAppWithMessage(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "message", "do-foo", "text", @@ -56,6 +57,7 @@ func TestGenerateAnAppWithMessage(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "message", "do-foo", "text", diff --git a/integration/cmd_query_test.go b/integration/cmd_query_test.go index 85fa8c2011..293cbc0beb 100644 --- a/integration/cmd_query_test.go +++ b/integration/cmd_query_test.go @@ -16,6 +16,7 @@ func TestGenerateAnAppWithQuery(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "query", "foo", "text", @@ -32,6 +33,7 @@ func TestGenerateAnAppWithQuery(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "query", "bar", "text", @@ -71,6 +73,7 @@ func TestGenerateAnAppWithQuery(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "query", "foo", "text", From 67e1870ffb47ba12b7506ed866c2ef19fb0a148f Mon Sep 17 00:00:00 2001 From: ltacker Date: Mon, 5 Jul 2021 10:55:53 +0200 Subject: [PATCH 30/30] Module create fix --- integration/cmd_app_test.go | 6 +++--- integration/cmd_ibc_test.go | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/integration/cmd_app_test.go b/integration/cmd_app_test.go index cf8d34a529..f0321cda36 100644 --- a/integration/cmd_app_test.go +++ b/integration/cmd_app_test.go @@ -114,8 +114,8 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", - "create", "example_with_dep", "--dep", "account,bank,staking,slashing,example", @@ -129,8 +129,8 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", - "create", "example_with_wrong_dep", "--dep", "dup,dup", @@ -145,8 +145,8 @@ func TestGenerateAStargateAppWithEmptyModule(t *testing.T) { step.NewSteps(step.New( step.Exec( "starport", + "s", "module", - "create", "example_with_no_dep", "--dep", "inexistent", diff --git a/integration/cmd_ibc_test.go b/integration/cmd_ibc_test.go index 4ed765ed8d..c5847da2b9 100644 --- a/integration/cmd_ibc_test.go +++ b/integration/cmd_ibc_test.go @@ -35,9 +35,8 @@ func TestCreateModuleWithIBC(t *testing.T) { "starport", "s", "module", - "create", - "--ibc", "orderedfoo", + "--ibc", "--ordering", "ordered", "--require-registration", @@ -52,9 +51,8 @@ func TestCreateModuleWithIBC(t *testing.T) { "starport", "s", "module", - "create", - "--ibc", "unorderedfoo", + "--ibc", "--ordering", "unordered", "--require-registration", @@ -76,7 +74,6 @@ func TestCreateModuleWithIBC(t *testing.T) { "starport", "s", "module", - "create", "example_with_dep", "--ibc", "--dep",