From 95abc45af6b0df27e96d625d155fb574eecd06e5 Mon Sep 17 00:00:00 2001 From: grepsuzette Date: Mon, 17 Jun 2024 14:30:40 +0800 Subject: [PATCH 1/5] fix(gnovm): show "out of gas" errors (#2365) --- gnovm/pkg/gnolang/preprocess.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 88c5c2f1da4..c640fb38655 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/store/types" ) const ( @@ -3065,6 +3066,10 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { if rerr, ok := r.(error); ok { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) + } else if ex, ok := r.(OutOfGasException); ok { + // NOTE: baseapp.runTx() handles OutOfGasException + // which it then converts into ABCIError(std.ErrOutOfGas) + panic(ex) } else { // NOTE: gotuna/gorilla expects error exceptions. panic(fmt.Errorf("%s: %v", loc.String(), r)) From edc0f3b6bfaaa4cf6020a45ab42dd167b33e13f8 Mon Sep 17 00:00:00 2001 From: grepsuzette Date: Mon, 17 Jun 2024 15:17:34 +0800 Subject: [PATCH 2/5] types --- gnovm/pkg/gnolang/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index c640fb38655..d566b071860 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3066,7 +3066,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { if rerr, ok := r.(error); ok { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) - } else if ex, ok := r.(OutOfGasException); ok { + } else if ex, ok := r.(types.OutOfGasException); ok { // NOTE: baseapp.runTx() handles OutOfGasException // which it then converts into ABCIError(std.ErrOutOfGas) panic(ex) From 99db4d6591774773c3bc6701e2aa70773a8ac748 Mon Sep 17 00:00:00 2001 From: grepsuzette Date: Mon, 17 Jun 2024 14:30:40 +0800 Subject: [PATCH 3/5] fix(gnovm): show "out of gas" errors (#2365) --- gnovm/pkg/gnolang/preprocess.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 8eb2b37fcc2..a0e13f9ac61 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/store/types" ) const ( @@ -2855,6 +2856,10 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { if rerr, ok := r.(error); ok { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) + } else if ex, ok := r.(OutOfGasException); ok { + // NOTE: baseapp.runTx() handles OutOfGasException + // which it then converts into ABCIError(std.ErrOutOfGas) + panic(ex) } else { // NOTE: gotuna/gorilla expects error exceptions. panic(fmt.Errorf("%s: %v", loc.String(), r)) From 2f18002b0bed8e149cea4467972f29e64b4b7881 Mon Sep 17 00:00:00 2001 From: grepsuzette Date: Mon, 17 Jun 2024 15:17:34 +0800 Subject: [PATCH 4/5] types --- gnovm/pkg/gnolang/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index a0e13f9ac61..177b99f2b94 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2856,7 +2856,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { if rerr, ok := r.(error); ok { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) - } else if ex, ok := r.(OutOfGasException); ok { + } else if ex, ok := r.(types.OutOfGasException); ok { // NOTE: baseapp.runTx() handles OutOfGasException // which it then converts into ABCIError(std.ErrOutOfGas) panic(ex) From 0b9fcccc09cff3c972fe0317477a504de8cad9de Mon Sep 17 00:00:00 2001 From: grepsuzette Date: Tue, 16 Jul 2024 17:23:13 +0800 Subject: [PATCH 5/5] add txtar --- .../cmd/gnoland/testdata/issue_2365.txtar | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 gno.land/cmd/gnoland/testdata/issue_2365.txtar diff --git a/gno.land/cmd/gnoland/testdata/issue_2365.txtar b/gno.land/cmd/gnoland/testdata/issue_2365.txtar new file mode 100644 index 00000000000..6c7e0144fb5 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue_2365.txtar @@ -0,0 +1,203 @@ +# issue 2365 +# +# addpkg with insufficient gas giving "VM call panic {WritePerByte}" +# instead of some "out of gas" message. + +adduserfrom bob 'source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast' +stdout 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' + +## start a new node +gnoland start + +## check users initial balance +gnokey query bank/balances/${USER_ADDR_bob} +stdout '10000000ugnot' + +# addpkg w/ 0.0000000001gnot should fail +! gnokey maketx addpkg -deposit 1ugnot -gas-fee 1ugnot -pkgpath gno.land/p/demo/mux -pkgdir $WORK -gas-wanted 9000000 -broadcast -chainid=tendermint_test ${USER_ADDR_bob} + +# before fix +# stderr '(.+\n)+VM call panic {WritePerByte}' + +# after #2368 +stderr '(.+\n)+Data: out of gas error' + +-- gno.mod -- +module gno.land/p/demo/ufmt + +-- ufmt.gno -- +// Package ufmt provides utility functions for formatting strings, similarly +// to the Go package "fmt", of which only a subset is currently supported +// (hence the name µfmt - micro fmt). +package ufmt + +import ( + "strconv" + "strings" +) + +// Println formats using the default formats for its operands and writes to standard output. +// Println writes the given arguments to standard output with spaces between arguments +// and a newline at the end. +func Println(args ...interface{}) { + var strs []string + for _, arg := range args { + switch v := arg.(type) { + case string: + strs = append(strs, v) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + strs = append(strs, Sprintf("%d", v)) + case bool: + if v { + strs = append(strs, "true") + + continue + } + + strs = append(strs, "false") + default: + strs = append(strs, "(unhandled)") + } + } + + // TODO: remove println after gno supports os.Stdout + println(strings.Join(strs, " ")) +} + +// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf +// equivalent available in many languages, including C/C++. +// The number of args passed must exactly match the arguments consumed by the format. +// A limited number of formatting verbs and features are currently supported, +// hence the name ufmt (µfmt, micro-fmt). +// +// The currently formatted verbs are the following: +// +// %s: places a string value directly. +// If the value implements the interface interface{ String() string }, +// the String() method is called to retrieve the value. +// %d: formats an integer value using package "strconv". +// Currently supports only uint, uint64, int, int64. +// %t: formats a boolean value to "true" or "false". +// %%: outputs a literal %. Does not consume an argument. +func Sprintf(format string, args ...interface{}) string { + // we use runes to handle multi-byte characters + sTor := []rune(format) + end := len(sTor) + argNum := 0 + argLen := len(args) + buf := "" + + for i := 0; i < end; { + isLast := i == end-1 + c := string(sTor[i]) + + if isLast || c != "%" { + // we don't check for invalid format like a one ending with "%" + buf += string(c) + i++ + continue + } + + verb := string(sTor[i+1]) + if verb == "%" { + buf += "%" + i += 2 + continue + } + + if argNum > argLen { + panic("invalid number of arguments to ufmt.Sprintf") + } + arg := args[argNum] + argNum++ + + switch verb { + case "s": + switch v := arg.(type) { + case interface{ String() string }: + buf += v.String() + case string: + buf += v + default: + buf += "(unhandled)" + } + case "d": + switch v := arg.(type) { + case int: + buf += strconv.Itoa(v) + case int8: + buf += strconv.Itoa(int(v)) + case int16: + buf += strconv.Itoa(int(v)) + case int32: + buf += strconv.Itoa(int(v)) + case int64: + buf += strconv.Itoa(int(v)) + case uint: + buf += strconv.FormatUint(uint64(v), 10) + case uint8: + buf += strconv.FormatUint(uint64(v), 10) + case uint16: + buf += strconv.FormatUint(uint64(v), 10) + case uint32: + buf += strconv.FormatUint(uint64(v), 10) + case uint64: + buf += strconv.FormatUint(v, 10) + default: + buf += "(unhandled)" + } + case "t": + switch v := arg.(type) { + case bool: + if v { + buf += "true" + } else { + buf += "false" + } + default: + buf += "(unhandled)" + } + // % handled before, as it does not consume an argument + default: + buf += "(unhandled)" + } + + i += 2 + } + if argNum < argLen { + panic("too many arguments to ufmt.Sprintf") + } + return buf +} + +// errMsg implements the error interface. +type errMsg struct { + msg string +} + +// Error defines the requirements of the error interface. +// It functions similarly to Go's errors.New() +func (e *errMsg) Error() string { + return e.msg +} + +// Errorf is a function that mirrors the functionality of fmt.Errorf. +// +// It takes a format string and arguments to create a formatted string, +// then sets this string as the 'msg' field of an errMsg struct and returns a pointer to this struct. +// +// This function operates in a similar manner to Go's fmt.Errorf, +// providing a way to create formatted error messages. +// +// The currently formatted verbs are the following: +// +// %s: places a string value directly. +// If the value implements the interface interface{ String() string }, +// the String() method is called to retrieve the value. +// %d: formats an integer value using package "strconv". +// Currently supports only uint, uint64, int, int64. +// %t: formats a boolean value to "true" or "false". +// %%: outputs a literal %. Does not consume an argument. +func Errorf(format string, args ...interface{}) error { + return &errMsg{Sprintf(format, args...)} +}