From 3e513b0195a12dba6ca296a86dee08904a23fa0a Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 06:21:19 -0600 Subject: [PATCH 1/7] Adds creation of a context to code execution under `gno run`. This addresses this issue: https://github.com/gnolang/gno/issues/2834 It tries to hew very closely to how the context is created under `gno test` so that there is consistency of results between code that is executed in the test and run contexts. --- gnovm/cmd/gno/run.go | 128 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 7 deletions(-) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index cfbfe995a46..0c07c67cd99 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -10,10 +10,16 @@ import ( "path/filepath" "strings" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/tests" + teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/std" ) type runCfg struct { @@ -112,13 +118,13 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { return errors.New("no files to run") } - m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: string(files[0].PkgName), - Input: stdin, - Output: stdout, - Store: testStore, - Debug: cfg.debug || cfg.debugAddr != "", - }) + m := runMachine( + testStore, + stdin, + stdout, + string(files[0].PkgName), + cfg.debug || cfg.debugAddr != "", + ) defer m.Release() @@ -136,6 +142,114 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { return nil } +func runMachine(store gno.Store, stdin io.Reader, stdout io.Writer, pkgPath string, debug bool) *gno.Machine { + var ( + send std.Coins + maxAlloc int64 + ) + + return runMachineCustom(store, pkgPath, stdin, stdout, maxAlloc, send, debug) +} + +func runMachineCustom(store gno.Store, pkgPath string, stdin io.Reader, stdout io.Writer, maxAlloc int64, send std.Coins, debug bool) *gno.Machine { + ctx := runContext(pkgPath, send) + m := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: pkgPath, + Output: stdout, + Store: store, + Context: ctx, + MaxAllocBytes: maxAlloc, + }) + return m +} + +// runContext() creates the context for gno run. It has been intentially setup to mirror the context that is +// created for gno test, so that behavior remains consistent between running code and testing code. +func runContext(pkgPath string, send std.Coins) *teststd.TestExecContext { + pkgAddr := gno.DerivePkgAddr(pkgPath) + caller := gno.DerivePkgAddr("user1.gno") + + pkgCoins := std.MustParseCoins(ugnot.ValueString(200000000)).Add(send) // >= send. + banker := newTestBanker(pkgAddr.Bech32(), pkgCoins) + ctx := stdlibs.ExecContext{ + ChainID: "run", + Height: 123, + Timestamp: 1234567890, + Msg: nil, + OrigCaller: caller.Bech32(), + OrigPkgAddr: pkgAddr.Bech32(), + OrigSend: send, + OrigSendSpent: new(std.Coins), + Banker: banker, + EventLogger: sdk.NewEventLogger(), + } + return &teststd.TestExecContext{ + ExecContext: ctx, + RealmFrames: make(map[*gno.Frame]teststd.RealmOverride), + } +} + +type testBanker struct { + coinTable map[crypto.Bech32Address]std.Coins +} + +func newTestBanker(args ...interface{}) *testBanker { + coinTable := make(map[crypto.Bech32Address]std.Coins) + if len(args)%2 != 0 { + panic("newTestBanker requires even number of arguments; addr followed by coins") + } + for i := 0; i < len(args); i += 2 { + addr := args[i].(crypto.Bech32Address) + amount := args[i+1].(std.Coins) + coinTable[addr] = amount + } + return &testBanker{ + coinTable: coinTable, + } +} + +func (tb *testBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { + return tb.coinTable[addr] +} + +func (tb *testBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { + fcoins, fexists := tb.coinTable[from] + if !fexists { + panic(fmt.Sprintf( + "source address %s does not exist", + from.String())) + } + if !fcoins.IsAllGTE(amt) { + panic(fmt.Sprintf( + "source address %s has %s; cannot send %s", + from.String(), fcoins, amt)) + } + // First, subtract from 'from'. + frest := fcoins.Sub(amt) + tb.coinTable[from] = frest + // Second, add to 'to'. + // NOTE: even works when from==to, due to 2-step isolation. + tcoins, _ := tb.coinTable[to] + tsum := tcoins.Add(amt) + tb.coinTable[to] = tsum +} + +func (tb *testBanker) TotalCoin(denom string) int64 { + panic("not yet implemented") +} + +func (tb *testBanker) IssueCoin(addr crypto.Bech32Address, denom string, amt int64) { + coins, _ := tb.coinTable[addr] + sum := coins.Add(std.Coins{{denom, amt}}) + tb.coinTable[addr] = sum +} + +func (tb *testBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amt int64) { + coins, _ := tb.coinTable[addr] + rest := coins.Sub(std.Coins{{denom, amt}}) + tb.coinTable[addr] = rest +} + func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { files := make([]*gno.FileNode, 0, len(fnames)) var hasError bool From f60b8d667ef760c4ba907f09849f64d1a79ddc2b Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 08:00:13 -0600 Subject: [PATCH 2/7] Add a basic test to show that the context is available in to the run command. --- gnovm/cmd/gno/run_test.go | 6 +++++- gnovm/cmd/gno/run_tests/context.gno | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 gnovm/cmd/gno/run_tests/context.gno diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index 79a873cdfe5..dbc6a2d059f 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -69,7 +69,7 @@ func TestRunApp(t *testing.T) { }, { args: []string{"run", "-debug", "../../tests/integ/debugger/sample.gno"}, - stdoutShouldContain: "Welcome to the Gnovm debugger", + stdoutShouldContain: "in main\nhello 3\n!zero\n2\nbye 4", }, { args: []string{"run", "-debug-addr", "invalidhost:17538", "../../tests/integ/debugger/sample.gno"}, @@ -79,6 +79,10 @@ func TestRunApp(t *testing.T) { args: []string{"run", "../../tests/integ/invalid_assign/main.gno"}, recoverShouldContain: "cannot use bool as main.C without explicit conversion", }, + { + args: []string{"run", "-expr", "Context()", "./run_tests/context.gno"}, + stdoutShouldContain: "Context worked", + }, // TODO: a test file // TODO: args // TODO: nativeLibs VS stdlibs diff --git a/gnovm/cmd/gno/run_tests/context.gno b/gnovm/cmd/gno/run_tests/context.gno new file mode 100644 index 00000000000..d60398799ad --- /dev/null +++ b/gnovm/cmd/gno/run_tests/context.gno @@ -0,0 +1,11 @@ +package runtests + +import ( + "fmt" + "std" +) + +func Context() { + // This requires a Context to work; it will fail ugly if the Context isn't available. + fmt.Printf("Context worked: %d\n", std.GetHeight()) +} From 491ad4a14c7bf6232e0c48dca4ef917257cc4f33 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 08:34:34 -0600 Subject: [PATCH 3/7] Fix formatting issues. --- gnovm/cmd/gno/run_test.go | 2 +- gnovm/cmd/gno/run_tests/context.gno | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index dbc6a2d059f..7e361db056c 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -80,7 +80,7 @@ func TestRunApp(t *testing.T) { recoverShouldContain: "cannot use bool as main.C without explicit conversion", }, { - args: []string{"run", "-expr", "Context()", "./run_tests/context.gno"}, + args: []string{"run", "-expr", "Context()", "./run_tests/context.gno"}, stdoutShouldContain: "Context worked", }, // TODO: a test file diff --git a/gnovm/cmd/gno/run_tests/context.gno b/gnovm/cmd/gno/run_tests/context.gno index d60398799ad..92a9cc632b7 100644 --- a/gnovm/cmd/gno/run_tests/context.gno +++ b/gnovm/cmd/gno/run_tests/context.gno @@ -2,7 +2,7 @@ package runtests import ( "fmt" - "std" + "std" ) func Context() { From df8c7aecac1dc449555c1fbb7a3d1fe01a0219e9 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 08:57:05 -0600 Subject: [PATCH 4/7] Move this test to the right location. --- gnovm/cmd/gno/run_test.go | 2 +- gnovm/{cmd/gno/run_tests => tests/integ/context}/context.gno | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename gnovm/{cmd/gno/run_tests => tests/integ/context}/context.gno (100%) diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index 7e361db056c..131e184b11e 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -80,7 +80,7 @@ func TestRunApp(t *testing.T) { recoverShouldContain: "cannot use bool as main.C without explicit conversion", }, { - args: []string{"run", "-expr", "Context()", "./run_tests/context.gno"}, + args: []string{"run", "-expr", "Context()", "../../tests/integ/context/context.gno"}, stdoutShouldContain: "Context worked", }, // TODO: a test file diff --git a/gnovm/cmd/gno/run_tests/context.gno b/gnovm/tests/integ/context/context.gno similarity index 100% rename from gnovm/cmd/gno/run_tests/context.gno rename to gnovm/tests/integ/context/context.gno From 08873dc196be32a17eb9d78d65440d7604658c79 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 09:38:07 -0600 Subject: [PATCH 5/7] I accidentlly deleted the input setup line. :/ Fixed. --- gnovm/cmd/gno/run.go | 3 ++- gnovm/cmd/gno/run_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 0c07c67cd99..d242e756848 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -156,9 +156,10 @@ func runMachineCustom(store gno.Store, pkgPath string, stdin io.Reader, stdout i m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: pkgPath, Output: stdout, + Input: stdin, Store: store, Context: ctx, - MaxAllocBytes: maxAlloc, + Debug: debug, }) return m } diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index 131e184b11e..975868b7daf 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -69,7 +69,7 @@ func TestRunApp(t *testing.T) { }, { args: []string{"run", "-debug", "../../tests/integ/debugger/sample.gno"}, - stdoutShouldContain: "in main\nhello 3\n!zero\n2\nbye 4", + stdoutShouldContain: "Welcome to the Gnovm debugger", }, { args: []string{"run", "-debug-addr", "invalidhost:17538", "../../tests/integ/debugger/sample.gno"}, From 203589ee419cde79dcb41bdbdc9b1e62c06f4434 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 26 Sep 2024 09:40:48 -0600 Subject: [PATCH 6/7] Fix syntax. --- gnovm/cmd/gno/run.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index d242e756848..df822b99d63 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -154,12 +154,12 @@ func runMachine(store gno.Store, stdin io.Reader, stdout io.Writer, pkgPath stri func runMachineCustom(store gno.Store, pkgPath string, stdin io.Reader, stdout io.Writer, maxAlloc int64, send std.Coins, debug bool) *gno.Machine { ctx := runContext(pkgPath, send) m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: pkgPath, - Output: stdout, - Input: stdin, - Store: store, - Context: ctx, - Debug: debug, + PkgPath: pkgPath, + Output: stdout, + Input: stdin, + Store: store, + Context: ctx, + Debug: debug, }) return m } From 54d976c02f8b913606a46a2e32ba871b227d8983 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Fri, 27 Sep 2024 01:50:05 -0600 Subject: [PATCH 7/7] Removed the code duplication from test, and just directly use test.TestContext. --- gnovm/cmd/gno/run.go | 132 ++++--------------------------------------- 1 file changed, 11 insertions(+), 121 deletions(-) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index df822b99d63..f174c2b4cc5 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -10,15 +10,10 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/tests" - teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -118,13 +113,17 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { return errors.New("no files to run") } - m := runMachine( - testStore, - stdin, - stdout, - string(files[0].PkgName), - cfg.debug || cfg.debugAddr != "", - ) + var send std.Coins + pkgPath := string(files[0].PkgName) + ctx := tests.TestContext(pkgPath, send) + m := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: pkgPath, + Output: stdout, + Input: stdin, + Store: testStore, + Context: ctx, + Debug: cfg.debug || cfg.debugAddr != "", + }) defer m.Release() @@ -142,115 +141,6 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { return nil } -func runMachine(store gno.Store, stdin io.Reader, stdout io.Writer, pkgPath string, debug bool) *gno.Machine { - var ( - send std.Coins - maxAlloc int64 - ) - - return runMachineCustom(store, pkgPath, stdin, stdout, maxAlloc, send, debug) -} - -func runMachineCustom(store gno.Store, pkgPath string, stdin io.Reader, stdout io.Writer, maxAlloc int64, send std.Coins, debug bool) *gno.Machine { - ctx := runContext(pkgPath, send) - m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: pkgPath, - Output: stdout, - Input: stdin, - Store: store, - Context: ctx, - Debug: debug, - }) - return m -} - -// runContext() creates the context for gno run. It has been intentially setup to mirror the context that is -// created for gno test, so that behavior remains consistent between running code and testing code. -func runContext(pkgPath string, send std.Coins) *teststd.TestExecContext { - pkgAddr := gno.DerivePkgAddr(pkgPath) - caller := gno.DerivePkgAddr("user1.gno") - - pkgCoins := std.MustParseCoins(ugnot.ValueString(200000000)).Add(send) // >= send. - banker := newTestBanker(pkgAddr.Bech32(), pkgCoins) - ctx := stdlibs.ExecContext{ - ChainID: "run", - Height: 123, - Timestamp: 1234567890, - Msg: nil, - OrigCaller: caller.Bech32(), - OrigPkgAddr: pkgAddr.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - Banker: banker, - EventLogger: sdk.NewEventLogger(), - } - return &teststd.TestExecContext{ - ExecContext: ctx, - RealmFrames: make(map[*gno.Frame]teststd.RealmOverride), - } -} - -type testBanker struct { - coinTable map[crypto.Bech32Address]std.Coins -} - -func newTestBanker(args ...interface{}) *testBanker { - coinTable := make(map[crypto.Bech32Address]std.Coins) - if len(args)%2 != 0 { - panic("newTestBanker requires even number of arguments; addr followed by coins") - } - for i := 0; i < len(args); i += 2 { - addr := args[i].(crypto.Bech32Address) - amount := args[i+1].(std.Coins) - coinTable[addr] = amount - } - return &testBanker{ - coinTable: coinTable, - } -} - -func (tb *testBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return tb.coinTable[addr] -} - -func (tb *testBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - fcoins, fexists := tb.coinTable[from] - if !fexists { - panic(fmt.Sprintf( - "source address %s does not exist", - from.String())) - } - if !fcoins.IsAllGTE(amt) { - panic(fmt.Sprintf( - "source address %s has %s; cannot send %s", - from.String(), fcoins, amt)) - } - // First, subtract from 'from'. - frest := fcoins.Sub(amt) - tb.coinTable[from] = frest - // Second, add to 'to'. - // NOTE: even works when from==to, due to 2-step isolation. - tcoins, _ := tb.coinTable[to] - tsum := tcoins.Add(amt) - tb.coinTable[to] = tsum -} - -func (tb *testBanker) TotalCoin(denom string) int64 { - panic("not yet implemented") -} - -func (tb *testBanker) IssueCoin(addr crypto.Bech32Address, denom string, amt int64) { - coins, _ := tb.coinTable[addr] - sum := coins.Add(std.Coins{{denom, amt}}) - tb.coinTable[addr] = sum -} - -func (tb *testBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amt int64) { - coins, _ := tb.coinTable[addr] - rest := coins.Sub(std.Coins{{denom, amt}}) - tb.coinTable[addr] = rest -} - func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { files := make([]*gno.FileNode, 0, len(fnames)) var hasError bool