Skip to content

Commit

Permalink
feat: IBC module generation (#214)
Browse files Browse the repository at this point in the history
* wip new ibcmodule in progress

* further IBC module work base

* self-ibc generation

* fix proto & stuff

* fix

* wip

* working demo

* complete ibc demo

* test: module generation for

* simplify module logic

* fixes & typos

* fix: docs

* update docs :D
  • Loading branch information
Reecepbcups authored Sep 12, 2024
1 parent 158a59d commit 288f5f3
Show file tree
Hide file tree
Showing 57 changed files with 6,984 additions and 307 deletions.
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

0 comments on commit 288f5f3

Please sign in to comment.