Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/spawn/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ var (
}
)

func NewRootCmd() *cobra.Command {
return rootCmd
}

func main() {
outOfDateChecker()

Expand Down
122 changes: 100 additions & 22 deletions cmd/spawn/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,41 @@ import (

const (
FlagIsIBCMiddleware = "ibc-middleware"
FlagIsIBCModule = "ibc-module"
)

type features struct {
ibcMiddleware bool
ibcModule bool
}

func (f features) validate() error {
if f.ibcMiddleware && f.ibcModule {
return fmt.Errorf("cannot set both IBC Middleware and IBC Module")
}

return nil
}

func (f features) getModuleType() string {
if f.ibcMiddleware {
return "ibcmiddleware"
} else if f.ibcModule {
return "ibcmodule"
}

return "example"
}
func (f features) isIBC() bool {
return f.ibcMiddleware || f.ibcModule
}

func normalizeModuleFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "ibcmiddleware", "middleware":
name = FlagIsIBCMiddleware
case "ibcmodule", "ibc":
name = FlagIsIBCModule
}

return pflag.NormalizedName(name)
Expand Down Expand Up @@ -64,7 +89,7 @@ func NewCmd() *cobra.Command {

// breaks proto-gen regex searches
if strings.Contains(extName, "module") {
logger.Error("Module names cannot start with 'module'")
logger.Error("Module names cannot contain 'module'")
return
}

Expand All @@ -78,7 +103,7 @@ func NewCmd() *cobra.Command {

cwd, err := os.Getwd()
if err != nil {
logger.Error("Error getting current working directory", err)
logger.Error("Error getting current working directory", "err", err)
return
}
if _, err := os.Stat(path.Join(cwd, "x", extName)); err == nil {
Expand All @@ -88,29 +113,41 @@ func NewCmd() *cobra.Command {

isIBCMiddleware, err := cmd.Flags().GetBool(FlagIsIBCMiddleware)
if err != nil {
logger.Error("Error getting IBC Middleware flag", err)
logger.Error("Error getting IBC Middleware flag", "err", err)
return
}

isIBCModule, err := cmd.Flags().GetBool(FlagIsIBCModule)
if err != nil {
logger.Error("Error getting IBC Module flag", "err", err)
return
}

feats := &features{
ibcMiddleware: isIBCMiddleware,
ibcModule: isIBCModule,
}

if err := feats.validate(); err != nil {
logger.Error("Error validating module flags", "err", err)
return
}

// Setup Proto files to match the new x/ cosmos module name & go.mod module namespace (i.e. github org).
if err := SetupModuleProtoBase(GetLogger(), extName, feats); err != nil {
logger.Error("Error setting up proto for module", err)
logger.Error("Error setting up proto for module", "err", err)
return
}

// sets up the files in x/
if err := SetupModuleExtensionFiles(GetLogger(), extName, feats); err != nil {
logger.Error("Error setting up x/ module files", err)
logger.Error("Error setting up x/ module files", "err", err)
return
}

// Import the files to app.go
if err := AddModuleToAppGo(GetLogger(), extName, feats); err != nil {
logger.Error("Error adding new x/ module to app.go", err)
logger.Error("Error adding new x/ module to app.go", "err", err)
return
}

Expand All @@ -121,7 +158,8 @@ func NewCmd() *cobra.Command {
},
}

cmd.Flags().Bool(FlagIsIBCMiddleware, false, "Set the module as an IBC Middleware module")
cmd.Flags().Bool(FlagIsIBCMiddleware, false, "Set the module as an IBC Middleware")
cmd.Flags().Bool(FlagIsIBCModule, false, "Set the module as an IBC Module")
cmd.Flags().SetNormalizeFunc(normalizeModuleFlags)

return cmd
Expand All @@ -145,10 +183,7 @@ func SetupModuleProtoBase(logger *slog.Logger, extName string, feats *features)
goModName := spawn.ReadCurrentGoModuleName(path.Join(cwd, "go.mod"))
protoNamespace := convertGoModuleNameToProtoNamespace(goModName)

moduleName := "example"
if feats.ibcMiddleware {
moduleName = "ibcmiddleware"
}
moduleName := feats.getModuleType()

logger.Debug("proto namespace", "goModName", goModName, "protoNamespace", protoNamespace, "moduleName", moduleName)

Expand All @@ -164,11 +199,15 @@ func SetupModuleProtoBase(logger *slog.Logger, extName string, feats *features)
// ignore emebeded files for modules we are not working with
switch moduleName {
case "example":
if strings.Contains(fc.NewPath, "ibcmiddleware") {
if !strings.Contains(fc.NewPath, "example") {
return nil
}
case "ibcmiddleware":
if strings.Contains(fc.NewPath, "example") {
if !strings.Contains(fc.NewPath, "ibcmiddleware") {
return nil
}
case "ibcmodule":
if !strings.Contains(fc.NewPath, "ibcmodule") {
return nil
}
}
Expand Down Expand Up @@ -204,11 +243,7 @@ func SetupModuleExtensionFiles(logger *slog.Logger, extName string, feats *featu
return err
}

moduleName := "example"
if feats.ibcMiddleware {
moduleName = "ibcmiddleware"
}

moduleName := feats.getModuleType()
goModName := spawn.ReadCurrentGoModuleName(path.Join(cwd, "go.mod"))

// copy x/example to x/extName
Expand All @@ -224,11 +259,15 @@ func SetupModuleExtensionFiles(logger *slog.Logger, extName string, feats *featu
// ignore emebeded files for modules we are not working with
switch moduleName {
case "example":
if strings.Contains(fc.NewPath, "ibcmiddleware") {
if !strings.Contains(fc.NewPath, "example") {
return nil
}
case "ibcmiddleware":
if strings.Contains(fc.NewPath, "example") {
if !strings.Contains(fc.NewPath, "ibcmiddleware") {
return nil
}
case "ibcmodule":
if !strings.Contains(fc.NewPath, "ibcmodule") {
return nil
}
}
Expand Down Expand Up @@ -310,6 +349,16 @@ func AddModuleToAppGo(logger *slog.Logger, extName string, feats *features) erro
app.MsgServiceRouter(),
app.IBCKeeper.ChannelKeeper,
)`+"\n", extName, extNameTitle, extName)
} else if feats.ibcModule {
keeperText = fmt.Sprintf(` // Create the %s IBC Module Keeper
app.%sKeeper = %skeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[%stypes.StoreKey]),
app.IBCKeeper.ChannelKeeper,
app.IBCKeeper.PortKeeper,
scoped%s,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)`+"\n", extName, extNameTitle, extName, extName, extNameTitle)
} else {
keeperText = fmt.Sprintf(` // Create the %s Keeper
app.%sKeeper = %skeeper.NewKeeper(
Expand All @@ -322,17 +371,46 @@ func AddModuleToAppGo(logger *slog.Logger, extName string, feats *features) erro

appGoLines = append(appGoLines[:evidenceTextLine+2], append([]string{keeperText}, appGoLines[evidenceTextLine+2:]...)...)

// ibcModule requires some more setup additions for scoped keepers and specific module routing within IBC.
if feats.ibcModule {
capabilityKeeperSeal := spawn.FindLineWithText(appGoLines, "app.CapabilityKeeper.Seal()")
logger.Debug("capabilityKeeperSeal", "extName", extName, "line", evidenceTextLine)

// scopedMynsibc := app.CapabilityKeeper...
scopedKeeperText := fmt.Sprintf(` scoped%s := app.CapabilityKeeper.ScopeToModule(%stypes.ModuleName)`, extNameTitle, extName)
appGoLines = append(appGoLines[:capabilityKeeperSeal], append([]string{scopedKeeperText}, appGoLines[capabilityKeeperSeal:]...)...)

// find ChainApp ScopedIBCKeeper (where keepers are saved) & save this new keeper to it
scopedIBCKeeperKeeper := spawn.FindLineWithText(appGoLines, "ScopedIBCKeeper")
logger.Debug("scopedIBCKeeperKeeper", "extName", extName, "line", scopedIBCKeeperKeeper)
scopedKeeper := fmt.Sprintf("Scoped%s", extNameTitle)

line := fmt.Sprintf(` %s capabilitykeeper.ScopedKeeper`, scopedKeeper)
appGoLines = append(appGoLines[:scopedIBCKeeperKeeper+1], append([]string{line}, appGoLines[scopedIBCKeeperKeeper+1:]...)...)

scopedIBCKeeper := spawn.FindLineWithText(appGoLines, "app.ScopedIBCKeeper =")
logger.Debug("scopedIBCKeeper", "extName", extName, "line", scopedIBCKeeper)

line = fmt.Sprintf(` app.%s = scoped%s`, scopedKeeper, extNameTitle)
appGoLines = append(appGoLines[:scopedIBCKeeper], append([]string{line}, appGoLines[scopedIBCKeeper:]...)...)

// find app.IBCKeeper.SetRouter
ibcKeeperSetRouter := spawn.FindLineWithText(appGoLines, "app.IBCKeeper.SetRouter(")
// place module above it `ibcRouter.AddRoute(nameserviceibctypes.ModuleName, nameserviceibc.NewIBCModule(app.NameserviceibcKeeper))`
newLine := fmt.Sprintf(` ibcRouter.AddRoute(%stypes.ModuleName, %s.NewExampleIBCModule(app.%sKeeper))`, extName, extName, extNameTitle)
appGoLines = append(appGoLines[:ibcKeeperSetRouter], append([]string{newLine}, appGoLines[ibcKeeperSetRouter:]...)...)
}

// Register the app module.
start, end = spawn.FindLinesWithText(appGoLines, "NewManager(")
logger.Debug("module manager", "extName", extName, "start", start, "end", end)

var newAppModuleText string
if feats.ibcMiddleware {
if feats.isIBC() {
newAppModuleText = fmt.Sprintf(` %s.NewAppModule(app.%sKeeper),`+"\n", extName, extNameTitle)
} else {
newAppModuleText = fmt.Sprintf(` %s.NewAppModule(appCodec, app.%sKeeper),`+"\n", extName, extNameTitle)
}

appGoLines = append(appGoLines[:end-1], append([]string{newAppModuleText}, appGoLines[end-1:]...)...)

// Set the begin block order of the new module.
Expand Down
90 changes: 90 additions & 0 deletions cmd/spawn/module_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main_test

import (
"bytes"
"fmt"
"os"
"path"
"testing"

main "github.com/rollchains/spawn/cmd/spawn"
"github.com/rollchains/spawn/spawn"
"github.com/stretchr/testify/require"
)

func TestModuleGeneration(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)

cfg := spawn.NewChainConfig{
ProjectName: "default",
Bech32Prefix: "cosmos",
HomeDir: ".default",
BinDaemon: main.RandStringBytes(6) + "d",
Denom: "token" + main.RandStringBytes(3),
GithubOrg: main.RandStringBytes(15),
IgnoreGitInit: false,
DisabledModules: []string{"explorer"},
Logger: main.Logger,
}

type mc struct {
Name string
Args []string
OutputContains string
}

mcs := []mc{
{
Name: "ibcmodule",
Args: []string{"new", "myibc", "--ibc-module"},
},
{
Name: "ibcmiddleware",
Args: []string{"new", "myibcmw", "--ibc-middleware"},
},
{
Name: "standard",
Args: []string{"new", "standard"},
},
}

for _, c := range mcs {
c := c
t.Run(c.Name, func(t *testing.T) {
name := "spawnmoduleunittest" + c.Name

cfg.ProjectName = name
cfg.HomeDir = "." + name
fmt.Println("=====\nName", name)

dirPath := path.Join(cwd, name)
require.NoError(t, os.RemoveAll(name))

require.NoError(t, cfg.ValidateAndRun(false), "failed to generate proper chain")

// move to new repo
require.NoError(t, os.Chdir(dirPath))

cmd := main.ModuleCmd()
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetErr(b)
cmd.SetArgs(c.Args)
cmd.Execute()
// out, err := io.ReadAll(b)
// if err != nil {
// t.Fatal(err)
// }

// TODO: this is not being read from. Fix.
// require.Contains(t, string(out), c.OutputContains, "output: "+string(out))

// validate the go source is good
main.AssertValidGeneration(t, dirPath, nil, nil, cfg)

require.NoError(t, os.Chdir(cwd))
require.NoError(t, os.RemoveAll(name))
})
}
}
Loading