Skip to content

Commit

Permalink
Add monogen tool (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshklop committed Sep 16, 2024
1 parent 609bc99 commit 84943f6
Show file tree
Hide file tree
Showing 8 changed files with 762 additions and 91 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ e2e/artifacts
# Ignore the forge artifacts and cache.
bindings/artifacts
bindings/cache

# Ignore binaries.
bin/
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ GOBIN ?= $$(go env GOPATH)/bin
COVER_OUT ?= cover.out
COVER_HTML ?= cover.html
SCRIPTS_PATH ?= scripts
BIN ?= bin

E2E_ARTIFACTS_PATH ?= e2e/artifacts
E2E_STATE_SETUP_PATH ?= e2e/optimism/.devnet
E2E_CONFIG_SETUP_PATH ?= e2e/optimism/packages/contracts-bedrock/deploy-config/devnetL1.json
FOUNDRY_ARTIFACTS_PATH ?= bindings/artifacts
FOUNDRY_CACHE_PATH ?= bindings/cache

.PHONY: monogen
monogen:
go build -o $(BIN)/monogen ./monogen/cmd

.PHONY: test
test:
go test -short ./...
Expand Down Expand Up @@ -86,6 +91,7 @@ clean:
if [ -f $(E2E_CONFIG_SETUP_PATH) ]; then rm $(E2E_CONFIG_SETUP_PATH); fi
if [ -d ${FOUNDRY_ARTIFACTS_PATH} ]; then rm -r ${FOUNDRY_ARTIFACTS_PATH}; fi
if [ -d ${FOUNDRY_CACHE_PATH} ]; then rm -r ${FOUNDRY_CACHE_PATH}; fi
if [ -d $(BIN) ]; then rm -r $(BIN); fi

.PHONY: setup-e2e
setup-e2e:
Expand Down
110 changes: 83 additions & 27 deletions go.mod

Large diffs are not rendered by default.

298 changes: 234 additions & 64 deletions go.sum

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions monogen/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/polymerdao/monomer/monogen"
"github.com/spf13/cobra"
)

var (
rootCmd = &cobra.Command{
Use: "monogen",
Short: "monogen scaffolds a Monomer project.",
Long: "monogen scaffolds a Monomer project. " +
"The resulting project is compatible with the ignite tool (https://github.com/ignite/cli).",
RunE: func(cmd *cobra.Command, _ []string) error {
return monogen.Generate(cmd.Context(), appDirPath, goModulePath, addressPrefix, skipGit)
},
}

skipGit bool
appDirPath string
goModulePath string
addressPrefix string
)

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

rootCmd.Flags().BoolVar(&skipGit, "skip-git", false, "skip git repository initialization")
rootCmd.Flags().StringVar(&appDirPath, "app-dir-path", "./testapp", "project directory")
rootCmd.Flags().StringVar(&goModulePath, "gomod-path", "github.com/testapp/testapp", "go module path")
rootCmd.Flags().StringVar(&addressPrefix, "address-prefix", "cosmos", "address prefix")

if err := rootCmd.ExecuteContext(ctx); err != nil {
fmt.Fprintf(os.Stderr, "error: %v", err)
cancel() // cancel is not called on os.Exit, we have to call it manually
os.Exit(1) //nolint:gocritic // Doesn't recognize that cancel() is called.
}
}
268 changes: 268 additions & 0 deletions monogen/monogen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package monogen

import (
"context"
"fmt"
"path/filepath"

"github.com/cometbft/cometbft/libs/os"
"github.com/gobuffalo/genny/v2"
"github.com/ignite/cli/v28/ignite/config"
"github.com/ignite/cli/v28/ignite/pkg/cache"
"github.com/ignite/cli/v28/ignite/pkg/gocmd"
"github.com/ignite/cli/v28/ignite/pkg/placeholder"
"github.com/ignite/cli/v28/ignite/pkg/xast"
"github.com/ignite/cli/v28/ignite/services/scaffolder"
"github.com/ignite/cli/v28/ignite/templates/module"
"github.com/ignite/cli/v28/ignite/version"
)

func Generate(ctx context.Context, appDirPath, goModulePath, addressPrefix string, skipGit bool) error {
if os.FileExists(appDirPath) {
return fmt.Errorf("refusing to overwrite directory: %s", appDirPath)
}

igniteRootDir, err := config.DirPath()
if err != nil {
return fmt.Errorf("get ignite config directory: %v", err)
}
// https://github.com/ignite/cli/blob/2a968e8684cae0a1d79ccb11f0db067a4605173e/ignite/cmd/cmd.go#L33-L34
cacheDBPath := filepath.Join(igniteRootDir, "ignite_cache.db")
cacheStorage, err := cache.NewStorage(cacheDBPath, cache.WithVersion(version.Version))
if err != nil {
return fmt.Errorf("new ignite cache storage: %v", err)
}

// Initial ignite scaffolding.
appDir, err := scaffolder.Init(
ctx,
cacheStorage,
placeholder.New(),
appDirPath,
goModulePath,
addressPrefix,
true, // no default module
skipGit,
false, // skip proto
true, // minimal
false, // consumer chain (ics)
nil, // params
)
if err != nil {
return fmt.Errorf("use ignite to scaffold initial cosmos sdk project: %v", err)
}

// monogen modifications.
g := genny.New()
g.RunFn(func(r *genny.Runner) error {
appGoPath := filepath.Join(appDir, "app", "app.go")
appConfigGoPath := filepath.Join(appDir, "app", "app_config.go")
if err := addRollupModule(r, appGoPath, appConfigGoPath); err != nil {
return fmt.Errorf("add rollup module: %v", err)
}
if err := removeConsensusModule(r, appGoPath, appConfigGoPath); err != nil {
return fmt.Errorf("remove consensus module: %v", err)
}
appName := filepath.Base(appDir)
if err := addMonomerCommand(r, filepath.Join(appDir, "cmd", appName+"d", "cmd", "commands.go")); err != nil {
return fmt.Errorf("add monomer command: %v", err)
}
if err := addReplaceDirectives(r, filepath.Join(appDir, "go.mod")); err != nil {
return fmt.Errorf("add replace directives: %v", err)
}
return nil
})
r := genny.WetRunner(ctx)
if err := r.With(g); err != nil {
return fmt.Errorf("attach genny generator to wet runner: %v", err)
}
if err := r.Run(); err != nil {
return fmt.Errorf("apply monogen modifications: %v", err)
}

if err := gocmd.Fmt(ctx, appDir); err != nil {
return fmt.Errorf("go fmt: %v", err)
}
_ = gocmd.GoImports(ctx, appDir) // From ignite: goimports installation could fail, so ignore the error
if err := gocmd.ModTidy(ctx, appDir); err != nil {
return fmt.Errorf("go mod tidy: %v", err)
}

return nil
}

func addRollupModule(r *genny.Runner, appGoPath, appConfigGoPath string) error {
replacer := placeholder.New()

// Modify the app config, usually at app/app_config.go.
appConfigGo, err := r.Disk.Find(appConfigGoPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}

// 1. Import the required rollup module packages.
content, err := xast.AppendImports(
appConfigGo.String(),
xast.WithLastNamedImport("rolluptypes", "github.com/polymerdao/monomer/x/rollup/types"),
xast.WithLastNamedImport("rollupmodulev1", "github.com/polymerdao/monomer/gen/rollup/module/v1"),
)
if err != nil {
return fmt.Errorf("append rollup module imports to %s: %v", appConfigGoPath, err)
}

// 2. Modify rollup module account permissions.
content = replacer.Replace(
content,
module.PlaceholderSgAppMaccPerms,
fmt.Sprintf(`{Account: rolluptypes.ModuleName, Permissions: []string{authtypes.Minter, authtypes.Burner}},
%s`, module.PlaceholderSgAppMaccPerms),
)

// 3. Add rollup module to app config.
content = replacer.Replace(content, module.PlaceholderSgAppModuleConfig, fmt.Sprintf(`{
Name: rolluptypes.ModuleName,
Config: appconfig.WrapAny(&rollupmodulev1.Module{}),
},
%s`, module.PlaceholderSgAppModuleConfig))

if err := r.File(genny.NewFileS(appConfigGoPath, content)); err != nil {
return fmt.Errorf("write %s: %v", appConfigGoPath, err)
}

// Modify the main application file, usually at app/app.go.
appGo, err := r.Disk.Find(appGoPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}

// 1. Import the required rollup module packages.
content, err = xast.AppendImports(
appGo.String(),
xast.WithLastNamedImport("rollupkeeper", "github.com/polymerdao/monomer/x/rollup/keeper"),
xast.WithLastNamedImport("_", "github.com/polymerdao/monomer/x/rollup"),
)
if err != nil {
return fmt.Errorf("append rollup module imports to %s: %v", appGoPath, err)
}

// 2. Add rollup module keeper declaration.
keeperDeclaration := fmt.Sprintf(`RollupKeeper *rollupkeeper.Keeper
%s`, module.PlaceholderSgAppKeeperDeclaration)
content = replacer.Replace(content, module.PlaceholderSgAppKeeperDeclaration, keeperDeclaration)

// 3. Add rollup module keeper definition.
keeperDefinition := fmt.Sprintf(`&app.RollupKeeper,
%s`, module.PlaceholderSgAppKeeperDefinition)
content = replacer.Replace(content, module.PlaceholderSgAppKeeperDefinition, keeperDefinition)

if err := r.File(genny.NewFileS(appGoPath, content)); err != nil {
return fmt.Errorf("write %s: %v", appGoPath, err)
}

return nil
}

func removeConsensusModule(r *genny.Runner, appGoPath, appConfigGoPath string) error {
replacer := placeholder.New()

// Modify the app config, usually at app/app_config.go.
appConfigGo, err := r.Disk.Find(appConfigGoPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}

// 1. Remove import.
content := replacer.Replace(appConfigGo.String(), `consensusmodulev1 "cosmossdk.io/api/cosmos/consensus/module/v1"
`, "")
content = replacer.Replace(content, `consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
`, "")

// 2. Remove module configuration.
content = replacer.Replace(content, `
{
Name: consensustypes.ModuleName,
Config: appconfig.WrapAny(&consensusmodulev1.Module{}),
},`, "")

if err := r.File(genny.NewFileS(appConfigGoPath, content)); err != nil {
return fmt.Errorf("write %s: %v", appConfigGoPath, err)
}

// Modify the app file, usually at app/app.go.
appGo, err := r.Disk.Find(appGoPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}

// 1. Remove import.
content = replacer.Replace(appGo.String(), `_ "github.com/cosmos/cosmos-sdk/x/consensus" // import for side-effects
`, "")
content = replacer.Replace(content, `consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
`, "")

// 2. Remove keeper declaration.
content = replacer.Replace(content, `ConsensusParamsKeeper consensuskeeper.Keeper
`, "")

// 3. Remove keeper definition.
content = replacer.Replace(content, `&app.ConsensusParamsKeeper,
`, "")

if err := r.File(genny.NewFileS(appGoPath, content)); err != nil {
return fmt.Errorf("write %s: %v", appGoPath, err)
}

return nil
}

func addMonomerCommand(r *genny.Runner, commandsGoPath string) error {
replacer := placeholder.New()

commandsGo, err := r.Disk.Find(commandsGoPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}

content, err := xast.AppendImports(commandsGo.String(), xast.WithLastImport("github.com/polymerdao/monomer/integrations"))
if err != nil {
return fmt.Errorf("append import: %v", err)
}

content = replacer.Replace(content, `
server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, addModuleInitFlags)`, `
monomerCmd := &cobra.Command{
Use: "monomer",
Short: "Monomer subcommands",
}
monomerCmd.AddCommand(server.StartCmdWithOptions(newApp, app.DefaultNodeHome, server.StartCmdOptions{
StartCommandHandler: integrations.StartCommandHandler,
}))
rootCmd.AddCommand(monomerCmd)`)

if err := r.File(genny.NewFileS(commandsGoPath, content)); err != nil {
return fmt.Errorf("write %s: %v", commandsGoPath, err)
}

return nil
}

func addReplaceDirectives(r *genny.Runner, goModPath string) error {
replacer := placeholder.New()

goMod, err := r.Disk.Find(goModPath)
if err != nil {
return fmt.Errorf("find: %v", err)
}
content := replacer.Replace(goMod.String(), "replace (", `replace (
cosmossdk.io/core => cosmossdk.io/core v0.11.1
github.com/btcsuite/btcd/btcec/v2 v2.3.4 => github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/crate-crypto/go-ipa => github.com/crate-crypto/go-ipa v0.0.0-20231205143816-408dbffb2041
github.com/crate-crypto/go-kzg-4844 v1.0.0 => github.com/crate-crypto/go-kzg-4844 v0.7.0
github.com/ethereum/go-ethereum => github.com/joshklop/op-geth v0.0.0-20240515205036-e3b990384a74
github.com/libp2p/go-libp2p => github.com/joshklop/go-libp2p v0.0.0-20240814165419-c6b91fa9f263
`)
if err := r.File(genny.NewFileS(goModPath, content)); err != nil {
return fmt.Errorf("write %s: %v", goModPath, err)
}
return nil
}
40 changes: 40 additions & 0 deletions monogen/monogen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

# Stop execution upon any command failure.
set -e

# Build the app.
# Because we transitively depend on github.com/fjl/memsize, we need to disable checklinkname in go1.23.0 and higher.
goVersion=$(go env GOVERSION)
[[ $goVersion > go1.23.0 || $goVersion == go1.23.0 ]] && ldflags="-ldflags=-checklinkname=0"
go build $ldflags -o testappd ./cmd/testappd

# The following process is identical for a standard Cosmos SDK chain.
# The Cosmos SDK documentation has more information in addition to what is presented here:
# - https://docs.cosmos.network/main/user/run-node/keyring#adding-keys-to-the-keyring
# - https://docs.cosmos.network/main/user/run-node/run-node#adding-genesis-accounts

# Initialize the application's config and data directories.
# The chain-id must be numeric as required by the OP Stack.
./testappd init my-app --chain-id 1

# The Cosmos SDK requires at least one validator.
# We will use a dummy account representing the sequencer.
./testappd keys add dummy-account --keyring-backend test

address=$(./testappd keys show dummy-account -a --keyring-backend test)

# Fund the dummy account at genesis.
./testappd genesis add-genesis-account $address 100000000000stake

# Make the dummy account self-delegate as a validator.
./testappd genesis gentx dummy-account 1000000000stake --chain-id 1 --keyring-backend test

# Add the gentx to the genesis file.
./testappd genesis collect-gentxs

# The testapp is ready to run with:
# ```
# ./testappd monomer start --minimum-gas-prices 0.01stake
# ```
# (the input to minimum-gas-prices is configurable).
Loading

0 comments on commit 84943f6

Please sign in to comment.