From 85f62bad851e4cfdd167c14fde4539e08dac01a0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 14:09:15 +0100 Subject: [PATCH 01/34] refactor(gnovm): rename precompiler to transpiler, move to own package --- PLAN.md | 2 +- docs/gno-tooling/cli/gno.md | 6 +- docs/reference/go-gno-compatibility.md | 2 +- examples/Makefile | 10 +-- gno.land/pkg/gnoclient/client_txs.go | 8 +- gno.land/pkg/keyscli/addpkg.go | 5 +- gno.land/pkg/keyscli/run.go | 5 +- gnovm/cmd/gno/README.md | 2 +- gnovm/cmd/gno/lint_test.go | 2 +- gnovm/cmd/gno/main.go | 2 +- gnovm/cmd/gno/precompile.go | 90 +++++++++---------- gnovm/cmd/gno/precompile_test.go | 4 +- gnovm/cmd/gno/test.go | 23 ++--- .../testdata/gno_precompile/01_no_args.txtar | 6 +- .../gno_precompile/02_empty_dir.txtar | 4 +- .../03_gno_files_parse_error.txtar | 6 +- .../gno_precompile/04_valid_gno_files.txtar | 4 +- .../gno_precompile/05_skip_fmt_flag.txtar | 6 +- .../gno_precompile/06_build_flag.txtar | 4 +- .../07_build_flag_with_build_error.txtar | 6 +- .../08_build_flag_with_parse_error.txtar | 4 +- .../09_gno_files_whitelist_error.txtar | 6 +- .../gno/testdata/gno_test/empty_gno1.txtar | 2 +- .../gno/testdata/gno_test/empty_gno2.txtar | 2 +- .../gno/testdata/gno_test/empty_gno3.txtar | 2 +- .../testdata/gno_test/failing_filetest.txtar | 2 +- .../gno/testdata/gno_test/failing_test.txtar | 2 +- gnovm/cmd/gno/util.go | 10 +-- gnovm/pkg/gnolang/realm.go | 13 +-- .../transpiler.go} | 44 ++++----- .../transpiler_test.go} | 8 +- gnovm/pkg/gnomod/file.go | 8 +- gnovm/pkg/gnomod/gnomod.go | 25 +++--- 33 files changed, 165 insertions(+), 160 deletions(-) rename gnovm/pkg/gnolang/{precompile.go => transpiler/transpiler.go} (87%) rename gnovm/pkg/gnolang/{precompile_test.go => transpiler/transpiler_test.go} (96%) diff --git a/PLAN.md b/PLAN.md index dfcae264727..138eee35bb8 100644 --- a/PLAN.md +++ b/PLAN.md @@ -16,7 +16,7 @@ the Discord server. ### Gnolang * Get basic Go tests working _COMPLETE_ -* Implement minimal toolsuite (precompile, gnodev) +* Implement minimal toolsuite (transpile, gnodev) * Tweak/enforce gas limitations * Implement flat-as-struct supported * Implement ownership/realm logic; phase 1: no cycles diff --git a/docs/gno-tooling/cli/gno.md b/docs/gno-tooling/cli/gno.md index e72f1414102..5c73a5cee34 100644 --- a/docs/gno-tooling/cli/gno.md +++ b/docs/gno-tooling/cli/gno.md @@ -19,7 +19,7 @@ gno {SUB_COMMAND} | Name | Description | | ------------ | ------------------------------------------ | | `test` | Tests a gno package. | -| `precompile` | Precompiles a `.gno` file to a `.go` file. | +| `transpile` | Transpiles a `.gno` file to a `.go` file. | | `repl` | Starts a GnoVM REPL. | ### `test` @@ -32,9 +32,9 @@ gno {SUB_COMMAND} | `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | | `run` | String | Test name filtering pattern. | | `timeout` | time.Duration | The maximum execution time in ns. | -| `precompile` | Boolean | Precompiles a `.gno` file to a `.go` file before testing. | +| `transpile` | Boolean | Transpiles a `.gno` file to a `.go` file before testing. | -### `precompile` +### `transpile` #### **Options** diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 71f46adf836..4b65b6c32ef 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -303,7 +303,7 @@ Legend: | + go mod init | gno mod init | same behavior | | + go mod download | gno mod download | same behavior | | + go mod tidy | gno mod tidy | same behavior | -| | gno precompile | | +| | gno transpile | | | go work | | | | | gno repl | | | go run | gno run | | diff --git a/examples/Makefile b/examples/Makefile index 56ad4b30b42..2fd5c70daf4 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -9,13 +9,13 @@ help: @echo "Available make commands:" @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' -.PHONY: precompile -precompile: - go run ../gnovm/cmd/gno precompile --verbose . +.PHONY: transpile +transpile: + go run ../gnovm/cmd/gno transpile --verbose . .PHONY: build -build: precompile - go run ../gnovm/cmd/gno build --verbose . +build: + go run ../gnovm/cmd/gno transpile --verbose --gobuild . .PHONY: test test: diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 39088bc30d5..34759b565e3 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,7 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -208,10 +208,10 @@ func (c *Client) Run(cfg RunCfg) (*ctypes.ResultBroadcastTxCommit, error) { caller := c.Signer.Info().GetAddress() - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { - return nil, errors.Wrap(err, "precompile and check") + return nil, errors.Wrap(err, "transpile and check") } memPkg.Name = "main" memPkg.Path = "" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 1882f6de3d0..82aef1315d4 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -101,8 +102,8 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { panic(err) } diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 7d329c18566..3dd1879bcfa 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -108,8 +109,8 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { panic(err) } diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md index 97285684785..d49ca85736e 100644 --- a/gnovm/cmd/gno/README.md +++ b/gnovm/cmd/gno/README.md @@ -10,7 +10,7 @@ * `gno run` - run a Gno file * `gno build` - build a gno package -* `gno precompile` - precompile .gno to .go +* `gno transpile` - transpile .gno to .go * `gno test` - test a gno package * `gno mod` - manages dependencies * `gno repl` start a GnoVM REPL diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index d700467965d..1966eab7c9e 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -31,7 +31,7 @@ func TestLintApp(t *testing.T) { // TODO: is gno source valid? // TODO: are dependencies valid? // TODO: is gno source using unsafe/discouraged features? - // TODO: consider making `gno precompile; go lint *gen.go` + // TODO: consider making `gno transpile; go lint *gen.go` // TODO: check for imports of native libs from non _test.gno files } testMainCaseRun(t, tc) diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index aecb5d0f1e5..5271bcb1e9f 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -28,7 +28,7 @@ func newGnocliCmd(io commands.IO) *commands.Command { newTestCmd(io), newLintCmd(io), newRunCmd(io), - newPrecompileCmd(io), + newTranspileCmd(io), newCleanCmd(io), newReplCmd(), newDocCmd(io), diff --git a/gnovm/cmd/gno/precompile.go b/gnovm/cmd/gno/precompile.go index 2d7a727a13a..7b32df556b5 100644 --- a/gnovm/cmd/gno/precompile.go +++ b/gnovm/cmd/gno/precompile.go @@ -10,13 +10,13 @@ import ( "os" "path/filepath" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) type importPath string -type precompileCfg struct { +type transpileCfg struct { verbose bool skipFmt bool skipImports bool @@ -26,52 +26,52 @@ type precompileCfg struct { output string } -type precompileOptions struct { - cfg *precompileCfg - // precompiled is the set of packages already - // precompiled from .gno to .go. - precompiled map[importPath]struct{} +type transpileOptions struct { + cfg *transpileCfg + // transpiled is the set of packages already + // transpiled from .gno to .go. + transpiled map[importPath]struct{} } -var defaultPrecompileCfg = &precompileCfg{ +var defaultTranspileCfg = &transpileCfg{ verbose: false, goBinary: "go", } -func newPrecompileOptions(cfg *precompileCfg) *precompileOptions { - return &precompileOptions{cfg, map[importPath]struct{}{}} +func newTranspileOptions(cfg *transpileCfg) *transpileOptions { + return &transpileOptions{cfg, map[importPath]struct{}{}} } -func (p *precompileOptions) getFlags() *precompileCfg { +func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *precompileOptions) isPrecompiled(pkg importPath) bool { - _, precompiled := p.precompiled[pkg] - return precompiled +func (p *transpileOptions) isTranspiled(pkg importPath) bool { + _, transpiled := p.transpiled[pkg] + return transpiled } -func (p *precompileOptions) markAsPrecompiled(pkg importPath) { - p.precompiled[pkg] = struct{}{} +func (p *transpileOptions) markAsTranspiled(pkg importPath) { + p.transpiled[pkg] = struct{}{} } -func newPrecompileCmd(io commands.IO) *commands.Command { - cfg := &precompileCfg{} +func newTranspileCmd(io commands.IO) *commands.Command { + cfg := &transpileCfg{} return commands.NewCommand( commands.Metadata{ - Name: "precompile", - ShortUsage: "precompile [flags] [...]", - ShortHelp: "Precompiles .gno files to .go", + Name: "transpile", + ShortUsage: "transpile [flags] [...]", + ShortHelp: "Transpiles .gno files to .go", }, cfg, func(_ context.Context, args []string) error { - return execPrecompile(cfg, args, io) + return execTranspile(cfg, args, io) }, ) } -func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { +func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar( &c.verbose, "verbose", @@ -90,7 +90,7 @@ func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { &c.skipImports, "skip-imports", false, - "do not precompile imports recursively", + "do not transpile imports recursively", ) fs.BoolVar( @@ -122,25 +122,25 @@ func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execPrecompile(cfg *precompileCfg, args []string, io commands.IO) error { +func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } - // precompile .gno files. + // transpile .gno files. paths, err := gnoFilesFromArgs(args) if err != nil { return fmt.Errorf("list paths: %w", err) } - opts := newPrecompileOptions(cfg) + opts := newTranspileOptions(cfg) var errlist scanner.ErrorList for _, filepath := range paths { - if err := precompileFile(filepath, opts); err != nil { + if err := transpileFile(filepath, opts); err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: precompile: %w", filepath, err) + return fmt.Errorf("%s: transpile: %w", filepath, err) } errlist = append(errlist, fileErrlist...) } @@ -169,16 +169,16 @@ func execPrecompile(cfg *precompileCfg, args []string, io commands.IO) error { for _, err := range errlist { io.ErrPrintfln(err.Error()) } - return fmt.Errorf("%d precompile error(s)", errlist.Len()) + return fmt.Errorf("%d transpile error(s)", errlist.Len()) } return nil } -func precompilePkg(pkgPath importPath, opts *precompileOptions) error { - if opts.isPrecompiled(pkgPath) { +func transpilePkg(pkgPath importPath, opts *transpileOptions) error { + if opts.isTranspiled(pkgPath) { return nil } - opts.markAsPrecompiled(pkgPath) + opts.markAsTranspiled(pkgPath) files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) if err != nil { @@ -186,7 +186,7 @@ func precompilePkg(pkgPath importPath, opts *precompileOptions) error { } for _, file := range files { - if err = precompileFile(file, opts); err != nil { + if err = transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -194,7 +194,7 @@ func precompilePkg(pkgPath importPath, opts *precompileOptions) error { return nil } -func precompileFile(srcPath string, opts *precompileOptions) error { +func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() gofmt := flags.gofmtBinary if gofmt == "" { @@ -212,12 +212,12 @@ func precompileFile(srcPath string, opts *precompileOptions) error { } // compute attributes based on filename. - targetFilename, tags := gno.GetPrecompileFilenameAndTags(srcPath) + targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) // preprocess. - precompileRes, err := gno.Precompile(string(source), tags, srcPath) + transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) if err != nil { - return fmt.Errorf("precompile: %w", err) + return fmt.Errorf("transpile: %w", err) } // resolve target path @@ -233,31 +233,31 @@ func precompileFile(srcPath string, opts *precompileOptions) error { } // write .go file. - err = WriteDirFile(targetPath, []byte(precompileRes.Translated)) + err = WriteDirFile(targetPath, []byte(transpileRes.Translated)) if err != nil { return fmt.Errorf("write .go file: %w", err) } // check .go fmt, if `SkipFmt` sets to false. if !flags.skipFmt { - err = gno.PrecompileVerifyFile(targetPath, gofmt) + err = transpiler.TranspileVerifyFile(targetPath, gofmt) if err != nil { return fmt.Errorf("check .go file: %w", err) } } - // precompile imported packages, if `SkipImports` sets to false + // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports { - importPaths := getPathsFromImportSpec(precompileRes.Imports) + importPaths := getPathsFromImportSpec(transpileRes.Imports) for _, path := range importPaths { - precompilePkg(path, opts) + transpilePkg(path, opts) } } return nil } -func goBuildFileOrPkg(fileOrPkg string, cfg *precompileCfg) error { +func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { verbose := cfg.verbose goBinary := cfg.goBinary @@ -265,5 +265,5 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *precompileCfg) error { fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) } - return gno.PrecompileBuildPackage(fileOrPkg, goBinary) + return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) } diff --git a/gnovm/cmd/gno/precompile_test.go b/gnovm/cmd/gno/precompile_test.go index f8436fad768..2770026a01a 100644 --- a/gnovm/cmd/gno/precompile_test.go +++ b/gnovm/cmd/gno/precompile_test.go @@ -9,9 +9,9 @@ import ( "github.com/gnolang/gno/gnovm/pkg/integration" ) -func Test_ScriptsPrecompile(t *testing.T) { +func Test_ScriptsTranspile(t *testing.T) { p := testscript.Params{ - Dir: "testdata/gno_precompile", + Dir: "testdata/gno_transpile", } if coverdir, ok := integration.ResolveCoverageDir(); ok { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 1deac5aac48..082e2066ff5 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,6 +19,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" @@ -33,7 +34,7 @@ type testCfg struct { rootDir string run string timeout time.Duration - precompile bool // TODO: precompile should be the default, but it needs to automatically precompile dependencies in memory. + transpile bool // TODO: transpile should be the default, but it needs to automatically transpile dependencies in memory. updateGoldenTests bool printRuntimeMetrics bool withNativeFallback bool @@ -110,10 +111,10 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { ) fs.BoolVar( - &c.precompile, - "precompile", + &c.transpile, + "transpile", false, - "precompile gno to go before testing", + "transpile gno to go before testing", ) fs.BoolVar( @@ -166,7 +167,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { verbose := cfg.verbose - tempdirRoot, err := os.MkdirTemp("", "gno-precompile") + tempdirRoot, err := os.MkdirTemp("", "gno-transpile") if err != nil { log.Fatal(err) } @@ -174,7 +175,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { // go.mod modPath := filepath.Join(tempdirRoot, "go.mod") - err = makeTestGoMod(modPath, gno.ImportPrefix, "1.21") + err = makeTestGoMod(modPath, transpiler.ImportPrefix, "1.21") if err != nil { return fmt.Errorf("write .mod file: %w", err) } @@ -208,14 +209,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range subPkgs { - if cfg.precompile { + if cfg.transpile { if verbose { io.ErrPrintfln("=== PREC %s", pkg.Dir) } - precompileOpts := newPrecompileOptions(&precompileCfg{ + transpileOpts := newTranspileOptions(&transpileCfg{ output: tempdirRoot, }) - err := precompilePkg(importPath(pkg.Dir), precompileOpts) + err := transpilePkg(importPath(pkg.Dir), transpileOpts) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") @@ -233,7 +234,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { return errors.New("cannot resolve build dir") } - err = goBuildFileOrPkg(tempDir, defaultPrecompileCfg) + err = goBuildFileOrPkg(tempDir, defaultTranspileCfg) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") @@ -317,7 +318,7 @@ func gnoTestPkg( gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) if gnoPkgPath == "" { // unable to read pkgPath from gno.mod, generate a random realm path - gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = transpiler.GnoRealmPkgsPrefixBefore + random.RandStr(8) } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) diff --git a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar index 239d3860e11..6d8592d4e3b 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar @@ -1,6 +1,6 @@ -# Run gno precompile without args +# Run gno transpile without args -! gno precompile +! gno transpile -! stdout .+ +! stdout .+ stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar index 7afc7ed0683..2bd1841d2b4 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar @@ -1,6 +1,6 @@ -# Run gno precompile on an empty dir +# Run gno transpile on an empty dir -gno precompile . +gno transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar index 6e2475e5962..b94b86992af 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar @@ -1,12 +1,12 @@ -# Run gno precompile with gno files with parse errors +# Run gno transpile with gno files with parse errors -! gno precompile . +! gno transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' stderr '^main.gno:7:2: expected declaration, found wrong$' stderr '^sub/sub.gno:3:1: expected declaration, found invalid$' -stderr '^3 precompile error\(s\)$' +stderr '^3 transpile error\(s\)$' # no *.gen.go files are created ! exec test -f main.gno.gen.go diff --git a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar index 0f9d909f627..4770386e7f5 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with valid gno files +# Run gno transpile with valid gno files -gno precompile . +gno transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar index 0f7780efdad..6f7a5e16665 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar @@ -1,8 +1,8 @@ -# Run gno precompile with -skip-fmt flag +# Run gno transpile with -skip-fmt flag # NOTE(tb): this flag doesn't actually prevent the code format, because -# `gnolang.Precompile()` calls `format.Node()`. +# `gnolang.Transpile()` calls `format.Node()`. -gno precompile -skip-fmt . +gno transpile -skip-fmt . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar index b93fae95fcf..23eac452058 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with -gobuild flag +# Run gno transpile with -gobuild flag -gno precompile -gobuild . +gno transpile -gobuild . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar index 85b411e9db1..1c3727e2072 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar @@ -1,11 +1,11 @@ -# Run gno precompile with -gobuild flag +# Run gno transpile with -gobuild flag -! gno precompile -gobuild . +! gno transpile -gobuild . ! stdout .+ stderr '^./main.gno:4:6: x declared and not used$' stderr '^./main.gno:5:6: y declared and not used$' -stderr '^2 precompile error\(s\)$' +stderr '^2 transpile error\(s\)$' cmp main.gno.gen.go main.gno.gen.go.golden cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden diff --git a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar index 74dbfa5d06f..629de2b7f3d 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with -gobuild flag on file with parse error +# Run gno transpile with -gobuild flag on file with parse error -! gno precompile -gobuild . +! gno transpile -gobuild . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar index a64a44f466c..79d4d6a4a2c 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar @@ -1,11 +1,11 @@ -# Run gno precompile with gno files with whitelist errors +# Run gno transpile with gno files with whitelist errors -! gno precompile . +! gno transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' stderr '^sub/sub.gno:3:8: import "xxx" is not in the whitelist$' -stderr '^2 precompile error\(s\)$' +stderr '^2 transpile error\(s\)$' # no *.gen.go files are created ! exec test -f main.gno.gen.go diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar index cc673bb38ff..6d7d73a5e20 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar @@ -5,7 +5,7 @@ gno test . ! stdout .+ stderr '\? \. \[no test files\]' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar index 0ea13294a1b..fa14c412548 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar @@ -5,7 +5,7 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar index c095de6c358..3ffe7adcedd 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar @@ -5,7 +5,7 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar index c739c1ce328..ae1db2b67e5 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar @@ -6,7 +6,7 @@ stdout 'Machine.RunMain\(\) panic: beep boop' stderr '=== RUN file/failing_filetest.gno' stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' -! gno test -verbose --precompile . +! gno test -verbose --transpile . stdout 'Machine.RunMain\(\) panic: beep boop' stderr '=== PREC \.' diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar index 0bf4aab21ff..e5c103fd95b 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar @@ -7,7 +7,7 @@ stderr '=== RUN TestAlwaysFailing' stderr '--- FAIL: TestAlwaysFailing' stderr 'FAIL: 0 build errors, 1 test errors' -! gno test -verbose --precompile . +! gno test -verbose --transpile . ! stdout .+ stderr '=== RUN TestAlwaysFailing' diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index a8e835d4759..c46e5d4e8f0 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" ) func isGnoFile(f fs.DirEntry) bool { @@ -203,8 +203,8 @@ func makeTestGoMod(path string, packageName string, goversion string) error { func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) { for _, i := range importSpec { path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"` - if strings.HasPrefix(path, gno.ImportPrefix) { - res := strings.TrimPrefix(path, gno.ImportPrefix) + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) importPaths = append(importPaths, importPath("."+res)) } } @@ -213,9 +213,9 @@ func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importP // ResolvePath joins the output dir with relative pkg path // e.g -// Output Dir: Temp/gno-precompile +// Output Dir: Temp/gno-transpile // Pkg Path: ../example/gno.land/p/pkg -// Returns -> Temp/gno-precompile/example/gno.land/p/pkg +// Returns -> Temp/gno-transpile/example/gno.land/p/pkg func ResolvePath(output string, path importPath) (string, error) { absOutput, err := filepath.Abs(output) if err != nil { diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 519b183ad3a..d146b3fa7db 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1511,13 +1511,14 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } +// realmPathPrefix is the prefix used to identify pkgpaths which are meant to +// be realms and as such to have their state persisted. This is used by [IsRealmPath]. +const realmPathPrefix = "gno.land/r/" + +// IsRealmPath determines whether the given pkgpath is for a realm, and as such +// should persist the global state. func IsRealmPath(pkgPath string) bool { - // TODO: make it more distinct to distinguish from normal paths. - if strings.HasPrefix(pkgPath, GnoRealmPkgsPrefixBefore) { - return true - } else { - return false - } + return strings.HasPrefix(pkgPath, realmPathPrefix) } func prettyJSON(jstr []byte) []byte { diff --git a/gnovm/pkg/gnolang/precompile.go b/gnovm/pkg/gnolang/transpiler/transpiler.go similarity index 87% rename from gnovm/pkg/gnolang/precompile.go rename to gnovm/pkg/gnolang/transpiler/transpiler.go index af51e962416..d9a3398f2f1 100644 --- a/gnovm/pkg/gnolang/precompile.go +++ b/gnovm/pkg/gnolang/transpiler/transpiler.go @@ -1,4 +1,4 @@ -package gnolang +package transpiler import ( "bytes" @@ -81,13 +81,13 @@ var importPrefixWhitelist = []string{ const ImportPrefix = "github.com/gnolang/gno" -type precompileResult struct { +type transpileResult struct { Imports []*ast.ImportSpec Translated string } -// TODO: func PrecompileFile: supports caching. -// TODO: func PrecompilePkg: supports directories. +// TODO: func TranspileFile: supports caching. +// TODO: func TranspilePkg: supports directories. func guessRootDir(fileOrPkg string, goBinary string) (string, error) { abs, err := filepath.Abs(fileOrPkg) @@ -105,8 +105,8 @@ func guessRootDir(fileOrPkg string, goBinary string) (string, error) { return rootDir, nil } -// GetPrecompileFilenameAndTags returns the filename and tags for precompiled files. -func GetPrecompileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { +// GetTranspileFilenameAndTags returns the filename and tags for transpiled files. +func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { nameNoExtension := strings.TrimSuffix(filepath.Base(gnoFilePath), ".gno") switch { case strings.HasSuffix(gnoFilePath, "_filetest.gno"): @@ -122,7 +122,7 @@ func GetPrecompileFilenameAndTags(gnoFilePath string) (targetFilename, tags stri return } -func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { +func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { gofmt := "gofmt" tmpDir, err := os.MkdirTemp("", mempkg.Name) @@ -136,7 +136,7 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { if !strings.HasSuffix(mfile.Name, ".gno") { continue // skip spurious file. } - res, err := Precompile(mfile.Body, "gno,tmp", mfile.Name) + res, err := Transpile(mfile.Body, "gno,tmp", mfile.Name) if err != nil { errs = multierr.Append(errs, err) continue @@ -147,7 +147,7 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { errs = multierr.Append(errs, err) continue } - err = PrecompileVerifyFile(tmpFile, gofmt) + err = TranspileVerifyFile(tmpFile, gofmt) if err != nil { errs = multierr.Append(errs, err) continue @@ -155,12 +155,12 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { } if errs != nil { - return fmt.Errorf("precompile package: %w", errs) + return fmt.Errorf("transpile package: %w", errs) } return nil } -func Precompile(source string, tags string, filename string) (*precompileResult, error) { +func Transpile(source string, tags string, filename string) (*transpileResult, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) if err != nil { @@ -170,9 +170,9 @@ func Precompile(source string, tags string, filename string) (*precompileResult, isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") shouldCheckWhitelist := !isTestFile - transformed, err := precompileAST(fset, f, shouldCheckWhitelist) + transformed, err := transpileAST(fset, f, shouldCheckWhitelist) if err != nil { - return nil, fmt.Errorf("precompileAST: %w", err) + return nil, fmt.Errorf("transpileAST: %w", err) } header := "// Code generated by github.com/gnolang/gno. DO NOT EDIT.\n\n" @@ -186,17 +186,17 @@ func Precompile(source string, tags string, filename string) (*precompileResult, return nil, fmt.Errorf("format.Node: %w", err) } - res := &precompileResult{ + res := &transpileResult{ Imports: f.Imports, Translated: out.String(), } return res, nil } -// PrecompileVerifyFile tries to run `go fmt` against a precompiled .go file. +// TranspileVerifyFile tries to run `go fmt` against a transpiled .go file. // // This is fast and won't look the imports. -func PrecompileVerifyFile(path string, gofmtBinary string) error { +func TranspileVerifyFile(path string, gofmtBinary string) error { // TODO: use cmd/parser instead of exec? args := strings.Split(gofmtBinary, " ") @@ -210,16 +210,16 @@ func PrecompileVerifyFile(path string, gofmtBinary string) error { return nil } -// PrecompileBuildPackage tries to run `go build` against the precompiled .go files. +// TranspileBuildPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that // all the import are valid and available. -func PrecompileBuildPackage(fileOrPkg, goBinary string) error { +func TranspileBuildPackage(fileOrPkg, goBinary string) error { // TODO: use cmd/compile instead of exec? // TODO: find the nearest go.mod file, chdir in the same folder, rim prefix? // TODO: temporarily create an in-memory go.mod or disable go modules for gno? // TODO: ignore .go files that were not generated from gno? - // TODO: automatically precompile if not yet done. + // TODO: automatically transpile if not yet done. files := []string{} @@ -272,7 +272,7 @@ var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) // Each errors are translated into their correlated gno files, by: // - changing the filename from *.gno.gen.go to *.gno // - shifting line number according to the added header in generated go files -// (see [Precompile] for that header). +// (see [Transpile] for that header). func parseGoBuildErrors(out string) error { var errList goscanner.ErrorList matches := errorRe.FindAllStringSubmatch(out, -1) @@ -293,7 +293,7 @@ func parseGoBuildErrors(out string) error { Filename: strings.TrimSuffix(filename, ".gen.go"), // Shift the 4 lines header added in *.gen.go files. // NOTE(tb): the 4 lines shift below assumes there's always a //go:build - // directive. But the tags are optional in the Precompile() function + // directive. But the tags are optional in the Transpile() function // so that leaves some doubts... We might want something more reliable than // constants to shift lines. Line: line - 4, @@ -303,7 +303,7 @@ func parseGoBuildErrors(out string) error { return errList.Err() } -func precompileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { +func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) diff --git a/gnovm/pkg/gnolang/precompile_test.go b/gnovm/pkg/gnolang/transpiler/transpiler_test.go similarity index 96% rename from gnovm/pkg/gnolang/precompile_test.go rename to gnovm/pkg/gnolang/transpiler/transpiler_test.go index 3f36a16a5e8..03659be6c37 100644 --- a/gnovm/pkg/gnolang/precompile_test.go +++ b/gnovm/pkg/gnolang/transpiler/transpiler_test.go @@ -1,4 +1,4 @@ -package gnolang +package transpiler import ( "go/ast" @@ -11,7 +11,7 @@ import ( "github.com/jaekwon/testify/require" ) -func TestPrecompile(t *testing.T) { +func TestTranspile(t *testing.T) { t.Parallel() cases := []struct { @@ -196,7 +196,7 @@ import "reflect" func foo() { _ = reflect.ValueOf } `, - expectedError: `precompileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, + expectedError: `transpileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, }, { name: "syntax-error", @@ -268,7 +268,7 @@ func foo() { _ = regexp.MatchString } // "\n" is added for better test case readability, now trim it source := strings.TrimPrefix(c.source, "\n") - res, err := Precompile(source, c.tags, "foo.gno") + res, err := Transpile(source, c.tags, "foo.gno") if c.expectedError != "" { require.EqualError(t, err, c.expectedError) diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index 25189ebc71d..d1dff4ee816 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -184,12 +184,12 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { continue } // skip if `std`, special case. - if path == gnolang.GnoStdPkgAfter { + if path == transpiler.GnoStdPkgAfter { continue } - if strings.HasPrefix(path, gnolang.ImportPrefix) { - path = strings.TrimPrefix(path, gnolang.ImportPrefix+"/examples/") + if strings.HasPrefix(path, transpiler.ImportPrefix) { + path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index d750b7c9f29..54a30df73dd 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" "golang.org/x/mod/module" @@ -61,21 +62,21 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err } } else { // Is File - // Precompile and write generated go file + // Transpile and write generated go file if strings.HasSuffix(fileName, ".gno") { filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := gnolang.GetPrecompileFilenameAndTags(filePath) - precompileRes, err := gnolang.Precompile(string(res.Data), "", fileName) + targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) + transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) if err != nil { - return nil, fmt.Errorf("precompile: %w", err) + return nil, fmt.Errorf("transpile: %w", err) } - for _, i := range precompileRes.Imports { + for _, i := range transpileRes.Imports { requirements = append(requirements, i.Path.Value) } targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) - err = os.WriteFile(targetFileNameWithPath, []byte(precompileRes.Translated), 0o644) + err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) if err != nil { return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) } @@ -97,9 +98,9 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err func GnoToGoMod(f File) (*File, error) { gnoModPath := GetGnoModPath() - if strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoPackagePrefixBefore) { - f.AddModuleStmt(gnolang.ImportPrefix + "/examples/" + f.Module.Mod.Path) + if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || + strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPackagePrefixBefore) { + f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) } for i := range f.Require { @@ -110,10 +111,10 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoPackagePrefixBefore) { + if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || + strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPackagePrefixBefore) { // Add dependency with a modified import path - f.AddRequire(gnolang.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) + f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) } f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") // Remove the old require since the new dependency was added above From 9409285591d21bddc3a9c6f7f0d1d6122d0843f4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 14:17:00 +0100 Subject: [PATCH 02/34] move transpiler to pkg --- gno.land/pkg/gnoclient/client_txs.go | 2 +- gno.land/pkg/keyscli/addpkg.go | 2 +- gno.land/pkg/keyscli/run.go | 2 +- gnovm/cmd/gno/test.go | 2 +- .../testdata/{gno_precompile => gno_transpile}/01_no_args.txtar | 0 .../{gno_precompile => gno_transpile}/02_empty_dir.txtar | 0 .../03_gno_files_parse_error.txtar | 0 .../{gno_precompile => gno_transpile}/04_valid_gno_files.txtar | 0 .../{gno_precompile => gno_transpile}/05_skip_fmt_flag.txtar | 0 .../{gno_precompile => gno_transpile}/06_build_flag.txtar | 0 .../07_build_flag_with_build_error.txtar | 0 .../08_build_flag_with_parse_error.txtar | 0 .../09_gno_files_whitelist_error.txtar | 0 gnovm/cmd/gno/{precompile.go => transpile.go} | 2 +- gnovm/cmd/gno/{precompile_test.go => transpile_test.go} | 0 gnovm/cmd/gno/util.go | 2 +- gnovm/pkg/gnomod/file.go | 2 +- gnovm/pkg/gnomod/gnomod.go | 2 +- gnovm/pkg/{gnolang => }/transpiler/transpiler.go | 0 gnovm/pkg/{gnolang => }/transpiler/transpiler_test.go | 0 20 files changed, 8 insertions(+), 8 deletions(-) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/01_no_args.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/02_empty_dir.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/03_gno_files_parse_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/04_valid_gno_files.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/05_skip_fmt_flag.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/06_build_flag.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/07_build_flag_with_build_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/08_build_flag_with_parse_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/09_gno_files_whitelist_error.txtar (100%) rename gnovm/cmd/gno/{precompile.go => transpile.go} (99%) rename gnovm/cmd/gno/{precompile_test.go => transpile_test.go} (100%) rename gnovm/pkg/{gnolang => }/transpiler/transpiler.go (100%) rename gnovm/pkg/{gnolang => }/transpiler/transpiler_test.go (100%) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 34759b565e3..ece7507d569 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,7 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 82aef1315d4..1f89b2c2b82 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,7 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 3dd1879bcfa..6fcee7434eb 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 082e2066ff5..a02c1abf192 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,7 +19,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" diff --git a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar diff --git a/gnovm/cmd/gno/precompile.go b/gnovm/cmd/gno/transpile.go similarity index 99% rename from gnovm/cmd/gno/precompile.go rename to gnovm/cmd/gno/transpile.go index 7b32df556b5..b7708469e9d 100644 --- a/gnovm/cmd/gno/precompile.go +++ b/gnovm/cmd/gno/transpile.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) diff --git a/gnovm/cmd/gno/precompile_test.go b/gnovm/cmd/gno/transpile_test.go similarity index 100% rename from gnovm/cmd/gno/precompile_test.go rename to gnovm/cmd/gno/transpile_test.go diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index c46e5d4e8f0..30d8f808d04 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func isGnoFile(f fs.DirEntry) bool { diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index d1dff4ee816..fda9263914e 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 54a30df73dd..af946f2bf39 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" "golang.org/x/mod/module" diff --git a/gnovm/pkg/gnolang/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go similarity index 100% rename from gnovm/pkg/gnolang/transpiler/transpiler.go rename to gnovm/pkg/transpiler/transpiler.go diff --git a/gnovm/pkg/gnolang/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go similarity index 100% rename from gnovm/pkg/gnolang/transpiler/transpiler_test.go rename to gnovm/pkg/transpiler/transpiler_test.go From 37c9e6690b64703670a9049191a7504cd6027fab Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 21:15:29 +0100 Subject: [PATCH 03/34] fixup --- .github/workflows/examples.yml | 2 +- docs/getting-started/local-setup.md | 2 +- gnovm/cmd/gno/test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index e5178c3e4c2..53d6a58abdd 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -32,7 +32,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno precompile --verbose --gobuild ./examples + - run: go run ./gnovm/cmd/gno transpile --verbose --gobuild ./examples test: strategy: fail-fast: false diff --git a/docs/getting-started/local-setup.md b/docs/getting-started/local-setup.md index cbef4522477..753b6688b1a 100644 --- a/docs/getting-started/local-setup.md +++ b/docs/getting-started/local-setup.md @@ -28,7 +28,7 @@ git clone https://github.com/gnolang/gno.git ## 2. Installing the `gno` development toolkit Next, we are going to build and install the `gno` development toolkit. -`gno` provides ample functionality to the user, among which is running, precompiling, testing and building `.gno` files. +`gno` provides ample functionality to the user, among which is running, transpiling, testing and building `.gno` files. To install the toolkit, navigate to the `gnovm` folder from the repository root, and run the `build` make directive: diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index a02c1abf192..ca64227aada 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,8 +19,8 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" From a889deab8e04a7ad927f6c2969f9d06adf681783 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 21:09:00 +0100 Subject: [PATCH 04/34] begin removing support for linked identifiers --- gnovm/stdlibs/std/banker.gno | 68 +++++++++++---- gnovm/stdlibs/std/banker.go | 138 ++++++++++++------------------ gnovm/stdlibs/std/context.gno | 4 - gnovm/stdlibs/std/context.go | 2 +- gnovm/stdlibs/std/native.gno | 73 ++++++++++++---- gnovm/stdlibs/std/native.go | 154 +++++++++++----------------------- 6 files changed, 211 insertions(+), 228 deletions(-) delete mode 100644 gnovm/stdlibs/std/context.gno diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 44e611b780f..9eea149062e 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -22,10 +22,11 @@ type Banker interface { RemoveCoin(addr Address, denom string, amount int64) } -// Also available natively in stdlibs/context.go +// BankerType represents the "permission level" requested for a banker, +// retrievable through [GetBanker]. type BankerType uint8 -// Also available natively in stdlibs/context.go +// Available types of banker. const ( // Can only read state. BankerTypeReadonly BankerType = iota @@ -35,36 +36,69 @@ const ( BankerTypeRealmSend // Can issue and remove realm coins. BankerTypeRealmIssue + + maxBanker ) //---------------------------------------- // adapter for native banker -type bankAdapter struct { - nativeBanker Banker +// GetBanker returns a new Banker, with its capabilities matching the given +// [BankerType]. +func GetBanker(bt BankerType) Banker { + if bt >= maxBanker { + panic("invalid banker type") + } + return banker{bt} +} + +// These are native bindings to the banker's functions. +func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) +func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) +func bankerTotalCoin(bt uint8, denom string) int64 +func bankerIssueCoin(bt uint8, addr string, denom string, amount string) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) + +type banker struct { + bt uint8 } -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) +func (b banker) GetCoins(addr Address) (dst Coins) { + denoms, amounts := bankerGetCoins(b.bt, string(addr)) + dst = make(Coins, len(denoms)) + for i := range dst { + dst[i] = Coin{denoms[i], amounts[i]} } return dst } -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) +func (b banker) SendCoins(from, to Address, amt Coins) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot send coins") + } + denoms := make([]string, len(amt)) + amounts := make([]int64, len(amt)) + for i, coin := range amt { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) } -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) +func (b banker) TotalCoin(denom string) int64 { + return bankerTotalCoin(b.bt, denom) } -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) +func (b banker) IssueCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot issue coins") + } + bankerIssueCoin(b.bt, string(addr), denom, amount) } -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) +func (b banker) RemoveCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot remove coins") + } + bankerRemoveCoin(b.bt, string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 7653f2a519f..695b4a26aa5 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -3,16 +3,17 @@ package std import ( "fmt" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) -// This has the same interface as stdlibs/std.Banker. -// The native implementation of Banker (wrapped by any -// wrappers for limiting functionality as necessary) -// becomes available in Gno that implements -// stdlibs/std.Banker. -type Banker interface { +// BankerInterface is the interface through which Gno is capable of accessing +// the blockchain's banker. +// +// The name is what it is to avoid a collision with Gno's Banker, when +// transpiling. +type BankerInterface interface { GetCoins(addr crypto.Bech32Address) (dst std.Coins) SendCoins(from, to crypto.Bech32Address, amt std.Coins) TotalCoin(denom string) int64 @@ -20,98 +21,63 @@ type Banker interface { RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) } -// Used in std.GetBanker(options). -// Also available as Gno in stdlibs/std/banker.gno -type BankerType uint8 - -// Also available as Gno in stdlibs/std/banker.gno const ( // Can only read state. - BankerTypeReadonly BankerType = iota + btReadonly uint8 = iota // Can only send from tx send. - BankerTypeOrigSend + btOrigSend // Can send from all realm coins. - BankerTypeRealmSend + btRealmSend // Can issue and remove realm coins. - BankerTypeRealmIssue + btRealmIssue ) -//---------------------------------------- -// ReadonlyBanker - -type ReadonlyBanker struct { - banker Banker -} - -func NewReadonlyBanker(banker Banker) ReadonlyBanker { - return ReadonlyBanker{banker} -} - -func (rb ReadonlyBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rb.banker.GetCoins(addr) -} - -func (rb ReadonlyBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - panic("ReadonlyBanker cannot send coins") -} - -func (rb ReadonlyBanker) TotalCoin(denom string) int64 { - return rb.banker.TotalCoin(denom) -} - -func (rb ReadonlyBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot issue coins") -} - -func (rb ReadonlyBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot remove coins") -} - -//---------------------------------------- -// OrigSendBanker - -type OrigSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address - origSend std.Coins - origSendSpent *std.Coins -} - -func NewOrigSendBanker(banker Banker, pkgAddr crypto.Bech32Address, origSend std.Coins, origSendSpent *std.Coins) OrigSendBanker { - if origSendSpent == nil { - panic("origSendSpent cannot be nil") - } - return OrigSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - origSend: origSend, - origSendSpent: origSendSpent, +func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) + denoms = make([]string, len(coins)) + amounts = make([]string, len(coins)) + for i, coin := range coins { + denoms[i] = coin.Denom + amounts[i] = coin.Amounts } -} - -func (osb OrigSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return osb.banker.GetCoins(addr) -} - -func (osb OrigSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) - } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + return denoms, amounts +} + +// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) +// - Make all the X_banker functions work as they should +// - Remove the identifier mapping logic from genstd. +// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. +// - Make precompilation understand that bodyless function -> native function. +// - Add whether the *gno.Machine parameter is present to the native function data. +// When you get back on track for the transpiler: +// - Make StaticCheck work +// - Add a way to recursively precompile dependencies +// - Work until gno transpile --gobuild can create fully buildable go code! + +func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { + // bt != BankerTypeReadonly (checked in gno) + if bt == btOrigSend { + if from != osb.pkgAddr { + panic(fmt.Sprintf( + "OrigSendBanker can only send from the realm package address %q, but got %q", + osb.pkgAddr, from)) + } + spent := (*osb.origSendSpent).Add(amt) + if !osb.origSend.IsAllGTE(spent) { + panic(fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, osb.origSend, *osb.origSendSpent)) + } + osb.banker.SendCoins(from, to, amt) + *osb.origSendSpent = spent } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent } -func (osb OrigSendBanker) TotalCoin(denom string) int64 { - return osb.banker.TotalCoin(denom) +func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { + return m.Context.(ExecContext).Banker.TotalCoin(denom) } +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { panic("OrigSendBanker cannot issue coins") diff --git a/gnovm/stdlibs/std/context.gno b/gnovm/stdlibs/std/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/std/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index c50e2e5e1b9..72ca7445aef 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -16,5 +16,5 @@ type ExecContext struct { OrigPkgAddr crypto.Bech32Address OrigSend std.Coins OrigSendSpent *std.Coins // mutable - Banker Banker + Banker BankerInterface } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 2f7da810bcb..b922376bc71 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,18 +1,59 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func CurrentRealmPath() string // injected -func GetChainID() string // injected -func GetHeight() int64 // injected -func GetOrigSend() Coins // injected -func GetOrigCaller() Address // injected -func CurrentRealm() Realm // injected -func PrevRealm() Realm // injected -func GetOrigPkgAddr() Address // injected -func GetCallerAt(n int) Address // injected -func GetBanker(bt BankerType) Banker // injected -func DerivePkgAddr(pkgPath string) Address // injected - -func EncodeBech32(prefix string, bz [20]byte) Address // injected -func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected + +func GetOrigSend() Coins { + den, amt := origSend() + coins := make(Coins, len(den)) + for i := range coins { + coins[i] = Coin{Denom: den[i], Amount: amt[i]} + } + return coins +} + +func GetOrigCaller() Address { + return Address(origCaller()) +} + +func CurrentRealm() Realm { + addr, path := getRealm(0) + return Realm{addr, path} +} + +func PrevRealm() Realm { + addr, path := getRealm(1) + return Realm{addr, path} +} + +func GetOrigPkgAddr() Address { + return Address(origPkgAddr()) +} + +func GetCallerAt(n int) Address { + return Address(callerAt(n)) +} +func DerivePkgAddr(pkgPath string) Address { + return Address(derivePkgAddr(pkgPath)) +} + +func EncodeBech32(prefix string, bz [20]byte) Address { + return Address(encodeBech32(prefix, bz)) +} + +func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { + return decodeBech32(string(addr)) +} + +// Variations which don't use named types. +func origSend() (denoms []string, amounts []int64) +func origCaller() string +func origPkgAddr() string +func callerAt(n int) string +func getRealm(height int) (address string, pkgPath string) +func derivePkgAddr(pkgPath string) string +func encodeBech32(prefix string, bz [20]byte) string +func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 044badaa308..344db126fb8 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,12 +1,9 @@ package std import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -34,82 +31,26 @@ func GetHeight(m *gno.Machine) int64 { return m.Context.(ExecContext).Height } -func GetOrigSend(m *gno.Machine) std.Coins { - return m.Context.(ExecContext).OrigSend -} - -func GetOrigCaller(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigCaller -} - -func CurrentRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage != nil && fr.LastPackage.IsRealm() { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = fr.LastPackage.PkgPath - break - } - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, +func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := m.Context.(ExecContext).OrigSend + denoms = make([]string, len(os)) + amounts = make([]int64, len(os)) + for i, coin := range os { + denoms[i] = coin.Denom + amounts[i] = coin.Amount } + return denoms, amounts } -func PrevRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { - // Ignore non-realm frame - continue - } - pkgPath := fr.LastPackage.PkgPath - // The first realm we encounter will be the one calling - // this function; to get the calling realm determine the first frame - // where fr.LastPackage changes. - if lastPkgPath == "" { - lastPkgPath = pkgPath - } else if lastPkgPath == pkgPath { - continue - } else { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = pkgPath - break - } - } - - // Empty the pkgPath if we return a user - if ctx.OrigCaller == lastCaller { - lastPkgPath = "" - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - } +func X_origCaller(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigCaller) } -func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigPkgAddr +func X_origPkgAddr(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigPkgAddr) } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" @@ -124,52 +65,57 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { if n == m.NumFrames() { // This makes it consistent with GetOrigCaller. ctx := m.Context.(ExecContext) - return ctx.OrigCaller + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { - ctx := m.Context.(ExecContext) - banker := ctx.Banker - switch bankerType { - case BankerTypeReadonly: - banker = NewReadonlyBanker(banker) - case BankerTypeOrigSend: - banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) - case BankerTypeRealmSend: - banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) - case BankerTypeRealmIssue: - banker = banker - default: - panic("should not happen") // defensive +func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { + var ( + ctx = m.Context.(ExecContext) + currentCaller crypto.Bech32Address + // Keeps track of the number of times currentCaller + // has changed. + changes int + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // current* values. + caller := fr.LastPackage.GetPkgAddr().Bech32() + pkgPath := fr.LastPackage.PkgPath + if caller != currentCaller { + if changes == height { + return string(caller), pkgPath + } + currentCaller = caller + changes++ + } } - m.Alloc.AllocateStruct() // defensive; native space not allocated. - m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - // make gno bankAdapter{rv} - btv := gno.Go2GnoNativeValue(m.Alloc, reflect.ValueOf(banker)) - bsv := m.Alloc.NewStructWithFields(btv) - bankAdapterType := m.Store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) - res0 := gno.TypedValue{T: bankAdapterType, V: bsv} + // Fallback case: return OrigCaller. + return string(ctx.OrigCaller), "" +} - return res0 +func X_derivePkgAddr(pkgPath string) string { + return string(gno.DerivePkgAddr(pkgPath).Bech32()) } -func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { +func X_encodeBech32(prefix string, bytes [20]byte) string { b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) if err != nil { panic(err) // should not happen } - return crypto.Bech32Address(b32) -} - -func DerivePkgAddr(pkgPath string) crypto.Bech32Address { - return gno.DerivePkgAddr(pkgPath).Bech32() + return b32 } -func DecodeBech32(addr crypto.Bech32Address) (prefix string, bytes [20]byte, ok bool) { - prefix, bz, err := bech32.Decode(string(addr)) +func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { + prefix, bz, err := bech32.Decode(addr) if err != nil || len(bz) != 20 { return "", [20]byte{}, false } From aae89db370d6a74865fdc4b6eaeb575f99a2ce3c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:56:05 +0100 Subject: [PATCH 05/34] convert native bindings to never use linked types --- gnovm/stdlibs/native.go | 270 ++++++++++++++++++------ gnovm/stdlibs/std/banker.gno | 48 +++-- gnovm/stdlibs/std/banker.go | 118 ++++------- gnovm/stdlibs/std/coins.gno | 12 +- gnovm/stdlibs/std/frame.go | 20 -- gnovm/stdlibs/std/native.gno | 4 +- gnovm/stdlibs/std/native.go | 33 ++- gnovm/stdlibs/stdlibs.go | 1 - gnovm/tests/files/zrealm_natbind0.gno | 57 ++--- gnovm/tests/stdlibs/native.go | 64 +++--- gnovm/tests/stdlibs/std/std.gno | 38 +++- gnovm/tests/stdlibs/std/std.go | 40 ++-- misc/genstd/exprstring.go | 290 -------------------------- misc/genstd/mapping.go | 98 +-------- 14 files changed, 431 insertions(+), 662 deletions(-) delete mode 100644 gnovm/stdlibs/std/frame.go delete mode 100644 misc/genstd/exprstring.go diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 4125c56ffe1..0cb7d9a7859 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -13,7 +13,6 @@ import ( libs_strconv "github.com/gnolang/gno/gnovm/stdlibs/strconv" libs_testing "github.com/gnolang/gno/gnovm/stdlibs/testing" libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" ) type nativeFunc struct { @@ -162,27 +161,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "AssertOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{}, + "bankerGetCoins", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, + }, func(m *gno.Machine) { - libs_std.AssertOriginCall( - m, + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := libs_std.X_bankerGetCoins( + m, + p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, + "bankerSendCoins", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("[]string")}, + {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.IsOriginCall( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []string + rp3 = reflect.ValueOf(&p3).Elem() + p4 []int64 + rp4 = reflect.ValueOf(&p4).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 4, "")).TV, rp4) + + libs_std.X_bankerSendCoins( m, + p0, p1, p2, p3, p4) + }, + }, + { + "std", + "bankerTotalCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_std.X_bankerTotalCoin( + m, + p0, p1) + m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, @@ -192,32 +270,90 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealmPath", - []gno.FieldTypeExpr{}, + "bankerIssueCoin", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.CurrentRealmPath( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerIssueCoin( m, + p0, p1, p2, p3) + }, + }, + { + "std", + "bankerRemoveCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() ) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerRemoveCoin( + m, + p0, p1, p2, p3) }, }, { "std", - "GetChainID", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + libs_std.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r0"), Type: gno.X("bool")}, }, func(m *gno.Machine) { - r0 := libs_std.GetChainID( + r0 := libs_std.IsOriginCall( m, ) @@ -230,13 +366,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetHeight", + "CurrentRealmPath", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("int64")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetHeight( + r0 := libs_std.CurrentRealmPath( m, ) @@ -249,13 +385,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigSend", + "GetChainID", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Coins")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigSend( + r0 := libs_std.GetChainID( m, ) @@ -268,13 +404,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigCaller", + "GetHeight", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigCaller( + r0 := libs_std.GetHeight( m, ) @@ -287,13 +423,14 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealm", + "origSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, func(m *gno.Machine) { - r0 := libs_std.CurrentRealm( + r0, r1 := libs_std.X_origSend( m, ) @@ -302,17 +439,22 @@ var nativeFuncs = [...]nativeFunc{ m.Store, reflect.ValueOf(&r0).Elem(), )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "PrevRealm", + "origCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.PrevRealm( + r0 := libs_std.X_origCaller( m, ) @@ -325,13 +467,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigPkgAddr", + "origPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigPkgAddr( + r0 := libs_std.X_origPkgAddr( m, ) @@ -344,12 +486,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -360,7 +502,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetCallerAt( + r0 := libs_std.X_callerAt( m, p0) @@ -373,37 +515,47 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetBanker", + "getRealm", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("BankerType")}, + {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Banker")}, + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 libs_std.BankerType + p0 int rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetBanker( + r0, r1 := libs_std.X_getRealm( m, p0) - m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "DerivePkgAddr", + "derivePkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -414,7 +566,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.DerivePkgAddr(p0) + r0 := libs_std.X_derivePkgAddr(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -425,13 +577,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "EncodeBech32", + "encodeBech32", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, {Name: gno.N("p1"), Type: gno.X("[20]byte")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -445,7 +597,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - r0 := libs_std.EncodeBech32(p0, p1) + r0 := libs_std.X_encodeBech32(p0, p1) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -456,9 +608,9 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "DecodeBech32", + "decodeBech32", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, @@ -468,13 +620,13 @@ var nativeFuncs = [...]nativeFunc{ func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0, r1, r2 := libs_std.DecodeBech32(p0) + r0, r1, r2 := libs_std.X_decodeBech32(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 9eea149062e..958fea369a3 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -1,5 +1,7 @@ package std +import "strconv" + // Realm functions can call std.GetBanker(options) to get // a banker instance. Banker objects cannot be persisted, // but can be passed onto other functions to be transacted @@ -40,6 +42,21 @@ const ( maxBanker ) +func (b BankerType) String() string { + switch b { + case BankerTypeReadonly: + return "BankerTypeReadonly" + case BankerTypeOrigSend: + return "BankerTypeOrigSend" + case BankerTypeRealmSend: + return "BankerTypeRealmSend" + case BankerTypeRealmIssue: + return "BankerTypeRealmIssue" + default: + panic("invalid BankerType: " + strconv.Itoa(int(b))) + } +} + //---------------------------------------- // adapter for native banker @@ -56,15 +73,15 @@ func GetBanker(bt BankerType) Banker { func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) func bankerTotalCoin(bt uint8, denom string) int64 -func bankerIssueCoin(bt uint8, addr string, denom string, amount string) -func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) +func bankerIssueCoin(bt uint8, addr string, denom string, amount int64) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount int64) type banker struct { - bt uint8 + bt BankerType } func (b banker) GetCoins(addr Address) (dst Coins) { - denoms, amounts := bankerGetCoins(b.bt, string(addr)) + denoms, amounts := bankerGetCoins(uint8(b.bt), string(addr)) dst = make(Coins, len(denoms)) for i := range dst { dst[i] = Coin{denoms[i], amounts[i]} @@ -76,29 +93,24 @@ func (b banker) SendCoins(from, to Address, amt Coins) { if b.bt == BankerTypeReadonly { panic("BankerTypeReadonly cannot send coins") } - denoms := make([]string, len(amt)) - amounts := make([]int64, len(amt)) - for i, coin := range amt { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) + denoms, amounts := amt.expandNative() + bankerSendCoins(uint8(b.bt), string(from), string(to), denoms, amounts) } func (b banker) TotalCoin(denom string) int64 { - return bankerTotalCoin(b.bt, denom) + return bankerTotalCoin(uint8(b.bt), denom) } func (b banker) IssueCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot issue coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot issue coins") } - bankerIssueCoin(b.bt, string(addr), denom, amount) + bankerIssueCoin(uint8(b.bt), string(addr), denom, amount) } func (b banker) RemoveCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot remove coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot remove coins") } - bankerRemoveCoin(b.bt, string(addr), denom, amount) + bankerRemoveCoin(uint8(b.bt), string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 695b4a26aa5..243d457b9be 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -33,96 +33,58 @@ const ( ) func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { - coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) - denoms = make([]string, len(coins)) - amounts = make([]string, len(coins)) - for i, coin := range coins { - denoms[i] = coin.Denom - amounts[i] = coin.Amounts - } - return denoms, amounts + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) + return ExpandCoins(coins) } -// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) -// - Make all the X_banker functions work as they should -// - Remove the identifier mapping logic from genstd. -// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. -// - Make precompilation understand that bodyless function -> native function. -// - Add whether the *gno.Machine parameter is present to the native function data. -// When you get back on track for the transpiler: -// - Make StaticCheck work -// - Add a way to recursively precompile dependencies -// - Work until gno transpile --gobuild can create fully buildable go code! - -func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { +func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []string, amounts []int64) { // bt != BankerTypeReadonly (checked in gno) - if bt == btOrigSend { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) + + ctx := m.Context.(ExecContext) + amt := CompactCoins(denoms, amounts) + from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) + + if bt == btOrigSend || bt == btRealmSend { + if from != ctx.OrigPkgAddr { + m.Panic(typedString( + fmt.Sprintf( + "can only send from the realm package address %q, but got %q", + ctx.OrigPkgAddr, from), + )) + return } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + } + + switch bt { + case btOrigSend: + // indirection allows us to "commit" in a second phase + spent := (*ctx.OrigSendSpent).Add(amt) + if !ctx.OrigSend.IsAllGTE(spent) { + m.Panic(typedString( + fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, ctx.OrigSend, *ctx.OrigSendSpent), + )) } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent + ctx.Banker.SendCoins(from, to, amt) + *ctx.OrigSendSpent = spent + case btRealmSend, btRealmIssue: + ctx.Banker.SendCoins(from, to, amt) + default: + panic(fmt.Sprintf("invalid banker type %d in bankerSendCoins", bt)) } } func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { return m.Context.(ExecContext).Banker.TotalCoin(denom) } -func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) -func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) - -func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot issue coins") -} - -func (osb OrigSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot remove coins") -} - -//---------------------------------------- -// RealmSendBanker - -type RealmSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address -} - -func NewRealmSendBanker(banker Banker, pkgAddr crypto.Bech32Address) RealmSendBanker { - return RealmSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - } -} - -func (rsb RealmSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rsb.banker.GetCoins(addr) -} - -func (rsb RealmSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != rsb.pkgAddr { - panic(fmt.Sprintf( - "RealmSendBanker can only send from the realm package address %q, but got %q", - rsb.pkgAddr, from)) - } - rsb.banker.SendCoins(from, to, amt) -} - -func (rsb RealmSendBanker) TotalCoin(denom string) int64 { - return rsb.banker.TotalCoin(denom) -} -func (rsb RealmSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot issue coins") +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } -func (rsb RealmSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot remove coins") +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index aaede81309d..dd138d30822 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -4,6 +4,7 @@ import "strconv" // NOTE: this is selectly copied over from pkgs/std/coin.go // TODO: import all functionality(?). +// TODO: implement Coin/Coins constructors. // Coin hold some amount of one currency. // A negative amount is invalid. @@ -62,4 +63,13 @@ func (a Coins) Add(b Coins) Coins { return c } -// TODO implement Coin/Coins constructors. +// expand for usage within natively bound functions. +func (cz Coins) expandNative() (denoms []string, amounts []int64) { + denoms = make([]string, len(cz)) + amounts = make([]int64, len(cz)) + for i, coin := range cz { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} diff --git a/gnovm/stdlibs/std/frame.go b/gnovm/stdlibs/std/frame.go deleted file mode 100644 index 2948813ad0f..00000000000 --- a/gnovm/stdlibs/std/frame.go +++ /dev/null @@ -1,20 +0,0 @@ -package std - -import "github.com/gnolang/gno/tm2/pkg/crypto" - -type Realm struct { - addr crypto.Bech32Address - pkgPath string -} - -func (r Realm) Addr() crypto.Bech32Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index b922376bc71..c079e0dd42b 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -21,12 +21,12 @@ func GetOrigCaller() Address { func CurrentRealm() Realm { addr, path := getRealm(0) - return Realm{addr, path} + return Realm{Address(addr), path} } func PrevRealm() Realm { addr, path := getRealm(1) - return Realm{addr, path} + return Realm{Address(addr), path} } func GetOrigPkgAddr() Address { diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 344db126fb8..0965ef74277 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -4,6 +4,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -33,13 +34,7 @@ func GetHeight(m *gno.Machine) int64 { func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { os := m.Context.(ExecContext).OrigSend - denoms = make([]string, len(os)) - amounts = make([]int64, len(os)) - for i, coin := range os { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - return denoms, amounts + return ExpandCoins(os) } func X_origCaller(m *gno.Machine) string { @@ -55,6 +50,8 @@ func X_callerAt(m *gno.Machine, n int) string { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -122,8 +119,26 @@ func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { return prefix, [20]byte(bz), true } -func typedString(s gno.StringValue) gno.TypedValue { +func typedString(s string) gno.TypedValue { tv := gno.TypedValue{T: gno.StringType} - tv.SetString(s) + tv.SetString(gno.StringValue(s)) return tv } + +func ExpandCoins(c std.Coins) (denoms []string, amounts []int64) { + denoms = make([]string, len(c)) + amounts = make([]int64, len(c)) + for i, coin := range c { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} + +func CompactCoins(denoms []string, amounts []int64) std.Coins { + coins := make(std.Coins, len(denoms)) + for i := range coins { + coins[i] = std.Coin{Denom: denoms[i], Amount: amounts[i]} + } + return coins +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 22054613c03..1e215b098e2 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -17,7 +17,6 @@ func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") - store.AddGo2GnoMapping(reflect.TypeOf(libsstd.Realm{}), "std", "Realm") } func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 60e0d448202..0d614f5e8a1 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -8,20 +8,23 @@ import ( var node interface{} func init() { - node = std.GetOrigCaller + node = std.GetHeight } func main() { - f := node.(func() std.Address) + // NOTE: this test uses GetHeight and CurrentRealmPath, which are "pure" + // natively bound functions (ie. not indirections through a wrapper fn, + // to convert the types to builtin go/gno identifiers). + f := node.(func() int64) println(f()) - node = std.DerivePkgAddr - g := node.(func(path string) std.Address) - println(g("x")) + node = std.CurrentRealmPath + g := node.(func() string) + println(g()) } // Output: -// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// g19kt9e22k34ny5jf5plrjdltmws0jc0qqd2cwky +// 123 +// gno.land/r/test // Realm: // switchrealm["gno.land/r/test"] @@ -120,25 +123,15 @@ func main() { // { // "T": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] @@ -148,12 +141,12 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:5" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:6" // }, // "FileName": "native.gno", // "IsMethod": false, -// "Name": "DerivePkgAddr", -// "NativeName": "DerivePkgAddr", +// "Name": "CurrentRealmPath", +// "NativeName": "CurrentRealmPath", // "NativePkg": "std", // "PkgPath": "std", // "Source": { @@ -161,32 +154,22 @@ func main() { // "BlockNode": null, // "Location": { // "File": "native.gno", -// "Line": "15", +// "Line": "5", // "Nonce": "0", // "PkgPath": "std" // } // }, // "Type": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 373be0ad23b..6964a003025 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -9,8 +9,6 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" - tm2_std "github.com/gnolang/gno/tm2/pkg/std" ) type nativeFunc struct { @@ -105,12 +103,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -121,7 +119,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := testlibs_std.GetCallerAt( + r0 := testlibs_std.X_callerAt( m, p0) @@ -134,94 +132,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "TestSetOrigCaller", + "testSetOrigCaller", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigCaller( + testlibs_std.X_testSetOrigCaller( m, p0) }, }, { "std", - "TestSetOrigPkgAddr", + "testSetOrigPkgAddr", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigPkgAddr( + testlibs_std.X_testSetOrigPkgAddr( m, p0) }, }, { "std", - "TestSetOrigSend", + "testSetOrigSend", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Coins")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("[]string")}, + {Name: gno.N("p1"), Type: gno.X("[]int64")}, + {Name: gno.N("p2"), Type: gno.X("[]string")}, + {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_std.Coins + p0 []string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []int64 rp1 = reflect.ValueOf(&p1).Elem() + p2 []string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []int64 + rp3 = reflect.ValueOf(&p3).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - testlibs_std.TestSetOrigSend( + testlibs_std.X_testSetOrigSend( m, - p0, p1) + p0, p1, p2, p3) }, }, { "std", - "TestIssueCoins", + "testIssueCoins", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[]string")}, + {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []string rp1 = reflect.ValueOf(&p1).Elem() + p2 []int64 + rp2 = reflect.ValueOf(&p2).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) - testlibs_std.TestIssueCoins( + testlibs_std.X_testIssueCoins( m, - p0, p1) + p0, p1, p2) }, }, { diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 380549be694..2b142634740 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,12 +1,30 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func TestCurrentRealm() string // injected -func TestSkipHeights(count int64) // injected -func ClearStoreCache() // injected -func GetCallerAt(n int) Address // injected -func TestSetOrigCaller(addr Address) // injected -func TestSetOrigPkgAddr(addr Address) // injected -func TestSetOrigSend(sent, spent Coins) // injected -func TestIssueCoins(addr Address, coins Coins) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected + +func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } +func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOrigSend(sent, spent Coins) { + sentDenom, sentAmt := sent.expandNative() + spentDenom, spentAmt := spent.expandNative() + testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) +} +func TestIssueCoins(addr Address, coins Coins) { + denom, amt := coins.expandNative() + testIssueCoins(string(addr), denom, amt) +} + +// GetCallerAt calls callerAt, which we overwrite +func callerAt(n int) string + +// native bindings +func testSetOrigCaller(s string) +func testSetOrigPkgAddr(s string) +func testSetOrigSend( + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64) +func testIssueCoins(addr string, denom []string, amt []int64) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 27ad079b4a6..0e15c8d0241 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -6,10 +6,8 @@ import ( "testing" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" - tm2std "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -68,11 +66,13 @@ func ClearStoreCache(m *gno.Machine) { } } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -82,35 +82,39 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(stdlibs.ExecContext) - return ctx.OrigCaller + ctx := m.Context.(std.ExecContext) + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { +func X_testSetOrigCaller(m *gno.Machine, addr string) { ctx := m.Context.(std.ExecContext) - ctx.OrigCaller = addr + ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigPkgAddr(m *gno.Machine, addr crypto.Bech32Address) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigPkgAddr = addr +func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { + ctx := m.Context.(std.ExecContext) + ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigSend(m *gno.Machine, sent, spent tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigSend = sent +func X_testSetOrigSend(m *gno.Machine, + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64, +) { + ctx := m.Context.(std.ExecContext) + ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent m.Context = ctx } -func TestIssueCoins(m *gno.Machine, addr crypto.Bech32Address, coins tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) +func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { + ctx := m.Context.(std.ExecContext) banker := ctx.Banker - for _, coin := range coins { - banker.IssueCoin(addr, coin.Denom, coin.Amount) + for i := range denom { + banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) } } diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go deleted file mode 100644 index c95c05c584e..00000000000 --- a/misc/genstd/exprstring.go +++ /dev/null @@ -1,290 +0,0 @@ -// Forked from go/types (go 1.20.3) to implement support for *linkedIdent. -// It cannot be easily split from the original as WriteExpr is highly recursive. - -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements printing of expressions. - -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/types" -) - -const ( - printerModeGoQualified = iota - printerModeGnoType -) - -type exprPrinter struct { - mode int -} - -// ExprString returns the (possibly shortened) string representation for x. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// ExprString is identical to [types.ExprString] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) ExprString(x ast.Expr) string { - var buf bytes.Buffer - ep.WriteExpr(&buf, x) - return buf.String() -} - -// WriteExpr writes the (possibly shortened) string representation for x to buf. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// WriteExpr is identical to [types.WriteExpr] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) WriteExpr(buf *bytes.Buffer, x ast.Expr) { - // The AST preserves source-level parentheses so there is - // no need to introduce them here to correct for different - // operator precedences. (This assumes that the AST was - // generated by a Go parser.) - - switch x := x.(type) { - default: - // fallback to go original -- for all non-recursive ast.Expr types - types.WriteExpr(buf, x) - - case *linkedIdent: - switch ep.mode { - case printerModeGoQualified: - n := pkgNameFromPath(x.lt.goPackage) - buf.WriteString(n) - buf.WriteByte('.') - buf.WriteString(x.lt.goName) - case printerModeGnoType: - buf.WriteString(x.lt.gnoName) - default: - panic(fmt.Errorf("invalid mode %d", ep.mode)) - } - - case *ast.Ellipsis: - buf.WriteString("...") - if x.Elt != nil { - ep.WriteExpr(buf, x.Elt) - } - - case *ast.FuncLit: - buf.WriteByte('(') - ep.WriteExpr(buf, x.Type) - buf.WriteString(" literal)") // shortened - - case *ast.CompositeLit: - ep.WriteExpr(buf, x.Type) - buf.WriteByte('{') - if len(x.Elts) > 0 { - buf.WriteString("…") - } - buf.WriteByte('}') - - case *ast.ParenExpr: - buf.WriteByte('(') - ep.WriteExpr(buf, x.X) - buf.WriteByte(')') - - case *ast.SelectorExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('.') - buf.WriteString(x.Sel.Name) - - case *ast.IndexExpr, *ast.IndexListExpr: - ix := tpUnpackIndexExpr(x) - ep.WriteExpr(buf, ix.X) - buf.WriteByte('[') - ep.writeExprList(buf, ix.Indices) - buf.WriteByte(']') - - case *ast.SliceExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('[') - if x.Low != nil { - ep.WriteExpr(buf, x.Low) - } - buf.WriteByte(':') - if x.High != nil { - ep.WriteExpr(buf, x.High) - } - if x.Slice3 { - buf.WriteByte(':') - if x.Max != nil { - ep.WriteExpr(buf, x.Max) - } - } - buf.WriteByte(']') - - case *ast.TypeAssertExpr: - ep.WriteExpr(buf, x.X) - buf.WriteString(".(") - ep.WriteExpr(buf, x.Type) - buf.WriteByte(')') - - case *ast.CallExpr: - ep.WriteExpr(buf, x.Fun) - buf.WriteByte('(') - ep.writeExprList(buf, x.Args) - if x.Ellipsis.IsValid() { - buf.WriteString("...") - } - buf.WriteByte(')') - - case *ast.StarExpr: - buf.WriteByte('*') - ep.WriteExpr(buf, x.X) - - case *ast.UnaryExpr: - buf.WriteString(x.Op.String()) - ep.WriteExpr(buf, x.X) - - case *ast.BinaryExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte(' ') - buf.WriteString(x.Op.String()) - buf.WriteByte(' ') - ep.WriteExpr(buf, x.Y) - - case *ast.ArrayType: - buf.WriteByte('[') - if x.Len != nil { - ep.WriteExpr(buf, x.Len) - } - buf.WriteByte(']') - ep.WriteExpr(buf, x.Elt) - - case *ast.StructType: - buf.WriteString("struct{") - ep.writeFieldList(buf, x.Fields.List, "; ", false) - buf.WriteByte('}') - - case *ast.FuncType: - buf.WriteString("func") - ep.writeSigExpr(buf, x) - - case *ast.InterfaceType: - buf.WriteString("interface{") - ep.writeFieldList(buf, x.Methods.List, "; ", true) - buf.WriteByte('}') - - case *ast.MapType: - buf.WriteString("map[") - ep.WriteExpr(buf, x.Key) - buf.WriteByte(']') - ep.WriteExpr(buf, x.Value) - - case *ast.ChanType: - var s string - switch x.Dir { - case ast.SEND: - s = "chan<- " - case ast.RECV: - s = "<-chan " - default: - s = "chan " - } - buf.WriteString(s) - ep.WriteExpr(buf, x.Value) - } -} - -func (ep *exprPrinter) writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { - buf.WriteByte('(') - ep.writeFieldList(buf, sig.Params.List, ", ", false) - buf.WriteByte(')') - - res := sig.Results - n := res.NumFields() - if n == 0 { - // no result - return - } - - buf.WriteByte(' ') - if n == 1 && len(res.List[0].Names) == 0 { - // single unnamed result - ep.WriteExpr(buf, res.List[0].Type) - return - } - - // multiple or named result(s) - buf.WriteByte('(') - ep.writeFieldList(buf, res.List, ", ", false) - buf.WriteByte(')') -} - -func (ep *exprPrinter) writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { - for i, f := range list { - if i > 0 { - buf.WriteString(sep) - } - - // field list names - ep.writeIdentList(buf, f.Names) - - // types of interface methods consist of signatures only - if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { - ep.writeSigExpr(buf, sig) - continue - } - - // named fields are separated with a blank from the field type - if len(f.Names) > 0 { - buf.WriteByte(' ') - } - - ep.WriteExpr(buf, f.Type) - - // ignore tag - } -} - -func (ep *exprPrinter) writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(x.Name) - } -} - -func (ep *exprPrinter) writeExprList(buf *bytes.Buffer, list []ast.Expr) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - ep.WriteExpr(buf, x) - } -} - -// The following are copied from go/internal/typeparams. -// We cannot use the original directly as it comes from an "internal" package. - -// tpIndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. -// -// Orig holds the original ast.Expr from which this IndexExpr was derived. -type tpIndexExpr struct { - Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. - *ast.IndexListExpr -} - -func tpUnpackIndexExpr(n ast.Node) *tpIndexExpr { - switch e := n.(type) { - case *ast.IndexExpr: - return &tpIndexExpr{e, &ast.IndexListExpr{ - X: e.X, - Lbrack: e.Lbrack, - Indices: []ast.Expr{e.Index}, - Rbrack: e.Rbrack, - }} - case *ast.IndexListExpr: - return &tpIndexExpr{e, e} - } - return nil -} diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 0e4034a1ab9..d87484ab865 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "go/ast" + "go/types" "path" "strconv" ) @@ -24,8 +25,6 @@ type mapping struct { } type mappingType struct { - // type of ast.Expr is from the normal ast.Expr types - // + *linkedIdent. Type ast.Expr // IsTypedValue is set to true if the parameter or result in go is of type @@ -35,21 +34,11 @@ type mappingType struct { } func (mt mappingType) GoQualifiedName() string { - return (&exprPrinter{ - mode: printerModeGoQualified, - }).ExprString(mt.Type) + return types.ExprString(mt.Type) } func (mt mappingType) GnoType() string { - return (&exprPrinter{ - mode: printerModeGnoType, - }).ExprString(mt.Type) -} - -type linkedIdent struct { - ast.BadExpr // Unused, but it makes *linkedIdent implement ast.Expr - - lt linkedType + return types.ExprString(mt.Type) } func linkFunctions(pkgs []*pkgData) []mapping { @@ -208,50 +197,17 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) // mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is // (recursively) newly allocated. func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { - resolveGoNamed := func(lt *linkedType) bool { - switch goe := goe.(type) { - case *ast.SelectorExpr: - // selector - resolve pkg ident to path - lt.goPackage = resolveSelectorImport(m.goImports, goe) - lt.goName = goe.Sel.Name - case *ast.Ident: - // local name -- use import path of go pkg - lt.goPackage = m.GoImportPath - lt.goName = goe.Name - default: - return false - } - return true - } - switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type - case *ast.SelectorExpr: - lt := linkedType{ - gnoPackage: resolveSelectorImport(m.gnoImports, gnoe), - gnoName: gnoe.Sel.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} case *ast.Ident: // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { return &ast.Ident{Name: gnoe.Name} } - - lt := linkedType{ - gnoPackage: m.GnoImportPath, - gnoName: gnoe.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} + panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: @@ -283,7 +239,8 @@ func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { *ast.FuncType, *ast.InterfaceType, *ast.MapType, - *ast.Ellipsis: + *ast.Ellipsis, + *ast.SelectorExpr: // TODO panic("not implemented") default: @@ -426,46 +383,3 @@ func isBuiltin(name string) bool { } return false } - -type linkedType struct { - gnoPackage string - gnoName string - goPackage string - goName string -} - -var linkedTypes = [...]linkedType{ - { - "std", "Address", - "github.com/gnolang/gno/tm2/pkg/crypto", "Bech32Address", - }, - { - "std", "Coin", - "github.com/gnolang/gno/tm2/pkg/std", "Coin", - }, - { - "std", "Coins", - "github.com/gnolang/gno/tm2/pkg/std", "Coins", - }, - { - "std", "Realm", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Realm", - }, - { - "std", "BankerType", - "github.com/gnolang/gno/gnovm/stdlibs/std", "BankerType", - }, - { - "std", "Banker", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Banker", - }, -} - -func linkedTypeExists(lt linkedType) bool { - for _, ltx := range linkedTypes { - if lt == ltx { - return true - } - } - return false -} From fd2722e35e15ae9cd55c522fbe5f1dd1b808fccf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:59:53 +0100 Subject: [PATCH 06/34] remove InjectNativeMappings --- gnovm/stdlibs/stdlibs.go | 10 ---------- gnovm/tests/imports.go | 2 -- 2 files changed, 12 deletions(-) diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 1e215b098e2..7939c0396d1 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -3,22 +3,12 @@ package stdlibs //go:generate go run github.com/gnolang/gno/misc/genstd import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" libsstd "github.com/gnolang/gno/gnovm/stdlibs/std" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) type ExecContext = libsstd.ExecContext -func InjectNativeMappings(store gno.Store) { - store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") -} - func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 0db5651fbcc..d0afee79910 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -400,8 +400,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri store.SetNativeStore(teststdlibs.NativeStore) store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) - // native mappings - stdlibs.InjectNativeMappings(store) return } From 88562255736ffdc45b6eb2059687ad9a8cda67de Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 23:01:54 +0100 Subject: [PATCH 07/34] fix errors in genstd --- misc/genstd/mapping.go | 88 +++++++++++++++++-------------------- misc/genstd/mapping_test.go | 86 +++++++++++++++--------------------- 2 files changed, 76 insertions(+), 98 deletions(-) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index d87484ab865..69aad8af3a6 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "go/ast" "go/types" @@ -110,8 +109,7 @@ func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) if m.isTypedValue(goe) { *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) } else { - merged := m.mergeTypes(gnoe, goe) - *dst = append(*dst, mappingType{Type: merged}) + *dst = append(*dst, mappingType{Type: gnoe}) } return nil }) @@ -183,57 +181,56 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) return nil } -// mergeTypes merges gnoe and goe into a single ast.Expr. -// -// gnoe and goe are expected to have the same underlying structure, but they -// may differ in their type identifiers (possibly qualified, ie pkg.T). -// if they differ, mergeTypes returns nil. -// -// When two type identifiers are found, they are checked against the list of -// linkedTypes to determine if they refer to a linkedType. If they are not, -// mergeTypes returns nil. If they are, the *ast.Ident/*ast.SelectorExpr is -// replaced with a *linkedIdent. -// -// mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is -// (recursively) newly allocated. -func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { +type typeMismatchError struct { + gnoe, goe ast.Expr +} + +func (te *typeMismatchError) Error() string { + return fmt.Sprintf("typesEqual: gno type %q does not match go type %q", + types.ExprString(te.gnoe), types.ExprString(te.goe)) +} + +// typesEqual ensures that the given gnoe and goe, expected to represent +// expressions to identify types, are equal. +func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { + // If a type assertion fails, like in the below + // goe, ok := ..., then goe will be set to a zero value, and might + // lead to nil pointer dereferences. Setting up the mismatch error + // here avoids that. + mismatch := typeMismatchError{gnoe, goe} + switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type case *ast.Ident: - // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) - if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { - return &ast.Ident{Name: gnoe.Name} + switch { + case !ok || gnoe.Name != goi.Name: + return &mismatch + case !isBuiltin(gnoe.Name): + return fmt.Errorf("typesEqual: usage of non-builtin type %q", gnoe.Name) + default: + return nil } - panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) - - // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: goe, ok := goe.(*ast.StarExpr) if !ok { - return nil + return &mismatch } - x := m.mergeTypes(gnoe.X, goe.X) - if x == nil { - return nil + if err := m.typesEqual(gnoe.X, goe.X); err != nil { + return err } - return &ast.StarExpr{X: x} + return nil case *ast.ArrayType: goe, ok := goe.(*ast.ArrayType) if !ok || !basicLitsEqual(gnoe.Len, goe.Len) { - return nil - } - elt := m.mergeTypes(gnoe.Elt, goe.Elt) - if elt == nil { - return nil + return &mismatch } - var l ast.Expr - if gnoe.Len != nil { - l = &ast.BasicLit{Value: gnoe.Len.(*ast.BasicLit).Value} + if err := m.typesEqual(gnoe.Elt, goe.Elt); err != nil { + return err } - return &ast.ArrayType{Len: l, Elt: elt} + return nil case *ast.StructType, *ast.FuncType, @@ -304,11 +301,13 @@ func basicLitsEqual(x1, x2 ast.Expr) bool { return l1.Value == l2.Value } -// Signatures match when they accept the same elementary types, or a linked -// type mapping (see [linkedTypes]). +// Signatures match when they accept the same, unnamed types. +// +// If the first parameter to the Go function is *[gnolang.Machine], it is +// ignored when matching to the Gno function. // -// Additionally, if the first parameter to the Go function is -// *[gnolang.Machine], it is ignored when matching to the Gno function. +// If a Go parameter is [gnolang.TypedValue], it always matches any +// corresponding parameter in Gno. func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { panic("type parameters not supported") @@ -329,8 +328,6 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) } -var errNoMatch = errors.New("no match") - func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if gnofl == nil || gofl == nil { return gnofl == nil && gofl == nil @@ -343,10 +340,7 @@ func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if m.isTypedValue(goe) { return nil } - if m.mergeTypes(gnoe, goe) == nil { - return errNoMatch - } - return nil + return m.typesEqual(gnoe, goe) }) return err == nil } diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go index b0cfa1bd4a7..292402a06ac 100644 --- a/misc/genstd/mapping_test.go +++ b/misc/genstd/mapping_test.go @@ -173,9 +173,9 @@ func Test_linkFunctions_noMatchSig(t *testing.T) { linkFunctions(pkgs) } -// mergeTypes - separate tests. +// typesEqual - separate tests. -var mergeTypesMapping = &mapping{ +var typesEqualMapping = &mapping{ GnoImportPath: "std", GnoFunc: "Fn", GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/std", @@ -191,7 +191,6 @@ var mergeTypesMapping = &mapping{ }, gnoImports: []*ast.ImportSpec{ { - // cheating a bit -- but we currently only have linked types in `std`. Path: &ast.BasicLit{Value: `"std"`}, }, { @@ -200,75 +199,60 @@ var mergeTypesMapping = &mapping{ }, } -func Test_mergeTypes(t *testing.T) { +func Test_typesEqual(t *testing.T) { tt := []struct { - gnoe, goe string - result ast.Expr + gnoe, goe string + errContains string }{ - {"int", "int", &ast.Ident{Name: "int"}}, - {"*[11][]rune", "*[11][]rune", &ast.StarExpr{ - X: &ast.ArrayType{Len: &ast.BasicLit{Value: "11"}, Elt: &ast.ArrayType{ - Elt: &ast.Ident{Name: "rune"}, - }}, - }}, - - {"Address", "crypto.Bech32Address", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Address", - goPackage: "github.com/gnolang/gno/tm2/pkg/crypto", - goName: "Bech32Address", - }}}, - {"std.Realm", "Realm", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Realm", - goPackage: "github.com/gnolang/gno/gnovm/stdlibs/std", - goName: "Realm", - }}}, + {"int", "int", ""}, + {"*[11][]rune", "*[11][ ]rune", ""}, + + {"madeup", "madeup", "non-builtin type"}, + + {"int", "string", "does not match"}, + {"*int", "int", "does not match"}, + {"string", "*string", "does not match"}, + {"*string", "*int", "does not match"}, + + {"[]int", "[1]int", "does not match"}, + {"[1]int", "[]int", "does not match"}, + {"[2]int", "[2]string", "does not match"}, + // valid, but unsupported (only BasicLits) + {"[(11)]int", "[(11)]string", "does not match"}, + + // even though mathematically equal, for simplicity we don't implement + // "true" basic lit equivalence + {"[8]int", "[0x8]int", "does not match"}, } - for _, tv := range tt { - t.Run(tv.gnoe, func(t *testing.T) { + for idx, tv := range tt { + t.Run(fmt.Sprintf("%02d_%s", idx, tv.gnoe), func(t *testing.T) { gnoe, err := parser.ParseExpr(tv.gnoe) require.NoError(t, err) goe, err := parser.ParseExpr(tv.goe) require.NoError(t, err) - result := mergeTypesMapping.mergeTypes(gnoe, goe) - assert.Equal(t, result, tv.result) + err = typesEqualMapping.typesEqual(gnoe, goe) + if tv.errContains == "" { + assert.NoError(t, err) + } else { + _ = assert.Error(t, err) && + assert.Contains(t, err.Error(), tv.errContains) + } }) } } -func Test_mergeTypes_invalid(t *testing.T) { +func Test_typesEqual_panic(t *testing.T) { tt := []struct { gnoe, goe string panic string }{ - {"int", "string", ""}, - - {"*int", "int", ""}, - {"string", "*string", ""}, - {"*string", "*int", ""}, - - {"[]int", "[1]int", ""}, - {"[1]int", "[]int", ""}, - {"[2]int", "[2]string", ""}, - // valid, but unsupported (only BasicLits) - {"[(11)]int", "[(11)]string", ""}, - - {"Address", "string", ""}, - {"math.X", "X", ""}, - {"map[string]string", "map[string]string", "not implemented"}, {"func(s string)", "func(s string)", "not implemented"}, {"interface{}", "interface{}", "not implemented"}, {"struct{}", "struct{}", "not implemented"}, - {"1 + 2", "1 + 2", "invalid expression"}, - - // even though semantically equal, for simplicity we don't implement - // "true" basic lit equivalence - {"[8]int", "[0x8]int", ""}, } for _, tv := range tt { @@ -287,7 +271,7 @@ func Test_mergeTypes_invalid(t *testing.T) { } }() - result := mergeTypesMapping.mergeTypes(gnoe, goe) + result := typesEqualMapping.typesEqual(gnoe, goe) assert.Nil(t, result) }) } From d6955d0316f322bb8494372dda83f7af4244f6fc Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 14:43:13 +0100 Subject: [PATCH 08/34] remove AddGo2GnoMapping, nolint on btReadonly --- gno.land/pkg/sdk/vm/builtins.go | 1 - gnovm/pkg/gnolang/gonative.go | 13 ------------- gnovm/pkg/gnolang/store.go | 1 - gnovm/stdlibs/std/banker.go | 2 +- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index ef2ac93f617..63062847e01 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -41,7 +41,6 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { } store.SetPackageGetter(getPackage) store.SetNativeStore(stdlibs.NativeStore) - stdlibs.InjectNativeMappings(store) } // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index f3739dad0f3..e634fbcf8f5 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -88,19 +88,6 @@ func (ds *defaultStore) SetStrictGo2GnoMapping(strict bool) { ds.go2gnoStrict = strict } -// Implements Store. -func (ds *defaultStore) AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) { - rtPkgPath := rt.PkgPath() - if rtPkgPath == "" { - panic(fmt.Sprintf("type has no associated package path: %v", rt)) - } - rtName := rt.Name() - if rtName == "" { - panic(fmt.Sprintf("type has no name: %v", rt)) - } - ds.go2gnoMap[rtPkgPath+"."+rtName] = pkgPath + "." + name -} - // Implements Store. // See go2GnoValue2(). Like go2GnoType() but also converts any // top-level complex types (or pointers to them). The result gets diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index be4a854e7ce..5b1bfe960ea 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -40,7 +40,6 @@ type Store interface { SetBlockNode(BlockNode) // UNSTABLE SetStrictGo2GnoMapping(bool) - AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) Go2GnoType(rt reflect.Type) Type GetAllocator() *Allocator NumMemPackages() int64 diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 243d457b9be..2533071ccd8 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -23,7 +23,7 @@ type BankerInterface interface { const ( // Can only read state. - btReadonly uint8 = iota + btReadonly uint8 = iota //nolint // Can only send from tx send. btOrigSend // Can send from all realm coins. From b3c7adb0a4033435f3b1ee8e30a3bbb7b7db34dc Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 15:30:50 +0100 Subject: [PATCH 09/34] fmt + bugfix --- gnovm/stdlibs/std/banker.go | 1 + gnovm/stdlibs/std/native.gno | 1 + 2 files changed, 2 insertions(+) diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 2533071ccd8..d60efc40819 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -65,6 +65,7 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str `cannot send "%v", limit "%v" exceeded with "%v" already spent`, amt, ctx.OrigSend, *ctx.OrigSendSpent), )) + return } ctx.Banker.SendCoins(from, to, amt) *ctx.OrigSendSpent = spent diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index c079e0dd42b..8043df49882 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -36,6 +36,7 @@ func GetOrigPkgAddr() Address { func GetCallerAt(n int) Address { return Address(callerAt(n)) } + func DerivePkgAddr(pkgPath string) Address { return Address(derivePkgAddr(pkgPath)) } From 250e29213360f8e02d65d6a7c0f7f82c1940a37a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 15:44:58 +0100 Subject: [PATCH 10/34] feat(gno.land): add go type checking to keeper --- gno.land/pkg/gnoclient/client_txs.go | 6 -- gno.land/pkg/keyscli/addpkg.go | 7 -- gno.land/pkg/keyscli/run.go | 7 +- gno.land/pkg/sdk/vm/builtins.go | 4 +- gno.land/pkg/sdk/vm/keeper.go | 14 ++++ gnovm/pkg/gnolang/go2gno.go | 102 ++++++++++++++++++++++++++- gnovm/pkg/gnolang/gonative_test.go | 7 +- gnovm/pkg/gnolang/store.go | 77 ++++++++++++++------ gnovm/pkg/transpiler/transpiler.go | 41 ----------- gnovm/stdlibs/io/io.gno | 31 +++++--- gnovm/tests/files/import6.gno | 2 +- gnovm/tests/imports.go | 10 +-- 12 files changed, 205 insertions(+), 103 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 2a83eef1b79..f2fa5ea6fb4 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,6 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -138,11 +137,6 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo caller := c.Signer.Info().GetAddress() - // Transpile and validate Gno syntax - if err = transpiler.TranspileAndCheckMempkg(msg.Package); err != nil { - return nil, err - } - msg.Package.Name = "main" msg.Package.Path = "" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 1f89b2c2b82..1812bf3fd86 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,7 +7,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -102,12 +101,6 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) - if err != nil { - panic(err) - } - // parse gas wanted & fee. gaswanted := cfg.RootCfg.GasWanted gasfee, err := std.ParseCoin(cfg.RootCfg.GasFee) diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 6fcee7434eb..66f4bc3c671 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -109,11 +108,7 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) - if err != nil { - panic(err) - } + memPkg.Name = "main" // Set to empty; this will be automatically set by the VM keeper. memPkg.Path = "" diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 63062847e01..368ada6ff82 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: store, + Store: newStore, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 54f424ee058..c06c3b3761c 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -160,6 +161,11 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } + // Validate Gno syntax and type check. + if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return err + } + // Pay deposit from creator. pkgAddr := gno.DerivePkgAddr(pkgPath) @@ -317,6 +323,11 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { return "", ErrInvalidPkgPath(err.Error()) } + // Validate Gno syntax and type check. + if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return "", err + } + // Send send-coins to pkg from caller. err = vm.bank.SendCoins(ctx, caller, pkgAddr, send) if err != nil { @@ -565,6 +576,9 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err return memFile.Body, nil } else { memPkg := store.GetMemPackage(dirpath) + if memPkg == nil { + return "", fmt.Errorf("package %q is not available", dirpath) // TODO: XSS protection + } for i, memfile := range memPkg.Files { if i > 0 { res += "\n" diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index ee82cb39555..c726e291abc 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -35,12 +35,16 @@ import ( "go/ast" "go/parser" "go/token" + "go/types" "os" "reflect" "strconv" + "strings" "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" + "go.uber.org/multierr" ) func MustReadFile(path string) *FileNode { @@ -102,7 +106,10 @@ func MustParseExpr(expr string) Expr { func ParseFile(filename string, body string) (fn *FileNode, err error) { // Use go parser to parse the body. fs := token.NewFileSet() - f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) + // TODO(morgan): would be nice to add parser.SkipObjectResolution as we don't + // seem to be using its features, but this breaks when testing (specifically redeclaration tests). + const parseOpts = parser.ParseComments | parser.DeclarationErrors + f, err := parser.ParseFile(fs, filename, body, parseOpts) if err != nil { return nil, err } @@ -469,6 +476,99 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } } +//---------------------------------------- +// type checking (using go/types) + +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// [Store], separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *std.MemPackage +} + +// TypeCheckMemPackage performs type validation and checking on the given +// mempkg. To retrieve dependencies, it uses getter. +// +// The syntax checking is performed entirely using Go's go/types package. +func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { + imp := &gnoImporter{ + getter: getter, + cache: map[string]any{}, + cfg: &types.Config{}, + } + imp.cfg.Importer = imp + + _, err := imp.parseCheckMemPackage(mempkg) + if err != nil { + return err + } + + return nil +} + +type gnoImporter struct { + getter MemPackageGetter + cache map[string]any // *types.Package or error + cfg *types.Config +} + +func (g *gnoImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + switch ret := pkg.(type) { + case *types.Package: + return ret, nil + case error: + return nil, ret + default: + panic(fmt.Sprintf("invalid type in gnoImporter.cache %T", ret)) + } + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + g.cache[path] = (*types.Package)(nil) + return nil, nil + } + result, err := g.parseCheckMemPackage(mpkg) + if err != nil { + g.cache[path] = err + return nil, err + } + g.cache[path] = result + return result, nil +} + +func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) + var errs error + for _, file := range mpkg.Files { + if !strings.HasSuffix(file.Name, ".gno") || + endsWith(file.Name, []string{"_test.gno", "_filetest.gno"}) { + continue // skip spurious file. + } + + const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution + f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) + if err != nil { + err = multierr.Append(errs, err) + continue + } + + files = append(files, f) + } + if errs != nil { + return nil, errs + } + + // TODO g.cfg.Error + return g.cfg.Check(mpkg.Path, fset, files, nil) +} + //---------------------------------------- // utility methods diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index e348ffe9c22..7b9167e681b 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) @@ -90,10 +90,11 @@ func main() { n := MustParseFile("main.go", c) m.RunFiles(n) m.RunMain() + // weird `+` is used to place a space, without having editors strip it away. assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } @@ -132,7 +133,7 @@ func main() { assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 5b1bfe960ea..306253e7f31 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + "slices" "strconv" "strings" @@ -11,8 +12,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) -// return nil if package doesn't exist. -type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) +// PackageGetter specifies how the store may retrieve packages which are not +// already in its cache. PackageGetter should return nil when the requested +// package does not exist. store should be used to run the machine, or otherwise +// call any methods which may call store.GetPackage, to avoid import cycles. +type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -79,8 +83,7 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. - current map[string]struct{} // for detecting import cycles. + opslog []StoreOp // for debugging and testing. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -95,7 +98,6 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore iavlStore: iavlStore, go2gnoMap: make(map[string]string), go2gnoStrict: true, - current: make(map[string]struct{}), } InitStoreCaches(ds) return ds @@ -109,16 +111,29 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } +type importerStore struct { + *defaultStore + importChain []string +} + +func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + if !isImport { + // if not an import, match behaviour to the defaultStore + return is.defaultStore.GetPackage(pkgPath, isImport) + } + // it is an import -- detect cyclic imports + if slices.Contains(is.importChain, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) + } + return is.getPackage(pkgPath, is) +} + // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - // detect circular imports - if isImport { - if _, exists := ds.current[pkgPath]; exists { - panic(fmt.Sprintf("import cycle detected: %q", pkgPath)) - } - ds.current[pkgPath] = struct{}{} - defer delete(ds.current, pkgPath) - } + return ds.getPackage(pkgPath, importerStore{}) +} + +func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -162,7 +177,12 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if pn, pv := ds.pkgGetter(pkgPath); pv != nil { + if impStore.defaultStore == nil { + // pre-allocate 16 strings to likely avoid further slice allocations. + impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} + } + impStore.importChain = append(impStore.importChain, pkgPath) + if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -534,20 +554,41 @@ func (ds *defaultStore) AddMemPackage(memPkg *std.MemPackage) { ds.iavlStore.Set(pathkey, bz) } +// GetMemPackage retrieves the MemPackage at the given path. +// It returns nil if the package could not be found. func (ds *defaultStore) GetMemPackage(path string) *std.MemPackage { + return ds.getMemPackage(path, false) +} + +func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage { pathkey := []byte(backendPackagePathKey(path)) bz := ds.iavlStore.Get(pathkey) if bz == nil { - panic(fmt.Sprintf( - "missing package at path %s", string(pathkey))) + // If this is the first try, attempt using GetPackage to retrieve the + // package, first. GetPackage can leverage pkgGetter, which in most + // implementations works by running Machine.RunMemPackage with save = true, + // which would add the package to the store after running. + // Some packages may never be persisted, thus why we only attempt this twice. + if !isRetry { + if pv := ds.GetPackage(path, false); pv != nil { + return ds.getMemPackage(path, true) + } + } + return nil } var memPkg *std.MemPackage amino.MustUnmarshal(bz, &memPkg) return memPkg } +// GetMemFile retrieves the MemFile with the given name, contained in the +// MemPackage at the given path. It returns nil if the file or the package +// do not exist. func (ds *defaultStore) GetMemFile(path string, name string) *std.MemFile { memPkg := ds.GetMemPackage(path) + if memPkg == nil { + return nil + } memFile := memPkg.GetFile(name) return memFile } @@ -587,9 +628,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. - if len(ds.current) > 0 { - ds.current = make(map[string]struct{}) - } ds.SetCachePackage(Uverse()) } @@ -610,7 +648,6 @@ func (ds *defaultStore) Fork() Store { go2gnoMap: ds.go2gnoMap, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. - current: make(map[string]struct{}), } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index a5e1e068ba5..77d05e392e2 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -16,10 +16,7 @@ import ( "strconv" "strings" - "go.uber.org/multierr" "golang.org/x/tools/go/ast/astutil" - - "github.com/gnolang/gno/tm2/pkg/std" ) const ( @@ -122,44 +119,6 @@ func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags strin return } -func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { - gofmt := "gofmt" - - tmpDir, err := os.MkdirTemp("", mempkg.Name) - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) //nolint: errcheck - - var errs error - for _, mfile := range mempkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // skip spurious file. - } - res, err := Transpile(mfile.Body, "gno,tmp", mfile.Name) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - tmpFile := filepath.Join(tmpDir, mfile.Name) - err = os.WriteFile(tmpFile, []byte(res.Translated), 0o644) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - err = TranspileVerifyFile(tmpFile, gofmt) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - } - - if errs != nil { - return fmt.Errorf("transpile package: %w", errs) - } - return nil -} - func Transpile(source string, tags string, filename string) (*transpileResult, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index 6ee52cfe293..bdbab135140 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -476,28 +476,29 @@ func (l *LimitedReader) Read(p []byte) (n int, err error) { return } -// NewSectionReader returns a SectionReader that reads from r +// NewSectionReader returns a [SectionReader] that reads from r // starting at offset off and stops with EOF after n bytes. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { var remaining int64 - const maxInt64 = 1<<63 - 1 - if off <= maxInt64-n { + const maxint64 = 1<<63 - 1 + if off <= maxint64-n { remaining = n + off } else { // Overflow, with no way to return error. - // Assume we can read up to an offset of `1<<63 - 1` bytes in this case. - remaining = maxInt64 + // Assume we can read up to an offset of 1<<63 - 1. + remaining = maxint64 } - return &SectionReader{r, off, off, off + n} + return &SectionReader{r, off, off, remaining, n} } // SectionReader implements Read, Seek, and ReadAt on a section -// of an underlying ReaderAt. +// of an underlying [ReaderAt]. type SectionReader struct { - r ReaderAt - base int64 + r ReaderAt // constant after creation + base int64 // constant after creation off int64 - limit int64 + limit int64 // constant after creation + n int64 // constant after creation } func (s *SectionReader) Read(p []byte) (n int, err error) { @@ -536,7 +537,7 @@ func (s *SectionReader) Seek(offset int64, whence int) (int64, error) { } func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { - if off < 0 || off >= s.limit-s.base { + if off < 0 || off >= s.Size() { return 0, EOF } off += s.base @@ -554,6 +555,14 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { // Size returns the size of the section in bytes. func (s *SectionReader) Size() int64 { return s.limit - s.base } +// Outer returns the underlying [ReaderAt] and offsets for the section. +// +// The returned values are the same that were passed to [NewSectionReader] +// when the [SectionReader] was created. +func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64) { + return s.r, s.base, s.n +} + // An OffsetWriter maps writers at offset base to offset base + off in the underlying writer. type OffsetWriter struct { w WriterAt diff --git a/gnovm/tests/files/import6.gno b/gnovm/tests/files/import6.gno index 8c974ea1893..da5dbfbd3b2 100644 --- a/gnovm/tests/files/import6.gno +++ b/gnovm/tests/files/import6.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" +// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" (through [github.com/gnolang/gno/_test/c1 github.com/gnolang/gno/_test/c2]) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d0afee79910..a1615bb81a2 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -62,7 +62,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -81,7 +81,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() @@ -93,7 +93,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -367,7 +367,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -384,7 +384,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) pn, pv = m2.RunMemPackage(memPkg, true) return From 0d8596d50766c507111f2f2d078fa22173e278f3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 16:12:45 +0100 Subject: [PATCH 11/34] lint + test fixes --- gno.land/pkg/gnoclient/integration_test.go | 3 --- gno.land/pkg/sdk/vm/keeper.go | 5 ++--- gno.land/pkg/sdk/vm/keeper_test.go | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index 554cba8ecf2..fa2e78cb397 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -239,7 +239,6 @@ func TestRunSingle_Integration(t *testing.T) { fileBody := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/tests" ) @@ -297,7 +296,6 @@ func TestRunMultiple_Integration(t *testing.T) { fileBody1 := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/tests" ) @@ -311,7 +309,6 @@ func main() { fileBody2 := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/deep/very/deep" ) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index c06c3b3761c..23fb3998131 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,7 +9,6 @@ import ( "regexp" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -162,7 +161,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { } // Validate Gno syntax and type check. - if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { return err } @@ -324,7 +323,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { return "", err } diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index bc6bc285704..8e46114d857 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -31,8 +31,6 @@ func TestVMKeeperAddPackage(t *testing.T) { Name: "test.gno", Body: `package test -import "std" - func Echo() string { return "hello world" }`, @@ -413,8 +411,6 @@ func TestNumberOfArgsError(t *testing.T) { Name: "test.gno", Body: `package test -import "std" - func Echo(msg string) string { return "echo:"+msg }`, From 9779ff30a61a6d066c7912ee17e68e5c59a7f376 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 12:51:06 +0100 Subject: [PATCH 12/34] use custom type instead of any in importer --- gnovm/pkg/gnolang/go2gno.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index c726e291abc..0bc9fa9a407 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -492,7 +492,7 @@ type MemPackageGetter interface { func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { imp := &gnoImporter{ getter: getter, - cache: map[string]any{}, + cache: map[string]gnoImporterResult{}, cfg: &types.Config{}, } imp.cfg.Importer = imp @@ -505,9 +505,14 @@ func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error return nil } +type gnoImporterResult struct { + pkg *types.Package + err error +} + type gnoImporter struct { getter MemPackageGetter - cache map[string]any // *types.Package or error + cache map[string]gnoImporterResult cfg *types.Config } @@ -519,27 +524,16 @@ func (g *gnoImporter) Import(path string) (*types.Package, error) { // path when imported by a package file located in dir. func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { if pkg, ok := g.cache[path]; ok { - switch ret := pkg.(type) { - case *types.Package: - return ret, nil - case error: - return nil, ret - default: - panic(fmt.Sprintf("invalid type in gnoImporter.cache %T", ret)) - } + return pkg.pkg, pkg.err } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - g.cache[path] = (*types.Package)(nil) + g.cache[path] = gnoImporterResult{} return nil, nil } result, err := g.parseCheckMemPackage(mpkg) - if err != nil { - g.cache[path] = err - return nil, err - } - g.cache[path] = result - return result, nil + g.cache[path] = gnoImporterResult{pkg: result, err: err} + return result, err } func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { From 7b239dfc856ddefec0da2709db6caf05aca38999 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 13:20:02 +0100 Subject: [PATCH 13/34] support multiple errors in type checker --- gnovm/pkg/gnolang/go2gno.go | 19 ++++++++++++------- gnovm/pkg/gnolang/go2gno_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 0bc9fa9a407..60ea2389ef6 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -490,19 +490,25 @@ type MemPackageGetter interface { // // The syntax checking is performed entirely using Go's go/types package. func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { + var errs error imp := &gnoImporter{ getter: getter, cache: map[string]gnoImporterResult{}, - cfg: &types.Config{}, + cfg: &types.Config{ + Error: func(err error) { + errs = multierr.Append(errs, err) + }, + }, } imp.cfg.Importer = imp _, err := imp.parseCheckMemPackage(mempkg) - if err != nil { - return err + // prefer to return errs instead of err: + // err will generally contain only the first error encountered. + if errs != nil { + return errs } - - return nil + return err } type gnoImporterResult struct { @@ -549,7 +555,7 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) if err != nil { - err = multierr.Append(errs, err) + errs = multierr.Append(errs, err) continue } @@ -559,7 +565,6 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package return nil, errs } - // TODO g.cfg.Error return g.cfg.Check(mpkg.Path, fset, files, nil) } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index d5b94c618b0..bb7dcd22559 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -4,7 +4,9 @@ import ( "fmt" "testing" - "github.com/jaekwon/testify/assert" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "go.uber.org/multierr" ) func TestParseForLoop(t *testing.T) { @@ -25,3 +27,26 @@ func main(){ fmt.Printf("AST:\n%#v\n\n", n) fmt.Printf("AST.String():\n%s\n", n.String()) } + +func TestTypeCheckMemPackage_MultiError(t *testing.T) { + const src = `package main +func main() { + _, _ = 11 + return 88, 88 +}` + err := TypeCheckMemPackage(&std.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/x", + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: src, + }, + }, + }, nil) + errs := multierr.Errors(err) + if assert.Len(t, errs, 2, "should contain two errors") { + assert.ErrorContains(t, errs[0], "assignment mismatch") + assert.ErrorContains(t, errs[1], "too many return values") + } +} From 1b323a2ac62319ea597ac894ce5bc3789f799e95 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 13:46:35 +0100 Subject: [PATCH 14/34] correctly return error in vm keeper --- gno.land/pkg/sdk/vm/errors.go | 26 +++++++++++++++++++++++++- gno.land/pkg/sdk/vm/keeper.go | 4 ++-- gno.land/pkg/sdk/vm/package.go | 1 + 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index 64778f8b467..132d98b7ecd 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -1,6 +1,11 @@ package vm -import "github.com/gnolang/gno/tm2/pkg/errors" +import ( + "strings" + + "github.com/gnolang/gno/tm2/pkg/errors" + "go.uber.org/multierr" +) // for convenience: type abciError struct{} @@ -13,11 +18,21 @@ type ( InvalidPkgPathError struct{ abciError } InvalidStmtError struct{ abciError } InvalidExprError struct{ abciError } + TypeCheckError struct { + abciError + Errors []string + } ) func (e InvalidPkgPathError) Error() string { return "invalid package path" } func (e InvalidStmtError) Error() string { return "invalid statement" } func (e InvalidExprError) Error() string { return "invalid expression" } +func (e TypeCheckError) Error() string { + var bld strings.Builder + bld.WriteString("invalid gno package; type check errors:\n") + bld.WriteString(strings.Join(e.Errors, "\n")) + return bld.String() +} func ErrInvalidPkgPath(msg string) error { return errors.Wrap(InvalidPkgPathError{}, msg) @@ -30,3 +45,12 @@ func ErrInvalidStmt(msg string) error { func ErrInvalidExpr(msg string) error { return errors.Wrap(InvalidExprError{}, msg) } + +func ErrTypeCheck(err error) error { + var tce TypeCheckError + errs := multierr.Errors(err) + for _, err := range errs { + tce.Errors = append(tce.Errors, err.Error()) + } + return errors.NewWithData(tce).Stacktrace() +} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 23fb3998131..12a1795b74b 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -162,7 +162,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { // Validate Gno syntax and type check. if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { - return err + return ErrTypeCheck(err) } // Pay deposit from creator. @@ -324,7 +324,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Validate Gno syntax and type check. if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { - return "", err + return "", ErrTypeCheck(err) } // Send send-coins to pkg from caller. diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index 01fad3284e3..b2e7fbecfc4 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -20,4 +20,5 @@ var Package = amino.RegisterPackage(amino.NewPackage( InvalidPkgPathError{}, "InvalidPkgPathError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", + TypeCheckError{}, "TypeCheckError", )) From df1679c771020d15882635385b10b4c718ced382 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 14:44:52 +0100 Subject: [PATCH 15/34] add tests to type checker --- gnovm/pkg/gnolang/go2gno.go | 10 +- gnovm/pkg/gnolang/go2gno_test.go | 267 ++++++++++++++++++++++++++++--- 2 files changed, 257 insertions(+), 20 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 60ea2389ef6..0b473f82da1 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -522,10 +522,15 @@ type gnoImporter struct { cfg *types.Config } +// Unused, but satisfies the Importer interface. func (g *gnoImporter) Import(path string) (*types.Package, error) { return g.ImportFrom(path, "", 0) } +type errImportNotFound string + +func (e errImportNotFound) Error() string { return "import not found: " + string(e) } + // ImportFrom returns the imported package for the given import // path when imported by a package file located in dir. func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { @@ -534,8 +539,9 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - g.cache[path] = gnoImporterResult{} - return nil, nil + err := errImportNotFound(path) + g.cache[path] = gnoImporterResult{err: err} + return nil, err } result, err := g.parseCheckMemPackage(mpkg) g.cache[path] = gnoImporterResult{pkg: result, err: err} diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index bb7dcd22559..403291b3f84 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -6,6 +6,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/multierr" ) @@ -28,25 +29,255 @@ func main(){ fmt.Printf("AST.String():\n%s\n", n.String()) } -func TestTypeCheckMemPackage_MultiError(t *testing.T) { - const src = `package main -func main() { - _, _ = 11 - return 88, 88 -}` - err := TypeCheckMemPackage(&std.MemPackage{ - Name: "main", - Path: "gno.land/p/demo/x", - Files: []*std.MemFile{ - { - Name: "main.gno", - Body: src, +func newMemPackage( + pkgName, pkgPath string, + namesAndFiles ...string, +) *std.MemPackage { + if len(namesAndFiles)%2 != 0 { + panic("namesAndFiles must be pairs") + } + files := make([]*std.MemFile, 0, len(namesAndFiles)/2) + for i := 0; i < len(namesAndFiles); i += 2 { + files = append(files, &std.MemFile{ + Name: namesAndFiles[i], + Body: namesAndFiles[i+1], + }) + } + return &std.MemPackage{ + Name: pkgName, + Path: pkgPath, + Files: files, + } +} + +type mockPackageGetter []*std.MemPackage + +func (mi mockPackageGetter) GetMemPackage(path string) *std.MemPackage { + for _, pkg := range mi { + if pkg.Path == path { + return pkg + } + } + return nil +} + +type mockPackageGetterCounts struct { + mockPackageGetter + counts map[string]int +} + +func (mpg mockPackageGetterCounts) GetMemPackage(path string) *std.MemPackage { + mpg.counts[path]++ + return mpg.mockPackageGetter.GetMemPackage(path) +} + +func TestTypeCheckMemPackage(t *testing.T) { + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and + // each error in order must contain the associated string. + errContains := func(s0 string, ss ...string) func(*testing.T, error) { + return func(t *testing.T, err error) { + t.Helper() + errs := multierr.Errors(err) + if len(errs) == 0 { + t.Errorf("expected an error, got nil") + return + } + want := len(ss) + 1 + if len(errs) != want { + t.Errorf("expected %d errors, got %d", want, len(errs)) + return + } + assert.ErrorContains(t, errs[0], s0) + for idx, err := range errs[1:] { + assert.ErrorContains(t, err, ss[idx]) + } + } + } + + type testCase struct { + name string + pkg *std.MemPackage + getter MemPackageGetter + check func(*testing.T, error) + } + tt := []testCase{ + { + "Simple", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + ), + nil, + nil, + }, + { + "WrongReturn", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + ), + nil, + errContains("cannot use 11"), + }, + { + "ParseError", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello! + func B() int { return 11 }`, + ), + nil, + errContains("found '!'"), + }, + { + "MultiError", + newMemPackage( + "main", "gno.land/p/demo/main", + + "hello.gno", + `package main + func main() { + _, _ = 11 + return 88, 88 + }`, + ), + nil, + errContains("assignment mismatch", "too many return values"), + }, + { + "TestsIgnored", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + func B() int { return 11 }`, + "hello_test.gno", + `This is not valid Gno code, but it doesn't matter because test + files are not checked.`, + ), + nil, + nil, + }, + { + "ImportFailed", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{}, + errContains("import not found: std"), + }, + { + "ImportSucceeded", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{ + newMemPackage( + "std", "std", + + "std.gno", + `package std + type Address string`, + ), }, + nil, }, - }, nil) - errs := multierr.Errors(err) - if assert.Len(t, errs, 2, "should contain two errors") { - assert.ErrorContains(t, errs[0], "assignment mismatch") - assert.ErrorContains(t, errs[1], "too many return values") + { + "ImportBadIdent", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{ + newMemPackage( + "a_completely_dfferent_identifier", "std", + + "std.gno", + `package a_completely_different_identifier + type Address string`, + ), + }, + errContains("undefined: std", "a_completely_different_identifier and not used"), + }, + } + + cacheMpg := mockPackageGetterCounts{ + mockPackageGetter{ + newMemPackage( + "bye", "bye", + + "bye.gno", + `package bye + import "std" + func Bye() std.Address { return "bye" }`, + ), + newMemPackage( + "std", "std", + + "std.gno", + `package std + type Address string`, + ), + }, + make(map[string]int), + } + + tt = append(tt, testCase{ + "ImportWithCache", + // This test will make use of the importer's internal cache for package `std`. + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + ), + cacheMpg, + func(t *testing.T, err error) { + require.NoError(t, err) + assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) + }, + }) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := TypeCheckMemPackage(tc.pkg, tc.getter) + if tc.check == nil { + assert.NoError(t, err) + } else { + tc.check(t, err) + } + }) } } From ee0448eb8d7c5a25b89d7f7c4a77b558299672aa Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 14:58:25 +0100 Subject: [PATCH 16/34] fixup --- gnovm/pkg/gnolang/go2gno.go | 6 +++--- gnovm/pkg/gnolang/go2gno_test.go | 1 + gnovm/pkg/gnolang/store.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 0b473f82da1..6b0cf4a6cda 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -527,9 +527,9 @@ func (g *gnoImporter) Import(path string) (*types.Package, error) { return g.ImportFrom(path, "", 0) } -type errImportNotFound string +type importNotFoundError string -func (e errImportNotFound) Error() string { return "import not found: " + string(e) } +func (e importNotFoundError) Error() string { return "import not found: " + string(e) } // ImportFrom returns the imported package for the given import // path when imported by a package file located in dir. @@ -539,7 +539,7 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - err := errImportNotFound(path) + err := importNotFoundError(path) g.cache[path] = gnoImporterResult{err: err} return nil, err } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 403291b3f84..35d00dce8f3 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -265,6 +265,7 @@ func TestTypeCheckMemPackage(t *testing.T) { ), cacheMpg, func(t *testing.T, err error) { + t.Helper() require.NoError(t, err) assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) }, diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 306253e7f31..491a2770be0 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -178,7 +178,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { if impStore.defaultStore == nil { - // pre-allocate 16 strings to likely avoid further slice allocations. + // pre-allocate 16 entries to likely avoid further slice allocations. impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} } impStore.importChain = append(impStore.importChain, pkgPath) From a1698000292fb02fce0ad365fa3a1f90100aedc1 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 15:02:26 +0100 Subject: [PATCH 17/34] add integration test for gno.land --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 2 +- .../cmd/gnoland/testdata/addpkg_invalid.txtar | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index e7437552b50..89da1378e0c 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -3,7 +3,7 @@ ## start a new node gnoland start -## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar +## add bar package located in $WORK directory as gno.land/r/foobar/bar gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 ## execute Render diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar new file mode 100644 index 00000000000..e0395fb479d --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -0,0 +1,21 @@ +# test for add package; ensuring type checker catches invalid code. + +# start a new node +gnoland start + +# add bar package located in $WORK directory as gno.land/r/foobar/bar +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +# compare render +! stdout .+ +stderr 'as string value in return statement' +stderr '"std" imported and not used' + +-- bar.gno -- +package bar + +import "std" + +func Render(path string) string { + return 89 +} From 0fa34fae17fed35a226e3a447c8c98a15fc599cf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 27 Mar 2024 19:29:04 +0100 Subject: [PATCH 18/34] remove newMemPackage --- gnovm/pkg/gnolang/go2gno_test.go | 304 ++++++++++++++++++------------- 1 file changed, 175 insertions(+), 129 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 35d00dce8f3..0cfe7610bd8 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -29,27 +29,6 @@ func main(){ fmt.Printf("AST.String():\n%s\n", n.String()) } -func newMemPackage( - pkgName, pkgPath string, - namesAndFiles ...string, -) *std.MemPackage { - if len(namesAndFiles)%2 != 0 { - panic("namesAndFiles must be pairs") - } - files := make([]*std.MemFile, 0, len(namesAndFiles)/2) - for i := 0; i < len(namesAndFiles); i += 2 { - files = append(files, &std.MemFile{ - Name: namesAndFiles[i], - Body: namesAndFiles[i+1], - }) - } - return &std.MemPackage{ - Name: pkgName, - Path: pkgPath, - Files: files, - } -} - type mockPackageGetter []*std.MemPackage func (mi mockPackageGetter) GetMemPackage(path string) *std.MemPackage { @@ -103,126 +82,178 @@ func TestTypeCheckMemPackage(t *testing.T) { tt := []testCase{ { "Simple", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - type S struct{} - func A() S { return S{} } - func B() S { return A() }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + }, + }, + }, nil, nil, }, { "WrongReturn", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - type S struct{} - func A() S { return S{} } - func B() S { return 11 }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + }, + }, + }, nil, errContains("cannot use 11"), }, { "ParseError", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello! - func B() int { return 11 }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello! + func B() int { return 11 }`, + }, + }, + }, nil, errContains("found '!'"), }, { "MultiError", - newMemPackage( - "main", "gno.land/p/demo/main", - - "hello.gno", - `package main - func main() { - _, _ = 11 - return 88, 88 - }`, - ), + &std.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/main", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package main + func main() { + _, _ = 11 + return 88, 88 + }`, + }, + }, + }, nil, errContains("assignment mismatch", "too many return values"), }, { "TestsIgnored", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - func B() int { return 11 }`, - "hello_test.gno", - `This is not valid Gno code, but it doesn't matter because test + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + func B() int { return 11 }`, + }, + { + Name: "hello_test.gno", + Body: `This is not valid Gno code, but it doesn't matter because test files are not checked.`, - ), + }, + }, + }, nil, nil, }, { "ImportFailed", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{}, errContains("import not found: std"), }, { "ImportSucceeded", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{ - newMemPackage( - "std", "std", - - "std.gno", - `package std - type Address string`, - ), + &std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package std + type Address string`, + }, + }, + }, }, nil, }, { "ImportBadIdent", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{ - newMemPackage( - "a_completely_dfferent_identifier", "std", - - "std.gno", - `package a_completely_different_identifier - type Address string`, - ), + &std.MemPackage{ + Name: "a_completely_different_identifier", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package a_completely_different_identifier + type Address string`, + }, + }, + }, }, errContains("undefined: std", "a_completely_different_identifier and not used"), }, @@ -230,21 +261,31 @@ func TestTypeCheckMemPackage(t *testing.T) { cacheMpg := mockPackageGetterCounts{ mockPackageGetter{ - newMemPackage( - "bye", "bye", - - "bye.gno", - `package bye - import "std" - func Bye() std.Address { return "bye" }`, - ), - newMemPackage( - "std", "std", - - "std.gno", - `package std - type Address string`, - ), + &std.MemPackage{ + Name: "bye", + Path: "bye", + Files: []*std.MemFile{ + { + Name: "bye.gno", + Body: ` + package bye + import "std" + func Bye() std.Address { return "bye" }`, + }, + }, + }, + &std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package std + type Address string`, + }, + }, + }, }, make(map[string]int), } @@ -252,17 +293,22 @@ func TestTypeCheckMemPackage(t *testing.T) { tt = append(tt, testCase{ "ImportWithCache", // This test will make use of the importer's internal cache for package `std`. - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import ( - "std" - "bye" - ) - func Hello() std.Address { return bye.Bye() }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + }, + }, + }, cacheMpg, func(t *testing.T, err error) { t.Helper() From 89d9887af1a3a181590928b074a5484c568c82b3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 27 Mar 2024 20:14:28 +0100 Subject: [PATCH 19/34] remove unused var --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index c42cca490b6..d6ca62609e6 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -24,7 +24,7 @@ stdout '10000 uint64' # unwrap 500 wugnot gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -# XXX without patching anything it will panic +# XXX without patching anything it will panic # panic msg: insufficient coins error # XXX with pathcing only wugnot.gnot it will panic # panic msg: RealmSendBanker can only send from the realm package address "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3", but got "g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6" @@ -72,7 +72,6 @@ func ProxyUnwrap(wugnotAmount uint64) { if wugnotAmount == 0 { return } - userOldWugnot := wugnot.BalanceOf(std.GetOrigCaller()) // SEND WUGNOT: USER -> PROXY_WUGNOT wugnot.TransferFrom(std.GetOrigCaller(), std.CurrentRealm().Addr(), wugnotAmount) @@ -83,4 +82,4 @@ func ProxyUnwrap(wugnotAmount uint64) { // SEND GNOT: PROXY_WUGNOT -> USER banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(std.CurrentRealm().Addr(), std.GetOrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) -} \ No newline at end of file +} From 338454f02b32a68ea608ee9d957d7334e02f1f4e Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 18:11:54 +0200 Subject: [PATCH 20/34] remove other usage in gnoclient --- gno.land/pkg/gnoclient/client_txs.go | 5 ----- gnovm/stdlibs/io/io.gno | 1 - 2 files changed, 6 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index ea523317e33..e306d737ede 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -257,11 +257,6 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul caller := c.Signer.Info().GetAddress() - // Transpile and validate Gno syntax - if err = transpiler.TranspileAndCheckMempkg(msg.Package); err != nil { - return nil, err - } - // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgAddPackage{ Creator: caller, diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index a4ab3b3f8fd..bdbab135140 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -489,7 +489,6 @@ func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { remaining = maxint64 } return &SectionReader{r, off, off, remaining, n} - } // SectionReader implements Read, Seek, and ReadAt on a section From 6b4ed9e500894c15c74f14459c07d964ae1010b9 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 18:17:35 +0200 Subject: [PATCH 21/34] remove unused import in test --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 2 -- 1 file changed, 2 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index d6ca62609e6..15747414fa9 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -46,8 +46,6 @@ import ( "std" "gno.land/r/demo/wugnot" - - "gno.land/p/demo/ufmt" ) func ProxyWrap() { From 9ccb5ee54beb3cf3b25907b785b6ba592a33f210 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 12:45:19 +0200 Subject: [PATCH 22/34] bump limits for issue-1786 test --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index b6162dfe392..47a1aef25ff 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -5,24 +5,24 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error @@ -31,7 +31,7 @@ gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '9500 uint64' From e20ebbe0baf93f0f279f2d575bd29e1230c0b1c4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 13:00:53 +0200 Subject: [PATCH 23/34] bump up to 4M --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index 47a1aef25ff..44ea17674c9 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -5,24 +5,24 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error @@ -31,7 +31,7 @@ gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '9500 uint64' From 66fb953a31eb4da2464f97fcc1f4d2e802213ebf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 6 May 2024 21:00:40 +0200 Subject: [PATCH 24/34] changes from code review --- gno.land/pkg/sdk/vm/keeper.go | 4 ++-- gnovm/pkg/gnolang/go2gno_test.go | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index e3737de7216..e7757235020 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -161,7 +161,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { } // Validate Gno syntax and type check. - if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { return ErrTypeCheck(err) } @@ -342,7 +342,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { return "", ErrTypeCheck(err) } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 0cfe7610bd8..995ca3e8c0e 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -51,6 +51,8 @@ func (mpg mockPackageGetterCounts) GetMemPackage(path string) *std.MemPackage { } func TestTypeCheckMemPackage(t *testing.T) { + t.Parallel() + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and // each error in order must contain the associated string. errContains := func(s0 string, ss ...string) func(*testing.T, error) { @@ -318,7 +320,10 @@ func TestTypeCheckMemPackage(t *testing.T) { }) for _, tc := range tt { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := TypeCheckMemPackage(tc.pkg, tc.getter) if tc.check == nil { assert.NoError(t, err) From 38cba04b121c40072eed25ece4e11062fbea9f3a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 10:10:19 +0200 Subject: [PATCH 25/34] fix gas numbers --- gno.land/pkg/sdk/vm/gas_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 75d13aa6c5d..9f4ae1a6678 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -86,7 +86,7 @@ func TestAddPkgDeliverTxFailed(t *testing.T) { gasDeliver := gctx.GasMeter().GasConsumed() assert.False(t, res.IsOK()) - assert.Equal(t, int64(17989), gasDeliver) + assert.Equal(t, int64(2231), gasDeliver) } // Not enough gas for a failed transaction. @@ -98,7 +98,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { ctx = ctx.WithMode(sdk.RunTxModeDeliver) simulate = false - tx.Fee.GasWanted = 17988 + tx.Fee.GasWanted = 2230 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) var res sdk.Result @@ -116,7 +116,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { assert.True(t, abort) assert.False(t, res.IsOK()) gasCheck := gctx.GasMeter().GasConsumed() - assert.Equal(t, int64(17989), gasCheck) + assert.Equal(t, int64(2231), gasCheck) } else { t.Errorf("should panic") } From bee0f302f0da38264c3a1e886106ad9134ab1804 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 16:31:09 +0200 Subject: [PATCH 26/34] add transaction simulation in gnokey --- .../cmd/gnoland/testdata/addpkg_invalid.txtar | 2 +- .../gnoland/testdata/gnokey_simulate.txtar | 93 +++++++++++++++++++ tm2/pkg/crypto/keys/client/broadcast.go | 19 +++- tm2/pkg/crypto/keys/client/maketx.go | 41 +++++++- tm2/pkg/crypto/keys/client/query.go | 2 +- 5 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar index e0395fb479d..5cfd48bf2ea 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -6,7 +6,7 @@ gnoland start # add bar package located in $WORK directory as gno.land/r/foobar/bar ! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -# compare render +# check error message ! stdout .+ stderr 'as string value in return statement' stderr '"std" imported and not used' diff --git a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar new file mode 100644 index 00000000000..dab238a6122 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar @@ -0,0 +1,93 @@ +# test for gnokey maketx -simulate options, and how they return any errors + +loadpkg gno.land/r/hello $WORK/hello + +# start a new node +gnoland start + +# Initial state: assert that sequence == 0. +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' + +# attempt adding the "test" package. +# the package has a syntax error; simulation should catch this ahead of time and prevent the tx. +# -simulate test +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' +# -simulate only +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' +# -simulate skip +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "1"' + +# attempt calling hello.SetName correctly. +# -simulate test and skip should do it successfully, -simulate only should not. +# -simulate test +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "2"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, John!' +# -simulate only +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "2"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, John!' +# -simulate skip +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' + +# attempt calling hello.Grumpy (always panics). +# all calls should fail, however -test skip should increase the account sequence. +# none should change the name (ie. panic rollbacks). +# -simulate test +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' +# -simulate only +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' +# -simulate skip +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "4"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' + +-- test/test.gno -- +package test + +func Render(path string) string { + return 89 +} + +-- hello/hello.gno -- +package hello + +var name = "Ringo" + +func SetName(newName string) { + name = newName +} + +func Hello() string { + return "Hello, " + name + "!" +} + +func Grumpy() string { + name = "SCOUNDREL" + panic("YOU MAY NOT GREET ME, " + name) +} diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index 423714b2141..c088c63d19f 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -21,6 +21,10 @@ type BroadcastCfg struct { // internal tx *std.Tx + // Set by SignAndBroadcastHandler, similar to DryRun. + // If true, simulation is attempted but not printed; + // the result is only returned in case of an error. + testSimulate bool } func NewBroadcastCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command { @@ -81,6 +85,8 @@ func execBroadcast(cfg *BroadcastCfg, args []string, io commands.IO) error { io.Println("OK!") io.Println("GAS WANTED:", res.DeliverTx.GasWanted) io.Println("GAS USED: ", res.DeliverTx.GasUsed) + io.Println("HEIGHT: ", res.Height) + io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) } return nil } @@ -91,7 +97,7 @@ func BroadcastHandler(cfg *BroadcastCfg) (*ctypes.ResultBroadcastTxCommit, error } remote := cfg.RootCfg.Remote - if remote == "" || remote == "y" { + if remote == "" { return nil, errors.New("missing remote url") } @@ -105,8 +111,15 @@ func BroadcastHandler(cfg *BroadcastCfg) (*ctypes.ResultBroadcastTxCommit, error return nil, err } - if cfg.DryRun { - return SimulateTx(cli, bz) + // Both for DryRun and testSimulate, we perform simulation. + // However, DryRun always returns here, while in case of success + // testSimulate continues onto broadcasting the transaction. + if cfg.DryRun || cfg.testSimulate { + res, err := SimulateTx(cli, bz) + hasError := err != nil || res.CheckTx.IsErr() || res.DeliverTx.IsErr() + if cfg.DryRun || hasError { + return res, err + } } bres, err := cli.BroadcastTxCommit(bz) diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 603be59396c..8410804665d 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -20,7 +20,25 @@ type MakeTxCfg struct { Memo string Broadcast bool - ChainID string + // Valid options are SimulateTest, SimulateSkip or SimulateOnly. + Simulate string + ChainID string +} + +// These are the valid options for MakeTxConfig.Simulate. +const ( + SimulateTest = "test" + SimulateSkip = "skip" + SimulateOnly = "only" +) + +func (m *MakeTxCfg) Validate() error { + switch m.Simulate { + case SimulateTest, SimulateSkip, SimulateOnly: + default: + return fmt.Errorf("invalid simulate option: %q", m.Simulate) + } + return nil } func NewMakeTxCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command { @@ -71,14 +89,24 @@ func (c *MakeTxCfg) RegisterFlags(fs *flag.FlagSet) { &c.Broadcast, "broadcast", false, - "sign and broadcast", + "sign, simulate and broadcast", + ) + + fs.StringVar( + &c.Simulate, + "simulate", + "test", + `select how to simulate the transaction (only useful with --broadcast); valid options are + - test: attempts simulating the transaction, and if successful performs broadcasting (default) + - skip: avoids performing transaction simulation + - only: avoids broadcasting transaction (ie. dry run)`, ) fs.StringVar( &c.ChainID, "chainid", "dev", - "chainid to sign for (only useful if --broadcast)", + "chainid to sign for (only useful with --broadcast)", ) } @@ -139,6 +167,9 @@ func SignAndBroadcastHandler( bopts := &BroadcastCfg{ RootCfg: baseopts, tx: &tx, + + DryRun: cfg.Simulate == SimulateOnly, + testSimulate: cfg.Simulate == SimulateTest, } return BroadcastHandler(bopts) @@ -150,6 +181,10 @@ func ExecSignAndBroadcast( tx std.Tx, io commands.IO, ) error { + if err := cfg.Validate(); err != nil { + return err + } + baseopts := cfg.RootCfg // query account diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index e44bb796b9d..f4b65adebc0 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -91,7 +91,7 @@ func execQuery(cfg *QueryCfg, args []string, io commands.IO) error { func QueryHandler(cfg *QueryCfg) (*ctypes.ResultABCIQuery, error) { remote := cfg.RootCfg.Remote - if remote == "" || remote == "y" { + if remote == "" { return nil, errors.New("missing remote url") } From 3a82a98e85a9649706c8d60f3fe0284e36f7e576 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 17:10:24 +0200 Subject: [PATCH 27/34] fixup lint --- tm2/pkg/crypto/keys/client/maketx.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 8410804665d..2afccf9141c 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -32,11 +32,11 @@ const ( SimulateOnly = "only" ) -func (m *MakeTxCfg) Validate() error { - switch m.Simulate { +func (c *MakeTxCfg) Validate() error { + switch c.Simulate { case SimulateTest, SimulateSkip, SimulateOnly: default: - return fmt.Errorf("invalid simulate option: %q", m.Simulate) + return fmt.Errorf("invalid simulate option: %q", c.Simulate) } return nil } From 39ae304e2fa930aa98523e430f00d4a16ed561f8 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 18:01:13 +0200 Subject: [PATCH 28/34] update docs --- docs/gno-tooling/cli/gnokey.md | 49 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/gno-tooling/cli/gnokey.md b/docs/gno-tooling/cli/gnokey.md index 8abd0722229..8479e9c112d 100644 --- a/docs/gno-tooling/cli/gnokey.md +++ b/docs/gno-tooling/cli/gnokey.md @@ -171,13 +171,14 @@ gnokey maketx addpkg \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|--------------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | Defines the chainid to sign for (should only be used with `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx AddPackage Options** @@ -208,13 +209,14 @@ gnokey maketx call \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx Call Options** @@ -246,13 +248,14 @@ gnokey maketx send \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|-------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (implies `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx Send Options** @@ -302,3 +305,7 @@ Broadcast a signed document with the following command. ```bash gnokey broadcast {signed transaction file document} ``` + +[^1]: `only` simulates the transaction as a "dry run" (ie. without committing to + the chain), `test` performs simulation and, if successful, commits the + transaction, `skip` skips simulation entirely and commits directly. From dbde296e6e1d1a90ee9052a190b95c47df9cfc05 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 9 May 2024 21:23:42 +0200 Subject: [PATCH 29/34] partial revert of store changes --- gno.land/pkg/sdk/vm/builtins.go | 4 +-- gnovm/pkg/gnolang/store.go | 48 ++++++++++++--------------------- gnovm/tests/imports.go | 10 +++---- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 368ada6ff82..63062847e01 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: newStore, + Store: store, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index fd46df41aaa..38a5f3a50ff 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -14,9 +14,8 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested -// package does not exist. store should be used to run the machine, or otherwise -// call any methods which may call store.GetPackage, to avoid import cycles. -type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) +// package does not exist. +type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -82,7 +81,8 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. + opslog []StoreOp // for debugging and testing. + current []string // for detecting import cycles. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,29 +109,18 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } -type importerStore struct { - *defaultStore - importChain []string -} - -func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - if !isImport { - // if not an import, match behaviour to the defaultStore - return is.defaultStore.GetPackage(pkgPath, isImport) - } - // it is an import -- detect cyclic imports - if slices.Contains(is.importChain, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) - } - return is.getPackage(pkgPath, is) -} - // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - return ds.getPackage(pkgPath, importerStore{}) -} - -func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { + // detect circular imports + if isImport { + if slices.Contains(ds.current, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) + } + ds.current = append(ds.current, pkgPath) + defer func() { + ds.current = ds.current[:len(ds.current)-1] + }() + } // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -175,12 +164,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if impStore.defaultStore == nil { - // pre-allocate 16 entries to likely avoid further slice allocations. - impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} - } - impStore.importChain = append(impStore.importChain, pkgPath) - if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { + if pn, pv := ds.pkgGetter(pkgPath); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -626,6 +610,7 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. + ds.current = nil ds.SetCachePackage(Uverse()) } @@ -645,6 +630,7 @@ func (ds *defaultStore) Fork() Store { nativeStore: ds.nativeStore, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. + current: nil, } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 1a41a1d2f1d..389c6717780 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -62,7 +62,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -81,7 +81,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() @@ -93,7 +93,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -377,7 +377,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -394,7 +394,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, }) pn, pv = m2.RunMemPackage(memPkg, true) return From f843acb5870cd6f84b9dfcc647c167a5f06a55ed Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 9 May 2024 21:29:17 +0200 Subject: [PATCH 30/34] fixup --- gnovm/pkg/gnolang/gonative_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index 42729b43699..33e687cd40a 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) From 2558b09dfae5e88e1a9ef5c54086915f0d71e07a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:29:57 +0200 Subject: [PATCH 31/34] Revert "fixup" This reverts commit f843acb5870cd6f84b9dfcc647c167a5f06a55ed. --- gnovm/pkg/gnolang/gonative_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index 33e687cd40a..42729b43699 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) From cc9d45e37a2219a2fa09a14f3ba48ce93807d42d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:30:56 +0200 Subject: [PATCH 32/34] Revert "partial revert of store changes" This reverts commit dbde296e6e1d1a90ee9052a190b95c47df9cfc05. --- gno.land/pkg/sdk/vm/builtins.go | 4 +-- gnovm/pkg/gnolang/store.go | 48 +++++++++++++++++++++------------ gnovm/tests/imports.go | 10 +++---- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 63062847e01..368ada6ff82 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: store, + Store: newStore, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 38a5f3a50ff..fd46df41aaa 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -14,8 +14,9 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested -// package does not exist. -type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) +// package does not exist. store should be used to run the machine, or otherwise +// call any methods which may call store.GetPackage, to avoid import cycles. +type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -81,8 +82,7 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. - current []string // for detecting import cycles. + opslog []StoreOp // for debugging and testing. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,18 +109,29 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } +type importerStore struct { + *defaultStore + importChain []string +} + +func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + if !isImport { + // if not an import, match behaviour to the defaultStore + return is.defaultStore.GetPackage(pkgPath, isImport) + } + // it is an import -- detect cyclic imports + if slices.Contains(is.importChain, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) + } + return is.getPackage(pkgPath, is) +} + // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - // detect circular imports - if isImport { - if slices.Contains(ds.current, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) - } - ds.current = append(ds.current, pkgPath) - defer func() { - ds.current = ds.current[:len(ds.current)-1] - }() - } + return ds.getPackage(pkgPath, importerStore{}) +} + +func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -164,7 +175,12 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if pn, pv := ds.pkgGetter(pkgPath); pv != nil { + if impStore.defaultStore == nil { + // pre-allocate 16 entries to likely avoid further slice allocations. + impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} + } + impStore.importChain = append(impStore.importChain, pkgPath) + if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -610,7 +626,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. - ds.current = nil ds.SetCachePackage(Uverse()) } @@ -630,7 +645,6 @@ func (ds *defaultStore) Fork() Store { nativeStore: ds.nativeStore, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. - current: nil, } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 43d0969c007..d5541fb0554 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -63,7 +63,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -84,7 +84,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, Context: ctx, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) @@ -97,7 +97,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -381,7 +381,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -400,7 +400,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, Context: ctx, }) pn, pv = m2.RunMemPackage(memPkg, true) From 09f081c276528c25c478b16d8c5861179bf2ed4c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:35:24 +0200 Subject: [PATCH 33/34] fixup --- gnovm/pkg/gnolang/store.go | 40 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index fd46df41aaa..21a07a60276 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -82,7 +82,8 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. + opslog []StoreOp // for debugging and testing. + current []string } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,29 +110,17 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } -type importerStore struct { - *defaultStore - importChain []string -} - -func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - if !isImport { - // if not an import, match behaviour to the defaultStore - return is.defaultStore.GetPackage(pkgPath, isImport) - } - // it is an import -- detect cyclic imports - if slices.Contains(is.importChain, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) - } - return is.getPackage(pkgPath, is) -} - // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - return ds.getPackage(pkgPath, importerStore{}) -} - -func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { + if isImport { + if slices.Contains(ds.current, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) + } + ds.current = append(ds.current, pkgPath) + defer func() { + ds.current = ds.current[:len(ds.current)-1] + }() + } // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -175,12 +164,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if impStore.defaultStore == nil { - // pre-allocate 16 entries to likely avoid further slice allocations. - impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} - } - impStore.importChain = append(impStore.importChain, pkgPath) - if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { + if pn, pv := ds.pkgGetter(pkgPath, ds); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") From 38719cdc7f694099ee45774be759a5917eb6cf0a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:38:31 +0200 Subject: [PATCH 34/34] update docs --- gnovm/pkg/gnolang/store.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 21a07a60276..7fc54c12b0f 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -15,7 +15,9 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested // package does not exist. store should be used to run the machine, or otherwise -// call any methods which may call store.GetPackage, to avoid import cycles. +// call any methods which may call store.GetPackage; avoid using any "global" +// store as the one passed to the PackageGetter may be a fork of that (ie. +// the original is not meant to be written to). type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node)