From 07e3c4914ff9569ac765e0cb9ad19a7cc45fdfd5 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 11 Feb 2025 15:46:37 +0100 Subject: [PATCH 01/47] refactor(gnovm): remove TRANS_BREAK from transcriber (#3626) was unused until now, and it looks redundant if we have TRANS_SKIP anyway. --- gnovm/pkg/gnolang/string_methods.go | 7 +- gnovm/pkg/gnolang/transcribe.go | 110 +++++++--------------------- 2 files changed, 28 insertions(+), 89 deletions(-) diff --git a/gnovm/pkg/gnolang/string_methods.go b/gnovm/pkg/gnolang/string_methods.go index 460e308fa9b..565b5860708 100644 --- a/gnovm/pkg/gnolang/string_methods.go +++ b/gnovm/pkg/gnolang/string_methods.go @@ -229,13 +229,12 @@ func _() { var x [1]struct{} _ = x[TRANS_CONTINUE-0] _ = x[TRANS_SKIP-1] - _ = x[TRANS_BREAK-2] - _ = x[TRANS_EXIT-3] + _ = x[TRANS_EXIT-2] } -const _TransCtrl_name = "TRANS_CONTINUETRANS_SKIPTRANS_BREAKTRANS_EXIT" +const _TransCtrl_name = "TRANS_CONTINUETRANS_SKIPTRANS_EXIT" -var _TransCtrl_index = [...]uint8{0, 14, 24, 35, 45} +var _TransCtrl_index = [...]uint8{0, 14, 24, 34} func (i TransCtrl) String() string { if i >= TransCtrl(len(_TransCtrl_index)-1) { diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go index dab539a8707..572810e9668 100644 --- a/gnovm/pkg/gnolang/transcribe.go +++ b/gnovm/pkg/gnolang/transcribe.go @@ -14,7 +14,6 @@ type ( const ( TRANS_CONTINUE TransCtrl = iota TRANS_SKIP - TRANS_BREAK TRANS_EXIT ) @@ -101,7 +100,7 @@ const ( TRANS_IMPORT_PATH TRANS_CONST_TYPE TRANS_CONST_VALUE - TRANS_VAR_NAME // XXX stringer + TRANS_VAR_NAME TRANS_VAR_TYPE TRANS_VAR_VALUE TRANS_TYPE_TYPE @@ -113,8 +112,6 @@ const ( // - TRANS_SKIP to break out of the // ENTER,CHILDS1,[BLOCK,CHILDS2]?,LEAVE sequence for that node, // i.e. skipping (the rest of) it; -// - TRANS_BREAK to break out of looping in CHILDS1 or CHILDS2, -// but still perform TRANS_LEAVE. // - TRANS_EXIT to stop traversing altogether. // // Do not mutate ns. @@ -168,9 +165,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Args { cnn.Args[idx] = transcribe(t, nns, TRANS_CALL_ARG, idx, cnn.Args[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -248,16 +243,12 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc k, v := kvx.Key, kvx.Value if k != nil { k = transcribe(t, nns, TRANS_COMPOSITE_KEY, idx, k, &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } v = transcribe(t, nns, TRANS_COMPOSITE_VALUE, idx, v, &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } cnn.Elts[idx] = KeyValueExpr{Key: k, Value: v} @@ -269,9 +260,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.HeapCaptures { cnn.HeapCaptures[idx] = *(transcribe(t, nns, TRANS_FUNCLIT_HEAP_CAPTURE, idx, &cnn.HeapCaptures[idx], &c).(*NameExpr)) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -285,9 +274,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FUNCLIT_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -321,9 +308,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *InterfaceTypeExpr: for idx := range cnn.Methods { cnn.Methods[idx] = *transcribe(t, nns, TRANS_INTERFACETYPE_METHOD, idx, &cnn.Methods[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -341,9 +326,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Results { cnn.Results[idx] = *transcribe(t, nns, TRANS_FUNCTYPE_RESULT, idx, &cnn.Results[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -359,9 +342,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *StructTypeExpr: for idx := range cnn.Fields { cnn.Fields[idx] = *transcribe(t, nns, TRANS_STRUCTTYPE_FIELD, idx, &cnn.Fields[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -373,17 +354,13 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *AssignStmt: for idx := range cnn.Lhs { cnn.Lhs[idx] = transcribe(t, nns, TRANS_ASSIGN_LHS, idx, cnn.Lhs[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } for idx := range cnn.Rhs { cnn.Rhs[idx] = transcribe(t, nns, TRANS_ASSIGN_RHS, idx, cnn.Rhs[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -398,9 +375,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_BLOCK_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -409,9 +384,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_DECL_BODY, idx, cnn.Body[idx], &c).(SimpleDeclStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -455,9 +428,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FOR_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -506,9 +477,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_IF_CASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -544,18 +513,14 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_RANGE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } case *ReturnStmt: for idx := range cnn.Results { cnn.Results[idx] = transcribe(t, nns, TRANS_RETURN_RESULT, idx, cnn.Results[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -564,9 +529,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *SelectStmt: for idx := range cnn.Cases { cnn.Cases[idx] = *transcribe(t, nns, TRANS_SELECT_CASE, idx, &cnn.Cases[idx], &c).(*SelectCaseStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -585,9 +548,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_SELECTCASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -632,9 +593,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Clauses { cnn.Clauses[idx] = *transcribe(t, nns, TRANS_SWITCH_CASE, idx, &cnn.Clauses[idx], &c).(*SwitchClauseStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -652,18 +611,14 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Cases { cnn.Cases[idx] = transcribe(t, nns, TRANS_SWITCHCASE_CASE, idx, cnn.Cases[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_SWITCHCASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -688,9 +643,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FUNC_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -709,9 +662,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Values { cnn.Values[idx] = transcribe(t, nns, TRANS_VAR_VALUE, idx, cnn.Values[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -730,9 +681,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Decls { cnn.Decls[idx] = transcribe(t, nns, TRANS_FILE_BODY, idx, cnn.Decls[idx], &c).(Decl) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -768,12 +717,3 @@ func isStopOrSkip(oldnc *TransCtrl, nc TransCtrl) (stop bool) { panic("should not happen") } } - -// returns true if transcribe() should break (a loop). -func isBreak(nc TransCtrl) (brek bool) { - if nc == TRANS_BREAK { - return true - } else { - return false - } -} From 4f036699e4dc0024195fc142f8de60c72a34d66f Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 11 Feb 2025 16:22:10 +0100 Subject: [PATCH 02/47] chore(gnovm): instrument debug tracing to display the caller site. (#3702) Running the gnovm with the debug traces enabled (build tag `debug`) allows to display the details of preprocessing and opcodes operations. This PR adds the caller location in source (file + line) to each trace, allowing to identify the calling context of opcodes, to ease understanding the gnovm behavior. No change when debug is disabled (the default mode). Tracing can be activated by: `go run -tags debug ./cmd/gno run args` Before: ```console DEBUG: |||| -v (true bool) DEBUG: EXEC: (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: |||| -s bodyStmt[0/0/1]=(end) DEBUG: |||| +o OpPopResults DEBUG: ||||| +x (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: ||||| +o OpEval DEBUG: |||||| -o OpEval DEBUG: EVAL: (*gnolang.CallExpr) (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: ||||| +o OpPrecall DEBUG: |||||| +x i DEBUG: |||||| +o OpEval DEBUG: ||||||| +x (const ("i:" string)) DEBUG: ||||||| +o OpEval DEBUG: |||||||| +x (const (println func(xs ...interface{})())) DEBUG: |||||||| +o OpEval DEBUG: ||||||||| -o OpEval DEBUG: EVAL: (*gnolang.ConstExpr) (const (println func(xs ...interface{})())) DEBUG: |||||||| -x (const (println func(xs ...interface{})())) DEBUG: |||||||| +v (println func(xs ...interface{})()) DEBUG: |||||||| -o OpEval DEBUG: EVAL: (*gnolang.ConstExpr) (const ("i:" string)) DEBUG: ||||||| -x (const ("i:" string)) DEBUG: ||||||| +v ("i:" string) DEBUG: ||||||| -o OpEval ``` After: ```console DEBUG: op_exec.go:99 : |||| -v (true bool) DEBUG: machine.go:1535: EXEC: (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: op_exec.go:484 : |||| -s bodyStmt[0/0/1]=(end) DEBUG: op_exec.go:488 : |||| +o OpPopResults DEBUG: op_exec.go:493 : ||||| +x (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: op_exec.go:494 : ||||| +o OpEval DEBUG: machine.go:1218: |||||| -o OpEval DEBUG: machine.go:1380: EVAL: (*gnolang.CallExpr) (const (println func(xs ...interface{})()))((const ("i:" string)), i) DEBUG: op_eval.go:243 : ||||| +o OpPrecall DEBUG: op_eval.go:247 : |||||| +x i DEBUG: op_eval.go:248 : |||||| +o OpEval DEBUG: op_eval.go:247 : ||||||| +x (const ("i:" string)) DEBUG: op_eval.go:248 : ||||||| +o OpEval DEBUG: op_eval.go:251 : |||||||| +x (const (println func(xs ...interface{})())) DEBUG: op_eval.go:252 : |||||||| +o OpEval DEBUG: machine.go:1218: ||||||||| -o OpEval DEBUG: machine.go:1380: EVAL: (*gnolang.ConstExpr) (const (println func(xs ...interface{})())) DEBUG: op_eval.go:317 : |||||||| -x (const (println func(xs ...interface{})())) DEBUG: op_eval.go:319 : |||||||| +v (println func(xs ...interface{})()) DEBUG: machine.go:1218: |||||||| -o OpEval DEBUG: machine.go:1380: EVAL: (*gnolang.ConstExpr) (const ("i:" string)) DEBUG: op_eval.go:317 : ||||||| -x (const ("i:" string)) DEBUG: op_eval.go:319 : ||||||| +v ("i:" string) DEBUG: machine.go:1218: ||||||| -o OpEval ``` --------- Co-authored-by: Morgan --- gnovm/pkg/gnolang/debug.go | 12 ++++++++++-- gnovm/pkg/gnolang/machine.go | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/gnovm/pkg/gnolang/debug.go b/gnovm/pkg/gnolang/debug.go index c7f9311ffe4..0f9cb9a1f9c 100644 --- a/gnovm/pkg/gnolang/debug.go +++ b/gnovm/pkg/gnolang/debug.go @@ -3,6 +3,8 @@ package gnolang import ( "fmt" "net/http" + "path" + "runtime" "strings" "time" @@ -48,7 +50,10 @@ var enabled bool = true func (debugging) Println(args ...interface{}) { if debug { if enabled { - fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) + _, file, line, _ := runtime.Caller(2) + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + fmt.Println(append([]interface{}{prefix}, args...)...) } } } @@ -56,7 +61,10 @@ func (debugging) Println(args ...interface{}) { func (debugging) Printf(format string, args ...interface{}) { if debug { if enabled { - fmt.Printf("DEBUG: "+format, args...) + _, file, line, _ := runtime.Caller(2) + caller := fmt.Sprintf("%.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + fmt.Printf(prefix+format, args...) } } } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 627854c9e9a..7b89e98eb6d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -3,7 +3,9 @@ package gnolang import ( "fmt" "io" + "path" "reflect" + "runtime" "slices" "strconv" "strings" @@ -2120,8 +2122,11 @@ func (m *Machine) Panic(ex TypedValue) { func (m *Machine) Println(args ...interface{}) { if debug { if enabled { - s := strings.Repeat("|", m.NumOps) - debug.Println(append([]interface{}{s}, args...)...) + _, file, line, _ := runtime.Caller(2) // get caller info + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + s := prefix + strings.Repeat("|", m.NumOps) + fmt.Println(append([]interface{}{s}, args...)...) } } } @@ -2129,8 +2134,11 @@ func (m *Machine) Println(args ...interface{}) { func (m *Machine) Printf(format string, args ...interface{}) { if debug { if enabled { - s := strings.Repeat("|", m.NumOps) - debug.Printf(s+" "+format, args...) + _, file, line, _ := runtime.Caller(2) // get caller info + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + s := prefix + strings.Repeat("|", m.NumOps) + fmt.Printf(s+" "+format, args...) } } } From 7ca5ed2718988736cd1dc7ed493a053d8b6518fa Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 11 Feb 2025 16:26:18 +0100 Subject: [PATCH 03/47] test(cmd/gno): don't fail if tests take more than 10 seconds (#3723) https://github.com/gnolang/gno/actions/runs/13265575679/job/37032064155 Changes the regex for txtar tests like `\d\.\d\ds` to have a + symbol instead, in case they take longer than 10 seconds. --- gnovm/cmd/gno/testdata/test/error_correct.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/filetest_events.txtar | 6 +++--- gnovm/cmd/gno/testdata/test/minim2.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/minim3.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/multitest_events.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/output_correct.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/output_sync.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/realm_correct.txtar | 6 +++--- gnovm/cmd/gno/testdata/test/realm_sync.txtar | 4 ++-- gnovm/cmd/gno/testdata/test/valid_filetest.txtar | 6 +++--- gnovm/cmd/gno/testdata/test/valid_test.txtar | 8 ++++---- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/gnovm/cmd/gno/testdata/test/error_correct.txtar b/gnovm/cmd/gno/testdata/test/error_correct.txtar index f9ce4dd9028..bcd2c87da5c 100644 --- a/gnovm/cmd/gno/testdata/test/error_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/error_correct.txtar @@ -3,8 +3,8 @@ gno test -v . stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/test/filetest_events.txtar b/gnovm/cmd/gno/testdata/test/filetest_events.txtar index 34da5fe2ff0..87f873980d5 100644 --- a/gnovm/cmd/gno/testdata/test/filetest_events.txtar +++ b/gnovm/cmd/gno/testdata/test/filetest_events.txtar @@ -3,14 +3,14 @@ gno test -print-events . ! stdout .+ -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' gno test -print-events -v . stdout 'test' stderr '=== RUN file/valid_filetest.gno' -stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/valid_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/minim2.txtar b/gnovm/cmd/gno/testdata/test/minim2.txtar index 3c4d1d085f0..d66d5076ef0 100644 --- a/gnovm/cmd/gno/testdata/test/minim2.txtar +++ b/gnovm/cmd/gno/testdata/test/minim2.txtar @@ -2,8 +2,8 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/test/minim3.txtar b/gnovm/cmd/gno/testdata/test/minim3.txtar index ac8ae0c41d4..ba1847a21df 100644 --- a/gnovm/cmd/gno/testdata/test/minim3.txtar +++ b/gnovm/cmd/gno/testdata/test/minim3.txtar @@ -2,8 +2,8 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar index 321c790561a..5cb134f46a1 100644 --- a/gnovm/cmd/gno/testdata/test/multitest_events.txtar +++ b/gnovm/cmd/gno/testdata/test/multitest_events.txtar @@ -2,10 +2,10 @@ gno test -print-events . -! stdout .+ +! stdout .+ stderr 'EVENTS: \[{\"type\":\"EventA\",\"attrs\":\[\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestA\"}\]' stderr 'EVENTS: \[{\"type\":\"EventB\",\"attrs\":\[{\"key\":\"keyA\",\"value\":\"valA\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"},{\"type\":\"EventC\",\"attrs\":\[{\"key\":\"keyD\",\"value\":\"valD\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"}\]' -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/output_correct.txtar b/gnovm/cmd/gno/testdata/test/output_correct.txtar index a8aa878e0a4..3a829e66bee 100644 --- a/gnovm/cmd/gno/testdata/test/output_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/output_correct.txtar @@ -5,8 +5,8 @@ gno test -v . stdout 'hey' stdout 'hru?' stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/test/output_sync.txtar b/gnovm/cmd/gno/testdata/test/output_sync.txtar index 45385a7eef9..1d701cc1c7f 100644 --- a/gnovm/cmd/gno/testdata/test/output_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/output_sync.txtar @@ -6,8 +6,8 @@ stdout 'hey' stdout '^hru\?' stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar index ae1212133fd..8b1478d0df7 100644 --- a/gnovm/cmd/gno/testdata/test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_correct.txtar @@ -4,8 +4,8 @@ gno test -v . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- // PKGPATH: gno.land/r/xx @@ -18,4 +18,4 @@ func main() { } // Realm: -// switchrealm["gno.land/r/xx"] \ No newline at end of file +// switchrealm["gno.land/r/xx"] diff --git a/gnovm/cmd/gno/testdata/test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar index 65a930b2f03..91c83235d15 100644 --- a/gnovm/cmd/gno/testdata/test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_sync.txtar @@ -4,8 +4,8 @@ gno test -v . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar index 4e24ad9ab08..bd73ce3dc99 100644 --- a/gnovm/cmd/gno/testdata/test/valid_filetest.txtar +++ b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar @@ -3,14 +3,14 @@ gno test . ! stdout .+ -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' gno test -v . stdout 'test' stderr '=== RUN file/valid_filetest.gno' -stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/valid_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/valid_test.txtar b/gnovm/cmd/gno/testdata/test/valid_test.txtar index 9590626776c..5a9fe37a4b0 100644 --- a/gnovm/cmd/gno/testdata/test/valid_test.txtar +++ b/gnovm/cmd/gno/testdata/test/valid_test.txtar @@ -2,13 +2,13 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' gno test ./... -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid From f4192a6fb8933316a8a27cd44d5cce91914c9512 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:23:20 +0100 Subject: [PATCH 04/47] feat(examples): add p/moul/dynreplacer (#3710) See https://github.com/gnolang/gno/issues/3249#issuecomment-2646045913 Expected usage: homepages that rely on other realms for parts of their rendered content. This approach simplifies updating the content layout, enables freeform content, and allows users to toggle blocks, change their order, or modify the layout itself (including titles, delimiters, etc.). --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../p/moul/dynreplacer/dynreplacer.gno | 111 +++++++++ .../p/moul/dynreplacer/dynreplacer_test.gno | 235 ++++++++++++++++++ examples/gno.land/p/moul/dynreplacer/gno.mod | 1 + 3 files changed, 347 insertions(+) create mode 100644 examples/gno.land/p/moul/dynreplacer/dynreplacer.gno create mode 100644 examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno create mode 100644 examples/gno.land/p/moul/dynreplacer/gno.mod diff --git a/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno b/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno new file mode 100644 index 00000000000..ea9687b7570 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno @@ -0,0 +1,111 @@ +// Package dynreplacer provides a simple template engine for handling dynamic +// content replacement. It is similar to strings.Replacer but with lazy +// execution of replacements, making it more optimization-friendly in several +// cases. While strings.Replacer requires all replacement values to be computed +// upfront, dynreplacer only executes the callback functions for placeholders +// that actually exist in the template, avoiding unnecessary computations. +// +// The package ensures efficient, non-recursive replacement of placeholders in a +// single pass. This lazy evaluation approach is particularly beneficial when: +// - Some replacement values are expensive to compute +// - Not all placeholders are guaranteed to be present in the template +// - Templates are reused with different content +// +// Example usage: +// +// r := dynreplacer.New( +// dynreplacer.Pair{":name:", func() string { return "World" }}, +// dynreplacer.Pair{":greeting:", func() string { return "Hello" }}, +// ) +// result := r.Replace("Hello :name:!") // Returns "Hello World!" +// +// The replacer caches computed values, so subsequent calls with the same +// placeholder will reuse the cached value instead of executing the callback +// again: +// +// r := dynreplacer.New() +// r.RegisterCallback(":expensive:", func() string { return "computed" }) +// r.Replace("Value1: :expensive:") // Computes the value +// r.Replace("Value2: :expensive:") // Uses cached value +// r.ClearCache() // Force re-computation on next use +package dynreplacer + +import ( + "strings" +) + +// Replacer manages dynamic placeholders, their associated functions, and cached +// values. +type Replacer struct { + callbacks map[string]func() string + cachedValues map[string]string +} + +// Pair represents a placeholder and its callback function +type Pair struct { + Placeholder string + Callback func() string +} + +// New creates a new Replacer instance with optional initial replacements. +// It accepts pairs where each pair consists of a placeholder string and +// its corresponding callback function. +// +// Example: +// +// New( +// Pair{":name:", func() string { return "World" }}, +// Pair{":greeting:", func() string { return "Hello" }}, +// ) +func New(pairs ...Pair) *Replacer { + r := &Replacer{ + callbacks: make(map[string]func() string), + cachedValues: make(map[string]string), + } + + for _, pair := range pairs { + r.RegisterCallback(pair.Placeholder, pair.Callback) + } + + return r +} + +// RegisterCallback associates a placeholder with a function to generate its +// content. +func (r *Replacer) RegisterCallback(placeholder string, callback func() string) { + r.callbacks[placeholder] = callback +} + +// Replace processes the given layout, replacing placeholders with cached or +// newly computed values. +func (r *Replacer) Replace(layout string) string { + replacements := []string{} + + // Check for placeholders and compute/retrieve values + hasReplacements := false + for placeholder, callback := range r.callbacks { + if strings.Contains(layout, placeholder) { + value, exists := r.cachedValues[placeholder] + if !exists { + value = callback() + r.cachedValues[placeholder] = value + } + replacements = append(replacements, placeholder, value) + hasReplacements = true + } + } + + // If no replacements were found, return the original layout + if !hasReplacements { + return layout + } + + // Create a strings.Replacer with all computed replacements + replacer := strings.NewReplacer(replacements...) + return replacer.Replace(layout) +} + +// ClearCache clears all cached values, forcing re-computation on next Replace. +func (r *Replacer) ClearCache() { + r.cachedValues = make(map[string]string) +} diff --git a/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno b/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno new file mode 100644 index 00000000000..51235a28950 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno @@ -0,0 +1,235 @@ +package dynreplacer + +import ( + "strings" + "testing" + + "gno.land/p/demo/uassert" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + pairs []Pair + }{ + { + name: "empty constructor", + pairs: []Pair{}, + }, + { + name: "single pair", + pairs: []Pair{ + {":name:", func() string { return "World" }}, + }, + }, + { + name: "multiple pairs", + pairs: []Pair{ + {":greeting:", func() string { return "Hello" }}, + {":name:", func() string { return "World" }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New(tt.pairs...) + uassert.True(t, r.callbacks != nil, "callbacks map should be initialized") + uassert.True(t, r.cachedValues != nil, "cachedValues map should be initialized") + + // Verify all callbacks were registered + for _, pair := range tt.pairs { + _, exists := r.callbacks[pair.Placeholder] + uassert.True(t, exists, "callback should be registered for "+pair.Placeholder) + } + }) + } +} + +func TestReplace(t *testing.T) { + tests := []struct { + name string + layout string + setup func(*Replacer) + expected string + }{ + { + name: "empty layout", + layout: "", + setup: func(r *Replacer) {}, + expected: "", + }, + { + name: "single replacement", + layout: "Hello :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "multiple replacements", + layout: ":greeting: :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":greeting:", func() string { return "Hello" }) + r.RegisterCallback(":name:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "no recursive replacement", + layout: ":outer:", + setup: func(r *Replacer) { + r.RegisterCallback(":outer:", func() string { return ":inner:" }) + r.RegisterCallback(":inner:", func() string { return "content" }) + }, + expected: ":inner:", + }, + { + name: "unused callbacks", + layout: "Hello :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + r.RegisterCallback(":unused:", func() string { return "Never Called" }) + }, + expected: "Hello World!", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New() + tt.setup(r) + result := r.Replace(tt.layout) + uassert.Equal(t, tt.expected, result) + }) + } +} + +func TestCaching(t *testing.T) { + r := New() + callCount := 0 + r.RegisterCallback(":expensive:", func() string { + callCount++ + return "computed" + }) + + layout := "Value: :expensive:" + + // First call should compute + result1 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result1) + uassert.Equal(t, 1, callCount) + + // Second call should use cache + result2 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result2) + uassert.Equal(t, 1, callCount) + + // After clearing cache, should recompute + r.ClearCache() + result3 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result3) + uassert.Equal(t, 2, callCount) +} + +func TestComplexExample(t *testing.T) { + layout := ` + # Welcome to gno.land + + ## Blog + :latest-blogposts: + + ## Events + :next-events: + + ## Awesome Gno + :awesome-gno: + ` + + r := New( + Pair{":latest-blogposts:", func() string { return "Latest blog posts content here" }}, + Pair{":next-events:", func() string { return "Upcoming events listed here" }}, + Pair{":awesome-gno:", func() string { return ":latest-blogposts: (This should NOT be replaced again)" }}, + ) + + result := r.Replace(layout) + + // Check that original placeholders are replaced + uassert.True(t, !strings.Contains(result, ":latest-blogposts:\n"), "':latest-blogposts:' placeholder should be replaced") + uassert.True(t, !strings.Contains(result, ":next-events:\n"), "':next-events:' placeholder should be replaced") + uassert.True(t, !strings.Contains(result, ":awesome-gno:\n"), "':awesome-gno:' placeholder should be replaced") + + // Check that the replacement content is present + uassert.True(t, strings.Contains(result, "Latest blog posts content here"), "Blog posts content should be present") + uassert.True(t, strings.Contains(result, "Upcoming events listed here"), "Events content should be present") + uassert.True(t, strings.Contains(result, ":latest-blogposts: (This should NOT be replaced again)"), + "Nested placeholder should not be replaced") +} + +func TestEdgeCases(t *testing.T) { + tests := []struct { + name string + layout string + setup func(*Replacer) + expected string + }{ + { + name: "empty string placeholder", + layout: "Hello :", + setup: func(r *Replacer) { + r.RegisterCallback("", func() string { return "World" }) + }, + expected: "WorldHWorldeWorldlWorldlWorldoWorld World:World", + }, + { + name: "overlapping placeholders", + layout: "Hello :name::greeting:", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + r.RegisterCallback(":greeting:", func() string { return "Hi" }) + r.RegisterCallback(":name::greeting:", func() string { return "Should not match" }) + }, + expected: "Hello WorldHi", + }, + { + name: "replacement order", + layout: ":a::b::c:", + setup: func(r *Replacer) { + r.RegisterCallback(":c:", func() string { return "3" }) + r.RegisterCallback(":b:", func() string { return "2" }) + r.RegisterCallback(":a:", func() string { return "1" }) + }, + expected: "123", + }, + { + name: "special characters in placeholders", + layout: "Hello :$name#123:!", + setup: func(r *Replacer) { + r.RegisterCallback(":$name#123:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "multiple occurrences of same placeholder", + layout: ":name: and :name: again", + setup: func(r *Replacer) { + callCount := 0 + r.RegisterCallback(":name:", func() string { + callCount++ + return "World" + }) + }, + expected: "World and World again", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New() + tt.setup(r) + result := r.Replace(tt.layout) + uassert.Equal(t, tt.expected, result) + }) + } +} diff --git a/examples/gno.land/p/moul/dynreplacer/gno.mod b/examples/gno.land/p/moul/dynreplacer/gno.mod new file mode 100644 index 00000000000..9e196721f24 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/dynreplacer From d0201fb33b271139c4fcfce95d96ad31c0ac11a6 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Wed, 12 Feb 2025 12:11:12 +0200 Subject: [PATCH 05/47] perf(tm2/pkg/*): use fmt.Fprintf instead of buf.Write([]byte(fmt.Sprintf(...))) expenses (#3732) In the spirit of following along with PR #3434, this change plumbs more uses of fmt.Fprint* as well as buf.WriteByte, buf.WriteString where necessary. --- tm2/pkg/amino/codec.go | 20 ++++++++++---------- tm2/pkg/amino/wellknown.go | 4 ++-- tm2/pkg/errors/errors.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tm2/pkg/amino/codec.go b/tm2/pkg/amino/codec.go index ba24f49a808..a441128458a 100644 --- a/tm2/pkg/amino/codec.go +++ b/tm2/pkg/amino/codec.go @@ -115,24 +115,24 @@ func (info *TypeInfo) String() string { buf := poolBytesBuffer.Get() defer poolBytesBuffer.Put(buf) - buf.Write([]byte("TypeInfo{")) - buf.Write([]byte(fmt.Sprintf("Type:%v,", info.Type))) + buf.WriteString("TypeInfo{") + fmt.Fprintf(buf, "Type:%v,", info.Type) if info.ConcreteInfo.Registered { - buf.Write([]byte("Registered:true,")) - buf.Write([]byte(fmt.Sprintf("PointerPreferred:%v,", info.PointerPreferred))) - buf.Write([]byte(fmt.Sprintf("TypeURL:\"%v\",", info.TypeURL))) + buf.WriteString("Registered:true,") + fmt.Fprintf(buf, "PointerPreferred:%v,", info.PointerPreferred) + fmt.Fprintf(buf, "TypeURL:\"%v\",", info.TypeURL) } else { - buf.Write([]byte("Registered:false,")) + buf.WriteString("Registered:false,") } if info.ReprType == info { - buf.Write([]byte(fmt.Sprintf("ReprType:,"))) + buf.WriteString("ReprType:,") } else { - buf.Write([]byte(fmt.Sprintf("ReprType:\"%v\",", info.ReprType))) + fmt.Fprintf(buf, "ReprType:\"%v\",", info.ReprType) } if info.Type.Kind() == reflect.Struct { - buf.Write([]byte(fmt.Sprintf("Fields:%v,", info.Fields))) + fmt.Fprintf(buf, "Fields:%v,", info.Fields) } - buf.Write([]byte("}")) + buf.WriteByte('}') return buf.String() } diff --git a/tm2/pkg/amino/wellknown.go b/tm2/pkg/amino/wellknown.go index 4053c23e893..53feb77be7e 100644 --- a/tm2/pkg/amino/wellknown.go +++ b/tm2/pkg/amino/wellknown.go @@ -425,7 +425,7 @@ func EncodeJSONTimeValue(w io.Writer, s int64, ns int32) (err error) { x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, ".000") - _, err = w.Write([]byte(fmt.Sprintf(`"%vZ"`, x))) + _, err = fmt.Fprintf(w, `"%vZ"`, x) return err } @@ -456,7 +456,7 @@ func EncodeJSONDurationValue(w io.Writer, s int64, ns int32) (err error) { x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, ".000") - _, err = w.Write([]byte(fmt.Sprintf(`"%vs"`, x))) + _, err = fmt.Fprintf(w, `"%vs"`, x) return err } diff --git a/tm2/pkg/errors/errors.go b/tm2/pkg/errors/errors.go index 1b40c903c41..f465e9898dd 100644 --- a/tm2/pkg/errors/errors.go +++ b/tm2/pkg/errors/errors.go @@ -154,7 +154,7 @@ func (err *cmnError) doTrace(msg string, n int) Error { func (err *cmnError) Format(s fmt.State, verb rune) { switch { case verb == 'p': - s.Write([]byte(fmt.Sprintf("%p", &err))) + fmt.Fprintf(s, "%p", &err) case verb == 'v' && s.Flag('+'): s.Write([]byte("--= Error =--\n")) // Write data. From bb0a7c8cc19148888322e1e9205e9672042a6bde Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 13 Feb 2025 16:54:43 +0100 Subject: [PATCH 06/47] fix(gno.land): limit gas in simulate and query (#3654) fixes #3612 fixes #3563 --- gno.land/pkg/gnoland/app_test.go | 2 +- gno.land/pkg/integration/process_test.go | 1 - .../integration/testdata/addpkg_invalid.txtar | 2 +- .../testdata/addpkg_namespace.txtar | 8 +- .../testdata/assertorigincall.txtar | 38 +++---- .../pkg/integration/testdata/ghverify.txtar | 3 +- .../testdata/gnokey_simulate.txtar | 4 + .../testdata/grc20_invalid_address.txtar | 4 +- ...roved-coins.txtar => improved_coins.txtar} | 0 .../integration/testdata/infinite_loop.txtar | 78 +++++++++++++++ gno.land/pkg/sdk/vm/gas_test.go | 18 +--- gno.land/pkg/sdk/vm/handler.go | 26 +---- gno.land/pkg/sdk/vm/handler_test.go | 5 + gno.land/pkg/sdk/vm/keeper.go | 98 ++++++++----------- tm2/pkg/sdk/auth/ante.go | 6 +- 15 files changed, 166 insertions(+), 127 deletions(-) rename gno.land/pkg/integration/testdata/{improved-coins.txtar => improved_coins.txtar} (100%) create mode 100644 gno.land/pkg/integration/testdata/infinite_loop.txtar diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 361d7505157..5d0de49d457 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -853,7 +853,7 @@ func newGasPriceTestApp(t *testing.T) abci.Application { } } - newCtx = auth.SetGasMeter(false, ctx, tx.Fee.GasWanted) + newCtx = auth.SetGasMeter(ctx, tx.Fee.GasWanted) count := getTotalCount(tx) diff --git a/gno.land/pkg/integration/process_test.go b/gno.land/pkg/integration/process_test.go index b8768ad0e63..56ae16ff596 100644 --- a/gno.land/pkg/integration/process_test.go +++ b/gno.land/pkg/integration/process_test.go @@ -25,7 +25,6 @@ func TestMain(m *testing.M) { // Check if the embedded command should be executed if !*runCommand { - fmt.Println("Running tests...") os.Exit(m.Run()) } diff --git a/gno.land/pkg/integration/testdata/addpkg_invalid.txtar b/gno.land/pkg/integration/testdata/addpkg_invalid.txtar index bcec784a530..cb8edd858d6 100644 --- a/gno.land/pkg/integration/testdata/addpkg_invalid.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_invalid.txtar @@ -4,7 +4,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 +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000000ugnot -gas-wanted 20000000 -broadcast -chainid=tendermint_test test1 # check error message stdout 'TX HASH: ' diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index 2cfd00acda4..e922a290443 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -43,7 +43,7 @@ stdout 'true' # Try to add a pkg an with unregistered user # gui addpkg -> gno.land/r//one -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test gui stderr 'unauthorized user' # Try to add a pkg with an unregistered user, on their own address as namespace @@ -55,8 +55,7 @@ stdout 'OK!' # Call addpkg with admin user on gui namespace # admin addpkg -> gno.land/r/guiland/one -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test admin +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test admin stderr 'unauthorized user' ## Test registered namespace @@ -78,8 +77,7 @@ stdout 'OK!' # Test admin publishing on guiland/two # admin addpkg -> gno.land/r/guiland/two -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/two -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test admin +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/two -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test admin stderr 'unauthorized user' -- one.gno -- diff --git a/gno.land/pkg/integration/testdata/assertorigincall.txtar b/gno.land/pkg/integration/testdata/assertorigincall.txtar index 2c5da25c0aa..f6893bd5ca6 100644 --- a/gno.land/pkg/integration/testdata/assertorigincall.txtar +++ b/gno.land/pkg/integration/testdata/assertorigincall.txtar @@ -35,68 +35,68 @@ gnoland start # Test cases ## 1. MsgCall -> myrlm.A: PANIC -! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 1 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## 2. MsgCall -> myrlm.B: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 150000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 3. MsgCall -> myrlm.C: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 4. MsgCall -> r/foo.A -> myrlm.A: PANIC -! gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 1 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## 5. MsgCall -> r/foo.B -> myrlm.B: PASS -gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 6. MsgCall -> r/foo.C -> myrlm.C: PANIC -! gnokey maketx call -pkgpath gno.land/r/foo -func C -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/foo -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## remove due to update to maketx call can only call realm (case 7,8,9) ## 7. MsgCall -> p/demo/bar.A: PANIC -## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' ## 8. MsgCall -> p/demo/bar.B: PASS -## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stdout 'OK!' ## 9. MsgCall -> p/demo/bar.C: PANIC -## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' ## 10. MsgRun -> run.main -> myrlm.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno stderr 'invalid non-origin call' ## 11. MsgRun -> run.main -> myrlm.B: PASS -gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno stdout 'OK!' ## 12. MsgRun -> run.main -> myrlm.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmC.gno stderr 'invalid non-origin call' ## 13. MsgRun -> run.main -> foo.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno stderr 'invalid non-origin call' ## 14. MsgRun -> run.main -> foo.B: PASS -gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno stdout 'OK!' ## 15. MsgRun -> run.main -> foo.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/fooC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooC.gno stderr 'invalid non-origin call' ## 16. MsgRun -> run.main -> bar.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno stderr 'invalid non-origin call' ## 17. MsgRun -> run.main -> bar.B: PASS @@ -104,16 +104,16 @@ gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid stdout 'OK!' ## 18. MsgRun -> run.main -> bar.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/barC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/barC.gno stderr 'invalid non-origin call' ## remove testcase 19 due to maketx call forced to call a realm ## 19. MsgCall -> std.AssertOriginCall: pass -## gnokey maketx call -pkgpath std -func AssertOriginCall -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +## gnokey maketx call -pkgpath std -func AssertOriginCall -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stdout 'OK!' ## 20. MsgRun -> std.AssertOriginCall: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno stderr 'invalid non-origin call' diff --git a/gno.land/pkg/integration/testdata/ghverify.txtar b/gno.land/pkg/integration/testdata/ghverify.txtar index 0f2d21f6bd5..a5df5e0ca29 100644 --- a/gno.land/pkg/integration/testdata/ghverify.txtar +++ b/gno.land/pkg/integration/testdata/ghverify.txtar @@ -20,8 +20,7 @@ gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetAddressByHandle stdout "" # fail on ingestion with a bad task ID -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,a' -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,a' -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 stderr 'invalid ingest id: a' # the agent publishes their response to the task and the verification is complete diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index 31b2249f8bb..438c452295b 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -67,6 +67,10 @@ stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' +# simulate should panic if gas-wanted is beyond the max block gas. +! gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 100_000_000_000_000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +stderr 'invalid gas wanted' + -- test/test.gno -- package test diff --git a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar index 0068384903e..d7c3a7d68cd 100644 --- a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar +++ b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar @@ -8,6 +8,6 @@ gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 10000000 stdout 'OK!' # execute Transfer for invalid address -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Transfer -args g1ubwj0apf60hd90txhnh855fkac34rxlsvua0aa -args 1 -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test test1 +# This is expected to fail at the transaction simulation stage. +! gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Transfer -args g1ubwj0apf60hd90txhnh855fkac34rxlsvua0aa -args 1 -gas-fee 1000000ugnot -gas-wanted 10_000_000 -simulate only -broadcast -chainid=tendermint_test test1 stderr '"gnokey" error: --= Error =--\nData: invalid address' diff --git a/gno.land/pkg/integration/testdata/improved-coins.txtar b/gno.land/pkg/integration/testdata/improved_coins.txtar similarity index 100% rename from gno.land/pkg/integration/testdata/improved-coins.txtar rename to gno.land/pkg/integration/testdata/improved_coins.txtar diff --git a/gno.land/pkg/integration/testdata/infinite_loop.txtar b/gno.land/pkg/integration/testdata/infinite_loop.txtar new file mode 100644 index 00000000000..30a32bdc588 --- /dev/null +++ b/gno.land/pkg/integration/testdata/infinite_loop.txtar @@ -0,0 +1,78 @@ +# regression test for https://github.com/gnolang/gno/issues/3612 +# infinite loops should panic in all of simulate, query, render, maketx run and addpkg. + +gnoland start + +# addpkg + -simulate skip +! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# addpkg + -simulate only +! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# run + -simulate skip +! gnokey maketx run -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# run + -simulate only +! gnokey maketx run -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# maketx addpkg on r2 (successful) +gnokey maketx addpkg -pkgdir $WORK/r2 -pkgpath gno.land/r/demo/r2 -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# qeval on the render function +! gnokey query vm/qeval --data "gno.land/r/demo/r2.Render(\"helloworld\")" +stderr 'out of gas.* location: CPUCycles' + +# qrender function +! gnokey query vm/qrender --data 'gno.land/r/demo/r2:noice' +stderr 'out of gas.* location: CPUCycles' + +# call on the render function +! gnokey maketx call -pkgpath gno.land/r/demo/r2 -func Render -args '' -simulate skip -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test test1 +stderr 'out of gas.* location: CPUCycles' + +# simulated call on the render function +! gnokey maketx call -pkgpath gno.land/r/demo/r2 -func Render -args '' -simulate only -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test test1 +stderr 'out of gas.* location: CPUCycles' + +-- run.gno -- +package main + +func main() { + for {} + + println("hey") +} + +-- r1/gno.mod -- +module gno.land/r/demo/r1 + + +-- r1/realm.gno -- +package f1 + +func init() { + for {} +} + +func main() {} + +-- r2/gno.mod -- +module gno.land/r/demo/r2 + + +-- r2/realm.gno -- +package r2 + +func Render(s string) string { + for {} + return "hello world!" +} diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index acde3d315c6..ff260d39090 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -25,9 +25,8 @@ func TestAddPkgDeliverTxInsuffGas(t *testing.T) { ctx, tx, vmHandler := setupAddPkg(isValidTx) ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate := false tx.Fee.GasWanted = 3000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) // Has to be set up after gas meter in the context; so the stores are // correctly wrapped in gas stores. gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) @@ -61,12 +60,9 @@ func TestAddPkgDeliverTx(t *testing.T) { isValidTx := true ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 500000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) @@ -83,12 +79,9 @@ func TestAddPkgDeliverTxFailed(t *testing.T) { isValidTx := false ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 500000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) @@ -103,12 +96,9 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { isValidTx := false ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 1230 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) var res sdk.Result diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index 5aebf1afe46..af7c79e152d 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -68,12 +68,10 @@ func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) { // query paths const ( - QueryPackage = "package" - QueryStore = "store" - QueryRender = "qrender" - QueryFuncs = "qfuncs" - QueryEval = "qeval" - QueryFile = "qfile" + QueryRender = "qrender" + QueryFuncs = "qfuncs" + QueryEval = "qeval" + QueryFile = "qfile" ) func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQuery { @@ -83,10 +81,6 @@ func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQ ) switch path { - case QueryPackage: - res = vh.queryPackage(ctx, req) - case QueryStore: - res = vh.queryStore(ctx, req) case QueryRender: res = vh.queryRender(ctx, req) case QueryFuncs: @@ -105,18 +99,6 @@ func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQ return res } -// queryPackage fetch a package's files. -func (vh vmHandler) queryPackage(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - res.Data = []byte(fmt.Sprintf("TODO: parse parts get or make fileset...")) - return -} - -// queryPackage fetch items from the store. -func (vh vmHandler) queryStore(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - res.Data = []byte(fmt.Sprintf("TODO: fetch from store")) - return -} - // queryRender calls .Render() in readonly mode. func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { reqData := string(req.Data) diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 0d238deed1f..a4f39242e3b 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -86,6 +86,10 @@ func TestVmHandlerQuery_Eval(t *testing.T) { {input: []byte(`gno.land/r/hello.sl`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value {input: []byte(`gno.land/r/hello.sl[1]`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value {input: []byte(`gno.land/r/hello.println(1234)`), expectedResultMatch: `^$`}, // XXX: compare stdout? + { + input: []byte(`gno.land/r/hello.func() string { return "hello123" + pvString }()`), + expectedResult: `("hello123private string" string)`, + }, // panics {input: []byte(`gno.land/r/hello`), expectedPanicMatch: `expected . syntax in query input data`}, @@ -95,6 +99,7 @@ func TestVmHandlerQuery_Eval(t *testing.T) { {input: []byte(`gno.land/r/doesnotexist.Foo`), expectedErrorMatch: `^invalid package path$`}, {input: []byte(`gno.land/r/hello.Panic()`), expectedErrorMatch: `^foo$`}, {input: []byte(`gno.land/r/hello.sl[6]`), expectedErrorMatch: `^slice index out of bounds: 6 \(len=5\)$`}, + {input: []byte(`gno.land/r/hello.func(){ for {} }()`), expectedErrorMatch: `out of gas in location: CPUCycles`}, } for _, tc := range tt { diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index bf16cd44243..1ecf280dc59 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -39,6 +39,7 @@ import ( const ( maxAllocTx = 500_000_000 maxAllocQuery = 1_500_000_000 // higher limit for queries + maxGasQuery = 3_000_000_000 // same as max block gas ) // vm.VMKeeperI defines a module interface that supports Gno @@ -512,14 +513,31 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { func doRecover(m *gno.Machine, e *error) { r := recover() + + // On normal transaction execution, out of gas panics are handled in the + // BaseApp, so repanic here. + const repanicOutOfGas = true + doRecoverInternal(m, e, r, repanicOutOfGas) +} + +func doRecoverQuery(m *gno.Machine, e *error) { + r := recover() + const repanicOutOfGas = false + doRecoverInternal(m, e, r, repanicOutOfGas) +} + +func doRecoverInternal(m *gno.Machine, e *error, r any, repanicOutOfGas bool) { if r == nil { return } if err, ok := r.(error); ok { var oog types.OutOfGasError if goerrors.As(err, &oog) { - // Re-panic and don't wrap. - panic(oog) + if repanicOutOfGas { + panic(oog) + } + *e = oog + return } var up gno.UnhandledPanicError if goerrors.As(err, &up) { @@ -708,51 +726,11 @@ func (vm *VMKeeper) QueryFuncs(ctx sdk.Context, pkgPath string) (fsigs FunctionS } // QueryEval evaluates a gno expression (readonly, for ABCI queries). -// TODO: modify query protocol to allow MsgEval. -// TODO: then, rename to "Eval". func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { - alloc := gno.NewAllocator(maxAllocQuery) - gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) - pkgAddr := gno.DerivePkgAddr(pkgPath) - // Get Package. - pv := gnostore.GetPackage(pkgPath, false) - if pv == nil { - err = ErrInvalidPkgPath(fmt.Sprintf( - "package not found: %s", pkgPath)) - return "", err - } - // Parse expression. - xx, err := gno.ParseExpr(expr) + rtvs, err := vm.queryEvalInternal(ctx, pkgPath, expr) if err != nil { return "", err } - // Construct new machine. - chainDomain := vm.getChainDomainParam(ctx) - msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - // OrigCaller: caller, - // OrigSend: send, - // OrigSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), - } - m := gno.NewMachineWithOptions( - gno.MachineOptions{ - PkgPath: pkgPath, - Output: vm.Output, - Store: gnostore, - Context: msgCtx, - Alloc: alloc, - GasMeter: ctx.GasMeter(), - }) - defer m.Release() - defer doRecover(m, &err) - rtvs := m.Eval(xx) res = "" for i, rtv := range rtvs { res += rtv.String() @@ -765,9 +743,22 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res // QueryEvalString evaluates a gno expression (readonly, for ABCI queries). // The result is expected to be a single string (not a tuple). -// TODO: modify query protocol to allow MsgEval. -// TODO: then, rename to "EvalString". func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { + rtvs, err := vm.queryEvalInternal(ctx, pkgPath, expr) + if err != nil { + return "", err + } + if len(rtvs) != 1 { + return "", errors.New("expected 1 string result, got %d", len(rtvs)) + } else if rtvs[0].T.Kind() != gno.StringKind { + return "", errors.New("expected 1 string result, got %v", rtvs[0].T.Kind()) + } + res = rtvs[0].GetString() + return res, nil +} + +func (vm *VMKeeper) queryEvalInternal(ctx sdk.Context, pkgPath string, expr string) (rtvs []gno.TypedValue, err error) { + ctx = ctx.WithGasMeter(store.NewGasMeter(maxGasQuery)) alloc := gno.NewAllocator(maxAllocQuery) gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) pkgAddr := gno.DerivePkgAddr(pkgPath) @@ -776,12 +767,12 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string if pv == nil { err = ErrInvalidPkgPath(fmt.Sprintf( "package not found: %s", pkgPath)) - return "", err + return nil, err } // Parse expression. xx, err := gno.ParseExpr(expr) if err != nil { - return "", err + return nil, err } // Construct new machine. chainDomain := vm.getChainDomainParam(ctx) @@ -791,7 +782,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string Height: ctx.BlockHeight(), Timestamp: ctx.BlockTime().Unix(), // OrigCaller: caller, - // OrigSend: jsend, + // OrigSend: send, // OrigSendSpent: nil, OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. @@ -808,15 +799,8 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string GasMeter: ctx.GasMeter(), }) defer m.Release() - defer doRecover(m, &err) - rtvs := m.Eval(xx) - if len(rtvs) != 1 { - return "", errors.New("expected 1 string result, got %d", len(rtvs)) - } else if rtvs[0].T.Kind() != gno.StringKind { - return "", errors.New("expected 1 string result, got %v", rtvs[0].T.Kind()) - } - res = rtvs[0].GetString() - return res, nil + defer doRecoverQuery(m, &err) + return m.Eval(xx), err } func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err error) { diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index f941f398b17..12e47494664 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -67,7 +67,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature } } - newCtx = SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + newCtx = SetGasMeter(ctx, tx.Fee.GasWanted) // AnteHandlers must have their own defer/recover in order for the BaseApp // to know how much gas was used! This is because the GasMeter is created in @@ -406,10 +406,10 @@ func EnsureSufficientMempoolFees(ctx sdk.Context, fee std.Fee) sdk.Result { } // SetGasMeter returns a new context with a gas meter set from a given context. -func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit int64) sdk.Context { +func SetGasMeter(ctx sdk.Context, gasLimit int64) sdk.Context { // In various cases such as simulation and during the genesis block, we do not // meter any gas utilization. - if simulate || ctx.BlockHeight() == 0 { + if ctx.BlockHeight() == 0 { return ctx.WithGasMeter(store.NewInfiniteGasMeter()) } From bd8f04e69c765dcb1a9801248a19127a1272d9f4 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 13 Feb 2025 16:56:51 +0100 Subject: [PATCH 07/47] feat(gnovm): enforce size of int and uint to 64 bits on all arch. (#3591) With these changes, `int` and `uint` are always represented in gnovm as `int64` and `uint64` whatever the underlying hardware architecture. There should be no change for 64 bits platforms. On other (unsupported) platforms, `int` and `uint` will be 64 bits instead of hardware size. Fixes #3288. --------- Co-authored-by: Morgan --- gno.land/pkg/sdk/vm/convert.go | 4 +- gnovm/pkg/gnolang/gonative.go | 20 ++-- .../gnolang/internal/softfloat/gen/main.go | 2 +- gnovm/pkg/gnolang/op_binary.go | 56 +++++----- gnovm/pkg/gnolang/op_exec.go | 10 +- gnovm/pkg/gnolang/op_expressions.go | 10 +- gnovm/pkg/gnolang/op_types.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 8 +- gnovm/pkg/gnolang/uverse.go | 18 +-- gnovm/pkg/gnolang/values.go | 35 +++--- gnovm/pkg/gnolang/values_conversions.go | 103 +++++++++--------- gnovm/stdlibs/encoding/base32/base32_test.gno | 25 +---- gnovm/stdlibs/math/const.gno | 2 +- gnovm/stdlibs/strconv/atoi_test.gno | 82 ++++---------- misc/genstd/util.go | 2 +- tm2/pkg/overflow/overflow_template.sh | 5 +- 16 files changed, 163 insertions(+), 221 deletions(-) diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index dbaabcfbc4b..508953731d8 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -50,7 +50,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { "error parsing int %q: %v", arg, err)) } - tv.SetInt(int(i64)) + tv.SetInt(i64) return case gno.Int8Type: assertNoPlusPrefix(arg) @@ -100,7 +100,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { "error parsing uint %q: %v", arg, err)) } - tv.SetUint(uint(u64)) + tv.SetUint(u64) return case gno.Uint8Type: assertNoPlusPrefix(arg) diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 85fc8b70051..a777c1cef3e 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -312,7 +312,7 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.String: tv.V = alloc.NewString(rv.String()) case reflect.Int: - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) case reflect.Int8: tv.SetInt8(int8(rv.Int())) case reflect.Int16: @@ -322,7 +322,7 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Int64: tv.SetInt64(rv.Int()) case reflect.Uint: - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) case reflect.Uint8: tv.SetUint8(uint8(rv.Uint())) case reflect.Uint16: @@ -391,7 +391,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case IntKind: if lvl != 0 { - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) } case Int8Kind: if lvl != 0 { @@ -411,7 +411,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case UintKind: if lvl != 0 { - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) } case Uint8Kind: if lvl != 0 { @@ -627,7 +627,7 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.String: tv.V = alloc.NewString(rv.String()) case reflect.Int: - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) case reflect.Int8: tv.SetInt8(int8(rv.Int())) case reflect.Int16: @@ -637,7 +637,7 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Int64: tv.SetInt64(rv.Int()) case reflect.Uint: - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) case reflect.Uint8: tv.SetUint8(uint8(rv.Uint())) case reflect.Uint16: @@ -1032,7 +1032,7 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case StringType, UntypedStringType: rv.SetString(tv.GetString()) case IntType: - rv.SetInt(int64(tv.GetInt())) + rv.SetInt(tv.GetInt()) case Int8Type: rv.SetInt(int64(tv.GetInt8())) case Int16Type: @@ -1042,7 +1042,7 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case Int64Type: rv.SetInt(tv.GetInt64()) case UintType: - rv.SetUint(uint64(tv.GetUint())) + rv.SetUint(tv.GetUint()) case Uint8Type: rv.SetUint(uint64(tv.GetUint8())) case Uint16Type: @@ -1222,7 +1222,7 @@ func (m *Machine) doOpArrayLitGoNative() { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? (also see doOpArrayLit()) k := kx.(*ConstExpr).ConvertGetInt() - rf := rv.Index(k) + rf := rv.Index(int(k)) // XXX: k may overflow on 32 bits plaforms. gno2GoValue(&itvs[i], rf) } else { rf := rv.Index(i) @@ -1261,7 +1261,7 @@ func (m *Machine) doOpSliceLitGoNative() { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? (also see doOpArrayLit()) k := kx.(*ConstExpr).ConvertGetInt() - rf := rv.Index(k) + rf := rv.Index(int(k)) // XXX: k may overflow on 32 bits plaforms. gno2GoValue(&itvs[i], rf) } else { rf := rv.Index(i) diff --git a/gnovm/pkg/gnolang/internal/softfloat/gen/main.go b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go index 7c89ff9b5a9..5895b53a93a 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/gen/main.go +++ b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go @@ -90,7 +90,7 @@ func gitRoot() (string, error) { } p := wd for { - if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { return p, nil } diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0e8eec9db23..42419ec5d54 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -1211,8 +1211,8 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { switch baseOf(lv.T) { case IntType: checkOverflow(func() bool { - l := big.NewInt(int64(lv.GetInt())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + l := big.NewInt(lv.GetInt()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt)) != 1 }) @@ -1221,7 +1221,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int8Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt8())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt8)) != 1 }) @@ -1230,7 +1230,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int16Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt16())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt16)) != 1 }) @@ -1239,7 +1239,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int32Type, UntypedRuneType: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt32())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt32)) != 1 }) @@ -1248,7 +1248,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int64Type: checkOverflow(func() bool { l := big.NewInt(lv.GetInt64()) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt64)) != 1 }) @@ -1256,8 +1256,8 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { lv.SetInt64(lv.GetInt64() << rv.GetUint()) case UintType: checkOverflow(func() bool { - l := big.NewInt(0).SetUint64(uint64(lv.GetUint())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + l := big.NewInt(0).SetUint64(lv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint)) != 1 }) @@ -1266,7 +1266,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint8Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint8())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1275,7 +1275,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case DataByteType: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetDataByte())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1284,7 +1284,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint16Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint16())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint16)) != 1 }) @@ -1293,7 +1293,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint32Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint32())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint32)) != 1 }) @@ -1302,7 +1302,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint64Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(lv.GetUint64()) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint64)) != 1 }) @@ -1310,7 +1310,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { lv.SetUint64(lv.GetUint64() << rv.GetUint()) case BigintType, UntypedBigintType: lb := lv.GetBigInt() - lb = big.NewInt(0).Lsh(lb, rv.GetUint()) + lb = big.NewInt(0).Lsh(lb, uint(rv.GetUint())) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -1335,8 +1335,8 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { switch baseOf(lv.T) { case IntType: checkOverflow(func() bool { - l := big.NewInt(int64(lv.GetInt())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + l := big.NewInt(lv.GetInt()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt)) != 1 }) @@ -1345,7 +1345,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int8Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt8())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt8)) != 1 }) @@ -1354,7 +1354,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int16Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt16())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt16)) != 1 }) @@ -1363,7 +1363,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int32Type, UntypedRuneType: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt32())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt32)) != 1 }) @@ -1372,7 +1372,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int64Type: checkOverflow(func() bool { l := big.NewInt(lv.GetInt64()) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt64)) != 1 }) @@ -1380,8 +1380,8 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { lv.SetInt64(lv.GetInt64() >> rv.GetUint()) case UintType: checkOverflow(func() bool { - l := big.NewInt(0).SetUint64(uint64(lv.GetUint())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + l := big.NewInt(0).SetUint64(lv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint)) != 1 }) @@ -1390,7 +1390,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint8Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint8())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1399,7 +1399,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case DataByteType: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetDataByte())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1408,7 +1408,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint16Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint16())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint16)) != 1 }) @@ -1417,7 +1417,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint32Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint32())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint32)) != 1 }) @@ -1426,7 +1426,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint64Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(lv.GetUint64()) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint64)) != 1 }) @@ -1434,7 +1434,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { lv.SetUint64(lv.GetUint64() >> rv.GetUint()) case BigintType, UntypedBigintType: lb := lv.GetBigInt() - lb = big.NewInt(0).Rsh(lb, rv.GetUint()) + lb = big.NewInt(0).Rsh(lb, uint(rv.GetUint())) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 5f71ffefa0c..d81c15956d2 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -166,7 +166,7 @@ func (m *Machine) doOpExec(op Op) { case -1: // assign list element. if bs.Key != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) switch bs.Op { case ASSIGN: m.PopAsPointer(bs.Key).Assign2(m.Alloc, m.Store, m.Realm, iv, false) @@ -180,7 +180,7 @@ func (m *Machine) doOpExec(op Op) { } if bs.Value != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) ev := xv.GetPointerAtIndex(m.Alloc, m.Store, &iv).Deref() switch bs.Op { case ASSIGN: @@ -262,7 +262,7 @@ func (m *Machine) doOpExec(op Op) { case -1: // assign list element. if bs.Key != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) switch bs.Op { case ASSIGN: m.PopAsPointer(bs.Key).Assign2(m.Alloc, m.Store, m.Realm, iv, false) @@ -904,7 +904,7 @@ func (m *Machine) doOpSwitchClause() { // caiv := m.PeekValue(2) // switch clause case index (reuse) cliv := m.PeekValue(3) // switch clause index (reuse) idx := cliv.GetInt() - if idx >= len(ss.Clauses) { + if int(idx) >= len(ss.Clauses) { // no clauses matched: do nothing. m.PopStmt() // pop switch stmt m.PopValue() // pop switch tag value @@ -980,7 +980,7 @@ func (m *Machine) doOpSwitchClauseCase() { clidx := cliv.GetInt() cl := ss.Clauses[clidx] caidx := caiv.GetInt() - if (caidx + 1) < len(cl.Cases) { + if int(caidx+1) < len(cl.Cases) { // try next clause case. m.PushOp(OpSwitchClauseCase) // TODO consider sticky caiv.SetInt(caidx + 1) diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index c0f6225740b..475a91580ad 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -91,15 +91,15 @@ func (m *Machine) doOpSlice() { var lowVal, highVal, maxVal int = -1, -1, -1 // max if sx.Max != nil { - maxVal = m.PopValue().ConvertGetInt() + maxVal = int(m.PopValue().ConvertGetInt()) } // high if sx.High != nil { - highVal = m.PopValue().ConvertGetInt() + highVal = int(m.PopValue().ConvertGetInt()) } // low if sx.Low != nil { - lowVal = m.PopValue().ConvertGetInt() + lowVal = int(m.PopValue().ConvertGetInt()) } else { lowVal = 0 } @@ -513,7 +513,7 @@ func (m *Machine) doOpArrayLit() { al, ad := av.List, av.Data vs := m.PopValues(ne) set := make([]bool, bt.Len) - idx := 0 + var idx int64 for i, v := range vs { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? @@ -593,7 +593,7 @@ func (m *Machine) doOpSliceLit2() { // peek slice type. st := m.PeekValue(1).V.(TypeValue).Type // calculate maximum index. - maxVal := 0 + var maxVal int64 for i := 0; i < el; i++ { itv := tvs[i*2+0] idx := itv.ConvertGetInt() diff --git a/gnovm/pkg/gnolang/op_types.go b/gnovm/pkg/gnolang/op_types.go index f7f3f75f91d..e3003066037 100644 --- a/gnovm/pkg/gnolang/op_types.go +++ b/gnovm/pkg/gnolang/op_types.go @@ -47,7 +47,7 @@ func (m *Machine) doOpArrayType() { panic("unexpected untyped const type for array type len during runtime") } } - t.Len = lv.GetInt() // TODO lazy convert? + t.Len = int(lv.GetInt()) // TODO lazy convert? } tv := m.PeekValue(1) // re-use t.Elt = tv.GetType() diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 0b86449b235..26e67e71fad 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1814,7 +1814,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if elt.Key == nil { idx++ } else { - k := evalConst(store, last, elt.Key).ConvertGetInt() + k := int(evalConst(store, last, elt.Key).ConvertGetInt()) if idx <= k { idx = k + 1 } else { @@ -1827,7 +1827,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // at.Vrd = false at.Len = idx // update node - cx := constInt(n, idx) + cx := constInt(n, int64(idx)) n.Type.(*ArrayTypeExpr).Len = cx } } @@ -3381,7 +3381,7 @@ func evalConst(store Store, last BlockNode, x Expr) *ConstExpr { switch fv.Name { case "cap", "len": tv := TypedValue{T: IntType} - tv.SetInt(ar.Len) + tv.SetInt(int64(ar.Len)) cx = &ConstExpr{ Source: x, TypedValue: tv, @@ -5010,7 +5010,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return "" } -func constInt(source Expr, i int) *ConstExpr { +func constInt(source Expr, i int64) *ConstExpr { cx := &ConstExpr{Source: source} cx.T = IntType cx.SetInt(i) diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 975038314ad..bfa4a6c65a7 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -637,7 +637,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(arg0.TV.GetCapacity()) + res0.SetInt(int64(arg0.TV.GetCapacity())) m.PushValue(res0) return }, @@ -693,7 +693,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(minl) + res0.SetInt(int64(minl)) m.PushValue(res0) return case *SliceType: @@ -719,7 +719,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(minl) + res0.SetInt(int64(minl)) m.PushValue(res0) return case *NativeType: @@ -791,7 +791,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(arg0.TV.GetLength()) + res0.SetInt(int64(arg0.TV.GetLength())) m.PushValue(res0) return }, @@ -814,7 +814,7 @@ func makeUverseNode() { et := bt.Elem() if vargsl == 1 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) if et.Kind() == Uint8Kind { arrayValue := m.Alloc.NewDataArray(li) m.PushValue(TypedValue{ @@ -840,9 +840,9 @@ func makeUverseNode() { } } else if vargsl == 2 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) cv := vargs.TV.GetPointerAtIndexInt(m.Store, 1).Deref() - ci := cv.ConvertGetInt() + ci := int(cv.ConvertGetInt()) if ci < li { panic(&Exception{Value: typedString(`makeslice: cap out of range`)}) @@ -896,7 +896,7 @@ func makeUverseNode() { return } else if vargsl == 1 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) m.PushValue(TypedValue{ T: tt, V: m.Alloc.NewMap(li), @@ -926,7 +926,7 @@ func makeUverseNode() { return } else if vargsl == 1 { sv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - si := sv.ConvertGetInt() + si := int(sv.ConvertGetInt()) m.PushValue(TypedValue{ T: tt, V: m.Alloc.NewNative( diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3bfd7c0286f..ef537502bda 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -1116,7 +1116,7 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { case UintType, Uint64Type: data = make([]byte, 8) binary.LittleEndian.PutUint64( - data, uint64(tv.GetUint())) + data, tv.GetUint()) return data case Float32Type: data = make([]byte, 4) @@ -1190,7 +1190,7 @@ func (tv *TypedValue) GetString() string { return string(tv.V.(StringValue)) } -func (tv *TypedValue) SetInt(n int) { +func (tv *TypedValue) SetInt(n int64) { if debug { if tv.T.Kind() != IntKind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1198,19 +1198,16 @@ func (tv *TypedValue) SetInt(n int) { tv.T.String())) } } - // XXX probably should be coerced into int64 for determinism. - // XXX otherwise, all nodes must run in 64bit. - // XXX alternatively, require 64bit. - *(*int)(unsafe.Pointer(&tv.N)) = n + *(*int64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) ConvertGetInt() int { +func (tv *TypedValue) ConvertGetInt() int64 { var store Store = nil // not used ConvertTo(nilAllocator, store, tv, IntType, false) return tv.GetInt() } -func (tv *TypedValue) GetInt() int { +func (tv *TypedValue) GetInt() int64 { if debug { if tv.T != nil && tv.T.Kind() != IntKind { panic(fmt.Sprintf( @@ -1218,7 +1215,7 @@ func (tv *TypedValue) GetInt() int { tv.T.String())) } } - return *(*int)(unsafe.Pointer(&tv.N)) + return *(*int64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) SetInt8(n int8) { @@ -1309,7 +1306,7 @@ func (tv *TypedValue) GetInt64() int64 { return *(*int64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetUint(n uint) { +func (tv *TypedValue) SetUint(n uint64) { if debug { if tv.T.Kind() != UintKind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1317,10 +1314,10 @@ func (tv *TypedValue) SetUint(n uint) { tv.T.String())) } } - *(*uint)(unsafe.Pointer(&tv.N)) = n + *(*uint64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetUint() uint { +func (tv *TypedValue) GetUint() uint64 { if debug { if tv.T != nil && tv.T.Kind() != UintKind { panic(fmt.Sprintf( @@ -1328,7 +1325,7 @@ func (tv *TypedValue) GetUint() uint { tv.T.String())) } } - return *(*uint)(unsafe.Pointer(&tv.N)) + return *(*uint64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) SetUint8(n uint8) { @@ -1971,7 +1968,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val // Convenience for GetPointerAtIndex(). Slow. func (tv *TypedValue) GetPointerAtIndexInt(store Store, ii int) PointerValue { iv := TypedValue{T: IntType} - iv.SetInt(ii) + iv.SetInt(int64(ii)) return tv.GetPointerAtIndex(nilAllocator, store, &iv) } @@ -1980,7 +1977,7 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed case PrimitiveType: if bt == StringType || bt == UntypedStringType { sv := tv.GetString() - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) bv := &TypedValue{ // heap alloc T: Uint8Type, } @@ -2003,14 +2000,14 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed tv.T.String())) case *ArrayType: av := tv.V.(*ArrayValue) - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) return av.GetPointerAtIndexInt2(store, ii, bt.Elt) case *SliceType: if tv.V == nil { panic("nil slice index (out of bounds)") } sv := tv.V.(*SliceValue) - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) return sv.GetPointerAtIndexInt2(store, ii, bt.Elt) case *MapType: if tv.V == nil { @@ -2032,7 +2029,7 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed rv := nv.Value switch rt.Kind() { case reflect.Array, reflect.Slice, reflect.String: - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) erv := rv.Index(ii) etv := go2GnoValue(alloc, erv) return PointerValue{ @@ -2654,7 +2651,7 @@ func defaultTypedValue(alloc *Allocator, t Type) TypedValue { func typedInt(i int) TypedValue { tv := TypedValue{T: IntType} - tv.SetInt(i) + tv.SetInt(int64(i)) return tv } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index e1c9378fe67..f6e39bd1912 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -130,13 +130,13 @@ GNO_CASE: tv.T = t tv.SetInt32(x) case Int64Kind: - x := int64(tv.GetInt()) + x := tv.GetInt() tv.T = t tv.SetInt64(x) case UintKind: validate(IntKind, UintKind, func() bool { return tv.GetInt() >= 0 }) - x := uint(tv.GetInt()) + x := uint64(tv.GetInt()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -164,11 +164,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := softfloat.Fintto32(int64(tv.GetInt())) + x := softfloat.Fintto32(tv.GetInt()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := softfloat.Fintto64(int64(tv.GetInt())) + x := softfloat.Fintto64(tv.GetInt()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -184,7 +184,7 @@ GNO_CASE: case Int8Kind: switch k { case IntKind: - x := int(tv.GetInt8()) + x := int64(tv.GetInt8()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -206,7 +206,7 @@ GNO_CASE: case UintKind: validate(Int8Kind, UintKind, func() bool { return tv.GetInt8() >= 0 }) - x := uint(tv.GetInt8()) + x := uint64(tv.GetInt8()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -253,7 +253,7 @@ GNO_CASE: case Int16Kind: switch k { case IntKind: - x := int(tv.GetInt16()) + x := int64(tv.GetInt16()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -277,7 +277,7 @@ GNO_CASE: case UintKind: validate(Int16Kind, UintKind, func() bool { return tv.GetInt16() >= 0 }) - x := uint(tv.GetInt16()) + x := uint64(tv.GetInt16()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -326,7 +326,7 @@ GNO_CASE: case Int32Kind: switch k { case IntKind: - x := int(tv.GetInt32()) + x := int64(tv.GetInt32()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -352,7 +352,7 @@ GNO_CASE: case UintKind: validate(Int32Kind, UintKind, func() bool { return tv.GetInt32() >= 0 }) - x := uint(tv.GetInt32()) + x := uint64(tv.GetInt32()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -401,7 +401,7 @@ GNO_CASE: case Int64Kind: switch k { case IntKind: - x := int(tv.GetInt64()) + x := tv.GetInt64() tv.T = t tv.SetInt(x) case Int8Kind: @@ -429,7 +429,7 @@ GNO_CASE: case UintKind: validate(Int64Kind, UintKind, func() bool { return tv.GetInt64() >= 0 && uint(tv.GetInt64()) <= math.MaxUint }) - x := uint(tv.GetInt64()) + x := uint64(tv.GetInt64()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -480,7 +480,7 @@ GNO_CASE: case IntKind: validate(UintKind, IntKind, func() bool { return tv.GetUint() <= math.MaxInt }) - x := int(tv.GetUint()) + x := int64(tv.GetUint()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -502,7 +502,7 @@ GNO_CASE: tv.T = t tv.SetInt32(x) case Int64Kind: - validate(UintKind, Int64Kind, func() bool { return uint64(tv.GetUint()) <= math.MaxInt64 }) + validate(UintKind, Int64Kind, func() bool { return tv.GetUint() <= math.MaxInt64 }) x := int64(tv.GetUint()) tv.T = t @@ -530,15 +530,15 @@ GNO_CASE: tv.T = t tv.SetUint32(x) case Uint64Kind: - x := uint64(tv.GetUint()) + x := tv.GetUint() tv.T = t tv.SetUint64(x) case Float32Kind: - x := softfloat.Fuint64to32(uint64(tv.GetUint())) + x := softfloat.Fuint64to32(tv.GetUint()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := softfloat.Fuint64to64(uint64(tv.GetUint())) + x := softfloat.Fuint64to64(tv.GetUint()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -555,7 +555,7 @@ GNO_CASE: case Uint8Kind: switch k { case IntKind: - x := int(tv.GetUint8()) + x := int64(tv.GetUint8()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -565,13 +565,13 @@ GNO_CASE: tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint8Kind, Int16Kind, func() bool { return int(tv.GetUint8()) <= math.MaxInt16 }) + validate(Uint8Kind, Int16Kind, func() bool { return int64(tv.GetUint8()) <= math.MaxInt16 }) x := int16(tv.GetUint8()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint8Kind, Int32Kind, func() bool { return int(tv.GetUint8()) <= math.MaxInt32 }) + validate(Uint8Kind, Int32Kind, func() bool { return int64(tv.GetUint8()) <= math.MaxInt32 }) x := int32(tv.GetUint8()) tv.T = t @@ -583,7 +583,7 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint8()) + x := uint64(tv.GetUint8()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -624,7 +624,7 @@ GNO_CASE: case Uint16Kind: switch k { case IntKind: - x := int(tv.GetUint16()) + x := int64(tv.GetUint16()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -640,7 +640,7 @@ GNO_CASE: tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint16Kind, Int32Kind, func() bool { return int(tv.GetUint16()) <= math.MaxInt32 }) + validate(Uint16Kind, Int32Kind, func() bool { return int64(tv.GetUint16()) <= math.MaxInt32 }) x := int32(tv.GetUint16()) tv.T = t @@ -652,11 +652,11 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint16()) + x := uint64(tv.GetUint16()) tv.T = t tv.SetUint(x) case Uint8Kind: - validate(Uint16Kind, Uint8Kind, func() bool { return int(tv.GetUint16()) <= math.MaxUint8 }) + validate(Uint16Kind, Uint8Kind, func() bool { return int64(tv.GetUint16()) <= math.MaxUint8 }) x := uint8(tv.GetUint16()) tv.T = t @@ -695,25 +695,25 @@ GNO_CASE: case Uint32Kind: switch k { case IntKind: - validate(Uint32Kind, IntKind, func() bool { return int(tv.GetUint32()) <= math.MaxInt }) + validate(Uint32Kind, IntKind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt }) - x := int(tv.GetUint32()) + x := int64(tv.GetUint32()) tv.T = t tv.SetInt(x) case Int8Kind: - validate(Uint32Kind, Int8Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt8 }) + validate(Uint32Kind, Int8Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt8 }) x := int8(tv.GetUint32()) tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint32Kind, Int16Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt16 }) + validate(Uint32Kind, Int16Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt16 }) x := int16(tv.GetUint32()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint32Kind, Int32Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt32 }) + validate(Uint32Kind, Int32Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt32 }) x := int32(tv.GetUint32()) tv.T = t @@ -723,9 +723,9 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint32()) + x := uint64(tv.GetUint32()) tv.T = t - tv.SetUint(x) + tv.SetUint64(x) case Uint8Kind: validate(Uint32Kind, Uint8Kind, func() bool { return int(tv.GetUint32()) <= math.MaxUint8 }) @@ -768,25 +768,25 @@ GNO_CASE: case Uint64Kind: switch k { case IntKind: - validate(Uint64Kind, IntKind, func() bool { return int(tv.GetUint64()) <= math.MaxInt }) + validate(Uint64Kind, IntKind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt }) - x := int(tv.GetUint64()) + x := int64(tv.GetUint64()) tv.T = t tv.SetInt(x) case Int8Kind: - validate(Uint64Kind, Int8Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt8 }) + validate(Uint64Kind, Int8Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt8 }) x := int8(tv.GetUint64()) tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint64Kind, Int16Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt16 }) + validate(Uint64Kind, Int16Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt16 }) x := int16(tv.GetUint64()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint64Kind, Int32Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt32 }) + validate(Uint64Kind, Int32Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt32 }) x := int32(tv.GetUint64()) tv.T = t @@ -800,17 +800,17 @@ GNO_CASE: case UintKind: validate(Uint64Kind, UintKind, func() bool { return tv.GetUint64() <= math.MaxUint }) - x := uint(tv.GetUint64()) + x := tv.GetUint64() tv.T = t - tv.SetUint(x) + tv.SetUint64(x) case Uint8Kind: - validate(Uint64Kind, Uint8Kind, func() bool { return int(tv.GetUint64()) <= math.MaxUint8 }) + validate(Uint64Kind, Uint8Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxUint8 }) x := uint8(tv.GetUint64()) tv.T = t tv.SetUint8(x) case Uint16Kind: - validate(Uint64Kind, Uint16Kind, func() bool { return int(tv.GetUint64()) <= math.MaxUint16 }) + validate(Uint64Kind, Uint16Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxUint16 }) x := uint16(tv.GetUint64()) tv.T = t @@ -858,7 +858,7 @@ GNO_CASE: return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(softfloat.F32toint64(tv.GetFloat32())) + x := softfloat.F32toint64(tv.GetFloat32()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -928,7 +928,7 @@ GNO_CASE: return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(softfloat.F32touint64(tv.GetFloat32())) + x := softfloat.F32touint64(tv.GetFloat32()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -1019,9 +1019,8 @@ GNO_CASE: }) xp, _ := softfloat.F64toint(tv.GetFloat64()) - x := int(xp) tv.T = t - tv.SetInt(x) + tv.SetInt(xp) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { trunc := softfloat.Ftrunc64(tv.GetFloat64()) @@ -1090,7 +1089,7 @@ GNO_CASE: return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(softfloat.F64touint64(tv.GetFloat64())) + x := softfloat.F64touint64(tv.GetFloat64()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -1372,7 +1371,7 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { // the value is within int64 or uint64. switch k { case IntKind: - dst.SetInt(int(sv)) + dst.SetInt(int64(sv)) case Int8Kind: if math.MaxInt8 < sv { panic("rune overflows target kind") @@ -1397,7 +1396,7 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { if sv < 0 { panic("rune underflows target kind") } - dst.SetUint(uint(sv)) + dst.SetUint(uint64(sv)) case Uint8Kind: if sv < 0 { panic("rune underflows target kind") @@ -1523,9 +1522,9 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if sv < math.MinInt32 { panic("bigint underflows target kind") } - dst.SetInt(int(sv)) + dst.SetInt(sv) } else if strconv.IntSize == 64 { - dst.SetInt(int(sv)) + dst.SetInt(sv) } else { panic("unexpected IntSize") } @@ -1560,9 +1559,9 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if math.MaxUint32 < uv { panic("bigint overflows target kind") } - dst.SetUint(uint(uv)) + dst.SetUint(uv) } else if strconv.IntSize == 64 { - dst.SetUint(uint(uv)) + dst.SetUint(uv) } else { panic("unexpected IntSize") } diff --git a/gnovm/stdlibs/encoding/base32/base32_test.gno b/gnovm/stdlibs/encoding/base32/base32_test.gno index fa09c2cd2f3..84d8e42df67 100644 --- a/gnovm/stdlibs/encoding/base32/base32_test.gno +++ b/gnovm/stdlibs/encoding/base32/base32_test.gno @@ -9,7 +9,6 @@ import ( "errors" "io" "math" - "strconv" "strings" "testing" ) @@ -765,15 +764,9 @@ func TestEncodedLen(t *testing.T) { {rawStdEncoding, 7, 12}, {rawStdEncoding, 10, 16}, {rawStdEncoding, 11, 18}, - } - // check overflow - switch strconv.IntSize { - case 32: - tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 429496730}) - tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) - case 64: - tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}) - tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) + // check overflow + {rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}, + {rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}, } for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { @@ -804,15 +797,9 @@ func TestDecodedLen(t *testing.T) { {rawStdEncoding, 12, 7}, {rawStdEncoding, 16, 10}, {rawStdEncoding, 18, 11}, - } - // check overflow - switch strconv.IntSize { - case 32: - tests = append(tests, test{rawStdEncoding, math.MaxInt/5 + 1, 268435456}) - tests = append(tests, test{rawStdEncoding, math.MaxInt, 1342177279}) - case 64: - tests = append(tests, test{rawStdEncoding, math.MaxInt/5 + 1, 1152921504606846976}) - tests = append(tests, test{rawStdEncoding, math.MaxInt, 5764607523034234879}) + // check overflow + {rawStdEncoding, math.MaxInt/5 + 1, 1152921504606846976}, + {rawStdEncoding, math.MaxInt, 5764607523034234879}, } for _, tt := range tests { if got := tt.enc.DecodedLen(tt.n); int64(got) != tt.want { diff --git a/gnovm/stdlibs/math/const.gno b/gnovm/stdlibs/math/const.gno index ad13d53477e..1835c9202ee 100644 --- a/gnovm/stdlibs/math/const.gno +++ b/gnovm/stdlibs/math/const.gno @@ -37,7 +37,7 @@ const ( // Integer limit values. const ( - intSize = 32 << (^uint(0) >> 63) // 32 or 64 + intSize = 64 // Integers are 64 bits only. MaxInt = 1<<(intSize-1) - 1 // == MaxInt64 MinInt = -1 << (intSize - 1) // == MinInt64 diff --git a/gnovm/stdlibs/strconv/atoi_test.gno b/gnovm/stdlibs/strconv/atoi_test.gno index 5dc0577924f..ad4e5e0528c 100644 --- a/gnovm/stdlibs/strconv/atoi_test.gno +++ b/gnovm/stdlibs/strconv/atoi_test.gno @@ -423,78 +423,38 @@ func TestParseInt64Base(t *testing.T) { } func TestParseUint(t *testing.T) { - switch strconv.IntSize { - case 32: - for i := range parseUint32Tests { - test := &parseUint32Tests[i] - out, err := strconv.ParseUint(test.in, 10, 0) - if uint64(test.out) != out || !errEqual(test.err, err) { - t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } - } - case 64: - for i := range parseUint64Tests { - test := &parseUint64Tests[i] - out, err := strconv.ParseUint(test.in, 10, 0) - if test.out != out || !errEqual(test.err, err) { - t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } + for i := range parseUint64Tests { + test := &parseUint64Tests[i] + out, err := strconv.ParseUint(test.in, 10, 0) + if test.out != out || !errEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) } } } func TestParseInt(t *testing.T) { - switch strconv.IntSize { - case 32: - for i := range parseInt32Tests { - test := &parseInt32Tests[i] - out, err := strconv.ParseInt(test.in, 10, 0) - if int64(test.out) != out || !errEqual(test.err, err) { - t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } - } - case 64: - for i := range parseInt64Tests { - test := &parseInt64Tests[i] - out, err := strconv.ParseInt(test.in, 10, 0) - if test.out != out || !errEqual(test.err, err) { - t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := strconv.ParseInt(test.in, 10, 0) + if test.out != out || !errEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) } } } func TestAtoi(t *testing.T) { - switch strconv.IntSize { - case 32: - for i := range parseInt32Tests { - test := &parseInt32Tests[i] - out, err := strconv.Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} - } - if int(test.out) != out || !errEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) - } + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := strconv.Atoi(test.in) + var testErr error + if test.err != nil { + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } - case 64: - for i := range parseInt64Tests { - test := &parseInt64Tests[i] - out, err := strconv.Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} - } - if test.out != int64(out) || !errEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) - } + if test.out != int64(out) || !errEqual(testErr, err) { + t.Errorf("Atoi(%q) = %v, %v want %v, %v", + test.in, out, err, test.out, testErr) } } } diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 025fe4b673e..1793e69c7b7 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -70,7 +70,7 @@ func findDirs() (gitRoot string, relPath string, err error) { } p := wd for { - if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { // make relPath relative to the git root rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / diff --git a/tm2/pkg/overflow/overflow_template.sh b/tm2/pkg/overflow/overflow_template.sh index 0cc3c9595bf..423770c22be 100755 --- a/tm2/pkg/overflow/overflow_template.sh +++ b/tm2/pkg/overflow/overflow_template.sh @@ -91,7 +91,6 @@ func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status -} -" + return c, a % b, status +}" done From c61e217968d5d01125ac467e99849ad75de16c6f Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Thu, 13 Feb 2025 17:13:53 +0100 Subject: [PATCH 08/47] fix(gnovm): object deletion; add comments (#3741) Closes [3688](https://github.com/gnolang/gno/issues/3688) --- gnovm/pkg/gnolang/ownership.go | 38 +++++++++++----- gnovm/tests/files/zrealm14.gno | 80 ---------------------------------- gnovm/tests/files/zrealm15.gno | 49 --------------------- 3 files changed, 28 insertions(+), 139 deletions(-) diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go index 511b44bfc73..da254d383a9 100644 --- a/gnovm/pkg/gnolang/ownership.go +++ b/gnovm/pkg/gnolang/ownership.go @@ -133,17 +133,29 @@ var ( ) type ObjectInfo struct { - ID ObjectID // set if real. - Hash ValueHash `json:",omitempty"` // zero if dirty. - OwnerID ObjectID `json:",omitempty"` // parent in the ownership tree. - ModTime uint64 // time last updated. - RefCount int // for persistence. deleted/gc'd if 0. - IsEscaped bool `json:",omitempty"` // hash in iavl. + ID ObjectID // set if real. + Hash ValueHash `json:",omitempty"` // zero if dirty. + OwnerID ObjectID `json:",omitempty"` // parent in the ownership tree. + ModTime uint64 // time last updated. + RefCount int // for persistence. deleted/gc'd if 0. + + // Object has multiple references (refcount > 1) and is persisted separately + IsEscaped bool `json:",omitempty"` // hash in iavl. + // MemRefCount int // consider for optimizations. - isDirty bool - isDeleted bool - isNewReal bool + // Object has been modified and needs to be saved + isDirty bool + + // Object has been permanently deleted + isDeleted bool + + // Object is newly created in current transaction and will be persisted + isNewReal bool + + // Object newly created multiple references in current transaction isNewEscaped bool + + // Object is marked for deletion in current transaction isNewDeleted bool // XXX huh? @@ -294,7 +306,13 @@ func (oi *ObjectInfo) SetIsDeleted(x bool, mt uint64) { // NOTE: Don't over-write modtime. // Consider adding a DelTime, or just log it somewhere, or // continue to ignore it. - oi.isDirty = x + + // The above comment is likely made because it could introduce complexity + // Objects can be "undeleted" if referenced during a transaction + // If an object is deleted and then undeleted in the same transaction + // If an object is deleted multiple times + // ie...continue to ignore it + oi.isDeleted = x } func (oi *ObjectInfo) GetIsNewReal() bool { diff --git a/gnovm/tests/files/zrealm14.gno b/gnovm/tests/files/zrealm14.gno index ffffa4883fd..84ef45982cb 100644 --- a/gnovm/tests/files/zrealm14.gno +++ b/gnovm/tests/files/zrealm14.gno @@ -65,86 +65,6 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "c" -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "c22ccb7832b422c83fec9943b751cb134fcbed0b", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "0" -// } -// } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "d" -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "86c916fd78da57d354cb38019923bf64c1a3471c", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "0" -// } -// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", diff --git a/gnovm/tests/files/zrealm15.gno b/gnovm/tests/files/zrealm15.gno index 4ca6ef3b03d..b8ba84bad04 100644 --- a/gnovm/tests/files/zrealm15.gno +++ b/gnovm/tests/files/zrealm15.gno @@ -69,55 +69,6 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "5bf603ab337f9f40f8b22441562319d67be402b2", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", -// "RefCount": "0" -// } -// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "Fields": [ // { From 52a4198467f002aa469a95c1a090405a842d5a03 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 13 Feb 2025 17:15:42 +0100 Subject: [PATCH 09/47] fix(tm2): overflow check when computing remaining gas. (#3745) Panic on overflow when computing the remaining gas, to avoid inconsistent and wrong gas amounts. --- tm2/pkg/store/types/gas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/store/types/gas.go b/tm2/pkg/store/types/gas.go index a86cff17d1a..8afbed698ea 100644 --- a/tm2/pkg/store/types/gas.go +++ b/tm2/pkg/store/types/gas.go @@ -79,7 +79,7 @@ func (g *basicGasMeter) Limit() Gas { } func (g *basicGasMeter) Remaining() Gas { - return g.Limit() - g.GasConsumedToLimit() + return overflow.Sub64p(g.Limit(), g.GasConsumedToLimit()) } func (g *basicGasMeter) GasConsumedToLimit() Gas { From e9e04ea7dfdcce7fb47b74d0b0a4bac27cd37f49 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 13 Feb 2025 17:17:11 +0100 Subject: [PATCH 10/47] chore(gnovm): remove unused Machine.MaxCycles (#3744) The logic to manage the CPU limit is already handled by the gasmeter, which has the ability to panic the gnovm if a cpu limit is reached. It's simpler and faster to have only one path of failure. No other changes in CPU cycles accounting, still performed in gnovm. --- gnovm/pkg/gnolang/machine.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 7b89e98eb6d..ac20a6540a1 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -43,7 +43,6 @@ type Machine struct { // Configuration PreprocessorMode bool // this is used as a flag when const values are evaluated during preprocessing ReadOnly bool - MaxCycles int64 Output io.Writer Store Store Context interface{} @@ -87,7 +86,6 @@ type MachineOptions struct { Context interface{} Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. - MaxCycles int64 // or 0 for no limit. GasMeter store.GasMeter } @@ -112,7 +110,6 @@ var machinePool = sync.Pool{ func NewMachineWithOptions(opts MachineOptions) *Machine { preprocessorMode := opts.PreprocessorMode readOnly := opts.ReadOnly - maxCycles := opts.MaxCycles vmGasMeter := opts.GasMeter output := opts.Output @@ -145,7 +142,6 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { mm.Alloc = alloc mm.PreprocessorMode = preprocessorMode mm.ReadOnly = readOnly - mm.MaxCycles = maxCycles mm.Output = output mm.Store = store mm.Context = context @@ -1025,13 +1021,9 @@ const GasFactorCPU int64 = 1 func (m *Machine) incrCPU(cycles int64) { if m.GasMeter != nil { gasCPU := overflow.Mul64p(cycles, GasFactorCPU) - m.GasMeter.ConsumeGas(gasCPU, "CPUCycles") + m.GasMeter.ConsumeGas(gasCPU, "CPUCycles") // May panic if out of gas. } - m.Cycles += cycles - if m.MaxCycles != 0 && m.Cycles > m.MaxCycles { - panic("CPU cycle overrun") - } } const ( From 70b8a7072c96117cb673b58434ea5801158a6afe Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:21:36 +0100 Subject: [PATCH 11/47] fix(gnodev): fix the use of filepath instead of path on windows (#3737) fix #3722 Using `filepath` to work with the `path` creates some obvious issues on Windows, primarily because the `Clean` function replaces `/` with `\\`. This PR updates the usage of `filepath` to `path` where necessary. At some point, adapting path-specific tests on Windows would be very helpful. --- @Milosevic02, can you confirm that this fix resolves most of your issues with gnodev? **EDIT:** It seems that the fix is effective: https://github.com/gnolang/gno/issues/3722#issuecomment-2653988097 --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnodev/cmd/gnobro/main.go | 5 +- contribs/gnodev/cmd/gnodev/command_staging.go | 3 +- contribs/gnodev/cmd/gnodev/setup_loader.go | 3 +- contribs/gnodev/pkg/browser/model.go | 4 +- contribs/gnodev/pkg/browser/utils.go | 4 +- contribs/gnodev/pkg/dev/query_path.go | 4 +- contribs/gnodev/pkg/events/events.go | 4 +- contribs/gnodev/pkg/packages/loader_glob.go | 17 +++--- .../gnodev/pkg/packages/resolver_local.go | 3 +- .../gnodev/pkg/packages/resolver_remote.go | 4 +- contribs/gnodev/pkg/packages/utils_other.go | 9 ++++ contribs/gnodev/pkg/packages/utils_windows.go | 11 ++++ contribs/gnodev/pkg/proxy/path_interceptor.go | 25 +++++---- .../gnodev/pkg/proxy/path_interceptor_test.go | 3 +- contribs/gnodev/pkg/watcher/watch.go | 52 +++++++++---------- gno.land/pkg/gnoweb/handler.go | 6 +-- gno.land/pkg/gnoweb/webclient_html.go | 6 +-- gno.land/pkg/gnoweb/weburl/url.go | 6 +-- 18 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 contribs/gnodev/pkg/packages/utils_other.go create mode 100644 contribs/gnodev/pkg/packages/utils_windows.go diff --git a/contribs/gnodev/cmd/gnobro/main.go b/contribs/gnodev/cmd/gnobro/main.go index 91713d6c6d8..7e47fb96780 100644 --- a/contribs/gnodev/cmd/gnobro/main.go +++ b/contribs/gnodev/cmd/gnobro/main.go @@ -11,7 +11,7 @@ import ( "net/url" "os" "os/signal" - "path/filepath" + gopath "path" "strings" "time" @@ -239,7 +239,6 @@ func runLocal(ctx context.Context, gnocl *gnoclient.Client, cfg *broCfg, bcfg br ) var errgs errgroup.Group - if cfg.dev { devpoint, err := getDevEndpoint(cfg) if err != nil { @@ -453,7 +452,7 @@ func ValidatePathCommandMiddleware(pathPrefix string) wish.Middleware { return case 1: // check for valid path path := cmd[0] - if strings.HasPrefix(path, pathPrefix) && filepath.Clean(path) == path { + if strings.HasPrefix(path, pathPrefix) && gopath.Clean(path) == path { s.Context().SetValue("path", path) next(s) return diff --git a/contribs/gnodev/cmd/gnodev/command_staging.go b/contribs/gnodev/cmd/gnodev/command_staging.go index 7b1a0ab3f5a..d02d7c289fe 100644 --- a/contribs/gnodev/cmd/gnodev/command_staging.go +++ b/contribs/gnodev/cmd/gnodev/command_staging.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + "path" "path/filepath" "github.com/gnolang/gno/contribs/gnodev/pkg/packages" @@ -28,7 +29,7 @@ var defaultStagingOptions = AppConfig{ interactive: false, unsafeAPI: false, lazyLoader: false, - paths: filepath.Join(DefaultDomain, "/**"), // Load every package under the main domain}, + paths: path.Join(DefaultDomain, "/**"), // Load every package under the main domain}, // As we have no reason to configure this yet, set this to random port // to avoid potential conflict with other app diff --git a/contribs/gnodev/cmd/gnodev/setup_loader.go b/contribs/gnodev/cmd/gnodev/setup_loader.go index 8f10a6a5a76..69342657352 100644 --- a/contribs/gnodev/cmd/gnodev/setup_loader.go +++ b/contribs/gnodev/cmd/gnodev/setup_loader.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log/slog" + gopath "path" "path/filepath" "regexp" "strings" @@ -103,5 +104,5 @@ func guessPath(cfg *AppConfig, dir string) (path string) { } rname := reInvalidChar.ReplaceAllString(filepath.Base(dir), "-") - return filepath.Join(cfg.chainDomain, "/r/dev/", rname) + return gopath.Join(cfg.chainDomain, "/r/dev/", rname) } diff --git a/contribs/gnodev/pkg/browser/model.go b/contribs/gnodev/pkg/browser/model.go index bdd7eac9c82..148497e77db 100644 --- a/contribs/gnodev/pkg/browser/model.go +++ b/contribs/gnodev/pkg/browser/model.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "log/slog" - "path/filepath" + gopath "path" "regexp" "strings" @@ -576,5 +576,5 @@ func (m *model) getCurrentPath() string { return m.urlPrefix } - return filepath.Join(m.urlPrefix, path) + return gopath.Join(m.urlPrefix, path) } diff --git a/contribs/gnodev/pkg/browser/utils.go b/contribs/gnodev/pkg/browser/utils.go index b322bea552a..94f0bd6344c 100644 --- a/contribs/gnodev/pkg/browser/utils.go +++ b/contribs/gnodev/pkg/browser/utils.go @@ -1,7 +1,7 @@ package browser import ( - "path/filepath" + gopath "path" "strings" "github.com/gnolang/gno/gno.land/pkg/gnoweb" @@ -27,7 +27,7 @@ func cleanupRealmPath(prefix, realm string) string { // trim any slash path = strings.TrimPrefix(path, "/") // clean up path - path = filepath.Clean(path) + path = gopath.Clean(path) return path } diff --git a/contribs/gnodev/pkg/dev/query_path.go b/contribs/gnodev/pkg/dev/query_path.go index e899d8212e4..b33576ccc71 100644 --- a/contribs/gnodev/pkg/dev/query_path.go +++ b/contribs/gnodev/pkg/dev/query_path.go @@ -3,7 +3,7 @@ package dev import ( "fmt" "net/url" - "path/filepath" + "path" "github.com/gnolang/gno/contribs/gnodev/pkg/address" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -24,7 +24,7 @@ func ResolveQueryPath(bk *address.Book, query string) (QueryPath, error) { return qpath, fmt.Errorf("malformed path/query: %w", err) } - qpath.Path = filepath.Clean(upath.Path) + qpath.Path = path.Clean(upath.Path) // Check for creator option creator := upath.Query().Get("creator") diff --git a/contribs/gnodev/pkg/events/events.go b/contribs/gnodev/pkg/events/events.go index c387c331eed..3137b5d50bb 100644 --- a/contribs/gnodev/pkg/events/events.go +++ b/contribs/gnodev/pkg/events/events.go @@ -41,8 +41,8 @@ type PackagesUpdate struct { } type PackageUpdate struct { - Package string `json:"package"` - Files []string `json:"files"` + PackageDir string `json:"package"` + Files []string `json:"files"` } func (PackagesUpdate) Type() Type { return EvtPackagesUpdate } diff --git a/contribs/gnodev/pkg/packages/loader_glob.go b/contribs/gnodev/pkg/packages/loader_glob.go index dabfe613574..6e3d1158cb5 100644 --- a/contribs/gnodev/pkg/packages/loader_glob.go +++ b/contribs/gnodev/pkg/packages/loader_glob.go @@ -5,6 +5,7 @@ import ( "go/token" "io/fs" "os" + "path" "path/filepath" "strings" ) @@ -33,7 +34,7 @@ func (l GlobLoader) MatchPaths(globs ...string) ([]string, error) { mpaths := []string{} for _, input := range globs { - cleanInput := filepath.Clean(input) + cleanInput := path.Clean(input) gpath, err := Parse(cleanInput) if err != nil { return nil, fmt.Errorf("invalid glob path %q: %w", input, err) @@ -45,18 +46,20 @@ func (l GlobLoader) MatchPaths(globs ...string) ([]string, error) { continue } - // root := filepath.Join(l.Root, base) root := l.Root err = filepath.WalkDir(root, func(dirpath string, d fs.DirEntry, err error) error { if err != nil { return err } - relPath, relErr := filepath.Rel(root, dirpath) - if relErr != nil { - return relErr + path, err := filepath.Rel(root, dirpath) + if err != nil { + return err } + // normalize filepath to path + path = NormalizeFilepathToPath(path) + if !d.IsDir() { return nil } @@ -65,8 +68,8 @@ func (l GlobLoader) MatchPaths(globs ...string) ([]string, error) { return fs.SkipDir } - if gpath.Match(relPath) { - mpaths = append(mpaths, relPath) + if gpath.Match(path) { + mpaths = append(mpaths, path) } return nil diff --git a/contribs/gnodev/pkg/packages/resolver_local.go b/contribs/gnodev/pkg/packages/resolver_local.go index 13448aca52d..0312b262403 100644 --- a/contribs/gnodev/pkg/packages/resolver_local.go +++ b/contribs/gnodev/pkg/packages/resolver_local.go @@ -3,6 +3,7 @@ package packages import ( "fmt" "go/token" + "path" "path/filepath" "strings" ) @@ -20,7 +21,7 @@ func NewLocalResolver(path, dir string) *LocalResolver { } func (r *LocalResolver) Name() string { - return fmt.Sprintf("local<%s>", filepath.Base(r.Dir)) + return fmt.Sprintf("local<%s>", path.Base(r.Dir)) } func (r LocalResolver) IsValid() bool { diff --git a/contribs/gnodev/pkg/packages/resolver_remote.go b/contribs/gnodev/pkg/packages/resolver_remote.go index 94396f70c83..ff7b8fa723a 100644 --- a/contribs/gnodev/pkg/packages/resolver_remote.go +++ b/contribs/gnodev/pkg/packages/resolver_remote.go @@ -6,7 +6,7 @@ import ( "fmt" "go/parser" "go/token" - "path/filepath" + gopath "path" "strings" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" @@ -56,7 +56,7 @@ func (res *remoteResolver) Resolve(fset *token.FileSet, path string) (*Package, files := bytes.Split(qres.Response.Data, []byte{'\n'}) for _, filename := range files { fname := string(filename) - fpath := filepath.Join(path, fname) + fpath := gopath.Join(path, fname) qres, err := res.RPCClient.ABCIQuery(qpath, []byte(fpath)) if err != nil { return nil, fmt.Errorf("unable to query path") diff --git a/contribs/gnodev/pkg/packages/utils_other.go b/contribs/gnodev/pkg/packages/utils_other.go new file mode 100644 index 00000000000..0455aa57480 --- /dev/null +++ b/contribs/gnodev/pkg/packages/utils_other.go @@ -0,0 +1,9 @@ +//go:build !windows +// +build !windows + +package packages + +// NormalizeFilepathToPath normalize path an unix like path +func NormalizeFilepathToPath(path string) string { + return path +} diff --git a/contribs/gnodev/pkg/packages/utils_windows.go b/contribs/gnodev/pkg/packages/utils_windows.go new file mode 100644 index 00000000000..23c7fa54379 --- /dev/null +++ b/contribs/gnodev/pkg/packages/utils_windows.go @@ -0,0 +1,11 @@ +//go:build windows +// +build windows + +package packages + +import "strings" + +// NormalizeFilepathToPath normalize path an unix like path +func NormalizeFilepathToPath(path string) string { + return strings.ReplaceAll(path, "\\", "/") +} diff --git a/contribs/gnodev/pkg/proxy/path_interceptor.go b/contribs/gnodev/pkg/proxy/path_interceptor.go index 84d2f92b22f..88240d904a5 100644 --- a/contribs/gnodev/pkg/proxy/path_interceptor.go +++ b/contribs/gnodev/pkg/proxy/path_interceptor.go @@ -10,7 +10,7 @@ import ( "log/slog" "net" "net/http" - "path/filepath" + gopath "path" "strings" "sync" @@ -220,7 +220,11 @@ func (upaths uniqPaths) list() []string { return paths } -func (upaths uniqPaths) add(path string) { upaths[path] = struct{}{} } +// Add a path to +func (upaths uniqPaths) add(path string) { + path = cleanupPath(path) + upaths[path] = struct{}{} +} // handleRequest parses and processes the RPC request body. func (proxy *PathInterceptor) handleRequest(body []byte) error { @@ -312,13 +316,6 @@ func handleQuery(path string, data []byte, upaths uniqPaths) error { case "vm/qrender", "vm/qfile", "vm/qfuncs", "vm/qeval": path, _, _ := strings.Cut(string(data), ":") // Cut arguments out - path = filepath.Clean(path) - - // If path is a file, grab the directory instead - if ext := filepath.Ext(path); ext != "" { - path = filepath.Dir(path) - } - upaths.add(path) return nil @@ -328,3 +325,13 @@ func handleQuery(path string, data []byte, upaths uniqPaths) error { // XXX: handle more cases } + +func cleanupPath(path string) string { + path = gopath.Clean(path) + // If path is a file, grab the directory instead + if ext := gopath.Ext(path); ext != "" { + path = gopath.Dir(path) + } + + return path +} diff --git a/contribs/gnodev/pkg/proxy/path_interceptor_test.go b/contribs/gnodev/pkg/proxy/path_interceptor_test.go index c7082adfa30..0e1e95e82a4 100644 --- a/contribs/gnodev/pkg/proxy/path_interceptor_test.go +++ b/contribs/gnodev/pkg/proxy/path_interceptor_test.go @@ -3,6 +3,7 @@ package proxy_test import ( "net" "net/http" + "path" "path/filepath" "testing" @@ -86,7 +87,7 @@ func TestProxy(t *testing.T) { cli, err := client.NewHTTPClient(interceptor.TargetAddress()) require.NoError(t, err) - res, err := cli.ABCIQuery("vm/qfile", []byte(filepath.Join(targetPath, "foo.gno"))) + res, err := cli.ABCIQuery("vm/qfile", []byte(path.Join(targetPath, "foo.gno"))) require.NoError(t, err) assert.Nil(t, res.Response.Error) diff --git a/contribs/gnodev/pkg/watcher/watch.go b/contribs/gnodev/pkg/watcher/watch.go index 5f277fd6646..0a7d284bd37 100644 --- a/contribs/gnodev/pkg/watcher/watch.go +++ b/contribs/gnodev/pkg/watcher/watch.go @@ -58,7 +58,7 @@ func (p *PackageWatcher) startWatching() { defer close(pkgsUpdateChan) var debounceTimer <-chan time.Time - pathList := []string{} + filesList := []string{} var err error for err == nil { @@ -69,10 +69,10 @@ func (p *PackageWatcher) startWatching() { err = fmt.Errorf("watch error: %w", watchErr) case <-debounceTimer: // Process and emit package updates after the debounce interval - updates := p.generatePackagesUpdateList(pathList) + updates := p.generatePackagesUpdateList(filesList) for _, update := range updates { p.logger.Info("packages update", - "pkg", update.Package, + "pkg", update.PackageDir, "files", update.Files, ) } @@ -84,7 +84,7 @@ func (p *PackageWatcher) startWatching() { }) // Reset the path list and debounce timer - pathList = []string{} + filesList = []string{} debounceTimer = nil case evt := <-p.watcher.Events: // Only handle write operations @@ -92,7 +92,7 @@ func (p *PackageWatcher) startWatching() { continue } - pathList = append(pathList, evt.Name) + filesList = append(filesList, evt.Name) // Set up the debounce timer debounceTimer = time.After(timeout) @@ -125,26 +125,26 @@ func (p *PackageWatcher) UpdatePackagesWatch(pkgs ...packages.Package) { continue } - path, err := filepath.Abs(pkg.Location) + dir, err := filepath.Abs(pkg.Location) if err != nil { p.logger.Error("Unable to get absolute path", "path", pkg.Location, "error", err) continue } - newPkgs[path] = struct{}{} + newPkgs[dir] = struct{}{} } - for path := range oldPkgs { - if _, exists := newPkgs[path]; !exists { - p.watcher.Remove(path) - p.logger.Debug("Watcher list: removed", "path", path) + for dir := range oldPkgs { + if _, exists := newPkgs[dir]; !exists { + p.watcher.Remove(dir) + p.logger.Debug("Watcher list: removed", "path", dir) } } - for path := range newPkgs { - if _, exists := oldPkgs[path]; !exists { - p.watcher.Add(path) - p.logger.Debug("Watcher list: added", "path", path) + for dir := range newPkgs { + if _, exists := oldPkgs[dir]; !exists { + p.watcher.Add(dir) + p.logger.Debug("Watcher list: added", "path", dir) } } } @@ -154,29 +154,29 @@ func (p *PackageWatcher) generatePackagesUpdateList(paths []string) PackageUpdat mpkgs := map[string]*events.PackageUpdate{} // Pkg -> Update watchList := p.watcher.WatchList() - for _, path := range paths { - for _, pkg := range watchList { - if len(pkg) == len(path) { - continue // Skip if pkg == path + for _, file := range paths { + for _, watchDir := range watchList { + if len(watchDir) == len(file) { + continue // Skip if watchDir == file } // Check if a package directory contain our path directory - dirPath := filepath.Dir(path) - if !strings.HasPrefix(pkg, dirPath) { + dir := filepath.Dir(file) + if !strings.HasPrefix(watchDir, dir) { continue } // Accumulate file updates for each package - pkgu, ok := mpkgs[pkg] + pkgu, ok := mpkgs[watchDir] if !ok { pkgsUpdate = append(pkgsUpdate, events.PackageUpdate{ - Package: pkg, - Files: []string{}, + PackageDir: watchDir, + Files: []string{}, }) pkgu = &pkgsUpdate[len(pkgsUpdate)-1] } - pkgu.Files = append(pkgu.Files, path) + pkgu.Files = append(pkgu.Files, file) } } @@ -188,7 +188,7 @@ type PackageUpdateList []events.PackageUpdate func (pkgsu PackageUpdateList) PackagesPath() []string { pkgs := make([]string, len(pkgsu)) for i, pkg := range pkgsu { - pkgs[i] = pkg.Package + pkgs[i] = pkg.PackageDir } return pkgs } diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index cfad9919506..41a61c4cead 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -6,7 +6,7 @@ import ( "fmt" "log/slog" "net/http" - "path/filepath" + "path" "strings" "time" @@ -207,14 +207,14 @@ func (h *WebHandler) GetHelpView(gnourl *weburl.GnoURL) (int, *components.View) } } - realmName := filepath.Base(gnourl.Path) + realmName := path.Base(gnourl.Path) return http.StatusOK, components.HelpView(components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, // TODO: get chain domain and use that. ChainId: h.Static.ChainId, - PkgPath: filepath.Join(h.Static.Domain, gnourl.Path), + PkgPath: path.Join(h.Static.Domain, gnourl.Path), Remote: h.Static.RemoteHelp, Functions: fsigs, }) diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go index 72b1b3f8b06..efbc08870de 100644 --- a/gno.land/pkg/gnoweb/webclient_html.go +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -5,7 +5,7 @@ import ( "fmt" "io" "log/slog" - "path/filepath" + gopath "path" "strings" "github.com/alecthomas/chroma/v2" @@ -121,7 +121,7 @@ func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMet } // XXX: Consider moving this into gnoclient - fullPath := filepath.Join(s.domain, strings.Trim(path, "/"), fileName) + fullPath := gopath.Join(s.domain, strings.Trim(path, "/"), fileName) source, err := s.query(qpath, []byte(fullPath)) if err != nil { @@ -230,7 +230,7 @@ func (s *HTMLWebClient) FormatSource(w io.Writer, fileName string, src []byte) e var lexer chroma.Lexer // Determine the lexer to be used based on the file extension. - switch strings.ToLower(filepath.Ext(fileName)) { + switch strings.ToLower(gopath.Ext(fileName)) { case ".gno": lexer = lexers.Get("go") case ".md": diff --git a/gno.land/pkg/gnoweb/weburl/url.go b/gno.land/pkg/gnoweb/weburl/url.go index cbe861e9e42..8b62ada198f 100644 --- a/gno.land/pkg/gnoweb/weburl/url.go +++ b/gno.land/pkg/gnoweb/weburl/url.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" "net/url" - "path/filepath" + gopath "path" "regexp" "slices" "strings" @@ -179,8 +179,8 @@ func ParseGnoURL(u *url.URL) (*GnoURL, error) { // A file is considered as one that either ends with an extension or // contains an uppercase rune - ext := filepath.Ext(upath) - base := filepath.Base(upath) + ext := gopath.Ext(upath) + base := gopath.Base(upath) if ext != "" || strings.ToLower(base) != base { file = base upath = strings.TrimSuffix(upath, base) From 041f56d6bdfa4a4deeb830f7344aa9ba6ec9598e Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 13 Feb 2025 19:03:19 +0200 Subject: [PATCH 12/47] fix(gnolang): make Go2Gno return a prespective error instead of sudden/elusive runtime panic with a bad receiver (#3733) The pattern: ```go func() A() ``` confuses Go into expecting a receiver and it returns a compile time error "missing receiver", but previously Gno panicked with a runtime error due to a deference yet the Recv.List was empty. This change fixes that by detecting that condition and prescriptively panicking which can then be relayed reasonably as expecting to the calling user. Fixes #3727 --------- Co-authored-by: Morgan Bazalgette --- gnovm/pkg/gnolang/go2gno.go | 5 ++++- gnovm/tests/files/parse_err0.gno | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 gnovm/tests/files/parse_err0.gno diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 34686dc4cc1..1ef23a0079e 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -433,7 +433,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { recv := FieldTypeExpr{} if isMethod { if len(gon.Recv.List) > 1 { - panic("*ast.FuncDecl cannot have multiple receivers") + panic("method has multiple receivers") + } + if len(gon.Recv.List) == 0 { + panic("method has no receiver") } recv = *Go2Gno(fs, gon.Recv.List[0]).(*FieldTypeExpr) } diff --git a/gnovm/tests/files/parse_err0.gno b/gnovm/tests/files/parse_err0.gno new file mode 100644 index 00000000000..88a8e0df132 --- /dev/null +++ b/gnovm/tests/files/parse_err0.gno @@ -0,0 +1,10 @@ +// https://github.com/gnolang/gno/issues/3727 + +package main + +func () A() + +func main() {} + +// Error: +// method has no receiver From 2e491411894e9bb2c882ad88bfe111a8cdb4a1d7 Mon Sep 17 00:00:00 2001 From: Mason McBride <56214403+masonmcbride@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:25:45 -0800 Subject: [PATCH 13/47] feat(examples): add mason realm to examples + md project (#3726) Hi I'm putting this here so I can be added into the examples folder. I will also be making a md implementation that's slightly different than moul and sunspirits but in the same spirit as them. --- examples/gno.land/p/mason/md/gno.mod | 1 + examples/gno.land/p/mason/md/md.gno | 48 +++++++++++++++ examples/gno.land/r/mason/home/gno.mod | 1 + examples/gno.land/r/mason/home/home.gno | 77 +++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 examples/gno.land/p/mason/md/gno.mod create mode 100644 examples/gno.land/p/mason/md/md.gno create mode 100644 examples/gno.land/r/mason/home/gno.mod create mode 100644 examples/gno.land/r/mason/home/home.gno diff --git a/examples/gno.land/p/mason/md/gno.mod b/examples/gno.land/p/mason/md/gno.mod new file mode 100644 index 00000000000..1f154210baf --- /dev/null +++ b/examples/gno.land/p/mason/md/gno.mod @@ -0,0 +1 @@ +module gno.land/p/mason/md diff --git a/examples/gno.land/p/mason/md/md.gno b/examples/gno.land/p/mason/md/md.gno new file mode 100644 index 00000000000..06e69b2c668 --- /dev/null +++ b/examples/gno.land/p/mason/md/md.gno @@ -0,0 +1,48 @@ +package md + +import ( + "strings" +) + +type MD struct { + elements []string +} + +func New() *MD { + return &MD{elements: []string{}} +} + +func (m *MD) H1(text string) { + m.elements = append(m.elements, "# "+text) +} + +func (m *MD) H3(text string) { + m.elements = append(m.elements, "### "+text) +} + +func (m *MD) P(text string) { + m.elements = append(m.elements, text) +} + +func (m *MD) Code(text string) { + m.elements = append(m.elements, " ```\n"+text+"\n```\n") +} + +func (m *MD) Im(path string, caption string) { + m.elements = append(m.elements, "!["+caption+"]("+path+" \""+caption+"\")") +} + +func (m *MD) Bullet(point string) { + m.elements = append(m.elements, "- "+point) +} + +func Link(text, url string, title ...string) string { + if len(title) > 0 && title[0] != "" { + return "[" + text + "](" + url + " \"" + title[0] + "\")" + } + return "[" + text + "](" + url + ")" +} + +func (m *MD) Render() string { + return strings.Join(m.elements, "\n\n") +} diff --git a/examples/gno.land/r/mason/home/gno.mod b/examples/gno.land/r/mason/home/gno.mod new file mode 100644 index 00000000000..0b934f1a5d1 --- /dev/null +++ b/examples/gno.land/r/mason/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/mason/home diff --git a/examples/gno.land/r/mason/home/home.gno b/examples/gno.land/r/mason/home/home.gno new file mode 100644 index 00000000000..557227cf5d4 --- /dev/null +++ b/examples/gno.land/r/mason/home/home.gno @@ -0,0 +1,77 @@ +package home + +import ( + "gno.land/p/mason/md" +) + +const ( + gnomeArt1 = ` /\ + / \ + ,,,,, + (o.o) + (\_/) + -"-"-` + dgnonut = ` + #$$$$$$$$* + #$$@@@@@@@@@@$$$# + #$$@@@@@@@@@@@@@$$$$# + #$$$@@@@$$$$$$$$$$$$$$$$* + #$$$$$$$$$$$$$$$$$$$$$$$$$#! + #$$$$$$$$$############$$$$$##* + !##$$$$$$####**********#####$###* + =##$$$$$###****!!!!!!!!!***######*! + *##$$$###***!!!!!!==!!!!!!**######*= + !*#######***!!!=;;;;;====!!!!**####** + !*#######**!!!==;;::::::;;==!!!**###**! + !*######***!==;::~~~~~~:::;;=!!!***#***= + =**#####**!!==;::~-,,,,,--~:;;=!!!******! + !**####***!==;:~-,.. ..,,-~:;==!!******!; + ;!**###***!!=;:~-,. ..-~:;==!!*****!= + =!*******!!==::-. .,-::==!!*****!= + =!*******!!=;:~, .-~:;=!!!****!=: + ~=!*******!==;:-. .,-:;=!!!****!=; + :=!*******!==;~,. ,-:;==!!!***!=; + :=!******!!==:~, ,-:;=!!!***!!=; + :=!!*****!!=;:~, ,~:;=!!****!!=;- + :=!!!****!!==;~, -~;==!!****!!=;- + :;=!!*****!!=;:- -:;=!!*****!!=:- + ~;=!!!****!!==;~ :;=!!*****!!!;:- + ~;==!!****!!!==: ;=!!******!!=;:, + ~:==!!!****!!!=;~ :=!********!!=;:. + -:;==!!*****!!!!; =!*********!==;: + ,~;==!!*******!!== =**#####****!==:~ + ,~:;=!!!!*********! **#######***!!=;~- + -~;;=!!!!**********! *##$$$$$###***!!=:~. + ,~:;==!!!****##########$$$$$$$$###****!=;:~ + -~:;==!!!***####$$$$$$@@@@@$$$###**!!=;:~, + ,-~:;=!!!***####$$$$@@@@@@$$$$##**!!!=;:-. + -~:;;=!!!***###$$$$$@@@@$$$$##***!!=;:-. + .-~:;;=!!!***###$$$$$$$$$$$##***!==;:~- + .-~:;==!!!!**####$$$$$$$###**!!==;:~- + ,-~::;==!!!!***########****!!==;:~-. + ,-~:;;==!!!!!***********!!!==;:~,. + ,,~~::;====!!!!!!!!!!!!!==;::~,. + .,-~::;;;===!!!!!!!!===;::~-,. + ,--~~:;;;;========;;::~--. + .,,-~~:::::::::::~~~-,,. + ..,---~~~~~~~~~--,.. + ..,,,,,,,,,... + ...` +) + +func Render(path string) string { + home := md.New() + home.H1("Mason's Realm") + + home.Im("https://cdn.esawebb.org/archives/images/screen/weic2428a.jpg", "Placeholder") + home.P("Welcome to my realm. " + md.Link("github", "https://github.com/masonmcbride")) + + home.H3("Dgnonut") + home.Code(dgnonut) + + home.H3("More") + home.Code(gnomeArt1) + home.Bullet("Credit to " + md.Link("JJOptimist", "https://gno.land/r/jjoptimist/home") + " for this gnome art.") + home.Bullet("I'm testing out my markdown system.") + return home.Render() +} From 9884ba10e8b06af670f3d2a7508e92595601f050 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:09:04 +0100 Subject: [PATCH 14/47] feat(gnoweb): add secure headers by default & timeout configuration (#3619) This PR adds the following secure headers by default `strict=true` to gnoweb: ```go func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Prevent MIME type sniffing by browsers. This ensures that the browser // does not interpret files as a different MIME type than declared. w.Header().Set("X-Content-Type-Options", "nosniff") // Prevent the page from being embedded in an iframe. This mitigates // clickjacking attacks by ensuring the page cannot be loaded in a frame. w.Header().Set("X-Frame-Options", "DENY") // Control the amount of referrer information sent in the Referer header. // 'no-referrer' ensures that no referrer information is sent, which // enhances privacy and prevents leakage of sensitive URLs. w.Header().Set("Referrer-Policy", "no-referrer") // In `strict` mode, prevent cross-site resource forgery and enforce https if strict { // Define a Content Security Policy (CSP) to restrict the sources of // scripts, styles, images, and other resources. This helps prevent // cross-site scripting (XSS) and other code injection attacks. // - 'self' allows resources from the same origin. // - '*' allows images from any external source. // - data: is not included to prevent inline images (e.g., base64-encoded images). w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' *; font-src 'self'") // Enforce HTTPS by telling browsers to only access the site over HTTPS // for a specified duration (1 year in this case). This also applies to // subdomains and allows preloading into the browser's HSTS list. w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } next.ServeHTTP(w, r) }) } ``` I've also enforced a timeout on read/write/idle (default to 1 minute). cc @kristovatlas --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Antoine Eddi <5222525+aeddi@users.noreply.github.com> Co-authored-by: alexiscolin Co-authored-by: Morgan --- gno.land/cmd/gnoweb/main.go | 64 +++++++++++++++++++- gno.land/pkg/gnoweb/Makefile | 3 +- gno.land/pkg/gnoweb/components/ui/icons.html | 2 +- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 8c0df00aa35..5b8961859f4 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -23,9 +23,11 @@ type webCfg struct { bind string faucetURL string assetsDir string + timeout time.Duration analytics bool json bool html bool + noStrict bool verbose bool } @@ -33,6 +35,7 @@ var defaultWebOptions = webCfg{ chainid: "dev", remote: "127.0.0.1:26657", bind: ":8888", + timeout: time.Minute, } func main() { @@ -127,7 +130,14 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { &c.analytics, "with-analytics", defaultWebOptions.analytics, - "nable privacy-first analytics", + "enable privacy-first analytics", + ) + + fs.BoolVar( + &c.noStrict, + "no-strict", + defaultWebOptions.noStrict, + "allow cross-site resource forgery and disable https enforcement", ) fs.BoolVar( @@ -136,6 +146,13 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { defaultWebOptions.verbose, "verbose logging mode", ) + + fs.DurationVar( + &c.timeout, + "timeout", + defaultWebOptions.timeout, + "set read/write/idle timeout for server connections", + ) } func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { @@ -179,11 +196,17 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) + // Setup security headers + secureHandler := SecureHeadersMiddleware(app, !cfg.noStrict) + // Setup server server := &http.Server{ - Handler: app, + Handler: secureHandler, Addr: bindaddr.String(), - ReadHeaderTimeout: 60 * time.Second, + ReadTimeout: cfg.timeout, // Time to read the request + WriteTimeout: cfg.timeout, // Time to write the entire response + IdleTimeout: cfg.timeout, // Time to keep idle connections open + ReadHeaderTimeout: time.Minute, // Time to read request headers } return func() error { @@ -191,6 +214,41 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Error("HTTP server stopped", "error", err) return commands.ExitCodeError(1) } + return nil }, nil } + +func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Prevent MIME type sniffing by browsers. This ensures that the browser + // does not interpret files as a different MIME type than declared. + w.Header().Set("X-Content-Type-Options", "nosniff") + + // Prevent the page from being embedded in an iframe. This mitigates + // clickjacking attacks by ensuring the page cannot be loaded in a frame. + w.Header().Set("X-Frame-Options", "DENY") + + // Control the amount of referrer information sent in the Referer header. + // 'no-referrer' ensures that no referrer information is sent, which + // enhances privacy and prevents leakage of sensitive URLs. + w.Header().Set("Referrer-Policy", "no-referrer") + + // In `strict` mode, prevent cross-site ressources forgery and enforce https + if strict { + // Define a Content Security Policy (CSP) to restrict the sources of + // scripts, styles, images, and other resources. This helps prevent + // cross-site scripting (XSS) and other code injection attacks. + // - 'self' allows resources from the same origin. + // - 'data:' allows inline images (e.g., base64-encoded images). + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'") + + // Enforce HTTPS by telling browsers to only access the site over HTTPS + // for a specified duration (1 year in this case). This also applies to + // subdomains and allows preloading into the browser's HSTS list. + w.Header().Set("Strict-Transport-Security", "max-age=31536000") + } + + next.ServeHTTP(w, r) + }) +} diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index c8d662ec3b5..be7c2c2a3a2 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -80,7 +80,8 @@ dev: # Go server in development mode dev.gnoweb: generate $(run_reflex) -s -r '.*\.(go|html)' -- \ - go run ../../cmd/gnoweb -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + go run ../../cmd/gnoweb -no-strict -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + 2>&1 | $(run_logname) gnoweb # Tailwind CSS in development mode diff --git a/gno.land/pkg/gnoweb/components/ui/icons.html b/gno.land/pkg/gnoweb/components/ui/icons.html index f1145d74359..61c11259817 100644 --- a/gno.land/pkg/gnoweb/components/ui/icons.html +++ b/gno.land/pkg/gnoweb/components/ui/icons.html @@ -1,5 +1,5 @@ {{ define "ui/icons" }} - +
\n\n" - out += gnoface.Render(strconv.Itoa(int(std.GetHeight()))) + out += gnoface.Render(strconv.Itoa(int(std.ChainHeight()))) out += "
\n\n" return out @@ -127,7 +127,7 @@ func renderGnoFace() string { func renderMillipede() string { out := "
\n\n" out += "Millipede\n\n" - out += "```\n" + millipede.Draw(int(std.GetHeight())%10+1) + "```\n" + out += "```\n" + millipede.Draw(int(std.ChainHeight())%10+1) + "```\n" out += "
\n\n" return out diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno index 2a9669c0b58..8a5a4135025 100644 --- a/examples/gno.land/r/matijamarjanovic/home/config.gno +++ b/examples/gno.land/r/matijamarjanovic/home/config.gno @@ -48,7 +48,7 @@ func SetBackup(newAddress std.Address) error { } func checkAuthorized() error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { return errorUnauthorized } @@ -57,7 +57,7 @@ func checkAuthorized() error { } func AssertAuthorized() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { panic(errorUnauthorized) } diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno index 3757324108a..133a5857f2b 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -71,21 +71,21 @@ func maxOfThree(a, b, c int64) int64 { } func VoteModern() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount modernVotes += votes updateCurrentTheme() } func VoteClassic() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount classicVotes += votes updateCurrentTheme() } func VoteMinimal() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount minimalVotes += votes updateCurrentTheme() @@ -106,10 +106,10 @@ func updateCurrentTheme() { func CollectBalance() { AssertAuthorized() - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) ownerAddr := Address() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } func Render(path string) string { diff --git a/examples/gno.land/r/matijamarjanovic/home/home_test.gno b/examples/gno.land/r/matijamarjanovic/home/home_test.gno index 8cc6e6e5608..10e2e6db6fc 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home_test.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home_test.gno @@ -11,7 +11,7 @@ import ( // Helper function to set up test environment func setupTest() { - std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + std.TestSetOriginCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) } func TestUpdatePFP(t *testing.T) { @@ -41,7 +41,7 @@ func TestVoteModern(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteModern() uassert.Equal(t, int64(75000000), modernVotes, "Modern votes should be calculated correctly") @@ -55,7 +55,7 @@ func TestVoteClassic(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteClassic() uassert.Equal(t, int64(75000000), classicVotes, "Classic votes should be calculated correctly") @@ -69,7 +69,7 @@ func TestVoteMinimal(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteMinimal() uassert.Equal(t, int64(75000000), minimalVotes, "Minimal votes should be calculated correctly") diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno index be9e9db6133..4930b0af209 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -43,22 +43,22 @@ const ( // Sign signs the guestbook, with the specified message. func Sign(message string) { - prev := std.PrevRealm() + prev := std.PreviousRealm() switch { case !prev.IsUser(): panic(errNotAUser) - case hasSigned.Has(prev.Addr().String()): + case hasSigned.Has(prev.Address().String()): panic(errAlreadySigned) } message = validateMessage(message) guestbook.Set(signatureID.Next().Binary(), Signature{ Message: message, - Author: prev.Addr(), + Author: prev.Address(), // NOTE: time.Now() will yield the "block time", which is deterministic. Time: time.Now(), }) - hasSigned.Set(prev.Addr().String(), struct{}{}) + hasSigned.Set(prev.Address().String(), struct{}{}) } func validateMessage(msg string) string { diff --git a/examples/gno.land/r/moul/config/config.gno b/examples/gno.land/r/moul/config/config.gno index a4f24411747..0302163c3c8 100644 --- a/examples/gno.land/r/moul/config/config.gno +++ b/examples/gno.land/r/moul/config/config.gno @@ -14,7 +14,7 @@ func UpdateAddr(newAddr std.Address) { } func AssertIsAdmin() { - if std.GetOrigCaller() != addr { + if std.OriginCaller() != addr { panic("restricted area") } } diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno index f471280d8ef..4ce99f21dfa 100644 --- a/examples/gno.land/r/moul/home/z2_filetest.gno +++ b/examples/gno.land/r/moul/home/z2_filetest.gno @@ -7,7 +7,7 @@ import ( ) func main() { - std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + std.TestSetOriginCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") home.AddTodo("aaa") home.AddTodo("bbb") home.AddTodo("ccc") @@ -62,10 +62,10 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | gno.land/r/moul/home | -// | `std.CurrentRealm().Addr()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | -// | `std.GetHeight()` | 123 | +// | `std.CurrentRealm().Address()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Address()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | +// | `std.ChainHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // // diff --git a/examples/gno.land/r/moul/microposts/realm.gno b/examples/gno.land/r/moul/microposts/realm.gno index a03b6dd958b..85155faf69a 100644 --- a/examples/gno.land/r/moul/microposts/realm.gno +++ b/examples/gno.land/r/moul/microposts/realm.gno @@ -11,7 +11,7 @@ var posts []*Post func CreatePost(text string) { posts = append(posts, &Post{ text: text, - author: std.PrevRealm().Addr(), // provided by env + author: std.PreviousRealm().Address(), // provided by env createdAt: time.Now(), }) } diff --git a/examples/gno.land/r/moul/present/present.gno b/examples/gno.land/r/moul/present/present.gno index b4f880318bf..fd08d53b1bc 100644 --- a/examples/gno.land/r/moul/present/present.gno +++ b/examples/gno.land/r/moul/present/present.gno @@ -107,7 +107,7 @@ func Set(slug, title, event, author, date, content string) string { Title: title, Event: event, Author: author, - Uploader: std.PrevRealm().Addr(), + Uploader: std.PreviousRealm().Address(), Date: parsedDate, Content: content, EditDate: time.Now(), @@ -128,7 +128,7 @@ func Delete(slug string) string { } // XXX: consider this: - // if entry.Obj.(*Presentation).Uploader != std.PrevRealm().Addr() { + // if entry.Obj.(*Presentation).Uploader != std.PreviousRealm().Address() { // return "401: unauthorized - only the uploader can delete their presentations" // } @@ -210,7 +210,7 @@ func (p *Presentation) LastSlide() string { out.WriteString(md.H1(p.Title)) out.WriteString(md.H2("Thank You!")) out.WriteString(md.Paragraph(p.Author)) - fullPath := "https://" + std.GetChainDomain() + localPath(p.Slug, nil) + fullPath := "https://" + std.ChainDomain() + localPath(p.Slug, nil) out.WriteString(md.Paragraph("🔗 " + md.Link(fullPath, fullPath))) // XXX: QRCode return out.String() diff --git a/examples/gno.land/r/n2p5/haystack/haystack_test.gno b/examples/gno.land/r/n2p5/haystack/haystack_test.gno index 52dadf8bf9e..2a25649ff5a 100644 --- a/examples/gno.land/r/n2p5/haystack/haystack_test.gno +++ b/examples/gno.land/r/n2p5/haystack/haystack_test.gno @@ -31,14 +31,14 @@ func TestHaystack(t *testing.T) { n2, _ := genNeedleHex(2) n3, _ := genNeedleHex(3) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.PanicsWithMessage(t, haystack.ErrorDuplicateNeedle.Error(), func() { Add(n1) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { Add(n2) }) urequire.NotPanics(t, func() { Add(n3) }) }) @@ -49,14 +49,14 @@ func TestHaystack(t *testing.T) { n1, h1 := genNeedleHex(4) _, h2 := genNeedleHex(5) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) diff --git a/examples/gno.land/r/n2p5/home/home.gno b/examples/gno.land/r/n2p5/home/home.gno index 69b82e86d68..d99ec4d3b7d 100644 --- a/examples/gno.land/r/n2p5/home/home.gno +++ b/examples/gno.land/r/n2p5/home/home.gno @@ -52,7 +52,7 @@ func Render(path string) string { // assertAdmin panics if the caller is not an admin as defined in the config realm. func assertAdmin() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !config.IsAdmin(caller) { panic("forbidden: must be admin") } diff --git a/examples/gno.land/r/n2p5/loci/loci.gno b/examples/gno.land/r/n2p5/loci/loci.gno index 36f282e729f..232de1e6459 100644 --- a/examples/gno.land/r/n2p5/loci/loci.gno +++ b/examples/gno.land/r/n2p5/loci/loci.gno @@ -23,7 +23,7 @@ func Set(value string) { panic(err) } store.Set(b) - std.Emit("SetValue", "ForAddr", string(std.PrevRealm().Addr())) + std.Emit("SetValue", "ForAddr", string(std.PreviousRealm().Address())) } // Get retrieves the value stored at the provided address and diff --git a/examples/gno.land/r/nemanya/config/config.gno b/examples/gno.land/r/nemanya/config/config.gno index 795e48c94c1..78fe329d5fe 100644 --- a/examples/gno.land/r/nemanya/config/config.gno +++ b/examples/gno.land/r/nemanya/config/config.gno @@ -52,7 +52,7 @@ func SetBackup(a std.Address) error { } func checkAuthorized() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == main || caller == backup if !isAuthorized { diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index 08e24baecfd..34545773f4d 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -137,7 +137,7 @@ func renderProjects(projectsMap map[string]Project, title string) string { } func UpdateLink(name, newURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -152,7 +152,7 @@ func UpdateLink(name, newURL string) { } func UpdateAboutMe(text string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -160,7 +160,7 @@ func UpdateAboutMe(text string) { } func AddGnoProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -174,7 +174,7 @@ func AddGnoProject(name, description, url, imageURL string) { } func DeleteGnoProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -186,7 +186,7 @@ func DeleteGnoProject(projectName string) { } func AddOtherProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -200,7 +200,7 @@ func AddOtherProject(name, description, url, imageURL string) { } func RemoveOtherProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -216,8 +216,8 @@ func isAuthorized(addr std.Address) bool { } func SponsorGnoProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -239,8 +239,8 @@ func SponsorGnoProject(projectName string) { } func SponsorOtherProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -262,12 +262,12 @@ func SponsorOtherProject(projectName string) { } func Withdraw() string { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } - banker := std.GetBanker(std.BankerTypeRealmSend) - realmAddress := std.GetOrigPkgAddr() + banker := std.NewBanker(std.BankerTypeRealmSend) + realmAddress := std.OriginPkgAddress() coins := banker.GetCoins(realmAddress) if len(coins) == 0 { diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno index b2384ba07f4..cddd99f947f 100644 --- a/examples/gno.land/r/stefann/fomo3d/fomo3d.gno +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno @@ -83,7 +83,7 @@ func StartGame() { } gameState.CurrentRound++ - gameState.StartBlock = std.GetHeight() + gameState.StartBlock = std.ChainHeight() gameState.EndBlock = gameState.StartBlock + TIME_EXTENSION // Initial 24h window gameState.LastKeyBlock = gameState.StartBlock gameState.Jackpot = gameState.NextPot @@ -109,13 +109,13 @@ func BuyKeys() { panic(ErrGameEnded.Error()) } - currentBlock := std.GetHeight() + currentBlock := std.ChainHeight() if currentBlock > gameState.EndBlock { panic(ErrGameTimeExpired.Error()) } // Get sent coins - sent := std.GetOrigSend() + sent := std.OriginSend() if len(sent) != 1 || sent[0].Denom != "ugnot" { panic(ErrInvalidPayment.Error()) } @@ -131,7 +131,7 @@ func BuyKeys() { excess := payment - actualCost // Update buyer's info - buyer := std.PrevRealm().Addr() + buyer := std.PreviousRealm().Address() var buyerInfo PlayerInfo if info, exists := players.Get(buyer.String()); exists { buyerInfo = info.(PlayerInfo) @@ -153,9 +153,9 @@ func BuyKeys() { // Return excess payment to buyer if any if excess > 0 { - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins( - std.CurrentRealm().Addr(), + std.CurrentRealm().Address(), buyer, std.NewCoins(std.NewCoin("ugnot", excess)), ) @@ -192,7 +192,7 @@ func BuyKeys() { // ClaimDividends allows players to withdraw their earned dividends func ClaimDividends() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() info, exists := players.Get(caller.String()) if !exists { @@ -209,9 +209,9 @@ func ClaimDividends() { playerInfo.Dividends = 0 players.Set(caller.String(), playerInfo) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins( - std.CurrentRealm().Addr(), + std.CurrentRealm().Address(), caller, std.NewCoins(std.NewCoin("ugnot", amount)), ) @@ -230,9 +230,9 @@ func ClaimOwnerFee() { amount := gameState.OwnerFee gameState.OwnerFee = 0 - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins( - std.CurrentRealm().Addr(), + std.CurrentRealm().Address(), Ownable.Owner(), std.NewCoins(std.NewCoin("ugnot", amount)), ) @@ -246,7 +246,7 @@ func EndGame() { panic(ErrGameEnded.Error()) } - currentBlock := std.GetHeight() + currentBlock := std.ChainHeight() if currentBlock <= gameState.EndBlock { panic(ErrGameNotInProgress.Error()) } @@ -258,9 +258,9 @@ func EndGame() { gameState.Ended = true // Send jackpot to winner - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins( - std.CurrentRealm().Addr(), + std.CurrentRealm().Address(), gameState.LastBuyer, std.NewCoins(std.NewCoin("ugnot", gameState.Jackpot)), ) diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno index 29f2a9b07a9..8fb4edf386b 100644 --- a/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno @@ -36,12 +36,12 @@ func TestOwnership(t *testing.T) { nonOwner := testutils.TestAddress("nonOwner") // Set up initial owner - std.TestSetOrigCaller(owner) - std.TestSetOrigPkgAddr(owner) + std.TestSetOriginCaller(owner) + std.TestSetOriginPkgAddress(owner) setupTestGame(t) // Transfer ownership to nonOwner first to test ownership functions - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) urequire.NotPanics(t, func() { Ownable.TransferOwnership(nonOwner) }) @@ -49,10 +49,10 @@ func TestOwnership(t *testing.T) { // Test fee accumulation StartGame() payment := MIN_KEY_PRICE * 10 - std.TestSetOrigCaller(owner) - std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) + std.TestSetOriginCaller(owner) + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) std.TestIssueCoins(owner, std.Coins{{"ugnot", payment}}) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) BuyKeys() // Verify fee accumulation @@ -61,20 +61,20 @@ func TestOwnership(t *testing.T) { urequire.Equal(t, expectedFees, fees) // Test unauthorized fee claim (using old owner) - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) urequire.PanicsWithMessage(t, "ownable: caller is not owner", ClaimOwnerFee) // Test authorized fee claim (using new owner) - std.TestSetOrigCaller(nonOwner) - initialBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) - std.TestIssueCoins(std.CurrentRealm().Addr(), std.Coins{{"ugnot", expectedFees}}) + std.TestSetOriginCaller(nonOwner) + initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + std.TestIssueCoins(std.CurrentRealm().Address(), std.Coins{{"ugnot", expectedFees}}) urequire.NotPanics(t, ClaimOwnerFee) // Verify fees were claimed _, feesAfter := GetOwnerInfo() urequire.Equal(t, int64(0), feesAfter) - finalBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + finalBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedFees, finalBalance.AmountOf("ugnot")) } @@ -94,23 +94,23 @@ func TestFullGameFlow(t *testing.T) { // Start game urequire.NotPanics(t, StartGame) urequire.Equal(t, false, gameState.Ended) - urequire.Equal(t, std.GetHeight(), gameState.StartBlock) + urequire.Equal(t, std.ChainHeight(), gameState.StartBlock) urequire.Equal(t, int64(1), gameState.CurrentRound) t.Run("buying keys", func(t *testing.T) { // Test insufficient payment - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) std.TestIssueCoins(player1, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) // Test successful key purchase payment := MIN_KEY_PRICE * 3 - std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) - currentBlock := std.GetHeight() + currentBlock := std.ChainHeight() urequire.NotPanics(t, BuyKeys) // Verify time extension @@ -139,10 +139,10 @@ func TestFullGameFlow(t *testing.T) { t.Run("dividend distribution and claiming", func(t *testing.T) { // Player 2 buys keys - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) payment := gameState.KeyPrice * 2 // Buy 2 keys using current keyPrice - std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) urequire.NotPanics(t, BuyKeys) // Check player1 received dividends @@ -155,15 +155,15 @@ func TestFullGameFlow(t *testing.T) { // Test claiming dividends { // Player1 claims dividends - std.TestSetOrigCaller(player1) - initialBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(player1) + std.TestSetOriginCaller(player1) + initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1) urequire.NotPanics(t, ClaimDividends) // Verify dividends were claimed _, dividendsAfter := GetPlayerInfo(player1.String()) urequire.Equal(t, int64(0), dividendsAfter) - lastBuyerBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(player1) + lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1) urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedDividends, lastBuyerBalance.AmountOf("ugnot")) } }) @@ -174,7 +174,7 @@ func TestFullGameFlow(t *testing.T) { // Skip to end of current time window currentEndBlock := gameState.EndBlock - std.TestSkipHeights(currentEndBlock - std.GetHeight() + 1) + std.TestSkipHeights(currentEndBlock - std.ChainHeight() + 1) // End game successfully urequire.NotPanics(t, EndGame) @@ -182,7 +182,7 @@ func TestFullGameFlow(t *testing.T) { urequire.Equal(t, int64(1), gameState.CurrentRound) // Verify winner received jackpot - lastBuyerBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(gameState.LastBuyer) + lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(gameState.LastBuyer) urequire.Equal(t, gameState.Jackpot, lastBuyerBalance.AmountOf("ugnot")) // Verify NFT was minted to winner @@ -226,7 +226,7 @@ func TestStartGame(t *testing.T) { // Test starting first game urequire.NotPanics(t, StartGame) urequire.Equal(t, false, gameState.Ended) - urequire.Equal(t, std.GetHeight(), gameState.StartBlock) + urequire.Equal(t, std.ChainHeight(), gameState.StartBlock) // Test cannot start while game in progress urequire.PanicsWithMessage(t, ErrGameInProgress.Error(), StartGame) @@ -237,30 +237,30 @@ func TestBuyKeys(t *testing.T) { StartGame() player := testutils.TestAddress("player") - std.TestSetOrigCaller(player) + std.TestSetOriginCaller(player) // Test invalid coin denomination std.TestIssueCoins(player, std.Coins{{"invalid", MIN_KEY_PRICE}}) - std.TestSetOrigSend(std.Coins{{"invalid", MIN_KEY_PRICE}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"invalid", MIN_KEY_PRICE}}) + std.TestSetOriginSend(std.Coins{{"invalid", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"invalid", MIN_KEY_PRICE}}) urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) // Test multiple coin types std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) // Test insufficient payment std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) // Test successful purchase std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) urequire.NotPanics(t, BuyKeys) } @@ -269,26 +269,26 @@ func TestClaimDividends(t *testing.T) { StartGame() player := testutils.TestAddress("player") - std.TestSetOrigCaller(player) + std.TestSetOriginCaller(player) // Test claiming with no dividends urequire.PanicsWithMessage(t, ErrNoDividendsToClaim.Error(), ClaimDividends) // Setup player with dividends std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}}) BuyKeys() // Have another player buy to generate dividends player2 := testutils.TestAddress("player2") - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) std.TestIssueCoins(player2, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) - std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) - std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) BuyKeys() // Test successful claim - std.TestSetOrigCaller(player) + std.TestSetOriginCaller(player) urequire.NotPanics(t, ClaimDividends) } diff --git a/examples/gno.land/r/stefann/fomo3d/render.gno b/examples/gno.land/r/stefann/fomo3d/render.gno index ba0c7b8f147..fb5fe5fe8f8 100644 --- a/examples/gno.land/r/stefann/fomo3d/render.gno +++ b/examples/gno.land/r/stefann/fomo3d/render.gno @@ -53,7 +53,7 @@ func RenderHome() string { } else { builder.WriteString("🟢 **Game Status:** Active\n\n") builder.WriteString(ufmt.Sprintf("🔄 **Round:** %d\n\n", gameState.CurrentRound)) - builder.WriteString(ufmt.Sprintf("⏱️ **Time Remaining:** %d blocks\n\n", gameState.EndBlock-std.GetHeight())) + builder.WriteString(ufmt.Sprintf("⏱️ **Time Remaining:** %d blocks\n\n", gameState.EndBlock-std.ChainHeight())) } builder.WriteString(ufmt.Sprintf("💰 **Jackpot:** %d ugnot\n\n", gameState.Jackpot)) builder.WriteString(ufmt.Sprintf("🔑 **Key Price:** %d ugnot\n\n", gameState.KeyPrice)) diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index f54721ce37c..53aa0fa5678 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -127,8 +127,8 @@ func UpdateMaxSponsors(newMax int) { } func Donate() { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -146,9 +146,9 @@ func Donate() { travel.currentCityIndex++ sponsorship.DonationsCount++ - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) ownerAddr := registry.MainAddr() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } type SponsorSlice []Sponsor diff --git a/examples/gno.land/r/stefann/home/home_test.gno b/examples/gno.land/r/stefann/home/home_test.gno index b8ea88670a6..8fa8337dab4 100644 --- a/examples/gno.land/r/stefann/home/home_test.gno +++ b/examples/gno.land/r/stefann/home/home_test.gno @@ -11,7 +11,7 @@ import ( func TestUpdateAboutMe(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -32,7 +32,7 @@ func TestUpdateAboutMe(t *testing.T) { func TestUpdateCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -54,7 +54,7 @@ func TestUpdateCities(t *testing.T) { func TestUpdateJarLink(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.jarLink = "" @@ -67,7 +67,7 @@ func TestUpdateJarLink(t *testing.T) { func TestUpdateMaxSponsors(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) sponsorship.maxSponsors = 0 @@ -87,7 +87,7 @@ func TestUpdateMaxSponsors(t *testing.T) { func TestAddCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -115,7 +115,7 @@ func TestAddCities(t *testing.T) { func TestAddAboutMeRows(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -140,7 +140,7 @@ func TestAddAboutMeRows(t *testing.T) { func TestDonate(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.DonationsCount = 0 @@ -148,7 +148,7 @@ func TestDonate(t *testing.T) { travel.currentCityIndex = 0 coinsSent := std.NewCoins(std.NewCoin("ugnot", 500)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists := sponsorship.sponsors.Get(string(user)) @@ -173,7 +173,7 @@ func TestDonate(t *testing.T) { } coinsSent = std.NewCoins(std.NewCoin("ugnot", 300)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists = sponsorship.sponsors.Get(string(user)) @@ -196,7 +196,7 @@ func TestDonate(t *testing.T) { func TestGetTopSponsors(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 @@ -227,7 +227,7 @@ func TestGetTopSponsors(t *testing.T) { func TestGetTotalDonations(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 diff --git a/examples/gno.land/r/sys/validators/v2/validators.gno b/examples/gno.land/r/sys/validators/v2/validators.gno index bf42ece4990..538525b754f 100644 --- a/examples/gno.land/r/sys/validators/v2/validators.gno +++ b/examples/gno.land/r/sys/validators/v2/validators.gno @@ -30,7 +30,7 @@ func addValidator(validator validators.Validator) { // Validator added, note the change ch := change{ - blockNum: std.GetHeight(), + blockNum: std.ChainHeight(), validator: val, } @@ -50,7 +50,7 @@ func removeValidator(address std.Address) { // Validator removed, note the change ch := change{ - blockNum: std.GetHeight(), + blockNum: std.ChainHeight(), validator: validators.Validator{ Address: val.Address, PubKey: val.PubKey, diff --git a/examples/gno.land/r/sys/validators/v2/validators_test.gno b/examples/gno.land/r/sys/validators/v2/validators_test.gno index 177d84144cb..dc159a3b957 100644 --- a/examples/gno.land/r/sys/validators/v2/validators_test.gno +++ b/examples/gno.land/r/sys/validators/v2/validators_test.gno @@ -67,7 +67,7 @@ func TestValidators_AddRemove(t *testing.T) { } // Save the beginning height for the removal - initialRemoveHeight := std.GetHeight() + initialRemoveHeight := std.ChainHeight() // Clear any changes changes = avl.NewTree() diff --git a/examples/gno.land/r/ursulovic/home/home.gno b/examples/gno.land/r/ursulovic/home/home.gno index cc420df5e6e..be96ab3f913 100644 --- a/examples/gno.land/r/ursulovic/home/home.gno +++ b/examples/gno.land/r/ursulovic/home/home.gno @@ -74,7 +74,7 @@ func UpdateSelectedImage(url string) { panic("Url is not valid!") } - sentCoins := std.GetOrigSend() + sentCoins := std.OriginSend() if len(sentCoins) != 1 && sentCoins.AmountOf("ugnot") == imageUpdatePrice { panic("Please send exactly " + strconv.Itoa(int(imageUpdatePrice)) + " ugnot") diff --git a/examples/gno.land/r/ursulovic/home/home_test.gno b/examples/gno.land/r/ursulovic/home/home_test.gno index ff3f763d62a..eea71c030e6 100644 --- a/examples/gno.land/r/ursulovic/home/home_test.gno +++ b/examples/gno.land/r/ursulovic/home/home_test.gno @@ -9,7 +9,7 @@ import ( func TestUpdateGithubUrl(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newUrl := "https://github.com/example" @@ -22,7 +22,7 @@ func TestUpdateGithubUrl(t *testing.T) { func TestUpdateLinkedinUrl(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newUrl := "https://www.linkedin.com/in/example" @@ -35,7 +35,7 @@ func TestUpdateLinkedinUrl(t *testing.T) { func TestUpdateAboutMe(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newAboutMe := "This is new description!" @@ -48,12 +48,12 @@ func TestUpdateAboutMe(t *testing.T) { func TestUpdateSelectedImage(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) validImageUrl := "https://i.ibb.co/hLtmnX0/beautiful-rain-forest-ang-ka-nature-trail-doi-inthanon-national-park-thailand-36703721.webp" coinsSent := std.NewCoins(std.NewCoin("ugnot", 5000000)) // Update to match the price expected by your function - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) UpdateSelectedImage(validImageUrl) @@ -72,7 +72,7 @@ func TestUpdateSelectedImage(t *testing.T) { UpdateSelectedImage(invalidImageUrl) invalidCoins := std.NewCoins(std.NewCoin("ugnot", 1000000)) - std.TestSetOrigSend(invalidCoins, std.NewCoins()) + std.TestSetOriginSend(invalidCoins, std.NewCoins()) defer func() { if r := recover(); r == nil { @@ -85,7 +85,7 @@ func TestUpdateSelectedImage(t *testing.T) { func TestUpdateImagePrice(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) var newImageUpdatePrice int64 = 3000000 diff --git a/examples/gno.land/r/ursulovic/registry/registry.gno b/examples/gno.land/r/ursulovic/registry/registry.gno index 0bbd6c80df5..f873bc6a120 100644 --- a/examples/gno.land/r/ursulovic/registry/registry.gno +++ b/examples/gno.land/r/ursulovic/registry/registry.gno @@ -50,7 +50,7 @@ func SetBackupAddress(addr std.Address) error { // It will stay here for now, might be useful later func assertAuthorized() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == mainAddress || caller == backupAddress if !isAuthorized { diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno index c73e99cc583..92b277d1b9c 100644 --- a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno @@ -25,7 +25,7 @@ func Register(fn RenderFn) { } proxyPath := std.CurrentRealm().PkgPath() - callerPath := std.PrevRealm().PkgPath() + callerPath := std.PreviousRealm().PkgPath() if !strings.HasPrefix(callerPath, proxyPath+"/") { panic("caller realm path must start with " + proxyPath) } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index 1298b2539be..170aba1c71a 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -24,9 +24,9 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index bf30ee1acab..80723ad01f1 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -28,9 +28,9 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno index 0a610b0b196..d71f5ec3db5 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno @@ -23,7 +23,7 @@ func SetCurrentImpl(pkgpath string) { } func assertIsCurrentImpl() { - if std.PrevRealm().PkgPath() != currentImpl { + if std.PreviousRealm().PkgPath() != currentImpl { panic("unauthorized") } } diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index 1ec801bb971..67311d401b3 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -36,7 +36,7 @@ func (c *Committee) DismissMembers(members []std.Address) []std.Address { func (c *Committee) AddCategory(name string, criteria []string) bool { // TODO error handling - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } category := NewCategory(name, criteria) @@ -45,7 +45,7 @@ func (c *Committee) AddCategory(name string, criteria []string) bool { } func (c *Committee) ApproveCategory(name string, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -58,8 +58,8 @@ func (c *Committee) ApproveCategory(name string, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) - category.votes.Set(std.GetOrigCaller().String(), vote) + vote := NewVote(std.OriginCaller(), option) + category.votes.Set(std.OriginCaller().String(), vote) category.Tally() // TODO Add threshold factor for a category approval @@ -81,7 +81,7 @@ func (c *Committee) ApproveCategory(name string, option string) bool { // TODO error handling func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (contributionId int, ok bool) { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return -1, false } // Check the category of the PR matches a category this committee evaluates @@ -95,7 +95,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c // TODO error handling func (c *Committee) ApproveContribution(id int, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -109,7 +109,7 @@ func (c *Committee) ApproveContribution(id int, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) + vote := NewVote(std.OriginCaller(), option) contribution.votes = append(contribution.votes, vote) contribution.Tally() diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno index 8a3d16fd7f7..39e7fb6cabf 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno @@ -36,7 +36,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { c.DesignateMembers([]std.Address{member}) t.Run("Add First Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category, criteria) value, exists := c.categories.Get(category) if !exists { @@ -49,7 +49,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Add Second Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category2, criteria2) value2, exists2 := c.categories.Get(category2) if !exists2 { @@ -62,7 +62,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Approve First Committee Category", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) approved := c.ApproveCategory(category, VoteYes) if !approved { value, exists := c.categories.Get(category) diff --git a/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno index 52670a5626b..eff6e669be0 100644 --- a/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno +++ b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno @@ -7,7 +7,7 @@ import ( ) func TestSkipHeights(t *testing.T) { - oldHeight := std.GetHeight() + oldHeight := std.ChainHeight() shouldEQ(t, oldHeight, 123) oldNow := time.Now().Unix() @@ -16,7 +16,7 @@ func TestSkipHeights(t *testing.T) { // skip 3 blocks == 15 seconds std.TestSkipHeights(3) - shouldEQ(t, std.GetHeight()-oldHeight, 3) + shouldEQ(t, std.ChainHeight()-oldHeight, 3) shouldEQ(t, time.Now().Unix()-oldNow, 15) } diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 9027d51c0ac..24c42220055 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -10,7 +10,7 @@ {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.NewBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.OriginPkgAddress()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.NewBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.OriginPkgAddress())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index 4377e10a575..ecfab2ea651 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -34,7 +34,7 @@ func TransferByName(name string, to string, amount uint64) string { if pair.name != name { continue } - if std.CurrentRealm().Addr().String() != pair.cb(std.Address(to), amount) { + if std.CurrentRealm().Address().String() != pair.cb(std.Address(to), amount) { return "invalid address, ownership issue :(" } return "same address, success!" @@ -58,6 +58,6 @@ package foo20 import "std" func Transfer(to std.Address, amount uint64) string { - println("transfer from=" + std.PrevRealm().Addr().String() + " to=" + to.String() + " some-amount") - return std.PrevRealm().Addr().String() + println("transfer from=" + std.PreviousRealm().Address().String() + " to=" + to.String() + " some-amount") + return std.PreviousRealm().Address().String() } diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 45101b74634..0b90188fd59 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -69,7 +69,7 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Mint(users.Resolve(to), tid) if err != nil { @@ -78,7 +78,7 @@ func Mint(to pusers.AddressOrName, tid grc721.TokenID) { } func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/gno.land/pkg/integration/testdata/initctx.txtar b/gno.land/pkg/integration/testdata/initctx.txtar index 9210268e66f..82e27dba642 100644 --- a/gno.land/pkg/integration/testdata/initctx.txtar +++ b/gno.land/pkg/integration/testdata/initctx.txtar @@ -18,8 +18,8 @@ var orig = std.Address("orig") var prev = std.Address("prev") func init() { - orig = std.GetOrigCaller() - prev = std.PrevRealm().Addr() + orig = std.OriginCaller() + prev = std.PreviousRealm().Address() } func Render(addr string) string { diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 71cd19e7ed7..284ea951e5b 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -49,7 +49,7 @@ import ( ) func ProxyWrap() { - sent := std.GetOrigSend() + sent := std.OriginSend() ugnotSent := uint64(sent.AmountOf("ugnot")) if ugnotSent == 0 { @@ -58,12 +58,12 @@ func ProxyWrap() { // WRAP IT wugnotAddr := std.DerivePkgAddr("gno.land/r/demo/wugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins(std.CurrentRealm().Address(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER - wugnot.Transfer(std.GetOrigCaller(), ugnotSent) + wugnot.Transfer(std.OriginCaller(), ugnotSent) } func ProxyUnwrap(wugnotAmount uint64) { @@ -72,12 +72,12 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(std.GetOrigCaller(), std.CurrentRealm().Addr(), wugnotAmount) + wugnot.TransferFrom(std.OriginCaller(), std.CurrentRealm().Address(), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) // SEND GNOT: PROXY_WUGNOT -> USER - banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), std.GetOrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins(std.CurrentRealm().Address(), std.OriginCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) } diff --git a/gno.land/pkg/integration/testdata/issue_2283.txtar b/gno.land/pkg/integration/testdata/issue_2283.txtar index 653a4dd79b0..222ca86f2fa 100644 --- a/gno.land/pkg/integration/testdata/issue_2283.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283.txtar @@ -46,7 +46,7 @@ import ( }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.NewBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -66,11 +66,11 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.NewBanker(std.BankerTypeOriginSend)\n\t// banker := std.NewBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.OriginPkgAddress()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Address()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Address()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Address() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Address()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Address()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Address() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar index 95bd48c0144..c5f1cf0ed6d 100644 --- a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar @@ -40,7 +40,7 @@ stdout OK! }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.NewBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -60,11 +60,11 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.NewBanker(std.BankerTypeOriginSend)\n\t// banker := std.NewBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.OriginPkgAddress()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Address()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Address()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Address() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Address()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Address()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Address() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 31f0ca336ba..73340d9b44d 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -1,4 +1,4 @@ -# This tests ensure the consistency of the std.PrevRealm function, in the +# This tests ensure the consistency of the std.PreviousRealm function, in the # following situations: # # @@ -16,8 +16,8 @@ # | 10 | | | myrlm.B() | r/foo | # | 11 | | through /p/demo/bar | bar.A() | user address | # | 12 | | | bar.B() | user address | -# | 13 | MsgCall | wallet direct | std.PrevRealm() | user address | -# | 14 | MsgRun | wallet direct | std.PrevRealm() | user address | +# | 13 | MsgCall | wallet direct | std.PreviousRealm() | user address | +# | 14 | MsgRun | wallet direct | std.PreviousRealm() | user address | # Init ## deploy myrlm @@ -82,11 +82,11 @@ stdout ${test1_user_addr} gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno stdout ${test1_user_addr} -## 13. MsgCall -> std.PrevRealm(): user address -## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 +## 13. MsgCall -> std.PreviousRealm(): user address +## gnokey maketx call -pkgpath std -func PreviousRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout ${test1_user_addr} -## 14. MsgRun -> std.PrevRealm(): user address +## 14. MsgRun -> std.PreviousRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno stdout ${test1_user_addr} @@ -96,7 +96,7 @@ package myrlm import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -120,7 +120,7 @@ package bar import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -180,5 +180,5 @@ package main import "std" func main() { - println(std.PrevRealm().Addr().String()) + println(std.PreviousRealm().Address().String()) } diff --git a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar index a55604267ae..a95f7e6f93d 100644 --- a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar @@ -79,12 +79,12 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } @@ -97,12 +97,12 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } @@ -114,11 +114,11 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, denom, amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, denom, amount) } diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index a4f39242e3b..4c4157cb94f 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -125,8 +125,8 @@ import "std" import "time" var _ = time.RFC3339 -func caller() std.Address { return std.GetOrigCaller() } -var GetHeight = std.GetHeight +func caller() std.Address { return std.OriginCaller() } +var GetHeight = std.ChainHeight var sl = []int{1,2,3,4,5} func fn() func(string) string { return Echo } type myStruct struct{a int} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 1ecf280dc59..db2574502f1 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -266,13 +266,13 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add // Parse and run the files, construct *PV. pkgAddr := gno.DerivePkgAddr(pkgPath) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), // XXX: should we remove the banker ? Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), @@ -371,17 +371,17 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSend: deposit, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSend: deposit, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } // Parse and run the files, construct *PV. m2 := gno.NewMachineWithOptions( @@ -462,17 +462,17 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { // could it be safely partially memoized? chainDomain := vm.getChainDomainParam(ctx) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } // Construct machine and evaluate. m := gno.NewMachineWithOptions( @@ -594,17 +594,17 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } buf := new(bytes.Buffer) @@ -781,13 +781,13 @@ func (vm *VMKeeper) queryEvalInternal(ctx sdk.Context, pkgPath string, expr stri ChainDomain: chainDomain, Height: ctx.BlockHeight(), Timestamp: ctx.BlockTime().Unix(), - // OrigCaller: caller, - // OrigSend: send, - // OrigSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + // OriginCaller: caller, + // OriginSend: send, + // OriginSendSpent: nil, + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( gno.MachineOptions{ diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index f8144988c44..243b2712632 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -106,7 +106,7 @@ func Echo() string {return "hello world"}`, } // Sending total send amount succeeds. -func TestVMKeeperOrigSend1(t *testing.T) { +func TestVMKeeperOriginSend1(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -128,10 +128,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -151,7 +151,7 @@ func Echo(msg string) string { } // Sending too much fails -func TestVMKeeperOrigSend2(t *testing.T) { +func TestVMKeeperOriginSend2(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -172,14 +172,14 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -205,7 +205,7 @@ func GetAdmin() string { } // Sending more than tx send fails. -func TestVMKeeperOrigSend3(t *testing.T) { +func TestVMKeeperOriginSend3(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -227,10 +227,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -271,10 +271,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -315,10 +315,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -390,8 +390,8 @@ func Do() string { assert.Equal(t, int64(1337), bar) } -// Assign admin as OrigCaller on deploying the package. -func TestVMKeeperOrigCallerInit(t *testing.T) { +// Assign admin as OriginCaller on deploying the package. +func TestVMKeeperOriginCallerInit(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -412,14 +412,14 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -500,7 +500,7 @@ package main import "std" func main() { - addr := std.GetOrigCaller() + addr := std.OriginCaller() println("hello world!", addr) } `}, diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 5e1f1a5ff0f..31907052225 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -36,7 +36,7 @@ package main import "std" func hello() { - std.GetChainID() + std.ChainID() } -- main.gno.gen.go.golden -- @@ -61,5 +61,5 @@ package main import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() { - std.GetChainID(nil) + std.ChainID(nil) } diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 0a2429fb1c6..2fbf34ed1b1 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -275,7 +275,7 @@ func Go2GnoNativeValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { return go2GnoValue(alloc, rv) } -// NOTE: used by imports_test.go TestSetOrigCaller. +// NOTE: used by imports_test.go TestSetOriginCaller. func Gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { return gno2GoValue(tv, rv) } diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 92a867e1886..71ec3bb2568 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -51,17 +51,17 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { }, } ctx := stdlibs.ExecContext{ - ChainID: "dev", - ChainDomain: "tests.gno.land", - Height: DefaultHeight, - Timestamp: DefaultTimestamp, - OrigCaller: DefaultCaller, - OrigPkgAddr: pkgAddr.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - Banker: banker, - Params: newTestParams(), - EventLogger: sdk.NewEventLogger(), + ChainID: "dev", + ChainDomain: "tests.gno.land", + Height: DefaultHeight, + Timestamp: DefaultTimestamp, + OriginCaller: DefaultCaller, + OriginPkgAddr: pkgAddr.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + Banker: banker, + Params: newTestParams(), + EventLogger: sdk.NewEventLogger(), } return &teststd.TestExecContext{ ExecContext: ctx, diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 63b77e49446..886253cdeb3 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -344,13 +344,13 @@ func Float32bits(i float32) uint32 func testfunc() { println(Float32bits(3.14159)) - std.GetChainID() + std.ChainID() } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.GetChainID() + std.ChainID() } `, expectedOutput: ` @@ -363,13 +363,13 @@ import "github.com/gnolang/gno/gnovm/stdlibs/std" func testfunc() { println(Float32bits(3.14159)) - std.GetChainID(nil) + std.ChainID(nil) } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.GetChainID(nil) + std.ChainID(nil) } `, expectedImports: []*ast.ImportSpec{ @@ -388,12 +388,12 @@ func otherFunc() { source: ` package std -func GetChainID() -func origCaller() string +func ChainID() +func originCaller() string func testfunc() { - GetChainID() - println(origCaller()) + ChainID() + println(originCaller()) } `, expectedOutput: ` @@ -403,8 +403,8 @@ func testfunc() { package std func testfunc() { - GetChainID(nil) - println(X_origCaller(nil)) + ChainID(nil) + println(X_originCaller(nil)) } `, }, diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 01e5d1831dd..25d9b974e85 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -430,14 +430,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "GetChainID", + "ChainID", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetChainID( + r0 := libs_std.ChainID( m, ) @@ -450,14 +450,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "GetChainDomain", + "ChainDomain", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetChainDomain( + r0 := libs_std.ChainDomain( m, ) @@ -470,14 +470,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "GetHeight", + "ChainHeight", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetHeight( + r0 := libs_std.ChainHeight( m, ) @@ -490,7 +490,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origSend", + "originSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]string")}, @@ -498,7 +498,7 @@ var nativeFuncs = [...]NativeFunc{ }, true, func(m *gno.Machine) { - r0, r1 := libs_std.X_origSend( + r0, r1 := libs_std.X_originSend( m, ) @@ -516,14 +516,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origCaller", + "originCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origCaller( + r0 := libs_std.X_originCaller( m, ) @@ -536,14 +536,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origPkgAddr", + "originPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origPkgAddr( + r0 := libs_std.X_originPkgAddr( m, ) diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 4c20e8d4b61..9db914e6a4f 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -5,7 +5,7 @@ import ( "strings" ) -// Realm functions can call std.GetBanker(options) to get +// Realm functions can call std.NewBanker(options) to get // a banker instance. Banker objects cannot be persisted, // but can be passed onto other functions to be transacted // on. A banker instance can be passed onto other realm @@ -28,7 +28,7 @@ type Banker interface { } // BankerType represents the "permission level" requested for a banker, -// retrievable through [GetBanker]. +// retrievable through [NewBanker]. type BankerType uint8 // Available types of banker. @@ -36,7 +36,7 @@ const ( // Can only read state. BankerTypeReadonly BankerType = iota // Can only send from tx send. - BankerTypeOrigSend + BankerTypeOriginSend // Can send from all realm coins. BankerTypeRealmSend // Can issue and remove realm coins. @@ -49,8 +49,8 @@ func (b BankerType) String() string { switch b { case BankerTypeReadonly: return "BankerTypeReadonly" - case BankerTypeOrigSend: - return "BankerTypeOrigSend" + case BankerTypeOriginSend: + return "BankerTypeOriginSend" case BankerTypeRealmSend: return "BankerTypeRealmSend" case BankerTypeRealmIssue: @@ -63,22 +63,22 @@ func (b BankerType) String() string { //---------------------------------------- // adapter for native banker -// GetBanker returns a new Banker, with its capabilities matching the given +// NewBanker returns a new Banker, with its capabilities matching the given // [BankerType]. -func GetBanker(bt BankerType) Banker { +func NewBanker(bt BankerType) Banker { assertCallerIsRealm() if bt >= maxBanker { panic("invalid banker type") } var pkgAddr Address - if bt == BankerTypeOrigSend { - pkgAddr = GetOrigPkgAddr() - if pkgAddr != CurrentRealm().Addr() { - panic("banker with type BankerTypeOrigSend can only be instantiated by the origin package") + if bt == BankerTypeOriginSend { + pkgAddr = OriginPkgAddress() + if pkgAddr != CurrentRealm().Address() { + panic("banker with type BankerTypeOriginSend can only be instantiated by the origin package") } } else if bt == BankerTypeRealmSend || bt == BankerTypeRealmIssue { - pkgAddr = CurrentRealm().Addr() + pkgAddr = CurrentRealm().Address() } return banker{ bt, diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index c57ba8529ed..ee02b0cfad0 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -25,7 +25,7 @@ const ( // Can only read state. btReadonly uint8 = iota //nolint // Can only send from tx send. - btOrigSend + btOriginSend // Can send from all realm coins. btRealmSend // Can issue and remove realm coins. @@ -45,19 +45,19 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) switch bt { - case btOrigSend: + case btOriginSend: // indirection allows us to "commit" in a second phase - spent := (*ctx.OrigSendSpent).Add(amt) - if !ctx.OrigSend.IsAllGTE(spent) { + spent := (*ctx.OriginSendSpent).Add(amt) + if !ctx.OriginSend.IsAllGTE(spent) { m.Panic(typedString( fmt.Sprintf( `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, ctx.OrigSend, *ctx.OrigSendSpent), + amt, ctx.OriginSend, *ctx.OriginSendSpent), )) return } ctx.Banker.SendCoins(from, to, amt) - *ctx.OrigSendSpent = spent + *ctx.OriginSendSpent = spent case btRealmSend, btRealmIssue: ctx.Banker.SendCoins(from, to, amt) default: diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index a8ef500c346..37d246b0f1a 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -8,18 +8,18 @@ import ( ) type ExecContext struct { - ChainID string - ChainDomain string - Height int64 - Timestamp int64 // seconds - TimestampNano int64 // nanoseconds, only used for testing. - OrigCaller crypto.Bech32Address - OrigPkgAddr crypto.Bech32Address - OrigSend std.Coins - OrigSendSpent *std.Coins // mutable - Banker BankerInterface - Params ParamsInterface - EventLogger *sdk.EventLogger + ChainID string + ChainDomain string + Height int64 + Timestamp int64 // seconds + TimestampNano int64 // nanoseconds, only used for testing. + OriginCaller crypto.Bech32Address + OriginPkgAddr crypto.Bech32Address + OriginSend std.Coins + OriginSendSpent *std.Coins // mutable + Banker BankerInterface + Params ParamsInterface + EventLogger *sdk.EventLogger } // GetContext returns the execution context. diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 10b00d62cfc..6e8f00ee3e8 100644 --- a/gnovm/stdlibs/std/emit_event.go +++ b/gnovm/stdlibs/std/emit_event.go @@ -18,7 +18,7 @@ func X_emit(m *gno.Machine, typ string, attrs []string) { } _, pkgPath := currentRealm(m) - fnIdent := getPrevFunctionNameFromTarget(m, "Emit") + fnIdent := getPreviousFunctionNameFromTarget(m, "Emit") ctx := GetContext(m) diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno index 1709f8cb8b5..bcffa458043 100644 --- a/gnovm/stdlibs/std/frame.gno +++ b/gnovm/stdlibs/std/frame.gno @@ -5,7 +5,7 @@ type Realm struct { pkgPath string } -func (r Realm) Addr() Address { +func (r Realm) Address() Address { return r.addr } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 2baa1f92f48..1b15bc21c29 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -5,13 +5,13 @@ package std // is invoked by another method (even from the same realm or package). // It also panic every time when the transaction is broadcasted via // MsgRun. -func AssertOriginCall() // injected -func GetChainID() string // injected -func GetChainDomain() string // injected -func GetHeight() int64 // injected +func AssertOriginCall() // injected +func ChainID() string // injected +func ChainDomain() string // injected +func ChainHeight() int64 // injected -func GetOrigSend() Coins { - den, amt := origSend() +func OriginSend() Coins { + den, amt := originSend() coins := make(Coins, len(den)) for i := range coins { coins[i] = Coin{Denom: den[i], Amount: amt[i]} @@ -19,8 +19,8 @@ func GetOrigSend() Coins { return coins } -func GetOrigCaller() Address { - return Address(origCaller()) +func OriginCaller() Address { + return Address(originCaller()) } func CurrentRealm() Realm { @@ -28,23 +28,23 @@ func CurrentRealm() Realm { return Realm{Address(addr), path} } -func PrevRealm() Realm { +func PreviousRealm() Realm { addr, path := getRealm(1) return Realm{Address(addr), path} } -func GetOrigPkgAddr() Address { - return Address(origPkgAddr()) +func OriginPkgAddress() Address { + return Address(originPkgAddr()) } -func GetCallerAt(n int) Address { +func CallerAt(n int) Address { return Address(callerAt(n)) } // Variations which don't use named types. -func origSend() (denoms []string, amounts []int64) -func origCaller() string -func origPkgAddr() string +func originSend() (denoms []string, amounts []int64) +func originCaller() string +func originPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) func assertCallerIsRealm() diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 68f4542f689..b226ca03afa 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -22,29 +22,29 @@ func isOriginCall(m *gno.Machine) bool { return n <= 2 && isMsgCall } -func GetChainID(m *gno.Machine) string { +func ChainID(m *gno.Machine) string { return GetContext(m).ChainID } -func GetChainDomain(m *gno.Machine) string { +func ChainDomain(m *gno.Machine) string { return GetContext(m).ChainDomain } -func GetHeight(m *gno.Machine) int64 { +func ChainHeight(m *gno.Machine) int64 { return GetContext(m).Height } -// getPrevFunctionNameFromTarget returns the last called function name (identifier) from the call stack. -func getPrevFunctionNameFromTarget(m *gno.Machine, targetFunc string) string { - targetIndex := findTargetFuncIndex(m, targetFunc) +// getPreviousFunctionNameFromTarget returns the last called function name (identifier) from the call stack. +func getPreviousFunctionNameFromTarget(m *gno.Machine, targetFunc string) string { + targetIndex := findTargetFunctionIndex(m, targetFunc) if targetIndex == -1 { return "" } - return findPrevFuncName(m, targetIndex) + return findPreviousFunctionName(m, targetIndex) } -// findTargetFuncIndex finds and returns the index of the target function in the call stack. -func findTargetFuncIndex(m *gno.Machine, targetFunc string) int { +// findTargetFunctionIndex finds and returns the index of the target function in the call stack. +func findTargetFunctionIndex(m *gno.Machine, targetFunc string) int { for i := len(m.Frames) - 1; i >= 0; i-- { currFunc := m.Frames[i].Func if currFunc != nil && currFunc.Name == gno.Name(targetFunc) { @@ -54,8 +54,8 @@ func findTargetFuncIndex(m *gno.Machine, targetFunc string) int { return -1 } -// findPrevFuncName returns the function name before the given index in the call stack. -func findPrevFuncName(m *gno.Machine, targetIndex int) string { +// findPreviousFunctionName returns the function name before the given index in the call stack. +func findPreviousFunctionName(m *gno.Machine, targetIndex int) string { for i := targetIndex - 1; i >= 0; i-- { currFunc := m.Frames[i].Func if currFunc != nil { @@ -66,25 +66,25 @@ func findPrevFuncName(m *gno.Machine, targetIndex int) string { panic("function name not found") } -func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { - os := GetContext(m).OrigSend +func X_originSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := GetContext(m).OriginSend return ExpandCoins(os) } -func X_origCaller(m *gno.Machine) string { - return string(GetContext(m).OrigCaller) +func X_originCaller(m *gno.Machine) string { + return string(GetContext(m).OriginCaller) } -func X_origPkgAddr(m *gno.Machine) string { - return string(GetContext(m).OrigPkgAddr) +func X_originPkgAddr(m *gno.Machine) string { + return string(GetContext(m).OriginPkgAddr) } func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage @@ -94,9 +94,9 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames() { - // This makes it consistent with GetOrigCaller. + // This makes it consistent with OriginCaller. ctx := GetContext(m) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } @@ -131,8 +131,8 @@ func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } // currentRealm retrieves the current realm's address and pkgPath. diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index acbd22055d6..e51badd4e2e 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -9,11 +9,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" ) -func TestPrevRealmIsOrigin(t *testing.T) { +func TestPreviousRealmIsOrigin(t *testing.T) { var ( user = gno.DerivePkgAddr("user1.gno").Bech32() ctx = ExecContext{ - OrigCaller: user, + OriginCaller: user, } msgCallFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "main"}} msgRunFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/g1337/run"}} diff --git a/gnovm/tests/files/std10.gno b/gnovm/tests/files/std10.gno index 7caf534c5ca..b549387feaa 100644 --- a/gnovm/tests/files/std10.gno +++ b/gnovm/tests/files/std10.gno @@ -7,8 +7,8 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(0) + std.CallerAt(0) } // Output: -// GetCallerAt requires positive arg +// CallerAt requires positive arg diff --git a/gnovm/tests/files/std11.gno b/gnovm/tests/files/std11.gno index ce02fa11ec3..70668fe443b 100644 --- a/gnovm/tests/files/std11.gno +++ b/gnovm/tests/files/std11.gno @@ -7,7 +7,7 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(42) + std.CallerAt(42) } // Output: diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2.gno index fe218f8b34a..71865fdf97d 100644 --- a/gnovm/tests/files/std2.gno +++ b/gnovm/tests/files/std2.gno @@ -3,7 +3,7 @@ package main import "std" func main() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3.gno index e8d4bc31a12..a95c7395664 100644 --- a/gnovm/tests/files/std3.gno +++ b/gnovm/tests/files/std3.gno @@ -6,8 +6,8 @@ import ( ) func main() { - caller := std.GetOrigCaller() - caller2 := std.GetOrigCaller() + caller := std.OriginCaller() + caller2 := std.OriginCaller() cmp := bytes.Compare([]byte(caller), []byte(caller2)) println(cmp) } diff --git a/gnovm/tests/files/std4.gno b/gnovm/tests/files/std4.gno index d09bc6251c0..aef32507b08 100644 --- a/gnovm/tests/files/std4.gno +++ b/gnovm/tests/files/std4.gno @@ -5,7 +5,7 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) } diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index 1f1d013c3df..81e066c3762 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -5,9 +5,9 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } @@ -15,7 +15,7 @@ func main() { // panic: frame not found // callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(2) +// std.CallerAt(2) // std/native.gno:41 // main() // main/files/std5.gno:10 diff --git a/gnovm/tests/files/std6.gno b/gnovm/tests/files/std6.gno index 20943f47d28..27c64503b13 100644 --- a/gnovm/tests/files/std6.gno +++ b/gnovm/tests/files/std6.gno @@ -3,9 +3,9 @@ package main import "std" func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } diff --git a/gnovm/tests/files/std7.gno b/gnovm/tests/files/std7.gno index 9d602cc2039..ce767fe59e9 100644 --- a/gnovm/tests/files/std7.gno +++ b/gnovm/tests/files/std7.gno @@ -7,11 +7,11 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) } diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index 3d0e4a7085e..509da757e87 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -7,13 +7,13 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) - caller4 := std.GetCallerAt(4) + caller4 := std.CallerAt(4) println(caller4) } @@ -25,7 +25,7 @@ func main() { // panic: frame not found // callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(4) +// std.CallerAt(4) // std/native.gno:41 // fn() // main/files/std8.gno:16 diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index 5936743ddc6..29295e99d10 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -9,8 +9,8 @@ import ( rtests "gno.land/r/demo/tests" ) -func getPrevRealm() std.Realm { - return std.PrevRealm() +func getPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { @@ -29,7 +29,7 @@ func main() { } assertRealm := func(r std.Realm) { - pkgPath := callersByAddr[r.Addr()] + pkgPath := callersByAddr[r.Address()] if r.IsUser() && pkgPath != "user1.gno" { panic(ufmt.Sprintf("ERROR: expected: 'user1.gno', got:'%s'", pkgPath)) } else if !r.IsUser() && pkgPath != r.PkgPath() { @@ -43,51 +43,51 @@ func main() { }{ { callStackAdd: "", - callerFn: std.PrevRealm, + callerFn: std.PreviousRealm, }, { - callStackAdd: " -> r/crossrealm_test.getPrevRealm", - callerFn: getPrevRealm, + callStackAdd: " -> r/crossrealm_test.getPreviousRealm", + callerFn: getPreviousRealm, }, { callStackAdd: " -> p/demo/tests", - callerFn: ptests.GetPrevRealm, + callerFn: ptests.GetPreviousRealm, }, { callStackAdd: " -> p/demo/tests -> p/demo/tests/subtests", - callerFn: ptests.GetPSubtestsPrevRealm, + callerFn: ptests.GetPSubtestsPreviousRealm, }, { callStackAdd: " -> r/demo/tests", - callerFn: rtests.GetPrevRealm, + callerFn: rtests.GetPreviousRealm, }, { callStackAdd: " -> r/demo/tests -> r/demo/tests/subtests", - callerFn: rtests.GetRSubtestsPrevRealm, + callerFn: rtests.GetRSubtestsPreviousRealm, }, } println("---") // needed to have space prefixes - printColumns("STACK", "std.PrevRealm") + printColumns("STACK", "std.PreviousRealm") printColumns("-----", "------------------") baseCallStack := "user1.gno -> r/crossrealm_test.main" for i, tt := range tests { - printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Addr()]) + printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Address()]) Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) rtests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) ptests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) } } @@ -111,16 +111,16 @@ func printColumns(left, right string) { // Output: // --- -// STACK = std.PrevRealm +// STACK = std.PreviousRealm // ----- = ------------------ // user1.gno -> r/crossrealm_test.main = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = gno.land/r/demo/tests -// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = gno.land/r/demo/tests +// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno // user1.gno -> r/crossrealm_test.main -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests = gno.land/r/demo/tests diff --git a/gnovm/tests/files/zrealm_crossrealm12.gno b/gnovm/tests/files/zrealm_crossrealm12.gno index f2f229cd5de..5ae4d69879a 100644 --- a/gnovm/tests/files/zrealm_crossrealm12.gno +++ b/gnovm/tests/files/zrealm_crossrealm12.gno @@ -21,13 +21,13 @@ func main() { for _, test := range tests { r := test.fn() - if std.DerivePkgAddr(r.PkgPath()) != r.Addr() { + if std.DerivePkgAddr(r.PkgPath()) != r.Address() { panic(fmt.Sprintf("ERROR: expected: %v, got: %v", - std.DerivePkgAddr(r.PkgPath()), r.Addr(), + std.DerivePkgAddr(r.PkgPath()), r.Address(), )) } - println(r.PkgPath(), r.Addr()) + println(r.PkgPath(), r.Address()) } } diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13.gno index 4daeb6de366..53504d095ec 100644 --- a/gnovm/tests/files/zrealm_crossrealm13.gno +++ b/gnovm/tests/files/zrealm_crossrealm13.gno @@ -7,15 +7,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -27,7 +27,7 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Because this is the context of a package, using PrintRealm() @@ -35,14 +35,14 @@ func PrintRealm() { // Output: // PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a.gno index 2fc37804fce..0434a0b6538 100644 --- a/gnovm/tests/files/zrealm_crossrealm13a.gno +++ b/gnovm/tests/files/zrealm_crossrealm13a.gno @@ -8,15 +8,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -28,19 +28,19 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Output: // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1user" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx.gno index 2fda65e7681..4fc05a6e59c 100644 --- a/gnovm/tests/files/zrealm_initctx.gno +++ b/gnovm/tests/files/zrealm_initctx.gno @@ -10,8 +10,8 @@ var addr = std.Address("test") var addrInit = std.Address("addrInit") func init() { - addr = std.GetOrigCaller() - addrInit = tests.InitOrigCaller() + addr = std.OriginCaller() + addrInit = tests.InitOriginCaller() } func main() { diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 6f8045107dc..5df7446d18c 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -8,16 +8,16 @@ import ( var node interface{} func init() { - node = std.GetHeight + node = std.ChainHeight } func main() { - // NOTE: this test uses GetHeight and GetChainID, which are "pure" + // NOTE: this test uses ChainHeight and ChainID, 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.GetChainID + node = std.ChainID g := node.(func() string) println(g()) } diff --git a/gnovm/tests/files/zrealm_natbind1_stdlibs.gno b/gnovm/tests/files/zrealm_natbind1_stdlibs.gno index f44b6ab4fcf..2011624b186 100644 --- a/gnovm/tests/files/zrealm_natbind1_stdlibs.gno +++ b/gnovm/tests/files/zrealm_natbind1_stdlibs.gno @@ -6,7 +6,7 @@ import ( ) func main() { - println(std.GetChainDomain()) + println(std.ChainDomain()) } // Output: diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0.gno index 3f6bdae2537..fff6d6882b8 100644 --- a/gnovm/tests/files/zrealm_std0.gno +++ b/gnovm/tests/files/zrealm_std0.gno @@ -6,7 +6,7 @@ import ( ) func main() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1.gno index d75a2c60b71..8206b7b4097 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1.gno @@ -8,14 +8,14 @@ import ( var aset *std.AddressList func init() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*aset) - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2.gno index 810210c6160..15d137bf556 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2.gno @@ -9,14 +9,14 @@ import ( var aset std.AddressSet func init() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*(aset.(*std.AddressList))) - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6.gno index 17a79c1d43b..b2ab6432d13 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6.gno @@ -6,7 +6,7 @@ import ( ) var ( - realmAddr std.Address = std.GetOrigPkgAddr() + realmAddr std.Address = std.OriginPkgAddress() ) func main() { diff --git a/gnovm/tests/integ/context/context.gno b/gnovm/tests/integ/context/context.gno index 92a9cc632b7..58274b8e5d4 100644 --- a/gnovm/tests/integ/context/context.gno +++ b/gnovm/tests/integ/context/context.gno @@ -7,5 +7,5 @@ import ( 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()) + fmt.Printf("Context worked: %d\n", std.ChainHeight()) } diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index d7417b0c77d..c731a2569a3 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -97,7 +97,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigCaller", + "testSetOriginCaller", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -112,14 +112,14 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.X_testSetOrigCaller( + testlibs_std.X_testSetOriginCaller( m, p0) }, }, { "std", - "testSetOrigPkgAddr", + "testSetOriginPkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -134,7 +134,7 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.X_testSetOrigPkgAddr( + testlibs_std.X_testSetOriginPkgAddr( m, p0) }, @@ -167,7 +167,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigSend", + "testSetOriginSend", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("[]string")}, {Name: gno.N("p1"), Type: gno.X("[]int64")}, @@ -194,7 +194,7 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - testlibs_std.X_testSetOrigSend( + testlibs_std.X_testSetOriginSend( m, p0, p1, p2, p3) }, diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index c30071313fe..8f1cf27d734 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -3,20 +3,20 @@ package std func AssertOriginCall() // injected func TestSkipHeights(count int64) // injected -func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } -func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOriginCaller(addr Address) { testSetOriginCaller(string(addr)) } +func TestSetOriginPkgAddress(addr Address) { testSetOriginPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. // After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of -// rlm, while if a realm function is called, using PrevRealm() will yield rlm. +// rlm, while if a realm function is called, using PreviousRealm() will yield rlm. func TestSetRealm(rlm Realm) { testSetRealm(string(rlm.addr), rlm.pkgPath) } -func TestSetOrigSend(sent, spent Coins) { +func TestSetOriginSend(sent, spent Coins) { sentDenom, sentAmt := sent.expandNative() spentDenom, spentAmt := spent.expandNative() - testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) + testSetOriginSend(sentDenom, sentAmt, spentDenom, spentAmt) } func TestIssueCoins(addr Address, coins Coins) { @@ -24,14 +24,14 @@ func TestIssueCoins(addr Address, coins Coins) { testIssueCoins(string(addr), denom, amt) } -// GetCallerAt calls callerAt, which we overwrite +// CallerAt calls callerAt, which we overwrite func callerAt(n int) string // native bindings -func testSetOrigCaller(s string) -func testSetOrigPkgAddr(s string) +func testSetOriginCaller(s string) +func testSetOriginPkgAddr(s string) func testSetRealm(addr, pkgPath string) -func testSetOrigSend( +func testSetOriginSend( 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 eac51c5fb0e..b6fcf23f048 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -14,7 +14,7 @@ import ( type TestExecContext struct { std.ExecContext - // These are used to set up the result of CurrentRealm() and PrevRealm(). + // These are used to set up the result of CurrentRealm() and PreviousRealm(). RealmFrames map[*gno.Frame]RealmOverride } @@ -71,10 +71,10 @@ func TestSkipHeights(m *gno.Machine, count int64) { func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage @@ -84,22 +84,22 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames()-1 { - // This makes it consistent with GetOrigCaller and TestSetOrigCaller. + // This makes it consistent with OriginCaller and TestSetOriginCaller. ctx := m.Context.(*TestExecContext) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func X_testSetOrigCaller(m *gno.Machine, addr string) { +func X_testSetOriginCaller(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) + ctx.OriginCaller = crypto.Bech32Address(addr) m.Context = ctx } -func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { +func X_testSetOriginPkgAddr(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigPkgAddr = crypto.Bech32Address(addr) + ctx.OriginPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } @@ -160,22 +160,22 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } func X_isRealm(m *gno.Machine, pkgPath string) bool { return gno.IsRealmPath(pkgPath) } -func X_testSetOrigSend(m *gno.Machine, +func X_testSetOriginSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { ctx := m.Context.(*TestExecContext) - ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + ctx.OriginSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) - ctx.OrigSendSpent = &spent + ctx.OriginSendSpent = &spent m.Context = ctx } From 955e1cadfe47b0a299382019681182cfc6182709 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Wed, 19 Feb 2025 20:07:17 +0200 Subject: [PATCH 30/47] feat(gnolang): make panics from Go2Gno produce lineno:column information (#3748) With this change, panics from Go2Gno now have the line-number and column information attached which will greatly aid in debugging. Fixes #3747 --------- Co-authored-by: Morgan --- gnovm/pkg/gnolang/go2gno.go | 30 +++++++++++++++++++----------- gnovm/tests/files/parse_err0.gno | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 1ef23a0079e..b98136b13b5 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -149,6 +149,13 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } }() } + + panicWithPos := func(fmtStr string, args ...any) { + pos := fs.Position(gon.Pos()) + loc := fmt.Sprintf("%s:%d:%d", pos.Filename, pos.Line, pos.Column) + panic(fmt.Errorf("%s: %v", loc, fmt.Sprintf(fmtStr, args...))) + } + switch gon := gon.(type) { case *ast.ParenExpr: return toExpr(fs, gon.X) @@ -245,10 +252,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { Tag: toExpr(fs, gon.Tag), } } else { - panic(fmt.Sprintf( + panicWithPos( "expected a Go Field with 1 name but got %v.\n"+ "maybe call toFields", - gon.Names)) + gon.Names) } case *ast.ArrayType: if _, ok := gon.Len.(*ast.Ellipsis); ok { @@ -330,7 +337,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { if cx, ok := gon.X.(*ast.CallExpr); ok { if ix, ok := cx.Fun.(*ast.Ident); ok && ix.Name == "panic" { if len(cx.Args) != 1 { - panic("expected panic statement to have single exception value") + panicWithPos("expected panic statement to have single exception value") } return &PanicStmt{ Exception: toExpr(fs, cx.Args[0]), @@ -412,9 +419,8 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { VarName: "", } default: - panic(fmt.Sprintf( - "unexpected *ast.TypeSwitchStmt.Assign type %s", - reflect.TypeOf(gon.Assign).String())) + panicWithPos("unexpected *ast.TypeSwitchStmt.Assign type %s", + reflect.TypeOf(gon.Assign).String()) } case *ast.SwitchStmt: x := toExpr(fs, gon.Tag) @@ -433,10 +439,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { recv := FieldTypeExpr{} if isMethod { if len(gon.Recv.List) > 1 { - panic("method has multiple receivers") + panicWithPos("method has multiple receivers") } if len(gon.Recv.List) == 0 { - panic("method has no receiver") + panicWithPos("method has no receiver") } recv = *Go2Gno(fs, gon.Recv.List[0]).(*FieldTypeExpr) } @@ -454,7 +460,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { Body: body, } case *ast.GenDecl: - panic("unexpected *ast.GenDecl; use toDecls(fs,) instead") + panicWithPos("unexpected *ast.GenDecl; use toDecls(fs,) instead") case *ast.File: pkgName := Name(gon.Name.Name) decls := make([]Decl, 0, len(gon.Decls)) @@ -473,11 +479,13 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { case *ast.EmptyStmt: return &EmptyStmt{} default: - panic(fmt.Sprintf("unknown Go type %v: %s\n", + panicWithPos("unknown Go type %v: %s\n", reflect.TypeOf(gon), spew.Sdump(gon), - )) + ) } + + return } //---------------------------------------- diff --git a/gnovm/tests/files/parse_err0.gno b/gnovm/tests/files/parse_err0.gno index 88a8e0df132..71b310c6644 100644 --- a/gnovm/tests/files/parse_err0.gno +++ b/gnovm/tests/files/parse_err0.gno @@ -7,4 +7,4 @@ func () A() func main() {} // Error: -// method has no receiver +// files/parse_err0.gno:5:1: method has no receiver From 0fb25b547aa0e763acffedf4c4096f1bd7a80b4a Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 20 Feb 2025 10:56:58 +0100 Subject: [PATCH 31/47] feat(gnovm): enable debugger for gno test (#3380) This change brings interactive debugging to gno tests, using -debug flag. New debugger commands have also been added: - next, to step over the next source line - stepout, to step out of the current function Usage example, from the clone root dir: ```console $ go run ./gnovm/cmd/gno test -v -debug ./examples/gno.land/p/demo/diff -run TestMyersDiff/Case_sensitivity Welcome to the Gnovm debugger. Type 'help' for list of commands. dbg> break gno.land/p/demo/diff/diff.gno:60 Breakpoint 0 at gno.land/p/demo/diff gno.land/p/demo/diff/diff.gno:60:0 dbg> c === RUN TestMyersDiff === RUN TestMyersDiff/Case_sensitivity > diff.MyersDiff() gno.land/p/demo/diff/diff.gno:60:10 55: // 56: // Returns: 57: // - A slice of Edit operations representing the minimum difference between the two strings. 58: func MyersDiff(old, new string) []Edit { 59: oldRunes, newRunes := []rune(old), []rune(new) => 60: n, m := len(oldRunes), len(newRunes) 61: 62: if n == 0 && m == 0 { 63: return []Edit{} 64: } dbg> p oldRunes (slice[(97 int32),(98 int32),(99 int32)] []int32) dbg> stack 0 in gno.land/p/demo/diff.MyersDiff at gno.land/p/demo/diff/diff.gno:60:10 1 in gno.land/p/demo/diff. at gno.land/p/demo/diff/diff_test.gno:176:4 2 in testing.tRunner at testing/testing.gno:364:2 3 in testing.(*testing.T).Run at testing/testing.gno:171:2 4 in gno.land/p/demo/diff.TestMyersDiff at gno.land/p/demo/diff/diff_test.gno:175:3 5 in testing.tRunner at testing/testing.gno:364:2 6 in testing.RunTest at testing/testing.gno:298:2 dbg> ``` --- gnovm/cmd/gno/test.go | 17 ++++++ gnovm/cmd/gno/tool_lint.go | 3 +- gnovm/pkg/gnolang/debugger.go | 98 +++++++++++++++++++++++------- gnovm/pkg/gnolang/debugger_test.go | 3 + gnovm/pkg/repl/repl.go | 2 +- gnovm/pkg/test/filetest.go | 1 + gnovm/pkg/test/test.go | 29 +++++++-- 7 files changed, 125 insertions(+), 28 deletions(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index ae67d69bc90..67cba458aa7 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -26,6 +26,8 @@ type testCfg struct { updateGoldenTests bool printRuntimeMetrics bool printEvents bool + debug bool + debugAddr string } func newTestCmd(io commands.IO) *commands.Command { @@ -143,6 +145,20 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { false, "print emitted events", ) + + fs.BoolVar( + &c.debug, + "debug", + false, + "enable interactive debugger using stdin and stdout", + ) + + fs.StringVar( + &c.debugAddr, + "debug-addr", + "", + "enable interactive debugger using tcp address in the form [host]:port", + ) } func execTest(cfg *testCfg, args []string, io commands.IO) error { @@ -189,6 +205,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { opts.Verbose = cfg.verbose opts.Metrics = cfg.printRuntimeMetrics opts.Events = cfg.printEvents + opts.Debug = cfg.debug buildErrCount := 0 testErrCount := 0 diff --git a/gnovm/cmd/gno/tool_lint.go b/gnovm/cmd/gno/tool_lint.go index 6983175cea0..812a2baf103 100644 --- a/gnovm/cmd/gno/tool_lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -159,7 +159,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath) } - tm := test.Machine(gs, goio.Discard, memPkg.Path) + tm := test.Machine(gs, goio.Discard, memPkg.Path, false) + defer tm.Release() // Check test files diff --git a/gnovm/pkg/gnolang/debugger.go b/gnovm/pkg/gnolang/debugger.go index f047a176af7..6b7b2f77175 100644 --- a/gnovm/pkg/gnolang/debugger.go +++ b/gnovm/pkg/gnolang/debugger.go @@ -10,11 +10,14 @@ import ( "io" "net" "os" + "path" "path/filepath" "sort" "strconv" "strings" "unicode" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" ) // DebugState is the state of the machine debugger, defined by a finite state @@ -43,24 +46,28 @@ type Debugger struct { out io.Writer // debugger output, defaults to Stdout scanner *bufio.Scanner // to parse input per line - state DebugState // current state of debugger - lastCmd string // last debugger command - lastArg string // last debugger command arguments - loc Location // source location of the current machine instruction - prevLoc Location // source location of the previous machine instruction - breakpoints []Location // list of breakpoints set by user, as source locations - call []Location // for function tracking, ideally should be provided by machine frame - frameLevel int // frame level of the current machine instruction - getSrc func(string) string // helper to access source from repl or others + state DebugState // current state of debugger + lastCmd string // last debugger command + lastArg string // last debugger command arguments + loc Location // source location of the current machine instruction + prevLoc Location // source location of the previous machine instruction + nextLoc Location // source location at the 'next' command + breakpoints []Location // list of breakpoints set by user, as source locations + call []Location // for function tracking, ideally should be provided by machine frame + frameLevel int // frame level of the current machine instruction + nextDepth int // function call depth at the 'next' command + getSrc func(string, string) string // helper to access source from repl or others + rootDir string } // Enable makes the debugger d active, using in as input reader, out as output writer and f as a source helper. -func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string) string) { +func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string, string) string) { d.in = in d.out = out d.enabled = true d.state = DebugAtInit d.getSrc = f + d.rootDir = gnoenv.RootDir() } // Disable makes the debugger d inactive. @@ -92,11 +99,11 @@ func init() { "list": {debugList, listUsage, listShort, listLong}, "print": {debugPrint, printUsage, printShort, ""}, "stack": {debugStack, stackUsage, stackShort, ""}, - // NOTE: the difference between continue, step and stepi is handled within - // the main Debug() loop. - "step": {debugContinue, stepUsage, stepShort, ""}, - "stepi": {debugContinue, stepiUsage, stepiShort, ""}, - "up": {debugUp, upUsage, upShort, ""}, + "next": {debugContinue, nextUsage, nextShort, ""}, + "step": {debugContinue, stepUsage, stepShort, ""}, + "stepi": {debugContinue, stepiUsage, stepiShort, ""}, + "stepout": {debugContinue, stepoutUsage, stepoutShort, ""}, + "up": {debugUp, upUsage, upShort, ""}, } // Sort command names for help. @@ -113,11 +120,13 @@ func init() { debugCmds["c"] = debugCmds["continue"] debugCmds["h"] = debugCmds["help"] debugCmds["l"] = debugCmds["list"] + debugCmds["n"] = debugCmds["next"] debugCmds["p"] = debugCmds["print"] debugCmds["quit"] = debugCmds["exit"] debugCmds["q"] = debugCmds["exit"] debugCmds["s"] = debugCmds["step"] debugCmds["si"] = debugCmds["stepi"] + debugCmds["so"] = debugCmds["stepout"] } // Debug is the debug callback invoked at each VM execution step. It implements the DebugState FSA. @@ -135,6 +144,9 @@ loop: fmt.Fprintln(m.Debugger.out, "Command failed:", err) } case DebugAtRun: + if !m.Debugger.enabled { + break loop + } switch m.Debugger.lastCmd { case "si", "stepi": m.Debugger.state = DebugAtCmd @@ -146,6 +158,21 @@ loop: debugList(m, "") continue loop } + case "n", "next": + if m.Debugger.loc != m.Debugger.prevLoc && m.Debugger.loc.File != "" && + (m.Debugger.nextDepth == 0 || !sameLine(m.Debugger.loc, m.Debugger.nextLoc) && callDepth(m) <= m.Debugger.nextDepth) { + m.Debugger.state = DebugAtCmd + m.Debugger.prevLoc = m.Debugger.loc + debugList(m, "") + continue loop + } + case "stepout", "so": + if callDepth(m) < m.Debugger.nextDepth { + m.Debugger.state = DebugAtCmd + m.Debugger.prevLoc = m.Debugger.loc + debugList(m, "") + continue loop + } default: if atBreak(m) { m.Debugger.state = DebugAtCmd @@ -172,6 +199,23 @@ loop: } } +// callDepth returns the function call depth. +func callDepth(m *Machine) int { + n := 0 + for _, f := range m.Frames { + if f.Func == nil { + continue + } + n++ + } + return n +} + +// sameLine returns true if both arguments are at the same line. +func sameLine(loc1, loc2 Location) bool { + return loc1.PkgPath == loc2.PkgPath && loc1.File == loc2.File && loc1.Line == loc2.Line +} + // atBreak returns true if current machine location matches a breakpoint, false otherwise. func atBreak(m *Machine) bool { loc := m.Debugger.loc @@ -317,7 +361,14 @@ func parseLocSpec(m *Machine, arg string) (loc Location, err error) { if loc.File, err = filepath.Abs(strs[0]); err != nil { return loc, err } - loc.File = filepath.Clean(loc.File) + loc.File = path.Clean(loc.File) + if m.Debugger.rootDir != "" && strings.HasPrefix(loc.File, m.Debugger.rootDir) { + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/gnovm/stdlibs/") + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/examples/") + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/") + loc.PkgPath = path.Dir(loc.File) + loc.File = path.Base(loc.File) + } } if line, err = strconv.Atoi(strs[1]); err != nil { return loc, err @@ -378,24 +429,29 @@ func debugClear(m *Machine, arg string) error { } // --------------------------------------- +// NOTE: the difference between continue, next, step, stepi and stepout is handled within the Debug() loop. const ( continueUsage = `continue|c` continueShort = `Run until breakpoint or program termination.` -) -const ( + nextUsage = `next|n` + nextShort = `Step over to next source line.` + stepUsage = `step|s` stepShort = `Single step through program.` -) -const ( stepiUsage = `stepi|si` stepiShort = `Single step a single VM instruction.` + + stepoutUsage = `stepout|so` + stepoutShort = `Step out of the current function.` ) func debugContinue(m *Machine, arg string) error { m.Debugger.state = DebugAtRun m.Debugger.frameLevel = 0 + m.Debugger.nextDepth = callDepth(m) + m.Debugger.nextLoc = m.Debugger.loc return nil } @@ -505,7 +561,7 @@ func debugList(m *Machine, arg string) (err error) { if err != nil { // Use optional getSrc helper as fallback to get source. if m.Debugger.getSrc != nil { - src = m.Debugger.getSrc(loc.File) + src = m.Debugger.getSrc(loc.PkgPath, loc.File) } if src == "" { return err diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index a9e0a4834d5..a4282225324 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -143,6 +143,9 @@ func TestDebug(t *testing.T) { {in: "b 27\nc\np b\n", out: `("!zero" string)`}, {in: "b 22\nc\np t.A[3]\n", out: "Command failed: &{(\"slice index out of bounds: 3 (len=3)\" string) }"}, {in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"}, + {in: "b 37\nc\nnext\n", out: "=> 39:"}, + {in: "b 40\nc\nnext\n", out: "=> 41:"}, + {in: "b 22\nc\nstepout\n", out: "=> 40:"}, }) runDebugTest(t, "../../tests/files/a1.gno", []dtest{ diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index fff80d672dc..56c8b4a53f3 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -156,7 +156,7 @@ func (r *Repl) Process(input string) (out string, err error) { r.state.id++ if r.debug { - r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(file string) string { + r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(ppath, file string) string { return r.state.files[file] }) r.debug = false diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index c24c014a9ba..61e6084aa23 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -63,6 +63,7 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er Store: opts.TestStore.BeginTransaction(cw, cw, nil), Context: ctx, MaxAllocBytes: maxAlloc, + Debug: opts.Debug, }) defer m.Release() result := opts.runTest(m, pkgPath, filename, source) diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 71ec3bb2568..f9b7f47dd29 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -70,11 +70,12 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { } // Machine is a minimal machine, set up with just the Store, Output and Context. -func Machine(testStore gno.Store, output io.Writer, pkgPath string) *gno.Machine { +func Machine(testStore gno.Store, output io.Writer, pkgPath string, debug bool) *gno.Machine { return gno.NewMachineWithOptions(gno.MachineOptions{ Store: testStore, Output: output, Context: Context(pkgPath, nil), + Debug: debug, }) } @@ -107,6 +108,8 @@ type TestOptions struct { Output io.Writer // Used for os.Stderr, and for printing errors. Error io.Writer + // Debug enables the interactive debugger on gno tests. + Debug bool // Not set by NewTestOptions: @@ -289,9 +292,8 @@ func (opts *TestOptions) runTestFiles( // reset store ops, if any - we only need them for some filetests. opts.TestStore.SetLogStoreOps(false) - // Check if we already have the package - it may have been eagerly - // loaded. - m = Machine(gs, opts.WriterForStore(), memPkg.Path) + // Check if we already have the package - it may have been eagerly loaded. + m = Machine(gs, opts.WriterForStore(), memPkg.Path, opts.Debug) m.Alloc = alloc if opts.TestStore.GetMemPackage(memPkg.Path) == nil { m.RunMemPackage(memPkg, true) @@ -311,7 +313,7 @@ func (opts *TestOptions) runTestFiles( // - Run the test files before this for loop (but persist it to store; // RunFiles doesn't do that currently) // - Wrap here. - m = Machine(gs, opts.Output, memPkg.Path) + m = Machine(gs, opts.Output, memPkg.Path, opts.Debug) m.Alloc = alloc.Reset() m.SetActivePackage(pv) @@ -319,6 +321,23 @@ func (opts *TestOptions) runTestFiles( testingtv := gno.TypedValue{T: &gno.PackageType{}, V: testingpv} testingcx := &gno.ConstExpr{TypedValue: testingtv} + if opts.Debug { + fileContent := func(ppath, name string) string { + p := filepath.Join(opts.RootDir, ppath, name) + b, err := os.ReadFile(p) + if err != nil { + p = filepath.Join(opts.RootDir, "gnovm", "stdlibs", ppath, name) + b, err = os.ReadFile(p) + } + if err != nil { + p = filepath.Join(opts.RootDir, "examples", ppath, name) + b, err = os.ReadFile(p) + } + return string(b) + } + m.Debugger.Enable(os.Stdin, os.Stdout, fileContent) + } + eval := m.Eval(gno.Call( gno.Sel(testingcx, "RunTest"), // Call testing.RunTest gno.Str(opts.RunFlag), // run flag From b04ca6c590c91395a9c74a8d2c71e593ee58fa47 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 20 Feb 2025 16:06:49 +0100 Subject: [PATCH 32/47] build: use go1.23.6 (#3767) Seeing as Go is now at version 1.24, making this PR to bump to the latest patch version of 1.23. This will also fix the CI on master. Changes aside from go.mod concern updating a few methods in `txlog` which were always intended to use 1.23 iterators, but couldn't until now. There is a language change (the aforementioned range funcs), but it shouldn't impact existing code. Pinging those who I think could verify this in the reviewers. Fixes #3033. --- .github/ISSUE_TEMPLATE/BUG-REPORT.md | 4 +-- .github/workflows/examples.yml | 2 +- .github/workflows/gnofmt_template.yml | 2 +- .github/workflows/main_template.yml | 1 - CONTRIBUTING.md | 3 +-- Dockerfile | 4 +-- contribs/github-bot/go.mod | 4 +-- contribs/gnodev/go.mod | 4 +-- contribs/gnofaucet/go.mod | 4 +-- contribs/gnogenesis/go.mod | 4 +-- contribs/gnohealth/go.mod | 2 +- contribs/gnokeykc/go.mod | 4 +-- contribs/gnomigrate/go.mod | 4 +-- .../local-setup/installation.md | 2 +- .../validators/connect-to-existing-chain.md | 2 +- .../validators/setting-up-a-new-chain.md | 2 +- gnovm/cmd/gno/bug.go | 2 +- gnovm/pkg/gnolang/internal/txlog/txlog.go | 25 ++++++++++++------- .../pkg/gnolang/internal/txlog/txlog_test.go | 21 ++++++++-------- gnovm/pkg/gnolang/store.go | 10 +++----- go.mod | 4 +-- misc/autocounterd/go.mod | 4 +-- misc/docs-linter/go.mod | 4 +-- misc/loop/go.mod | 4 +-- misc/stdlib_diff/go.mod | 4 +-- 25 files changed, 54 insertions(+), 72 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.md b/.github/ISSUE_TEMPLATE/BUG-REPORT.md index 3aca60cdbb7..3f2fe8452b5 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.md @@ -13,7 +13,7 @@ Describe your issue in as much detail as possible here ### Your environment -* Go version (example: go1.23.4) +* Go version (example: go1.23.6) * OS and CPU architecture (example: linux/amd64) * Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) @@ -37,4 +37,4 @@ Please paste any logs here that demonstrate the issue, if they exist ### Proposed solution -If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it \ No newline at end of file +If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 09c0ee0bbd9..ee18c864236 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -89,7 +89,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [ "1.23.x" ] + go-version: ["1.23.x"] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/gnofmt_template.yml b/.github/workflows/gnofmt_template.yml index c47ef35efd7..edf9311f480 100644 --- a/.github/workflows/gnofmt_template.yml +++ b/.github/workflows/gnofmt_template.yml @@ -30,4 +30,4 @@ jobs: - name: Check for unformatted code run: | - git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) diff --git a/.github/workflows/main_template.yml b/.github/workflows/main_template.yml index 260c983e871..6b62a176cae 100644 --- a/.github/workflows/main_template.yml +++ b/.github/workflows/main_template.yml @@ -39,4 +39,3 @@ jobs: tests-extra-args: ${{ inputs.tests-extra-args }} secrets: codecov-token: ${{ secrets.codecov-token }} - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9562a531227..2e0e989ccac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.22+) +- Go (version 1.23+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 @@ -514,4 +514,3 @@ automatic label management. | info needed | Issue is lacking information needed for resolving | | investigating | Issue is still being investigated by the team | | question | Issue starts a discussion or raises a question | - diff --git a/Dockerfile b/Dockerfile index effc30ca32f..057d4388415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build gno -FROM golang:1.22-alpine AS build-gno +FROM golang:1.23-alpine AS build-gno RUN go env -w GOMODCACHE=/root/.cache/go-build WORKDIR /gnoroot ENV GNOROOT="/gnoroot" @@ -11,7 +11,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./ RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gno ./gnovm/cmd/gno # build misc binaries -FROM golang:1.22-alpine AS build-misc +FROM golang:1.23-alpine AS build-misc RUN go env -w GOMODCACHE=/root/.cache/go-build WORKDIR /gnoroot ENV GNOROOT="/gnoroot" diff --git a/contribs/github-bot/go.mod b/contribs/github-bot/go.mod index f8914819d54..e3072115c95 100644 --- a/contribs/github-bot/go.mod +++ b/contribs/github-bot/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/github-bot -go 1.22.0 - -toolchain go1.23.2 +go 1.23.6 replace github.com/gnolang/gno => ../.. diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 9aea08c4a30..f520e2fbb79 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 replace github.com/gnolang/gno => ../.. diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 88c05e0d778..0a862162331 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require ( github.com/gnolang/faucet v0.3.2 diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 8af370f8169..e03ad1dde42 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/contribs/gnogenesis -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod index 76d7cd9c437..81c0ae7325b 100644 --- a/contribs/gnohealth/go.mod +++ b/contribs/gnohealth/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnohealth -go 1.22.4 +go 1.23.6 replace github.com/gnolang/gno => ../.. diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 3abcf3d834f..0ecc7b34cf3 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 replace github.com/gnolang/gno => ../.. diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index 96f6dc9bdc6..0381c6acd01 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gnomigrate -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index e05c2f9b205..2d091d6bf44 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -13,7 +13,7 @@ to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.23+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: diff --git a/docs/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md index f15f6bb59e2..82adeb557c4 100644 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -12,7 +12,7 @@ In this tutorial, you will learn how to start a local Gno node and connect to an - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.23+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment diff --git a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md index 5db8a7f1a59..58ae7e011b8 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -13,7 +13,7 @@ Additionally, you will see the different options you can use to make your Gno in - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.23+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index 6f4f78410d6..fd5f907bc40 100644 --- a/gnovm/cmd/gno/bug.go +++ b/gnovm/cmd/gno/bug.go @@ -69,7 +69,7 @@ func newBugCmd(io commands.IO) *commands.Command { The new issue body is prefilled for you with the following information: - Gno version (the output of "gno version") -- Go version (example: go1.22.4) +- Go version (example: go1.23.4) - OS and CPU architecture (example: linux/amd64) - Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) diff --git a/gnovm/pkg/gnolang/internal/txlog/txlog.go b/gnovm/pkg/gnolang/internal/txlog/txlog.go index cda11083672..0c5e8640bf7 100644 --- a/gnovm/pkg/gnolang/internal/txlog/txlog.go +++ b/gnovm/pkg/gnolang/internal/txlog/txlog.go @@ -7,12 +7,14 @@ // when calling [MapCommitter.Commit]. package txlog +import "iter" + // Map is a generic interface to a key/value map, like Go's builtin map. type Map[K comparable, V any] interface { Get(K) (V, bool) Set(K, V) Delete(K) - Iterate() func(yield func(K, V) bool) + Iterate() iter.Seq2[K, V] } // MapCommitter is a Map which also implements a Commit() method, which writes @@ -47,7 +49,7 @@ func (m GoMap[K, V]) Delete(k K) { } // Iterate implements [Map]. -func (m GoMap[K, V]) Iterate() func(yield func(K, V) bool) { +func (m GoMap[K, V]) Iterate() iter.Seq2[K, V] { return func(yield func(K, V) bool) { for k, v := range m { if !yield(k, v) { @@ -103,21 +105,26 @@ func (b txLog[K, V]) Delete(k K) { b.dirty[k] = deletable[V]{deleted: true} } -func (b txLog[K, V]) Iterate() func(yield func(K, V) bool) { +func (b txLog[K, V]) Iterate() iter.Seq2[K, V] { return func(yield func(K, V) bool) { // go through b.source; skip deleted values, and use updated values // for those which exist in b.dirty. - b.source.Iterate()(func(k K, v V) bool { + for k, v := range b.source.Iterate() { if dirty, ok := b.dirty[k]; ok { if dirty.deleted { - return true + continue + } + if !yield(k, dirty.v) { + return } - return yield(k, dirty.v) + continue } // not in dirty - return yield(k, v) - }) + if !yield(k, v) { + return + } + } // iterate over all "new" values (ie. exist in b.dirty but not b.source). for k, v := range b.dirty { @@ -129,7 +136,7 @@ func (b txLog[K, V]) Iterate() func(yield func(K, V) bool) { continue } if !yield(k, v.v) { - break + return } } } diff --git a/gnovm/pkg/gnolang/internal/txlog/txlog_test.go b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go index b0780fc8380..fd2a2597194 100644 --- a/gnovm/pkg/gnolang/internal/txlog/txlog_test.go +++ b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go @@ -51,17 +51,17 @@ func ExampleWrap() { func Test_txLog(t *testing.T) { t.Parallel() - type Value = struct{} + type Value = struct{ b byte } // Full "integration test" of the txLog + mapwrapper. source := GoMap[int, *Value](map[int]*Value{}) // create 4 empty values (we'll just use the pointers) vs := [...]*Value{ - {}, - {}, - {}, - {}, + {0}, + {1}, + {2}, + {3}, } source.Set(0, vs[0]) source.Set(1, vs[1]) @@ -121,8 +121,8 @@ func Test_txLog(t *testing.T) { assert.Equal(t, saved, txm.source) // double-check on the iterators. - verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[0]}) - verifyHashMapValues(t, txm, map[int]*Value{2: vs[2], 3: vs[3]}) + verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[2]}) + verifyHashMapValues(t, txm, map[int]*Value{2: vs[0], 3: vs[3]}) } { @@ -146,17 +146,16 @@ func Test_txLog(t *testing.T) { } } -func verifyHashMapValues(t *testing.T, m Map[int, *struct{}], expectedReadonly map[int]*struct{}) { +func verifyHashMapValues(t *testing.T, m Map[int, *struct{ b byte }], expectedReadonly map[int]*struct{ b byte }) { t.Helper() expected := maps.Clone(expectedReadonly) - m.Iterate()(func(k int, v *struct{}) bool { + for k, v := range m.Iterate() { ev, eok := expected[k] _ = assert.True(t, eok, "mapping %d:%v should exist in expected map", k, v) && assert.Equal(t, ev, v, "values should match") delete(expected, k) - return true - }) + } assert.Empty(t, expected, "(some) expected values not found in the Map") } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 84625bd92c1..1ddc97ed0d0 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -246,14 +246,12 @@ func CopyFromCachedStore(destStore, cachedStore Store, cachedBase, cachedIavl st ds.iavlStore.Set(iter.Key(), iter.Value()) } - ss.cacheTypes.Iterate()(func(k TypeID, v Type) bool { + for k, v := range ss.cacheTypes.Iterate() { ds.cacheTypes.Set(k, v) - return true - }) - ss.cacheNodes.Iterate()(func(k Location, v BlockNode) bool { + } + for k, v := range ss.cacheNodes.Iterate() { ds.cacheNodes.Set(k, v) - return true - }) + } } func (ds *defaultStore) GetAllocator() *Allocator { diff --git a/go.mod b/go.mod index 027ba6359bc..026351cec77 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/gnolang/gno -go 1.22.0 +go 1.23 -toolchain go1.22.10 +toolchain go1.23.6 require ( dario.cat/mergo v1.0.1 diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 972975d4fb0..7d9def2b3cc 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,8 +1,6 @@ module autocounterd -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require github.com/gnolang/gno v0.0.0-00010101000000-000000000000 diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index 2951bcbef54..a98f8115b1c 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,8 +1,6 @@ module linter -go 1.22.0 - -toolchain go1.23.2 +go 1.23.6 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 4c5a3f41839..7d0252da82c 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,8 +1,6 @@ module loop -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require ( github.com/docker/docker v25.0.6+incompatible diff --git a/misc/stdlib_diff/go.mod b/misc/stdlib_diff/go.mod index f2ddffba2b2..7ec537a127e 100644 --- a/misc/stdlib_diff/go.mod +++ b/misc/stdlib_diff/go.mod @@ -1,7 +1,5 @@ module github.com/gnolang/gno/misc/stdlib_diff -go 1.22.0 - -toolchain go1.22.10 +go 1.23.6 require github.com/hexops/gotextdiff v1.0.3 From de3e4e5668c64ee58bb76e455cfcf99061d7a56f Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 20 Feb 2025 17:00:42 +0100 Subject: [PATCH 33/47] feat(gnovm): disallow internal imports (#3774) #2190 This PR disables support for importing /internal/ subdirectories, for packages outside of the root of the internal package. It remains undecided whether we should allow `maketx call` on the internal package (currently possible on a r/ path) --- docs/concepts/effective-gno.md | 40 ++++++++++++++----- examples/gno.land/p/demo/flow/io_test.gno | 2 +- .../p/demo/tamagotchi/z0_filetest.gno | 3 +- examples/no_cycles_test.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 20 ++++++++-- gnovm/pkg/test/imports.go | 2 +- .../tests/files/extern/foo/internal/aa/aa.gno | 3 ++ .../files/extern/foo/internal/internal.gno | 8 ++++ gnovm/tests/files/import12.gno | 14 +++++++ gnovm/tests/files/import13.gno | 14 +++++++ gnovm/tests/files/import13b.gno | 14 +++++++ gnovm/tests/files/import14.gno | 17 ++++++++ 12 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 gnovm/tests/files/extern/foo/internal/aa/aa.gno create mode 100644 gnovm/tests/files/extern/foo/internal/internal.gno create mode 100644 gnovm/tests/files/import12.gno create mode 100644 gnovm/tests/files/import13.gno create mode 100644 gnovm/tests/files/import13b.gno create mode 100644 gnovm/tests/files/import14.gno diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index 74886a8e515..5b0f5a75323 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -102,7 +102,7 @@ everything and not save wrong changes. In Gno, the use of `panic()` and `error` should be context-dependent to ensure clarity and proper error handling: - Use `panic()` to immediately halt execution and roll back the transaction when - encountering critical issues or invalid inputs that cannot be recovered from. + encountering critical issues or invalid inputs that cannot be recovered from. - Return an `error` when the situation allows for the possibility of recovery or when the caller should decide how to handle the error. @@ -199,7 +199,7 @@ code readability and trust. A Gno contract is not just its lines of code, but also the imports it uses. More importantly, Gno contracts are not just for developers. For the first time, it -makes sense for users to see what functionality they are executing too. Code simplicity, transparency, +makes sense for users to see what functionality they are executing too. Code simplicity, transparency, explicitness, and trustability are paramount. Another good reason for creating simple, focused libraries is the composability @@ -402,10 +402,30 @@ just to have internal libraries that you created to centralize your helpers and don't expect that other people will use your helpers, then you should probably use subdirectories like `p/NAMESPACE/DAPP/foo/bar/baz`. +Packages which contain `internal` as an element of the path (ie. at the end, or +in between, like `gno.land/p/demo/seqid/internal`, or +`gno.land/p/demo/seqid/internal/base32`) can only be imported by packages +sharing the same root as the `internal` package. That is, given a package +structure as follows: + +``` +gno.land/p/demo/seqid +├── generator +└── internal + ├── base32 + └── cford32 +``` + +The `seqid/internal`, `seqid/internal/base32` and `seqid/internal/cford32` +packages can only be imported by `seqid` and `seqid/generator`. + +This works for both realm and packages, and can be used to create entirely +restricted packages and realms that are not meant for outside consumption. + ### Define types and interfaces in pure packages (p/) In Gno, it's common to create `p/NAMESPACE/DAPP` for defining types and -interfaces, and `r/NAMESPACE/DAPP` for the runtime, especially when the goal +interfaces, and `r/NAMESPACE/DAPP` for the runtime, especially when the goal for the realm is to become a standard that could be imported by `p/`. The reason for this is that `p/` can only import `p/`, while `r/` can import @@ -465,19 +485,19 @@ checks. ### Emit Gno events to make life off-chain easier -Gno provides users the ability to log specific occurrences that happened in their +Gno provides users the ability to log specific occurrences that happened in their on-chain apps. An `event` log is stored in the ABCI results of each block, and -these logs can be indexed, filtered, and searched by external services, allowing +these logs can be indexed, filtered, and searched by external services, allowing them to monitor the behaviour of on-chain apps. -It is good practice to emit events when any major action in your code is +It is good practice to emit events when any major action in your code is triggered. For example, good times to emit an event is after a balance transfer, ownership change, profile created, etc. Alternatively, you can view event emission -as a way to include data for monitoring purposes, given the indexable nature of +as a way to include data for monitoring purposes, given the indexable nature of events. Events consist of a type and a slice of strings representing `key:value` pairs. -They are emitted with the `Emit()` function, contained in the `std` package in +They are emitted with the `Emit()` function, contained in the `std` package in the Gno standard library: ```go @@ -499,7 +519,7 @@ func ChangeOwner(newOwner std.Address) { if caller != owner { panic("access denied") } - + owner = newOwner std.Emit("OwnershipChange", "newOwner", newOwner.String()) } @@ -701,7 +721,7 @@ func (s *MySafeStruct) Inc() { Then, you can register this object in another or several other realms so other realms can access the object, but still following your own rules. -```go +```go import "gno.land/r/otherrealm" func init() { diff --git a/examples/gno.land/p/demo/flow/io_test.gno b/examples/gno.land/p/demo/flow/io_test.gno index e881854b7a1..6b64a29431f 100644 --- a/examples/gno.land/p/demo/flow/io_test.gno +++ b/examples/gno.land/p/demo/flow/io_test.gno @@ -9,7 +9,7 @@ import ( "testing" "time" - ios_test "internal/os_test" + ios_test "os_test" ) // XXX ugh, I can't even sleep milliseconds. diff --git a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno index 17d6c466ed5..2aebd9d4aae 100644 --- a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno +++ b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno @@ -1,10 +1,9 @@ package main import ( + "os_test" "time" - "internal/os_test" - "gno.land/p/demo/tamagotchi" ) diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index 7cc6fbfd183..4c89c112178 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -19,7 +19,7 @@ import ( // XXX: move this into `gno lint` -var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_test"} +var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "os_test"} // TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples func TestNoCycles(t *testing.T) { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index e30f02b1b87..14beec0d6e7 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4674,7 +4674,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // recursively predefine dependencies. for { - un := tryPredefine(store, last, d) + un := tryPredefine(store, pkg, last, d) if un != "" { // check circularity. for _, n := range *stack { @@ -4801,7 +4801,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // side effects. This function works for all block nodes and // must be called for name declarations within (non-file, // non-package) stmt bodies. -func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { +func tryPredefine(store Store, pkg *PackageNode, last BlockNode, d Decl) (un Name) { if d.GetAttribute(ATTR_PREDEFINED) == true { panic(fmt.Sprintf("decl node already predefined! %v", d)) } @@ -4817,6 +4817,21 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { // so value paths cannot be used here. switch d := d.(type) { case *ImportDecl: + // stdlib internal package + if strings.HasPrefix(d.PkgPath, "internal/") && !IsStdlib(pkg.PkgPath) { + panic("cannot import stdlib internal/ package outside of standard library") + } + + // Restrict imports to /internal packages to a package rooted at base. + base, suff, isInternal := strings.Cut(d.PkgPath, "/internal") + // /internal should be either at the end, or be a part: /internal/ + isInternal = isInternal && (suff == "" || suff[0] == '/') + if isInternal && + pkg.PkgPath != base && + !strings.HasPrefix(pkg.PkgPath, base+"/") { + panic("internal/ packages can only be imported by packages rooted at the parent of \"internal\"") + } + pv := store.GetPackage(d.PkgPath, true) if pv == nil { panic(fmt.Sprintf( @@ -4980,7 +4995,6 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define package-level function. ft := &FuncType{} - pkg := skipFile(last).(*PackageNode) // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. // The body may get altered during preprocessing later. diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 95302ecffb0..b7744b08178 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -131,7 +131,7 @@ func StoreWithOptions( pkg.DefineGoNativeValue("Unmarshal", json.Unmarshal) pkg.DefineGoNativeValue("Marshal", json.Marshal) return pkg, pkg.NewPackage() - case "internal/os_test": + case "os_test": pkg := gno.NewPackageNode("os_test", pkgPath, nil) pkg.DefineNative("Sleep", gno.Flds( // params diff --git a/gnovm/tests/files/extern/foo/internal/aa/aa.gno b/gnovm/tests/files/extern/foo/internal/aa/aa.gno new file mode 100644 index 00000000000..477b3b21412 --- /dev/null +++ b/gnovm/tests/files/extern/foo/internal/aa/aa.gno @@ -0,0 +1,3 @@ +package aa + +const A = 456 diff --git a/gnovm/tests/files/extern/foo/internal/internal.gno b/gnovm/tests/files/extern/foo/internal/internal.gno new file mode 100644 index 00000000000..646402b1242 --- /dev/null +++ b/gnovm/tests/files/extern/foo/internal/internal.gno @@ -0,0 +1,8 @@ +package internal + +import "github.com/gnolang/gno/_test/foo/internal/aa" + +const ( + A = 123 + B = aa.A +) diff --git a/gnovm/tests/files/import12.gno b/gnovm/tests/files/import12.gno new file mode 100644 index 00000000000..a3f3fe4fbf6 --- /dev/null +++ b/gnovm/tests/files/import12.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "internal/bytealg" +) + +func main() { + println(bytealg.PrimeRK) +} + +// Error: +// gno.land/p/demo/files/import12.gno:6:2: cannot import stdlib internal/ package outside of standard library diff --git a/gnovm/tests/files/import13.gno b/gnovm/tests/files/import13.gno new file mode 100644 index 00000000000..c0aa0e28a01 --- /dev/null +++ b/gnovm/tests/files/import13.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "github.com/gnolang/gno/_test/foo/internal/aa" +) + +func main() { + println(aa.A) +} + +// Error: +// gno.land/p/demo/files/import13.gno:6:2: internal/ packages can only be imported by packages rooted at the parent of "internal" diff --git a/gnovm/tests/files/import13b.gno b/gnovm/tests/files/import13b.gno new file mode 100644 index 00000000000..bedebb89b2f --- /dev/null +++ b/gnovm/tests/files/import13b.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "github.com/gnolang/gno/_test/foo/internal" +) + +func main() { + println(internal.A) +} + +// Error: +// gno.land/p/demo/files/import13b.gno:6:2: internal/ packages can only be imported by packages rooted at the parent of "internal" diff --git a/gnovm/tests/files/import14.gno b/gnovm/tests/files/import14.gno new file mode 100644 index 00000000000..07b57240bd1 --- /dev/null +++ b/gnovm/tests/files/import14.gno @@ -0,0 +1,17 @@ +// PKGPATH: github.com/gnolang/gno/_test/foo + +package foo + +import ( + "github.com/gnolang/gno/_test/foo/internal" + "github.com/gnolang/gno/_test/foo/internal/aa" +) + +func main() { + println(internal.A, internal.B) + println(aa.A) +} + +// Output: +// 123 456 +// 456 From 8591793819bdfa1aa65dcd88638bebf1b4a91845 Mon Sep 17 00:00:00 2001 From: Sergio Maria Matone Date: Fri, 21 Feb 2025 09:59:13 +0100 Subject: [PATCH 34/47] chore: Adding Stdlib to Gnodev Docker image (#3798) Adding Stdlib to Gnodev Docker image --- .github/goreleaser.yaml | 6 ++++-- Dockerfile.release | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml index 186bac6f31a..a411484520d 100644 --- a/.github/goreleaser.yaml +++ b/.github/goreleaser.yaml @@ -339,9 +339,10 @@ dockers: ids: - gnodev extra_files: - - examples - gno.land/genesis/genesis_balances.txt - gno.land/genesis/genesis_txs.jsonl + - examples + - gnovm/stdlibs - use: buildx dockerfile: Dockerfile.release goos: linux @@ -359,9 +360,10 @@ dockers: ids: - gnodev extra_files: - - examples - gno.land/genesis/genesis_balances.txt - gno.land/genesis/genesis_txs.jsonl + - examples + - gnovm/stdlibs # gnocontribs - use: buildx diff --git a/Dockerfile.release b/Dockerfile.release index 69a508000aa..ddca77a5ebf 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -50,6 +50,7 @@ FROM base as gnodev COPY ./gnodev /usr/bin/gnodev COPY ./examples /gnoroot/examples/ +COPY ./gnovm/stdlibs /gnoroot/gnovm/stdlibs/ COPY ./gno.land/genesis/genesis_balances.txt /gnoroot/gno.land/genesis/genesis_balances.txt COPY ./gno.land/genesis/genesis_txs.jsonl /gnoroot/gno.land/genesis/genesis_txs.jsonl # gnoweb exposed by default From bf2e03fc0e6387e484ee637c2dda93b9f9d54654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:03:42 +0100 Subject: [PATCH 35/47] chore(deps): bump sigstore/cosign-installer from 3.8.0 to 3.8.1 in the actions group (#3796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions group with 1 update: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer). Updates `sigstore/cosign-installer` from 3.8.0 to 3.8.1
Release notes

Sourced from sigstore/cosign-installer's releases.

v3.8.1

What's Changed

Full Changelog: https://github.com/sigstore/cosign-installer/compare/v3...v3.8.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sigstore/cosign-installer&package-manager=github_actions&previous-version=3.8.0&new-version=3.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/releaser-master.yml | 2 +- .github/workflows/releaser-nightly.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 5b6f76a9135..e8ae0a5777c 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -29,7 +29,7 @@ jobs: go-version-file: go.mod cache: true - - uses: sigstore/cosign-installer@v3.8.0 + - uses: sigstore/cosign-installer@v3.8.1 - uses: anchore/sbom-action/download-syft@v0.18.0 - uses: docker/login-action@v3 diff --git a/.github/workflows/releaser-nightly.yml b/.github/workflows/releaser-nightly.yml index 62066db24a1..73f9515521f 100644 --- a/.github/workflows/releaser-nightly.yml +++ b/.github/workflows/releaser-nightly.yml @@ -23,7 +23,7 @@ jobs: go-version-file: go.mod cache: true - - uses: sigstore/cosign-installer@v3.8.0 + - uses: sigstore/cosign-installer@v3.8.1 - uses: anchore/sbom-action/download-syft@v0.18.0 - uses: docker/login-action@v3 From 2c2c018d5320d85b6180e3790571d0a9004f7e40 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:16:24 +0100 Subject: [PATCH 36/47] fix(docs): originpkgaddr type (#3804) ## Description Fixes wrong type in the ref docs. h/t @ajnavarro --- docs/reference/stdlibs/std/chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 9129cd17665..2ec9741595f 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -91,7 +91,7 @@ caller := std.OriginCaller() ## OriginPkgAddress ```go -func OriginPkgAddress() string +func OriginPkgAddress() Address ``` Returns the address of the first (entry point) realm/package in a sequence of realm/package calls. From 85b3c0b7957e57fee0bba1e8fe030bf64931e19e Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Fri, 21 Feb 2025 17:46:41 +0100 Subject: [PATCH 37/47] fix(gnovm): protect TypedValue stringer against deep recursivity (#3790) The protectedStringer is improved by limiting the successive number of nested calls when constructing the string representation of a deeply recursive value. Fixes #3471. --------- Co-authored-by: ltzmaxwell --- gnovm/pkg/gnolang/values_string.go | 16 +++++++++++++--- gnovm/tests/files/recurse1.gno | 12 ++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 gnovm/tests/files/recurse1.gno diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index b18bdb0dddc..18a1ece679f 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -12,11 +12,17 @@ type protectedStringer interface { ProtectedString(*seenValues) string } -// This indicates the maximum anticipated depth of the stack when printing a Value type. -const defaultSeenValuesSize = 32 +const ( + // defaultSeenValuesSize indicates the maximum anticipated depth of the stack when printing a Value type. + defaultSeenValuesSize = 32 + + // nestedLimit indicates the maximum nested level when printing a deeply recursive value. + nestedLimit = 10 +) type seenValues struct { values []Value + nc int // nested counter, to limit recursivity } func (sv *seenValues) Put(v Value) { @@ -46,7 +52,7 @@ func (sv *seenValues) Pop() { } func newSeenValues() *seenValues { - return &seenValues{values: make([]Value, 0, defaultSeenValuesSize)} + return &seenValues{values: make([]Value, 0, defaultSeenValuesSize), nc: nestedLimit} } func (v StringValue) String() string { @@ -74,6 +80,10 @@ func (av *ArrayValue) ProtectedString(seen *seenValues) string { return fmt.Sprintf("%p", av) } + seen.nc-- + if seen.nc < 0 { + return "..." + } seen.Put(av) defer seen.Pop() diff --git a/gnovm/tests/files/recurse1.gno b/gnovm/tests/files/recurse1.gno new file mode 100644 index 00000000000..1d5f1c7fc72 --- /dev/null +++ b/gnovm/tests/files/recurse1.gno @@ -0,0 +1,12 @@ +package main + +func main() { + var x interface{} + for i := 0; i < 10000; i++ { + x = [1]interface{}{x} + } + println(x) +} + +// Output: +// array[(array[(array[(array[(array[(array[(array[(array[(array[(array[(... [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] [1]interface{})] From a4b5c9cb70812ffb62451d41b6423992eb1e9805 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 24 Feb 2025 14:51:12 +0100 Subject: [PATCH 38/47] fix(gnovm): allow private struct fields to be set (#3814) Go reflect doesn't allow to set unexported struct fields. We use `unsafe` to override this limitation. Fixes #1155, fixes #3802 --- gnovm/pkg/gnolang/gonative.go | 8 +++++++- gnovm/tests/files/struct59.gno | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 gnovm/tests/files/struct59.gno diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 2fbf34ed1b1..cd0e3b9ce9f 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "reflect" + "unsafe" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) @@ -1133,7 +1134,12 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { if ftv.IsUndefined() { continue } - gno2GoValue(ftv, rv.Field(i)) + fv := rv.Field(i) + if !fv.CanSet() { + // Normally private fields can not bet set via reflect. Override this limitation. + fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem() + } + gno2GoValue(ftv, fv) } case *MapType: // If uninitialized map, return zero value. diff --git a/gnovm/tests/files/struct59.gno b/gnovm/tests/files/struct59.gno new file mode 100644 index 00000000000..6c26978a808 --- /dev/null +++ b/gnovm/tests/files/struct59.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type X struct { + v string +} + +func main() { + x := X{v: "a"} + fmt.Printf("test %#v\n", x) +} + +// Output: +// test struct { v string }{v:"a"} From 27fd12dffb2101a05788bcf29d08f933ee3e527f Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:04:18 +0100 Subject: [PATCH 39/47] docs: archive test4 (#3810) ## Description Deprecates test4 in the docs. --------- Co-authored-by: Jeff Thompson --- docs/concepts/namespaces.md | 6 +++--- docs/concepts/testnets.md | 22 +++++++--------------- docs/reference/network-config.md | 1 - 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md index c7f03ec1f0a..7e16dd9c48b 100644 --- a/docs/concepts/namespaces.md +++ b/docs/concepts/namespaces.md @@ -9,7 +9,7 @@ similar to GitHub's user and organization model. :::warning Not enabled -This feature isn't enabled by default on the portal loop chain and is currently available only on test4.gno.land. +This feature isn't enabled by default on the Portal Loop chain and is currently available only on test5.gno.land. ::: @@ -56,7 +56,7 @@ $ gnokey maketx call -pkgpath gno.land/r/demo/users \ -func Register \ -gas-fee 1000000ugnot -gas-wanted 2000000 \ -broadcast \ - -chainid=test4 \ + -chainid=test5 \ -send=20000000ugnot \ -args '' \ -args 'patrick' \ @@ -86,6 +86,6 @@ $ gnokey maketx addpkg \ --gas-fee 1000000ugnot \ --gas-wanted 2000000 \ --broadcast \ - --chainid test4 \ + --chainid test5 \ test1 ``` diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md index b5286eaec57..9769c8f873f 100644 --- a/docs/concepts/testnets.md +++ b/docs/concepts/testnets.md @@ -63,21 +63,6 @@ Test5 was launched in November 2024. - **Versioning strategy**: - Test5 is to be release-based, following releases of the Gno tech stack. -## Test4 - -Test4 is the first permanent multi-node testnet, launched in July 2024. - -- **Persistence of state:** - - State is fully persisted unless there are breaking changes in a new release, - where persistence partly depends on implementing a migration strategy -- **Timeliness of code:** - - Versioning mechanisms for packages & realms will be implemented for test4 -- **Intended purpose** - - Running a full node, testing validator coordination, deploying stable Gno - dApps, creating tools that require persisted state & transaction history -- **Versioning strategy**: - - Test4 is the first gno.land testnet to be release-based, following releases -of the Gno tech stack. ## Staging @@ -98,6 +83,13 @@ Staging is a testnet that is reset once every 60 minutes. These testnets are deprecated and currently serve as archives of previous progress. +## Test4 + +Test4 is the first permanent multi-node testnet. Archived data for test4 can be found [here](https://github.com/gnolang/tx-exports/tree/main/test4.gno.land). + +Launch date: July 10th 2024 +Release commit: [194903d](https://github.com/gnolang/gno/commit/194903db0350ace7d57910e6c34125d3aa9817da) + ### Test3 (archive) The third Gno testnet. Archived data for test3 can be found [here](https://github.com/gnolang/tx-exports/tree/main/test3.gno.land). diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md index 45a56b772ae..1e50864372b 100644 --- a/docs/reference/network-config.md +++ b/docs/reference/network-config.md @@ -8,7 +8,6 @@ id: network-config |-------------|----------------------------------|---------------| | Portal Loop | https://rpc.gno.land:443 | `portal-loop` | | Test5 | https://rpc.test5.gno.land:443 | `test5` | -| Test4 | https://rpc.test4.gno.land:443 | `test4` | | Staging | https://rpc.staging.gno.land:443 | `staging` | ### WebSocket endpoints From 300dcfde8de225ddf8366220a0837ff821cbf355 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 24 Feb 2025 16:32:06 +0100 Subject: [PATCH 40/47] feat(sdk/vm): disallow calling internal realms (#3794) continuation of #3774, fixes #2190. --- gno.land/pkg/sdk/vm/msgs.go | 3 +++ .../pkg/sdk/vm/{msg_test.go => msgs_test.go} | 14 ++++++++++++++ gnovm/pkg/gnolang/helpers.go | 16 ++++++++++++++++ gnovm/pkg/gnolang/preprocess.go | 5 +---- 4 files changed, 34 insertions(+), 4 deletions(-) rename gno.land/pkg/sdk/vm/{msg_test.go => msgs_test.go} (95%) diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index 38f35ab7110..51d573837fc 100644 --- a/gno.land/pkg/sdk/vm/msgs.go +++ b/gno.land/pkg/sdk/vm/msgs.go @@ -120,6 +120,9 @@ func (msg MsgCall) ValidateBasic() error { if !gno.IsRealmPath(msg.PkgPath) { return ErrInvalidPkgPath("pkgpath must be of a realm") } + if _, isInt := gno.IsInternalPath(msg.PkgPath); isInt { + return ErrInvalidPkgPath("pkgpath must not be of an internal package") + } if msg.Func == "" { // XXX return ErrInvalidExpr("missing function to call") } diff --git a/gno.land/pkg/sdk/vm/msg_test.go b/gno.land/pkg/sdk/vm/msgs_test.go similarity index 95% rename from gno.land/pkg/sdk/vm/msg_test.go rename to gno.land/pkg/sdk/vm/msgs_test.go index 684dc21e9f2..56b3931ef8d 100644 --- a/gno.land/pkg/sdk/vm/msg_test.go +++ b/gno.land/pkg/sdk/vm/msgs_test.go @@ -164,6 +164,20 @@ func TestMsgCall_ValidateBasic(t *testing.T) { }, expectErr: InvalidPkgPathError{}, }, + { + name: "pkgPath should not be an internal path", + msg: MsgCall{ + Caller: caller, + PkgPath: "gno.land/r/demo/avl/internal/sort", + Func: funcName, + Args: args, + Send: std.Coins{std.Coin{ + Denom: "ugnot", + Amount: 1000, + }}, + }, + expectErr: InvalidPkgPathError{}, + }, { name: "missing function name to call", msg: MsgCall{ diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index ddc1fd2fa55..5f0064cc4d9 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -28,6 +28,22 @@ func IsRealmPath(pkgPath string) bool { !ReGnoRunPath.MatchString(pkgPath) } +// IsInternalPath determines whether the given pkgPath refers to an internal +// package, that may not be called directly or imported by packages that don't +// share the same root. +// +// If isInternal is true, base will be set to the root of the internal package, +// which must also be an ancestor or the same path that imports the given +// internal package. +func IsInternalPath(pkgPath string) (base string, isInternal bool) { + // Restrict imports to /internal packages to a package rooted at base. + var suff string + base, suff, isInternal = strings.Cut(pkgPath, "/internal") + // /internal should be either at the end, or be a part: /internal/ + isInternal = isInternal && (suff == "" || suff[0] == '/') + return +} + // IsPurePackagePath determines whether the given pkgpath is for a published Gno package. // It only considers "pure" those starting with gno.land/p/, so it returns false for // stdlib packages and MsgRun paths. diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 14beec0d6e7..100f555b145 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4822,10 +4822,7 @@ func tryPredefine(store Store, pkg *PackageNode, last BlockNode, d Decl) (un Nam panic("cannot import stdlib internal/ package outside of standard library") } - // Restrict imports to /internal packages to a package rooted at base. - base, suff, isInternal := strings.Cut(d.PkgPath, "/internal") - // /internal should be either at the end, or be a part: /internal/ - isInternal = isInternal && (suff == "" || suff[0] == '/') + base, isInternal := IsInternalPath(d.PkgPath) if isInternal && pkg.PkgPath != base && !strings.HasPrefix(pkg.PkgPath, base+"/") { From 41a43baa6456fe38840eea4cc6c0896ffcc49f8d Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:07:46 +0100 Subject: [PATCH 41/47] feat(examples): add p/moul/fifo (#3736) Signed-off-by: moul <94029+moul@users.noreply.github.com> --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/moul/fifo/fifo.gno | 246 ++++++++++++++++ examples/gno.land/p/moul/fifo/fifo_test.gno | 294 ++++++++++++++++++++ examples/gno.land/p/moul/fifo/gno.mod | 1 + 3 files changed, 541 insertions(+) create mode 100644 examples/gno.land/p/moul/fifo/fifo.gno create mode 100644 examples/gno.land/p/moul/fifo/fifo_test.gno create mode 100644 examples/gno.land/p/moul/fifo/gno.mod diff --git a/examples/gno.land/p/moul/fifo/fifo.gno b/examples/gno.land/p/moul/fifo/fifo.gno new file mode 100644 index 00000000000..d0291a941c0 --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo.gno @@ -0,0 +1,246 @@ +// Package fifo implements a fixed-size FIFO (First-In-First-Out) list data structure +// using a singly-linked list. The implementation prioritizes storage efficiency by minimizing +// storage operations - each add/remove operation only updates 1-2 pointers, regardless of +// list size. +// +// Key features: +// - Fixed-size with automatic removal of oldest entries when full +// - Support for both prepend (add at start) and append (add at end) operations +// - Constant storage usage through automatic pruning +// - O(1) append operations and latest element access +// - Iterator support for sequential access +// - Dynamic size adjustment via SetMaxSize +// +// This implementation is optimized for frequent updates, as insertions and deletions only +// require updating 1-2 pointers. However, random access operations are O(n) as they require +// traversing the list. For use cases where writes are rare, a slice-based +// implementation might be more suitable. +// +// The linked list structure is equally efficient for storing both small values (like pointers) +// and larger data structures, as each node maintains a single next-pointer regardless of the +// stored value's size. +// +// Example usage: +// +// list := fifo.New(3) // Create a new list with max size 3 +// list.Append("a") // List: [a] +// list.Append("b") // List: [a b] +// list.Append("c") // List: [a b c] +// list.Append("d") // List: [b c d] (oldest element "a" was removed) +// latest := list.Latest() // Returns "d" +// all := list.Entries() // Returns ["b", "c", "d"] +package fifo + +// node represents a single element in the linked list +type node struct { + value interface{} + next *node +} + +// List represents a fixed-size FIFO list +type List struct { + head *node + tail *node + size int + maxSize int +} + +// New creates a new FIFO list with the specified maximum size +func New(maxSize int) *List { + return &List{ + maxSize: maxSize, + } +} + +// Prepend adds a new entry at the start of the list. If the list exceeds maxSize, +// the last entry is automatically removed. +func (l *List) Prepend(entry interface{}) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + newNode.next = l.head + l.head = newNode + + if l.size < l.maxSize { + l.size++ + } else { + // Remove last element by traversing to second-to-last + if l.size == 1 { + // Special case: if size is 1, just update both pointers + l.head = newNode + l.tail = newNode + newNode.next = nil + } else { + // Find second-to-last node + current := l.head + for current.next != l.tail { + current = current.next + } + current.next = nil + l.tail = current + } + } +} + +// Append adds a new entry at the end of the list. If the list exceeds maxSize, +// the first entry is automatically removed. +func (l *List) Append(entry interface{}) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + l.tail.next = newNode + l.tail = newNode + + if l.size < l.maxSize { + l.size++ + } else { + l.head = l.head.next + } +} + +// Get returns the entry at the specified index. +// Index 0 is the oldest entry, Size()-1 is the newest. +func (l *List) Get(index int) interface{} { + if index < 0 || index >= l.size { + return nil + } + + current := l.head + for i := 0; i < index; i++ { + current = current.next + } + return current.value +} + +// Size returns the current number of entries in the list +func (l *List) Size() int { + return l.size +} + +// MaxSize returns the maximum size configured for this list +func (l *List) MaxSize() int { + return l.maxSize +} + +// Entries returns all current entries as a slice +func (l *List) Entries() []interface{} { + entries := make([]interface{}, l.size) + current := l.head + for i := 0; i < l.size; i++ { + entries[i] = current.value + current = current.next + } + return entries +} + +// Iterator returns a function that can be used to iterate over the entries +// from oldest to newest. Returns nil when there are no more entries. +func (l *List) Iterator() func() interface{} { + current := l.head + return func() interface{} { + if current == nil { + return nil + } + value := current.value + current = current.next + return value + } +} + +// Latest returns the most recent entry. +// Returns nil if the list is empty. +func (l *List) Latest() interface{} { + if l.tail == nil { + return nil + } + return l.tail.value +} + +// SetMaxSize updates the maximum size of the list. +// If the new maxSize is smaller than the current size, +// the oldest entries are removed to fit the new size. +func (l *List) SetMaxSize(maxSize int) { + if maxSize < 0 { + maxSize = 0 + } + + // If new maxSize is smaller than current size, + // remove oldest entries until we fit + if maxSize < l.size { + // Special case: if new maxSize is 0, clear the list + if maxSize == 0 { + l.head = nil + l.tail = nil + l.size = 0 + } else { + // Keep the newest entries by moving head forward + diff := l.size - maxSize + for i := 0; i < diff; i++ { + l.head = l.head.next + } + l.size = maxSize + } + } + + l.maxSize = maxSize +} + +// Delete removes the element at the specified index. +// Returns true if an element was removed, false if the index was invalid. +func (l *List) Delete(index int) bool { + if index < 0 || index >= l.size { + return false + } + + // Special case: deleting the only element + if l.size == 1 { + l.head = nil + l.tail = nil + l.size = 0 + return true + } + + // Special case: deleting first element + if index == 0 { + l.head = l.head.next + l.size-- + return true + } + + // Find the node before the one to delete + current := l.head + for i := 0; i < index-1; i++ { + current = current.next + } + + // Special case: deleting last element + if index == l.size-1 { + l.tail = current + current.next = nil + } else { + current.next = current.next.next + } + + l.size-- + return true +} diff --git a/examples/gno.land/p/moul/fifo/fifo_test.gno b/examples/gno.land/p/moul/fifo/fifo_test.gno new file mode 100644 index 00000000000..1e3d27509c1 --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo_test.gno @@ -0,0 +1,294 @@ +package fifo + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestNew(t *testing.T) { + l := New(5) + uassert.Equal(t, 5, l.MaxSize()) + uassert.Equal(t, 0, l.Size()) +} + +func TestAppend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Append(1) + l.Append(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + + // Test overflow behavior + l.Append(3) + l.Append(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 4, l.Get(2)) +} + +func TestPrepend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Prepend(1) + l.Prepend(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 1, l.Get(1)) + + // Test overflow behavior + l.Prepend(3) + l.Prepend(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 4, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 2, l.Get(2)) +} + +func TestGet(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + // Test valid indices + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + uassert.Equal(t, 3, l.Get(2)) + + // Test invalid indices + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(3) == nil) +} + +func TestEntries(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 3, entries[2]) +} + +func TestLatest(t *testing.T) { + l := New(5) + + // Test empty list + uassert.True(t, l.Latest() == nil) + + // Test single entry + l.Append(1) + uassert.Equal(t, 1, l.Latest()) + + // Test multiple entries + l.Append(2) + l.Append(3) + uassert.Equal(t, 3, l.Latest()) + + // Test after overflow + l.Append(4) + l.Append(5) + l.Append(6) + uassert.Equal(t, 6, l.Latest()) +} + +func TestIterator(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.Equal(t, 3, iter()) + uassert.True(t, iter() == nil) +} + +func TestMixedOperations(t *testing.T) { + l := New(3) + + // Mix of append and prepend operations + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 4, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 1, entries[2]) +} + +func TestEmptyList(t *testing.T) { + l := New(3) + + // Test operations on empty list + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.Equal(t, 0, len(l.Entries())) + uassert.True(t, l.Latest() == nil) + + iter := l.Iterator() + uassert.True(t, iter() == nil) +} + +func TestEdgeCases(t *testing.T) { + // Test zero-size list + l := New(0) + uassert.Equal(t, 0, l.MaxSize()) + l.Append(1) // Should be no-op + uassert.Equal(t, 0, l.Size()) + + // Test single-element list + l = New(1) + l.Append(1) + l.Append(2) // Should replace 1 + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 2, l.Latest()) + + // Test rapid append/prepend alternation + l = New(3) + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + l.Append(5) // [2,1,5] + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 1, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test nil values + l = New(2) + l.Append(nil) + l.Prepend(nil) + uassert.Equal(t, 2, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test index bounds + l = New(3) + l.Append(1) + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test iterator exhaustion + l = New(2) + l.Append(1) + l.Append(2) + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.True(t, iter() == nil) + uassert.True(t, iter() == nil) + + // Test prepend on full list + l = New(2) + l.Append(1) + l.Append(2) // [1,2] + l.Prepend(3) // [3,1] + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 1, entries[1]) +} + +func TestSetMaxSize(t *testing.T) { + l := New(5) + + // Fill the list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + l.Append(5) + + // Test increasing maxSize + l.SetMaxSize(7) + uassert.Equal(t, 7, l.MaxSize()) + uassert.Equal(t, 5, l.Size()) + + // Test reducing maxSize + l.SetMaxSize(3) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test setting to zero + l.SetMaxSize(0) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) + + // Test negative maxSize + l.SetMaxSize(-1) + uassert.Equal(t, 0, l.MaxSize()) + + // Test setting back to positive + l.SetMaxSize(2) + l.Append(1) + l.Append(2) + l.Append(3) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 3, entries[1]) +} + +func TestDelete(t *testing.T) { + l := New(5) + + // Test delete on empty list + uassert.False(t, l.Delete(0)) + uassert.False(t, l.Delete(-1)) + + // Fill list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + + // Test invalid indices + uassert.False(t, l.Delete(-1)) + uassert.False(t, l.Delete(4)) + + // Test deleting from middle + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 3, entries[1]) + uassert.Equal(t, 4, entries[2]) + + // Test deleting from head + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + + // Test deleting from tail + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 3, l.Latest()) + + // Test deleting last element + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) +} diff --git a/examples/gno.land/p/moul/fifo/gno.mod b/examples/gno.land/p/moul/fifo/gno.mod new file mode 100644 index 00000000000..dccbc39453b --- /dev/null +++ b/examples/gno.land/p/moul/fifo/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/fifo From c3c6e66b2557c0ebc66c4505bba33e24a1ac9a90 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Mon, 24 Feb 2025 18:38:23 +0200 Subject: [PATCH 42/47] fix(gnolang): prescriptively reject *ast.(IndexListExpr, GoStmt) as unrecognized for Gno (#3752) The *ast.IndexListExpr is used for generics but in assignment operations it is illegal to use. This change returns a proper error and matches Go's output. Also *ast.GoStmt is for spawning Go routines but those are forbidden in Gno, hence reject them prescriptively instead of just spewing out the raw ast type. Fixes #3731 Updates #3751 --------- Co-authored-by: Morgan --- gnovm/pkg/gnolang/go2gno.go | 7 +++++++ gnovm/tests/files/parse_err1.gno | 16 ++++++++++++++++ gnovm/tests/files/parse_err2.gno | 16 ++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 gnovm/tests/files/parse_err1.gno create mode 100644 gnovm/tests/files/parse_err2.gno diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index b98136b13b5..8379bfef425 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -478,6 +478,13 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } case *ast.EmptyStmt: return &EmptyStmt{} + case *ast.IndexListExpr: + if len(gon.Indices) > 1 { + panicWithPos("invalid operation: more than one index") + } + panicWithPos("invalid operation: indexList is not permitted in Gno") + case *ast.GoStmt: + panicWithPos("goroutines are not permitted") default: panicWithPos("unknown Go type %v: %s\n", reflect.TypeOf(gon), diff --git a/gnovm/tests/files/parse_err1.gno b/gnovm/tests/files/parse_err1.gno new file mode 100644 index 00000000000..efa21e883b0 --- /dev/null +++ b/gnovm/tests/files/parse_err1.gno @@ -0,0 +1,16 @@ +// https://github.com/gnolang/gno/issues/3731 + +package main + +type node struct { + r []int +} + +func (n *node) foo(targ, wndex int) { + _ = n.r[targ, wndex] +} + +func main() {} + +// Error: +// files/parse_err1.gno:10:6: invalid operation: more than one index diff --git a/gnovm/tests/files/parse_err2.gno b/gnovm/tests/files/parse_err2.gno new file mode 100644 index 00000000000..3e0f79d6935 --- /dev/null +++ b/gnovm/tests/files/parse_err2.gno @@ -0,0 +1,16 @@ +// https://github.com/gnolang/gno/issues/3751 + +package math + +import "testing" + +func Add(a, b int) int { + return a + b +} + +func TestAdd(t *testing.T) { + go Add(1, 1) +} + +// Error: +// files/parse_err2.gno:12:2: goroutines are not permitted From 6803b7741ce6862d2704ffc1d4de280d518b16e7 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 24 Feb 2025 17:43:37 +0100 Subject: [PATCH 43/47] fix(gnovm): remove `preprocessing` variable (#3728) This PR removes the `preprocessing` global variable. This was only used in one function; now that we have `PreprocessingMode` in the machine, we can move this check out of the specific function and into the places where we need to check it --- gnovm/pkg/gnolang/op_assign.go | 6 ++++ gnovm/pkg/gnolang/op_decl.go | 44 +++++++++++-------------- gnovm/pkg/gnolang/uverse.go | 3 ++ gnovm/pkg/gnolang/values_conversions.go | 14 -------- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 5e841fb18fd..b42bb4744c0 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -20,6 +20,9 @@ func (m *Machine) doOpDefine() { } } } + if !m.PreprocessorMode && isUntyped(rvs[i].T) && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") + } ptr.Assign2(m.Alloc, m.Store, m.Realm, rvs[i], true) } } @@ -41,6 +44,9 @@ func (m *Machine) doOpAssign() { } } } + if !m.PreprocessorMode && isUntyped(rvs[i].T) && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") + } lv.Assign2(m.Alloc, m.Store, m.Realm, rvs[i], true) } } diff --git a/gnovm/pkg/gnolang/op_decl.go b/gnovm/pkg/gnolang/op_decl.go index c9c04ccf76d..6dbae2d3edf 100644 --- a/gnovm/pkg/gnolang/op_decl.go +++ b/gnovm/pkg/gnolang/op_decl.go @@ -29,35 +29,31 @@ func (m *Machine) doOpValueDecl() { } else { tv = rvs[i] } - if nt != nil { - if nt.Kind() == InterfaceKind { - if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nil) - } else { - // keep type as is. + + if isUntyped(tv.T) { + if !s.Const { + if !m.PreprocessorMode && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") } - } else { - if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nt) - } else { - if debug { - if nt.TypeID() != tv.T.TypeID() && - baseOf(nt).TypeID() != tv.T.TypeID() { - panic(fmt.Sprintf( - "type mismatch: %s vs %s", - nt.TypeID(), - tv.T.TypeID(), - )) - } + ConvertUntypedTo(&tv, nil) + } + } else if nt != nil { + // if nt.T is an interface, maintain tv.T as-is. + if nt.Kind() != InterfaceKind { + if debug { + if nt.TypeID() != tv.T.TypeID() && + baseOf(nt).TypeID() != tv.T.TypeID() { + panic(fmt.Sprintf( + "type mismatch: %s vs %s", + nt.TypeID(), + tv.T.TypeID(), + )) } - tv.T = nt } + tv.T = nt } - } else if s.Const { - // leave untyped as is. - } else if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nil) } + nx := &s.NameExprs[i] ptr := lb.GetPointerToMaybeHeapDefine(m.Store, nx) ptr.Assign2(m.Alloc, m.Store, m.Realm, tv, false) diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 55791ba4900..074f7a51b66 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -1050,6 +1050,9 @@ func makeUverseNode() { } } + if isUntyped(exception.Value.T) { + ConvertUntypedTo(&exception.Value, nil) + } m.PushValue(exception.Value) // Recover complete; remove exceptions. m.Exceptions = nil diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index 920ed655ec9..ce256680de6 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -1316,28 +1316,14 @@ func ConvertUntypedTo(tv *TypedValue, t Type) { } switch tv.T { case UntypedBoolType: - if debug { - if t.Kind() != BoolKind { - panic("untyped bool can only be converted to bool kind") - } - } tv.T = t case UntypedRuneType: ConvertUntypedRuneTo(tv, t) case UntypedBigintType: - if preprocessing.Load() == 0 { - panic("untyped Bigint conversion should not happen during interpretation") - } ConvertUntypedBigintTo(tv, tv.V.(BigintValue), t) case UntypedBigdecType: - if preprocessing.Load() == 0 { - panic("untyped Bigdec conversion should not happen during interpretation") - } ConvertUntypedBigdecTo(tv, tv.V.(BigdecValue), t) case UntypedStringType: - if preprocessing.Load() == 0 { - panic("untyped String conversion should not happen during interpretation") - } if t.Kind() == StringKind { tv.T = t return From 83c83f9e7b5fdf04b3ac5f3fd3d0fba47563f13e Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:05:10 +0100 Subject: [PATCH 44/47] feat(examples): add p/moul/authz (#3700) We should probably replace most of the current Ownable-based contracts with this option. This way, we can start with a central admin and later transition to a DAO. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/moul/authz/authz.gno | 261 ++++++++++++ examples/gno.land/p/moul/authz/authz_test.gno | 384 ++++++++++++++++++ .../gno.land/p/moul/authz/example_test.gno | 88 ++++ examples/gno.land/p/moul/authz/gno.mod | 1 + examples/gno.land/p/moul/once/gno.mod | 1 + examples/gno.land/p/moul/once/once.gno | 100 +++++ examples/gno.land/p/moul/once/once_test.gno | 231 +++++++++++ .../pkg/integration/testdata/moul_authz.txtar | 82 ++++ 8 files changed, 1148 insertions(+) create mode 100644 examples/gno.land/p/moul/authz/authz.gno create mode 100644 examples/gno.land/p/moul/authz/authz_test.gno create mode 100644 examples/gno.land/p/moul/authz/example_test.gno create mode 100644 examples/gno.land/p/moul/authz/gno.mod create mode 100644 examples/gno.land/p/moul/once/gno.mod create mode 100644 examples/gno.land/p/moul/once/once.gno create mode 100644 examples/gno.land/p/moul/once/once_test.gno create mode 100644 gno.land/pkg/integration/testdata/moul_authz.txtar diff --git a/examples/gno.land/p/moul/authz/authz.gno b/examples/gno.land/p/moul/authz/authz.gno new file mode 100644 index 00000000000..059d1234b10 --- /dev/null +++ b/examples/gno.land/p/moul/authz/authz.gno @@ -0,0 +1,261 @@ +// Package authz provides flexible authorization control for privileged actions. +// +// # Authorization Strategies +// +// The package supports multiple authorization strategies: +// - Member-based: Single user or team of users +// - Contract-based: Async authorization (e.g., via DAO) +// - Auto-accept: Allow all actions +// - Drop: Deny all actions +// +// Core Components +// +// - Authority interface: Base interface implemented by all authorities +// - Authorizer: Main wrapper object for authority management +// - MemberAuthority: Manages authorized addresses +// - ContractAuthority: Delegates to another contract +// - AutoAcceptAuthority: Accepts all actions +// - DroppedAuthority: Denies all actions +// +// Quick Start +// +// // Initialize with contract deployer as authority +// var auth = authz.New() +// +// // Create functions that require authorization +// func UpdateConfig(newValue string) error { +// return auth.Do("update_config", func() error { +// config = newValue +// return nil +// }) +// } +// +// See example_test.gno for more usage examples. +package authz + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/rotree" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/addrset" + "gno.land/p/moul/once" +) + +// Authorizer is the main wrapper object that handles authority management +type Authorizer struct { + current Authority +} + +// Authority represents an entity that can authorize privileged actions +type Authority interface { + // Authorize executes a privileged action if the caller is authorized + // Additional args can be provided for context (e.g., for proposal creation) + Authorize(title string, action PrivilegedAction, args ...interface{}) error + + // String returns a human-readable description of the authority + String() string +} + +// PrivilegedAction defines a function that performs a privileged action. +type PrivilegedAction func() error + +// PrivilegedActionHandler is called by contract-based authorities to handle +// privileged actions. +type PrivilegedActionHandler func(title string, action PrivilegedAction) error + +// New creates a new Authorizer with the current realm's address as authority +func New() *Authorizer { + return &Authorizer{ + current: NewMemberAuthority(std.PreviousRealm().Address()), + } +} + +// NewWithAuthority creates a new Authorizer with a specific authority +func NewWithAuthority(authority Authority) *Authorizer { + return &Authorizer{ + current: authority, + } +} + +// Current returns the current authority implementation +func (a *Authorizer) Current() Authority { + return a.current +} + +// Transfer changes the current authority after validation +func (a *Authorizer) Transfer(newAuthority Authority) error { + // Ask current authority to validate the transfer + return a.current.Authorize("transfer_authority", func() error { + a.current = newAuthority + return nil + }) +} + +// Do executes a privileged action through the current authority +func (a *Authorizer) Do(title string, action PrivilegedAction, args ...interface{}) error { + return a.current.Authorize(title, action, args...) +} + +// String returns a string representation of the current authority +func (a *Authorizer) String() string { + return a.current.String() +} + +// MemberAuthority is the default implementation using addrset for member management +type MemberAuthority struct { + members addrset.Set +} + +func NewMemberAuthority(members ...std.Address) *MemberAuthority { + auth := &MemberAuthority{} + for _, addr := range members { + auth.members.Add(addr) + } + return auth +} + +func (a *MemberAuthority) Authorize(title string, action PrivilegedAction, args ...interface{}) error { + caller := std.PreviousRealm().Address() + if !a.members.Has(caller) { + return errors.New("unauthorized") + } + + if err := action(); err != nil { + return err + } + return nil +} + +func (a *MemberAuthority) String() string { + return ufmt.Sprintf("member_authority[size=%d]", a.members.Size()) +} + +// AddMember adds a new member to the authority +func (a *MemberAuthority) AddMember(addr std.Address) error { + return a.Authorize("add_member", func() error { + a.members.Add(addr) + return nil + }) +} + +// RemoveMember removes a member from the authority +func (a *MemberAuthority) RemoveMember(addr std.Address) error { + return a.Authorize("remove_member", func() error { + a.members.Remove(addr) + return nil + }) +} + +// Tree returns a read-only view of the members tree +func (a *MemberAuthority) Tree() *rotree.ReadOnlyTree { + tree := a.members.Tree().(*avl.Tree) + return rotree.Wrap(tree, nil) +} + +// Has checks if the given address is a member of the authority +func (a *MemberAuthority) Has(addr std.Address) bool { + return a.members.Has(addr) +} + +// ContractAuthority implements async contract-based authority +type ContractAuthority struct { + contractPath string + contractAddr std.Address + contractHandler PrivilegedActionHandler + proposer Authority // controls who can create proposals + executionOnce once.Once +} + +func NewContractAuthority(path string, handler PrivilegedActionHandler) *ContractAuthority { + return &ContractAuthority{ + contractPath: path, + contractAddr: std.DerivePkgAddr(path), + contractHandler: handler, + proposer: NewAutoAcceptAuthority(), // default: anyone can propose + executionOnce: once.Once{}, // initialize execution once + } +} + +// NewRestrictedContractAuthority creates a new contract authority with a proposer restriction +func NewRestrictedContractAuthority(path string, handler PrivilegedActionHandler, proposer Authority) Authority { + if path == "" { + panic("contract path cannot be empty") + } + if handler == nil { + panic("contract handler cannot be nil") + } + if proposer == nil { + panic("proposer cannot be nil") + } + return &ContractAuthority{ + contractPath: path, + contractAddr: std.DerivePkgAddr(path), + contractHandler: handler, + proposer: proposer, + executionOnce: once.Once{}, + } +} + +func (a *ContractAuthority) Authorize(title string, action PrivilegedAction, args ...interface{}) error { + if a.contractHandler == nil { + return errors.New("contract handler is not set") + } + + // Wrap the action to ensure it can only be executed by the contract + wrappedAction := func() error { + caller := std.PreviousRealm().Address() + if caller != a.contractAddr { + return errors.New("action can only be executed by the contract") + } + return a.executionOnce.DoErr(func() error { + return action() + }) + } + + // Use the proposer authority to control who can create proposals + return a.proposer.Authorize(title+"_proposal", func() error { + if err := a.contractHandler(title, wrappedAction); err != nil { + return err + } + return nil + }, args...) +} + +func (a *ContractAuthority) String() string { + return ufmt.Sprintf("contract_authority[contract=%s]", a.contractPath) +} + +// AutoAcceptAuthority implements an authority that accepts all actions +// AutoAcceptAuthority is a simple authority that automatically accepts all actions. +// It can be used as a proposer authority to allow anyone to create proposals. +type AutoAcceptAuthority struct{} + +func NewAutoAcceptAuthority() *AutoAcceptAuthority { + return &AutoAcceptAuthority{} +} + +func (a *AutoAcceptAuthority) Authorize(title string, action PrivilegedAction, args ...interface{}) error { + return action() +} + +func (a *AutoAcceptAuthority) String() string { + return "auto_accept_authority" +} + +// droppedAuthority implements an authority that denies all actions +type droppedAuthority struct{} + +func NewDroppedAuthority() Authority { + return &droppedAuthority{} +} + +func (a *droppedAuthority) Authorize(title string, action PrivilegedAction, args ...interface{}) error { + return errors.New("dropped authority: all actions are denied") +} + +func (a *droppedAuthority) String() string { + return "dropped_authority" +} diff --git a/examples/gno.land/p/moul/authz/authz_test.gno b/examples/gno.land/p/moul/authz/authz_test.gno new file mode 100644 index 00000000000..f8915cc2448 --- /dev/null +++ b/examples/gno.land/p/moul/authz/authz_test.gno @@ -0,0 +1,384 @@ +package authz + +import ( + "errors" + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" +) + +func TestNew(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Check that the current authority is a MemberAuthority + memberAuth, ok := auth.Current().(*MemberAuthority) + uassert.True(t, ok, "expected MemberAuthority") + + // Check that the caller is a member + uassert.True(t, memberAuth.Has(testAddr), "caller should be a member") + + // Check string representation + expected := ufmt.Sprintf("member_authority[size=%d]", 1) + uassert.Equal(t, expected, auth.String()) +} + +func TestNewWithAuthority(t *testing.T) { + testAddr := testutils.TestAddress("alice") + memberAuth := NewMemberAuthority(testAddr) + + auth := NewWithAuthority(memberAuth) + + // Check that the current authority is the one we provided + uassert.True(t, auth.Current() == memberAuth, "expected provided authority") +} + +func TestAuthorizerAuthorize(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Test successful action with args + executed := false + args := []interface{}{"test_arg", 123} + err := auth.Do("test_action", func() error { + executed = true + return nil + }, args...) + + uassert.True(t, err == nil, "expected no error") + uassert.True(t, executed, "action should have been executed") + + // Test unauthorized action with args + std.TestSetOriginCaller(testutils.TestAddress("bob")) + + executed = false + err = auth.Do("test_action", func() error { + executed = true + return nil + }, "unauthorized_arg") + + uassert.True(t, err != nil, "expected error") + uassert.False(t, executed, "action should not have been executed") + + // Test action returning error + std.TestSetOriginCaller(testAddr) + expectedErr := errors.New("test error") + + err = auth.Do("test_action", func() error { + return expectedErr + }) + + uassert.True(t, err == expectedErr, "expected specific error") +} + +func TestAuthorizerTransfer(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Test transfer to new member authority + newAddr := testutils.TestAddress("bob") + newAuth := NewMemberAuthority(newAddr) + + err := auth.Transfer(newAuth) + uassert.True(t, err == nil, "expected no error") + uassert.True(t, auth.Current() == newAuth, "expected new authority") + + // Test unauthorized transfer + std.TestSetOriginCaller(testutils.TestAddress("carol")) + + err = auth.Transfer(NewMemberAuthority(testAddr)) + uassert.True(t, err != nil, "expected error") + + // Test transfer to contract authority + std.TestSetOriginCaller(newAddr) + + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + return action() + }) + + err = auth.Transfer(contractAuth) + uassert.True(t, err == nil, "expected no error") + uassert.True(t, auth.Current() == contractAuth, "expected contract authority") +} + +func TestAuthorizerTransferChain(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + // Create a chain of transfers + auth := New() + + // First transfer to a new member authority + newAddr := testutils.TestAddress("bob") + memberAuth := NewMemberAuthority(newAddr) + + err := auth.Transfer(memberAuth) + uassert.True(t, err == nil, "unexpected error in first transfer") + + // Then transfer to a contract authority + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + return action() + }) + + std.TestSetOriginCaller(newAddr) + err = auth.Transfer(contractAuth) + uassert.True(t, err == nil, "unexpected error in second transfer") + + // Finally transfer to an auto-accept authority + autoAuth := NewAutoAcceptAuthority() + + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = auth.Transfer(autoAuth) + uassert.True(t, err == nil, "unexpected error in final transfer") + uassert.True(t, auth.Current() == autoAuth, "expected auto-accept authority") +} + +func TestAuthorizerWithDroppedAuthority(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Transfer to dropped authority + err := auth.Transfer(NewDroppedAuthority()) + uassert.True(t, err == nil, "expected no error") + + // Try to execute action + err = auth.Do("test_action", func() error { + return nil + }) + uassert.True(t, err != nil, "expected error from dropped authority") + + // Try to transfer again + err = auth.Transfer(NewMemberAuthority(testAddr)) + uassert.True(t, err != nil, "expected error when transferring from dropped authority") +} + +func TestContractAuthorityExecutionOnce(t *testing.T) { + attempts := 0 + executed := 0 + var capturedArgs []interface{} + + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + // Try to execute the action twice in the same handler + if err := action(); err != nil { + return err + } + attempts++ + + // Second execution should fail + if err := action(); err != nil { + return err + } + attempts++ + return nil + }) + + // Set caller to contract address + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + + testArgs := []interface{}{"proposal_id", 42, "metadata", map[string]string{"key": "value"}} + err := contractAuth.Authorize("test_action", func() error { + executed++ + return nil + }, testArgs...) + + uassert.True(t, err == nil, "handler execution should succeed") + uassert.True(t, attempts == 2, "handler should have attempted execution twice") + uassert.True(t, executed == 1, "handler should have executed once") +} + +func TestContractAuthorityWithProposer(t *testing.T) { + testAddr := testutils.TestAddress("alice") + memberAuth := NewMemberAuthority(testAddr) + + handlerCalled := false + actionExecuted := false + + contractAuth := NewRestrictedContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + handlerCalled = true + // Set caller to contract address before executing action + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + return action() + }, memberAuth) + + // Test authorized member + std.TestSetOriginCaller(testAddr) + testArgs := []interface{}{"proposal_metadata", "test value"} + err := contractAuth.Authorize("test_action", func() error { + actionExecuted = true + return nil + }, testArgs...) + + uassert.True(t, err == nil, "authorized member should be able to propose") + uassert.True(t, handlerCalled, "contract handler should be called") + uassert.True(t, actionExecuted, "action should be executed") + + // Reset flags for unauthorized test + handlerCalled = false + actionExecuted = false + + // Test unauthorized proposer + std.TestSetOriginCaller(testutils.TestAddress("bob")) + err = contractAuth.Authorize("test_action", func() error { + actionExecuted = true + return nil + }, testArgs...) + + uassert.True(t, err != nil, "unauthorized member should not be able to propose") + uassert.False(t, handlerCalled, "contract handler should not be called for unauthorized proposer") + uassert.False(t, actionExecuted, "action should not be executed for unauthorized proposer") +} + +func TestAutoAcceptAuthority(t *testing.T) { + auth := NewAutoAcceptAuthority() + + // Test that any action is authorized + executed := false + err := auth.Authorize("test_action", func() error { + executed = true + return nil + }) + + uassert.True(t, err == nil, "auto-accept should not return error") + uassert.True(t, executed, "action should have been executed") + + // Test with different caller + std.TestSetOriginCaller(testutils.TestAddress("random")) + executed = false + err = auth.Authorize("test_action", func() error { + executed = true + return nil + }) + + uassert.True(t, err == nil, "auto-accept should not care about caller") + uassert.True(t, executed, "action should have been executed") +} + +func TestAutoAcceptAuthorityWithArgs(t *testing.T) { + auth := NewAutoAcceptAuthority() + + // Test that any action is authorized with args + executed := false + testArgs := []interface{}{"arg1", 42, "arg3"} + err := auth.Authorize("test_action", func() error { + executed = true + return nil + }, testArgs...) + + uassert.True(t, err == nil, "auto-accept should not return error") + uassert.True(t, executed, "action should have been executed") +} + +func TestMemberAuthorityMultipleMembers(t *testing.T) { + alice := testutils.TestAddress("alice") + bob := testutils.TestAddress("bob") + carol := testutils.TestAddress("carol") + + // Create authority with multiple members + auth := NewMemberAuthority(alice, bob) + + // Test that both members can execute actions + for _, member := range []std.Address{alice, bob} { + std.TestSetOriginCaller(member) + err := auth.Authorize("test_action", func() error { + return nil + }) + uassert.True(t, err == nil, "member should be authorized") + } + + // Test that non-member cannot execute + std.TestSetOriginCaller(carol) + err := auth.Authorize("test_action", func() error { + return nil + }) + uassert.True(t, err != nil, "non-member should not be authorized") + + // Test Tree() functionality + tree := auth.Tree() + uassert.True(t, tree.Size() == 2, "tree should have 2 members") + + // Verify both members are in the tree + found := make(map[std.Address]bool) + tree.Iterate("", "", func(key string, _ interface{}) bool { + found[std.Address(key)] = true + return false + }) + uassert.True(t, found[alice], "alice should be in the tree") + uassert.True(t, found[bob], "bob should be in the tree") + uassert.False(t, found[carol], "carol should not be in the tree") + + // Test read-only nature of the tree + defer func() { + r := recover() + uassert.True(t, r != nil, "modifying read-only tree should panic") + }() + tree.Set(string(carol), nil) // This should panic +} + +func TestAuthorizerCurrentNeverNil(t *testing.T) { + auth := New() + + // Current should never be nil after initialization + uassert.True(t, auth.Current() != nil, "current authority should not be nil") + + // Current should not be nil after transfer + err := auth.Transfer(NewAutoAcceptAuthority()) + uassert.True(t, err == nil, "transfer should succeed") + uassert.True(t, auth.Current() != nil, "current authority should not be nil after transfer") +} + +func TestAuthorizerString(t *testing.T) { + auth := New() + + // Test initial string representation + str := auth.String() + uassert.True(t, str != "", "string representation should not be empty") + + // Test string after transfer + autoAuth := NewAutoAcceptAuthority() + err := auth.Transfer(autoAuth) + uassert.True(t, err == nil, "transfer should succeed") + + newStr := auth.String() + uassert.True(t, newStr != "", "string representation should not be empty") + uassert.True(t, newStr != str, "string should change after transfer") +} + +func TestContractAuthorityValidation(t *testing.T) { + // Test empty path + auth := NewContractAuthority("", nil) + std.TestSetOriginCaller(std.DerivePkgAddr("")) + err := auth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err != nil, "empty path authority should fail to authorize") + + // Test nil handler + auth = NewContractAuthority("gno.land/r/test", nil) + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = auth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err != nil, "nil handler authority should fail to authorize") + + // Test valid configuration + handler := func(title string, action PrivilegedAction) error { + return nil + } + contractAuth := NewContractAuthority("gno.land/r/test", handler) + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = contractAuth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err == nil, "valid contract authority should authorize successfully") +} diff --git a/examples/gno.land/p/moul/authz/example_test.gno b/examples/gno.land/p/moul/authz/example_test.gno new file mode 100644 index 00000000000..a283204dfc2 --- /dev/null +++ b/examples/gno.land/p/moul/authz/example_test.gno @@ -0,0 +1,88 @@ +package authz + +import ( + "std" +) + +// Example_basic demonstrates initializing and using a basic member authority +func Example_basic() { + // Initialize with contract deployer as authority + auth := New() + + // Use the authority to perform a privileged action + auth.Do("update_config", func() error { + // config = newValue + return nil + }) +} + +// Example_addingMembers demonstrates how to add new members to a member authority +func Example_addingMembers() { + // Initialize with contract deployer as authority + auth := New() + + // Add a new member to the authority + memberAuth := auth.Current().(*MemberAuthority) + memberAuth.AddMember(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) +} + +// Example_contractAuthority demonstrates using a contract-based authority +func Example_contractAuthority() { + // Initialize with contract authority (e.g., DAO) + auth := NewWithAuthority( + NewContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, // defined elsewhere for example + ), + ) + + // Privileged actions will be handled by the contract + auth.Do("update_params", func() error { + // Executes after DAO approval + return nil + }) +} + +// Example_restrictedContractAuthority demonstrates a contract authority with member-only proposals +func Example_restrictedContractAuthority() { + // Initialize member authority for proposers + proposerAuth := NewMemberAuthority( + std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), // admin1 + std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"), // admin2 + ) + + // Create contract authority with restricted proposers + auth := NewWithAuthority( + NewRestrictedContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, + proposerAuth, + ), + ) + + // Only members can propose, and contract must approve + auth.Do("update_params", func() error { + // Executes after: + // 1. Proposer initiates + // 2. DAO approves + return nil + }) +} + +// Example_switchingAuthority demonstrates switching from member to contract authority +func Example_switchingAuthority() { + // Start with member authority (deployer) + auth := New() + + // Create and switch to contract authority + daoAuthority := NewContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, + ) + auth.Transfer(daoAuthority) +} + +// Mock handler for examples +func mockDAOHandler(title string, action PrivilegedAction) error { + return action() +} diff --git a/examples/gno.land/p/moul/authz/gno.mod b/examples/gno.land/p/moul/authz/gno.mod new file mode 100644 index 00000000000..2ae057d875b --- /dev/null +++ b/examples/gno.land/p/moul/authz/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/authz diff --git a/examples/gno.land/p/moul/once/gno.mod b/examples/gno.land/p/moul/once/gno.mod new file mode 100644 index 00000000000..29afe6841b2 --- /dev/null +++ b/examples/gno.land/p/moul/once/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/once diff --git a/examples/gno.land/p/moul/once/once.gno b/examples/gno.land/p/moul/once/once.gno new file mode 100644 index 00000000000..b9b6054c037 --- /dev/null +++ b/examples/gno.land/p/moul/once/once.gno @@ -0,0 +1,100 @@ +// Package once provides utilities for one-time execution patterns. +// It extends the concept of sync.Once with error handling and panic options. +package once + +import ( + "errors" +) + +// Once represents a one-time execution guard +type Once struct { + done bool + err error + paniced bool + value interface{} // stores the result of the execution +} + +// New creates a new Once instance +func New() *Once { + return &Once{} +} + +// Do executes fn only once and returns nil on subsequent calls +func (o *Once) Do(fn func()) { + if o.done { + return + } + defer func() { o.done = true }() + fn() +} + +// DoErr executes fn only once and returns the same error on subsequent calls +func (o *Once) DoErr(fn func() error) error { + if o.done { + return o.err + } + defer func() { o.done = true }() + o.err = fn() + return o.err +} + +// DoOrPanic executes fn only once and panics on subsequent calls +func (o *Once) DoOrPanic(fn func()) { + if o.done { + panic("once: multiple execution attempted") + } + defer func() { o.done = true }() + fn() +} + +// DoValue executes fn only once and returns its value, subsequent calls return the cached value +func (o *Once) DoValue(fn func() interface{}) interface{} { + if o.done { + return o.value + } + defer func() { o.done = true }() + o.value = fn() + return o.value +} + +// DoValueErr executes fn only once and returns its value and error +// Subsequent calls return the cached value and error +func (o *Once) DoValueErr(fn func() (interface{}, error)) (interface{}, error) { + if o.done { + return o.value, o.err + } + defer func() { o.done = true }() + o.value, o.err = fn() + return o.value, o.err +} + +// Reset resets the Once instance to its initial state +// This is mainly useful for testing purposes +func (o *Once) Reset() { + o.done = false + o.err = nil + o.paniced = false + o.value = nil +} + +// IsDone returns whether the Once has been executed +func (o *Once) IsDone() bool { + return o.done +} + +// Error returns the error from the last execution if any +func (o *Once) Error() error { + return o.err +} + +var ( + ErrNotExecuted = errors.New("once: not executed yet") +) + +// Value returns the stored value and an error if not executed yet +func (o *Once) Value() (interface{}, error) { + if !o.done { + return nil, ErrNotExecuted + } + return o.value, nil +} diff --git a/examples/gno.land/p/moul/once/once_test.gno b/examples/gno.land/p/moul/once/once_test.gno new file mode 100644 index 00000000000..7d6114f4a5a --- /dev/null +++ b/examples/gno.land/p/moul/once/once_test.gno @@ -0,0 +1,231 @@ +package once + +import ( + "errors" + "testing" +) + +func TestOnce_Do(t *testing.T) { + counter := 0 + once := New() + + increment := func() { + counter++ + } + + // First call should execute + once.Do(increment) + if counter != 1 { + t.Errorf("expected counter to be 1, got %d", counter) + } + + // Second call should not execute + once.Do(increment) + if counter != 1 { + t.Errorf("expected counter to still be 1, got %d", counter) + } +} + +func TestOnce_DoErr(t *testing.T) { + once := New() + expectedErr := errors.New("test error") + + fn := func() error { + return expectedErr + } + + // First call should return error + if err := once.DoErr(fn); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } + + // Second call should return same error + if err := once.DoErr(fn); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } +} + +func TestOnce_DoOrPanic(t *testing.T) { + once := New() + executed := false + + fn := func() { + executed = true + } + + // First call should execute + once.DoOrPanic(fn) + if !executed { + t.Error("function should have executed") + } + + // Second call should panic + defer func() { + if r := recover(); r == nil { + t.Error("expected panic on second execution") + } + }() + once.DoOrPanic(fn) +} + +func TestOnce_DoValue(t *testing.T) { + once := New() + expected := "test value" + counter := 0 + + fn := func() interface{} { + counter++ + return expected + } + + // First call should return value + if result := once.DoValue(fn); result != expected { + t.Errorf("expected %v, got %v", expected, result) + } + + // Second call should return cached value + if result := once.DoValue(fn); result != expected { + t.Errorf("expected %v, got %v", expected, result) + } + + if counter != 1 { + t.Errorf("function should have executed only once, got %d executions", counter) + } +} + +func TestOnce_DoValueErr(t *testing.T) { + once := New() + expectedVal := "test value" + expectedErr := errors.New("test error") + counter := 0 + + fn := func() (interface{}, error) { + counter++ + return expectedVal, expectedErr + } + + // First call should return value and error + val, err := once.DoValueErr(fn) + if val != expectedVal || err != expectedErr { + t.Errorf("expected (%v, %v), got (%v, %v)", expectedVal, expectedErr, val, err) + } + + // Second call should return cached value and error + val, err = once.DoValueErr(fn) + if val != expectedVal || err != expectedErr { + t.Errorf("expected (%v, %v), got (%v, %v)", expectedVal, expectedErr, val, err) + } + + if counter != 1 { + t.Errorf("function should have executed only once, got %d executions", counter) + } +} + +func TestOnce_Reset(t *testing.T) { + once := New() + counter := 0 + + fn := func() { + counter++ + } + + once.Do(fn) + if counter != 1 { + t.Errorf("expected counter to be 1, got %d", counter) + } + + once.Reset() + once.Do(fn) + if counter != 2 { + t.Errorf("expected counter to be 2 after reset, got %d", counter) + } +} + +func TestOnce_IsDone(t *testing.T) { + once := New() + + if once.IsDone() { + t.Error("new Once instance should not be done") + } + + once.Do(func() {}) + + if !once.IsDone() { + t.Error("Once instance should be done after execution") + } +} + +func TestOnce_Error(t *testing.T) { + once := New() + expectedErr := errors.New("test error") + + if err := once.Error(); err != nil { + t.Errorf("expected nil error, got %v", err) + } + + once.DoErr(func() error { + return expectedErr + }) + + if err := once.Error(); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } +} + +func TestOnce_Value(t *testing.T) { + once := New() + + // Test unexecuted state + val, err := once.Value() + if err != ErrNotExecuted { + t.Errorf("expected ErrNotExecuted, got %v", err) + } + if val != nil { + t.Errorf("expected nil value, got %v", val) + } + + // Test after execution + expected := "test value" + once.DoValue(func() interface{} { + return expected + }) + + val, err = once.Value() + if err != nil { + t.Errorf("expected nil error, got %v", err) + } + if val != expected { + t.Errorf("expected value %v, got %v", expected, val) + } +} + +func TestOnce_DoValueErr_Panic_MarkedDone(t *testing.T) { + once := New() + count := 0 + fn := func() (interface{}, error) { + count++ + panic("panic") + } + var r interface{} + func() { + defer func() { r = recover() }() + once.DoValueErr(fn) + }() + if r == nil { + t.Error("expected panic on first call") + } + if !once.IsDone() { + t.Error("expected once to be marked as done after panic") + } + r = nil + func() { + defer func() { r = recover() }() + once.DoValueErr(fn) + }() + if r != nil { + t.Error("expected no panic on subsequent call") + } + if count != 1 { + t.Errorf("expected count to be 1, got %d", count) + } +} diff --git a/gno.land/pkg/integration/testdata/moul_authz.txtar b/gno.land/pkg/integration/testdata/moul_authz.txtar new file mode 100644 index 00000000000..d0da3f535e8 --- /dev/null +++ b/gno.land/pkg/integration/testdata/moul_authz.txtar @@ -0,0 +1,82 @@ +loadpkg gno.land/p/moul/authz +loadpkg gno.land/r/testing/admin $WORK/admin +loadpkg gno.land/r/testing/resource $WORK/resource + +adduserfrom alice 'smooth crawl poverty trumpet glare useful curtain annual pluck lunar example merge ready forum better verb rescue rule mechanic dynamic drift bench release weekend' +stdout 'g1rfznvu6qfa0sc76cplk5wpqexvefqccjunady0' + +gnoland start + +gnokey maketx call -pkgpath gno.land/r/testing/resource -func Edit -args edited -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice + +gnokey maketx call -pkgpath gno.land/r/testing/admin -func ExecuteAction -args 0 -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice + +gnokey maketx call -pkgpath gno.land/r/testing/resource -func Value -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice +stdout 'edited' + + +-- admin/gno.mod -- +module gno.land/r/testing/admin + +-- admin/admin.gno -- +package admin + +import ( + "std" + "errors" + "gno.land/p/moul/authz" +) + +type prop struct { + title string + action authz.PrivilegedAction +} + +var props []*prop + +func HandlePrivilegedAction(title string, action authz.PrivilegedAction) error { + if std.PreviousRealm().PkgPath() != "gno.land/r/testing/resource" { + return errors.New("unauthorized proposer") + } + props = append(props, &prop{title: title, action: action}) + return nil +} + +func ExecuteAction(index int) { + if std.PreviousRealm().Address() != "g1rfznvu6qfa0sc76cplk5wpqexvefqccjunady0" { + panic(errors.New("not alice")) + } + if err := props[index].action(); err != nil { + panic(err) + } +} + +-- resource/gno.mod -- +module gno.land/r/testing/resource + +-- resource/resource.gno -- +package resource + +import ( + "gno.land/p/moul/authz" + "gno.land/r/testing/admin" +) + +var a *authz.Authorizer = authz.NewWithAuthority(authz.NewContractAuthority("gno.land/r/testing/admin", admin.HandlePrivilegedAction)) + +var value = "init" + +func Value() string { + return value +} + +func Edit(newValue string) { + doEdit := func() error { + value = newValue + return nil + } + if err := a.Do("Edit", doEdit); err != nil { + panic(err) + } +} + From 3288fe82f77afb475796a6ebbe395975ee6f9f0f Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Tue, 25 Feb 2025 17:25:07 +0900 Subject: [PATCH 45/47] fix: CSP to allow pixel gif from simple analytics (#3822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to #3619 This PR updates the Content Security Policy (CSP) to allow Simple Analytics to function correctly. The img-src directive now includes https://sa.gno.services, enabling the tracking pixel to load without being blocked. The tracking pixel (a 1×1 GIF) is essential for Simple Analytics to collect pageview data without requiring JavaScript. When loaded, it sends necessary metadata (e.g., page URL, referrer, user-agent) via the request, allowing privacy-friendly analytics to work. Without this fix, Simple Analytics is unable to capture visits. --- gno.land/cmd/gnoweb/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 0bd38489ff6..8e0b64122a6 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -242,7 +242,7 @@ func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { // - 'self' allows resources from the same origin. // - 'data:' allows inline images (e.g., base64-encoded images). // - 'https://gnolang.github.io' allows images from this specific domain - used by gno.land. TODO: use a proper generic whitelisted service - w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' https://sa.gno.services; style-src 'self'; img-src 'self' data: https://gnolang.github.io; font-src 'self'") + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' https://sa.gno.services; style-src 'self'; img-src 'self' data: https://gnolang.github.io https://sa.gno.services; font-src 'self'") // Enforce HTTPS by telling browsers to only access the site over HTTPS // for a specified duration (1 year in this case). This also applies to From 6bf3889b69bd46139abe8a308beec3deda679d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jer=C3=B3nimo=20Albi?= Date: Tue, 25 Feb 2025 17:35:24 +0100 Subject: [PATCH 46/47] feat(examples): add `p/jeronimoalbi/pager` package (#3806) Package implements a way to support paging in generic cases that don't use an AVL trees. --- .../gno.land/p/jeronimoalbi/pager/gno.mod | 1 + .../gno.land/p/jeronimoalbi/pager/pager.gno | 204 ++++++++++++++++++ .../p/jeronimoalbi/pager/pager_options.gno | 33 +++ .../p/jeronimoalbi/pager/pager_test.gno | 204 ++++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 examples/gno.land/p/jeronimoalbi/pager/gno.mod create mode 100644 examples/gno.land/p/jeronimoalbi/pager/pager.gno create mode 100644 examples/gno.land/p/jeronimoalbi/pager/pager_options.gno create mode 100644 examples/gno.land/p/jeronimoalbi/pager/pager_test.gno diff --git a/examples/gno.land/p/jeronimoalbi/pager/gno.mod b/examples/gno.land/p/jeronimoalbi/pager/gno.mod new file mode 100644 index 00000000000..e775954b9fe --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/gno.mod @@ -0,0 +1 @@ +module gno.land/p/jeronimoalbi/pager diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager.gno b/examples/gno.land/p/jeronimoalbi/pager/pager.gno new file mode 100644 index 00000000000..7b8a1948b3b --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager.gno @@ -0,0 +1,204 @@ +// Package pager provides pagination functionality through a generic pager implementation. +// +// Example usage: +// +// import ( +// "strconv" +// "strings" +// +// "gno.land/p/jeronimoalbi/pager" +// ) +// +// func Render(path string) string { +// // Define the items to paginate +// items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} +// +// // Create a pager that paginates 4 items at a time +// p, err := pager.New(path, len(items), pager.WithPageSize(4)) +// if err != nil { +// panic(err) +// } +// +// // Render items for the current page +// var output strings.Builder +// p.Iterate(func(i int) bool { +// output.WriteString("- " + strconv.Itoa(items[i]) + "\n") +// return false +// }) +// +// // Render page picker +// if p.HasPages() { +// output.WriteString("\n" + pager.Picker(p)) +// } +// +// return output.String() +// } +package pager + +import ( + "errors" + "math" + "net/url" + "strconv" + "strings" +) + +var ErrInvalidPageNumber = errors.New("invalid page number") + +// PagerIterFn defines a callback to iterate page items. +type PagerIterFn func(index int) (stop bool) + +// New creates a new pager. +func New(rawURL string, totalItems int, options ...PagerOption) (Pager, error) { + u, err := url.Parse(rawURL) + if err != nil { + return Pager{}, err + } + + p := Pager{ + query: u.RawQuery, + pageQueryParam: DefaultPageQueryParam, + pageSize: DefaultPageSize, + page: 1, + totalItems: totalItems, + } + for _, apply := range options { + apply(&p) + } + + p.pageCount = int(math.Ceil(float64(p.totalItems) / float64(p.pageSize))) + + rawPage := u.Query().Get(p.pageQueryParam) + if rawPage != "" { + p.page, _ = strconv.Atoi(rawPage) + if p.page == 0 || p.page > p.pageCount { + return Pager{}, ErrInvalidPageNumber + } + } + + return p, nil +} + +// MustNew creates a new pager or panics if there is an error. +func MustNew(rawURL string, totalItems int, options ...PagerOption) Pager { + p, err := New(rawURL, totalItems, options...) + if err != nil { + panic(err) + } + return p +} + +// Pager allows paging items. +type Pager struct { + query, pageQueryParam string + pageSize, page, pageCount, totalItems int +} + +// TotalItems returns the total number of items to paginate. +func (p Pager) TotalItems() int { + return p.totalItems +} + +// PageSize returns the size of each page. +func (p Pager) PageSize() int { + return p.pageSize +} + +// Page returns the current page number. +func (p Pager) Page() int { + return p.page +} + +// PageCount returns the number pages. +func (p Pager) PageCount() int { + return p.pageCount +} + +// Offset returns the index of the first page item. +func (p Pager) Offset() int { + return (p.page - 1) * p.pageSize +} + +// HasPages checks if pager has more than one page. +func (p Pager) HasPages() bool { + return p.pageCount > 1 +} + +// GetPageURI returns the URI for a page. +// An empty string is returned when page doesn't exist. +func (p Pager) GetPageURI(page int) string { + if page < 1 || page > p.PageCount() { + return "" + } + + values, _ := url.ParseQuery(p.query) + values.Set(p.pageQueryParam, strconv.Itoa(page)) + return "?" + values.Encode() +} + +// PrevPageURI returns the URI path to the previous page. +// An empty string is returned when current page is the first page. +func (p Pager) PrevPageURI() string { + if p.page == 1 || !p.HasPages() { + return "" + } + return p.GetPageURI(p.page - 1) +} + +// NextPageURI returns the URI path to the next page. +// An empty string is returned when current page is the last page. +func (p Pager) NextPageURI() string { + if p.page == p.pageCount { + // Current page is the last page + return "" + } + return p.GetPageURI(p.page + 1) +} + +// Iterate allows iterating page items. +func (p Pager) Iterate(fn PagerIterFn) bool { + if p.totalItems == 0 { + return true + } + + start := p.Offset() + end := start + p.PageSize() + if end > p.totalItems { + end = p.totalItems + } + + for i := start; i < end; i++ { + if fn(i) { + return true + } + } + return false +} + +// TODO: Support different types of pickers (ex. with clickable page numbers) + +// Picker returns a string with the pager as Markdown. +// An empty string is returned when the pager has no pages. +func Picker(p Pager) string { + if !p.HasPages() { + return "" + } + + var out strings.Builder + + if s := p.PrevPageURI(); s != "" { + out.WriteString("[«](" + s + ") | ") + } else { + out.WriteString("\\- | ") + } + + out.WriteString("page " + strconv.Itoa(p.Page()) + " of " + strconv.Itoa(p.PageCount())) + + if s := p.NextPageURI(); s != "" { + out.WriteString(" | [»](" + s + ")") + } else { + out.WriteString(" | \\-") + } + + return out.String() +} diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno b/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno new file mode 100644 index 00000000000..3feb467682b --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno @@ -0,0 +1,33 @@ +package pager + +import "strings" + +const ( + DefaultPageSize = 50 + DefaultPageQueryParam = "page" +) + +// PagerOption configures the pager. +type PagerOption func(*Pager) + +// WithPageSize assigns a page size to a pager. +func WithPageSize(size int) PagerOption { + return func(p *Pager) { + if size < 1 { + p.pageSize = DefaultPageSize + } else { + p.pageSize = size + } + } +} + +// WithPageQueryParam assigns the name of the URL query param for the page value. +func WithPageQueryParam(name string) PagerOption { + return func(p *Pager) { + name = strings.TrimSpace(name) + if name == "" { + name = DefaultPageQueryParam + } + p.pageQueryParam = name + } +} diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno b/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno new file mode 100644 index 00000000000..f498079cad3 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno @@ -0,0 +1,204 @@ +package pager + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestPager(t *testing.T) { + cases := []struct { + name, uri, prevPath, nextPath, param string + offset, pageSize, page, pageCount int + hasPages bool + items []int + err error + }{ + { + name: "page 1", + uri: "gno.land/r/demo/test:foo/bar?page=1&foo=bar", + items: []int{1, 2, 3, 4, 5, 6}, + hasPages: true, + nextPath: "?foo=bar&page=2", + pageSize: 5, + page: 1, + pageCount: 2, + }, + { + name: "page 2", + uri: "gno.land/r/demo/test:foo/bar?page=2&foo=bar", + items: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + hasPages: true, + prevPath: "?foo=bar&page=1", + nextPath: "", + offset: 5, + pageSize: 5, + page: 2, + pageCount: 2, + }, + { + name: "custom query param", + uri: "gno.land/r/demo/test:foo/bar?current=2&foo=bar", + items: []int{1, 2, 3}, + param: "current", + hasPages: true, + prevPath: "?current=1&foo=bar", + nextPath: "", + offset: 2, + pageSize: 2, + page: 2, + pageCount: 2, + }, + { + name: "missing page", + uri: "gno.land/r/demo/test:foo/bar?page=3&foo=bar", + err: ErrInvalidPageNumber, + }, + { + name: "invalid page zero", + uri: "gno.land/r/demo/test:foo/bar?page=0", + err: ErrInvalidPageNumber, + }, + { + name: "invalid page number", + uri: "gno.land/r/demo/test:foo/bar?page=foo", + err: ErrInvalidPageNumber, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Act + p, err := New(tc.uri, len(tc.items), WithPageSize(tc.pageSize), WithPageQueryParam(tc.param)) + + // Assert + if tc.err != nil { + urequire.ErrorIs(t, err, tc.err, "expected an error") + return + } + + urequire.NoError(t, err, "expect no error") + uassert.Equal(t, len(tc.items), p.TotalItems(), "total items") + uassert.Equal(t, tc.page, p.Page(), "page number") + uassert.Equal(t, tc.pageCount, p.PageCount(), "number of pages") + uassert.Equal(t, tc.pageSize, p.PageSize(), "page size") + uassert.Equal(t, tc.prevPath, p.PrevPageURI(), "prev URL page") + uassert.Equal(t, tc.nextPath, p.NextPageURI(), "next URL page") + uassert.Equal(t, tc.hasPages, p.HasPages(), "has pages") + uassert.Equal(t, tc.offset, p.Offset(), "item offset") + }) + } +} + +func TestPagerIterate(t *testing.T) { + cases := []struct { + name, uri string + items, page []int + stop bool + }{ + { + name: "page 1", + uri: "gno.land/r/demo/test:foo/bar?page=1", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{1, 2, 3}, + }, + { + name: "page 2", + uri: "gno.land/r/demo/test:foo/bar?page=2", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{4, 5, 6}, + }, + { + name: "page 3", + uri: "gno.land/r/demo/test:foo/bar?page=3", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{7}, + }, + { + name: "stop iteration", + uri: "gno.land/r/demo/test:foo/bar?page=1", + items: []int{1, 2, 3}, + stop: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + var ( + items []int + p = MustNew(tc.uri, len(tc.items), WithPageSize(3)) + ) + + // Act + stopped := p.Iterate(func(i int) bool { + if tc.stop { + return true + } + + items = append(items, tc.items[i]) + return false + }) + + // Assert + uassert.Equal(t, tc.stop, stopped) + urequire.Equal(t, len(tc.page), len(items), "expect iteration of the right number of items") + + for i, v := range items { + urequire.Equal(t, tc.page[i], v, "expect iterated items to match") + } + }) + } +} + +func TestPicker(t *testing.T) { + pageSize := 3 + cases := []struct { + name, uri, output string + totalItems int + }{ + { + name: "one page", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 3, + output: "", + }, + { + name: "two pages", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 4, + output: "\\- | page 1 of 2 | [»](?page=2)", + }, + { + name: "three pages", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 7, + output: "\\- | page 1 of 3 | [»](?page=2)", + }, + { + name: "three pages second page", + uri: "gno.land/r/demo/test:foo/bar?page=2", + totalItems: 7, + output: "[«](?page=1) | page 2 of 3 | [»](?page=3)", + }, + { + name: "three pages third page", + uri: "gno.land/r/demo/test:foo/bar?page=3", + totalItems: 7, + output: "[«](?page=2) | page 3 of 3 | \\-", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + p := MustNew(tc.uri, tc.totalItems, WithPageSize(pageSize)) + + // Act + output := Picker(p) + + // Assert + uassert.Equal(t, tc.output, output) + }) + } +} From c433aa0c4049fababb0f2c95b7d94105cf81aa7a Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:56:48 +0100 Subject: [PATCH 47/47] chore(docs): rm mentions of staging testnet (#3828) ## Description Removes `staging` from the docs. --- docs/concepts/testnets.md | 16 ---------------- docs/reference/gno-js-client/gno-provider.md | 4 ++-- docs/reference/network-config.md | 1 - .../tm2-js-client/Provider/json-rpc-provider.md | 2 +- .../tm2-js-client/Provider/ws-provider.md | 10 +++++----- 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md index 9769c8f873f..40cf22ed7f0 100644 --- a/docs/concepts/testnets.md +++ b/docs/concepts/testnets.md @@ -63,22 +63,6 @@ Test5 was launched in November 2024. - **Versioning strategy**: - Test5 is to be release-based, following releases of the Gno tech stack. - -## Staging - -Staging is a testnet that is reset once every 60 minutes. - -- **Persistence of state:** - - State is fully discarded -- **Timeliness of code:** - - With every reset, the latest commit of the Gno tech stack is applied, including - the demo packages and realms -- **Intended purpose** - - Demoing, single-use code in a staging environment, testing automation which - uploads code to the chain, etc. -- **Versioning strategy**: - - Staging is reset every 60 minutes to match the latest monorepo commit - ## TestX These testnets are deprecated and currently serve as archives of previous progress. diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md index c76bfebfe31..cef6e04eb4a 100644 --- a/docs/reference/gno-js-client/gno-provider.md +++ b/docs/reference/gno-js-client/gno-provider.md @@ -20,7 +20,7 @@ Same as [`tm2-js-client` `WSProvider`](../tm2-js-client/Provider/ws-provider.md) #### Usage ```ts -new GnoWSProvider('ws://staging.gno.land:26657/ws'); +new GnoWSProvider('ws://gno.land:443/ws'); // provider with WS connection is created ``` @@ -35,7 +35,7 @@ Same as [`tm2-js-client` `JSONRPCProvider`](../tm2-js-client/Provider/json-rpc-p #### Usage ```ts -new GnoJSONRPCProvider('http://staging.gno.land:36657'); +new GnoJSONRPCProvider('https://gno.land:443'); // provider is created ``` diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md index 1e50864372b..025f9f3171f 100644 --- a/docs/reference/network-config.md +++ b/docs/reference/network-config.md @@ -8,7 +8,6 @@ id: network-config |-------------|----------------------------------|---------------| | Portal Loop | https://rpc.gno.land:443 | `portal-loop` | | Test5 | https://rpc.test5.gno.land:443 | `test5` | -| Staging | https://rpc.staging.gno.land:443 | `staging` | ### WebSocket endpoints All networks follow the same pattern for websocket connections: diff --git a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md index b7700e1d97c..284b383bd1c 100644 --- a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md +++ b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md @@ -17,6 +17,6 @@ Creates a new instance of the JSON-RPC Provider #### Usage ```ts -new JSONRPCProvider('http://staging.gno.land:36657'); +new JSONRPCProvider('https://gno.land:443'); // provider is created ``` diff --git a/docs/reference/tm2-js-client/Provider/ws-provider.md b/docs/reference/tm2-js-client/Provider/ws-provider.md index 9d4f2390c28..a2c4fa1d0c0 100644 --- a/docs/reference/tm2-js-client/Provider/ws-provider.md +++ b/docs/reference/tm2-js-client/Provider/ws-provider.md @@ -18,7 +18,7 @@ Creates a new instance of the WebSocket Provider #### Usage ```ts -new WSProvider('ws://staging.gno.land:26657/ws'); +new WSProvider('ws://gno.land:443/ws'); // provider with WS connection is created ``` @@ -30,7 +30,7 @@ with the WS provider #### Usage ```ts -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); +const wsProvider = new WSProvider('ws://gno.land:443/ws'); wsProvider.closeConnection(); // WS connection is now closed @@ -52,7 +52,7 @@ Returns **Promise>** ```ts const request: RPCRequest = // ... -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); +const wsProvider = new WSProvider('ws://gno.land:443/ws'); wsProvider.sendRequest(request); // request is sent over the open WS connection @@ -73,7 +73,7 @@ Returns **Result** ```ts const response: RPCResponse = // ... -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); +const wsProvider = new WSProvider('ws://gno.land:443/ws'); wsProvider.parseResponse(response); // response is parsed @@ -88,7 +88,7 @@ Returns **Promise** #### Usage ```ts -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); +const wsProvider = new WSProvider('ws://gno.land:443/ws'); await wsProvider.waitForOpenConnection() // status of the connection is: CONNECTED