Skip to content

Commit

Permalink
feat: cosmwasm official app (#60)
Browse files Browse the repository at this point in the history
* Initialized with Ignite CLI

* add wasm commands

* scaffold chain wasm

* remove wasm* from gitignore

* use chain into the scaffold struct

* add config command

* fix client genesis

* format code

* add tests without test cases

* add some test cases

* add test cases for the goanalysis package

* add comments

* remove unused file

* remove duplicated .gitignore

* add wasm integration verification

* add integration tests

* Update official/wasm/pkg/goanalysis/goanalysis.go

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* Update official/wasm/services/scaffolder/wasm.go

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* Update official/wasm/cmd/config.go

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* Update official/wasm/main.go

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* Update official/wasm/go.mod

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* Update official/wasm/pkg/config/config_test.go

Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>

* usa github.com/ignite/apps/wasm namespace instead only wasm

* apply some comments suggestions

* run go mod tidy

* add configurable wasm version

* add readme

---------

Co-authored-by: Developer Experience team at Ignite <hello@ignite.com>
Co-authored-by: Pantani <Pantani>
Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent 607099f commit 3c71122
Show file tree
Hide file tree
Showing 23 changed files with 3,891 additions and 0 deletions.
37 changes: 37 additions & 0 deletions wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Wasm

`wasm` is an app developed for [Ignite CLI](https://github.com/ignite/cli).

The app adds `ignite wasm` commands to add a [CosmWasm](https://cosmwasm.com/) integration into a chain.

## How to use

- Install the Wasm app:
```shell
ignite app install -g github.com/ignite/apps/wasm
```

- You must scaffold a new chain with version `v28.2.1` or greater. Or migrate your chain using the [Ignite migration guides](https://docs.ignite.com/migration).

- Now you can add a Wasm support to your chain by running this command into the chain directory:
```shell
ignite wasm add
```

- The command will automatically add the Wasm integration into your code and the Wasm config into your chain config. But if your chain is not initiated yet, the chain config does not exist, so you need to add the Wasm config later running:
```shell
ignite wasm add
```

_All commands should be run in the chain directory._

## Developer instruction

- clone this repo locally
- Run `ignite app add -g /absolute/path/to/app/wasm` to add the app to global config
- `ignite wasm` command is now available with the local version of the app.

Then repeat the following loop:

- Hack on the app code
- Rerun `ignite wasm` to recompile the app and test
77 changes: 77 additions & 0 deletions wasm/cmd/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cmd

import (
"github.com/blang/semver/v4"
"github.com/ignite/cli/v28/ignite/pkg/cliui"
"github.com/ignite/cli/v28/ignite/pkg/placeholder"
"github.com/ignite/cli/v28/ignite/services/chain"
"github.com/spf13/cobra"

"github.com/ignite/apps/wasm/services/scaffolder"
)

// NewWasmAdd add wasm integration to a chain.
func NewWasmAdd() *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "Add wasm support",
Args: cobra.NoArgs,
RunE: wasmAddExecuteHandler,
}

flagSetPath(c)
flagSetHome(c)
flagSetWasmConfigs(c)
flagSetWasmVersion(c)

return c
}

func wasmAddExecuteHandler(cmd *cobra.Command, args []string) error {
session := cliui.New(cliui.StartSpinnerWithText(statusScaffolding))
defer session.End()

var (
simulationGasLimit = getSimulationGasLimit(cmd)
smartQueryGasLimit = getSmartQueryGasLimit(cmd)
memoryCacheSize = getMemoryCacheSize(cmd)
wasmVersion = getWasmVersion(cmd)
)

wasmSemVer, err := semver.Parse(wasmVersion)
if err != nil {
return err
}

c, err := newChainWithHomeFlags(cmd, chain.WithOutputer(session), chain.CollectEvents(session.EventBus()))
if err != nil {
return err
}

sc, err := scaffolder.New(c, session)
if err != nil {
return err
}

sm, err := sc.AddWasm(
cmd.Context(),
placeholder.New(),
scaffolder.WithWasmVersion(wasmSemVer),
scaffolder.WithSimulationGasLimit(simulationGasLimit),
scaffolder.WithSmartQueryGasLimit(smartQueryGasLimit),
scaffolder.WithMemoryCacheSize(memoryCacheSize),
)
if err != nil {
return err
}

modificationsStr, err := sourceModificationToString(sm)
if err != nil {
return err
}

session.Println(modificationsStr)
session.Printf("\n🎉 CosmWasm added (`%[1]v`).\n\n", c.AppPath())

return nil
}
161 changes: 161 additions & 0 deletions wasm/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package cmd

import (
"os"
"path/filepath"
"sort"
"strings"

"github.com/ignite/cli/v28/ignite/pkg/cliui/colors"
"github.com/ignite/cli/v28/ignite/pkg/xgenny"
"github.com/ignite/cli/v28/ignite/services/chain"
"github.com/spf13/cobra"
)

// NewWasm creates a new wasm command that holds
// some other sub commands related to CosmWasm.
func NewWasm() *cobra.Command {
c := &cobra.Command{
Use: "wasm [command]",
Aliases: []string{"w"},
Short: "Ignite wasm integration",
SilenceUsage: true,
SilenceErrors: true,
}

// add sub commands.
c.AddCommand(
NewWasmAdd(),
NewWasmConfig(),
)
return c
}

const (
flagPath = "path"
flagHome = "home"
flagVersion = "version"

statusScaffolding = "Scaffolding..."
statusAddingConfig = "Adding config..."

defaultWasmVersion = "v0.50.0"
)

var (
modifyPrefix = colors.Modified("modify ")
createPrefix = colors.Success("create ")
removePrefix = func(s string) string {
return strings.TrimPrefix(strings.TrimPrefix(s, modifyPrefix), createPrefix)
}
)

func flagSetPath(cmd *cobra.Command) {
cmd.Flags().StringP(flagPath, "p", ".", "path of the app")
}

func flagSetHome(cmd *cobra.Command) {
cmd.Flags().String(flagHome, "", "directory where the blockchain node is initialized")
}

func flagSetWasmVersion(cmd *cobra.Command) {
cmd.Flags().String(flagVersion, defaultWasmVersion, "wasmd semantic version")
}

func flagSetWasmConfigs(cmd *cobra.Command) {
cmd.Flags().Uint64(flagSimulationGasLimit, 0, "the max gas to be used in a tx simulation call. When not set the consensus max block gas is used instead")
cmd.Flags().Uint64(flagSmartQueryGasLimit, 3_000_000, "the max gas to be used in a smart query contract call")
cmd.Flags().Uint64(flagMemoryCacheSize, 100, "memory cache size in MiB not bytes")
}

func getPath(cmd *cobra.Command) string {
path, _ := cmd.Flags().GetString(flagPath)
return path
}

func getHome(cmd *cobra.Command) string {
home, _ := cmd.Flags().GetString(flagHome)
return home
}

func getWasmVersion(cmd *cobra.Command) string {
version, _ := cmd.Flags().GetString(flagVersion)
version = strings.Replace(version, "v", "", 1)
return version
}

func getSimulationGasLimit(cmd *cobra.Command) uint64 {
simulationGasLimit, _ := cmd.Flags().GetUint64(flagSimulationGasLimit)
return simulationGasLimit
}

func getSmartQueryGasLimit(cmd *cobra.Command) uint64 {
smartQueryGasLimit, _ := cmd.Flags().GetUint64(flagSmartQueryGasLimit)
return smartQueryGasLimit
}

func getMemoryCacheSize(cmd *cobra.Command) uint64 {
memoryCacheSize, _ := cmd.Flags().GetUint64(flagMemoryCacheSize)
return memoryCacheSize
}

// newChainWithHomeFlags create new *chain.Chain with home and path flags.
func newChainWithHomeFlags(cmd *cobra.Command, chainOption ...chain.Option) (*chain.Chain, error) {
// Check if custom home is provided
if home := getHome(cmd); home != "" {
chainOption = append(chainOption, chain.HomePath(home))
}

appPath := getPath(cmd)
absPath, err := filepath.Abs(appPath)
if err != nil {
return nil, err
}

return chain.New(absPath, chainOption...)
}

// sourceModificationToString output the modifications into a readable text.
func sourceModificationToString(sm xgenny.SourceModification) (string, error) {
// get file names and add prefix
var files []string
for _, modified := range sm.ModifiedFiles() {
// get the relative app path from the current directory
relativePath, err := relativePath(modified)
if err != nil {
return "", err
}
files = append(files, modifyPrefix+relativePath)
}
for _, created := range sm.CreatedFiles() {
// get the relative app path from the current directory
relativePath, err := relativePath(created)
if err != nil {
return "", err
}
files = append(files, createPrefix+relativePath)
}

// sort filenames without prefix
sort.Slice(files, func(i, j int) bool {
s1 := removePrefix(files[i])
s2 := removePrefix(files[j])

return strings.Compare(s1, s2) == -1
})

return "\n" + strings.Join(files, "\n"), nil
}

// relativePath return the relative app path from the current directory.
func relativePath(appPath string) (string, error) {
pwd, err := os.Getwd()
if err != nil {
return "", err
}
path, err := filepath.Rel(pwd, appPath)
if err != nil {
return "", err
}
return path, nil
}
46 changes: 46 additions & 0 deletions wasm/cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmd

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func Test_relativePath(t *testing.T) {
tests := []struct {
name string
appPath string
want string
}{
{
name: "Relative path within current directory",
appPath: "subdir/file.txt",
want: "subdir/file.txt",
},
{
name: "Relative path outside current directory",
appPath: "/path/file.txt",
want: "../../../../../../../../../../../path/file.txt",
},
{
name: "App path is current directory",
appPath: ".",
want: ".",
},
{
name: "App path is parent directory",
appPath: "..",
want: "..",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
absPath, err := filepath.Abs(tt.appPath)
require.NoError(t, err)
got, err := relativePath(absPath)
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}
71 changes: 71 additions & 0 deletions wasm/cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"os"

"github.com/ignite/cli/v28/ignite/pkg/cliui"
"github.com/ignite/cli/v28/ignite/pkg/errors"
"github.com/ignite/cli/v28/ignite/services/chain"
"github.com/spf13/cobra"

"github.com/ignite/apps/wasm/pkg/config"
)

const (
// flagSimulationGasLimit is the max gas to be used in a tx simulation call.
// When not set the consensus max block gas is used instead
flagSimulationGasLimit = "simulation-gas-limit"
// flagSmartQueryGasLimit is the max gas to be used in a smart query contract call
flagSmartQueryGasLimit = "query-gas-limit"
// flagMemoryCacheSize in MiB not bytes
flagMemoryCacheSize = "memory-cache-size"
)

// NewWasmConfig add wasm config to a chain.
func NewWasmConfig() *cobra.Command {
c := &cobra.Command{
Use: "config",
Short: "Add wasm config support",
Args: cobra.NoArgs,
RunE: wasmConfigExecuteHandler,
}

flagSetPath(c)
flagSetHome(c)
flagSetWasmConfigs(c)

return c
}

func wasmConfigExecuteHandler(cmd *cobra.Command, args []string) error {
session := cliui.New(cliui.StartSpinnerWithText(statusAddingConfig))
defer session.End()

var (
simulationGasLimit = getSimulationGasLimit(cmd)
smartQueryGasLimit = getSmartQueryGasLimit(cmd)
memoryCacheSize = getMemoryCacheSize(cmd)
)

c, err := newChainWithHomeFlags(cmd, chain.WithOutputer(session), chain.CollectEvents(session.EventBus()))
if err != nil {
return err
}

configTOML, err := c.ConfigTOMLPath()
if _, err := os.Stat(configTOML); os.IsNotExist(err) {
return errors.Errorf("chain %s not initialized yet (%s)", c.Name(), c.AppPath())
}

if err := config.AddWasm(
configTOML,
config.WithSimulationGasLimit(simulationGasLimit),
config.WithSmartQueryGasLimit(smartQueryGasLimit),
config.WithMemoryCacheSize(memoryCacheSize),
); err != nil {
return err
}
session.Printf("\n🎉 CosmWasm config added at `%[1]v`.\n\n", configTOML)

return nil
}
Loading

0 comments on commit 3c71122

Please sign in to comment.