From 49e718c0fb98fe221b8c990b4c539cee221dc4e3 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:32:06 -0500 Subject: [PATCH 01/34] feat: add `p/moul/txlink` + `p/moul/helplink` (#2887) This PR aimed to promote the use of a `p/` library for managing special help links from contracts. It also provided an opportunity for me to realize that our discussion about changing the `$` symbol would require some parsing and detection from the `gnoweb` perspective. If we want a simple library like this one, the goal should be to ideally craft a link to the current package without specifying the realm path. Relative URLs worked well with `?`, but they won't function with `$`. As an alternative, we can have this package look for `std.PrevRealm().PkgAddr` if it is not specified. cc @jeronimoalbi @thehowl @leohhhn Related with #2602 Related with #2876 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/moul/helplink/gno.mod | 6 ++ .../gno.land/p/moul/helplink/helplink.gno | 79 +++++++++++++++++++ .../p/moul/helplink/helplink_test.gno | 78 ++++++++++++++++++ examples/gno.land/p/moul/txlink/gno.mod | 3 + examples/gno.land/p/moul/txlink/txlink.gno | 74 +++++++++++++++++ .../gno.land/p/moul/txlink/txlink_test.gno | 37 +++++++++ examples/gno.land/r/demo/boards/board.gno | 5 +- examples/gno.land/r/demo/boards/gno.mod | 1 + examples/gno.land/r/demo/boards/post.gno | 30 +++---- .../gno.land/r/demo/boards/z_0_filetest.gno | 2 +- .../r/demo/boards/z_10_c_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_10_filetest.gno | 2 +- .../r/demo/boards/z_11_d_filetest.gno | 8 +- .../gno.land/r/demo/boards/z_11_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_12_filetest.gno | 2 +- .../gno.land/r/demo/boards/z_2_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_3_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_4_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_5_c_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_5_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_6_filetest.gno | 8 +- .../gno.land/r/demo/boards/z_7_filetest.gno | 2 +- .../gno.land/r/demo/boards/z_8_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_9_filetest.gno | 2 +- 24 files changed, 327 insertions(+), 50 deletions(-) create mode 100644 examples/gno.land/p/moul/helplink/gno.mod create mode 100644 examples/gno.land/p/moul/helplink/helplink.gno create mode 100644 examples/gno.land/p/moul/helplink/helplink_test.gno create mode 100644 examples/gno.land/p/moul/txlink/gno.mod create mode 100644 examples/gno.land/p/moul/txlink/txlink.gno create mode 100644 examples/gno.land/p/moul/txlink/txlink_test.gno diff --git a/examples/gno.land/p/moul/helplink/gno.mod b/examples/gno.land/p/moul/helplink/gno.mod new file mode 100644 index 00000000000..1b106749260 --- /dev/null +++ b/examples/gno.land/p/moul/helplink/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/moul/helplink + +require ( + gno.land/p/demo/urequire v0.0.0-latest + gno.land/p/moul/txlink v0.0.0-latest +) diff --git a/examples/gno.land/p/moul/helplink/helplink.gno b/examples/gno.land/p/moul/helplink/helplink.gno new file mode 100644 index 00000000000..b9ac8e102b9 --- /dev/null +++ b/examples/gno.land/p/moul/helplink/helplink.gno @@ -0,0 +1,79 @@ +// Package helplink provides utilities for creating help page links compatible +// with Gnoweb, Gnobro, and other clients that support the Gno contracts' +// flavored Markdown format. +// +// This package simplifies the generation of dynamic, context-sensitive help +// links, enabling users to navigate relevant documentation seamlessly within +// the Gno ecosystem. +// +// For a more lightweight alternative, consider using p/moul/txlink. +// +// The primary functions — Func, FuncURL, and Home — are intended for use with +// the "relative realm". When specifying a custom Realm, you can create links +// that utilize either the current realm path or a fully qualified path to +// another realm. +package helplink + +import ( + "strings" + + "gno.land/p/moul/txlink" +) + +const chainDomain = "gno.land" // XXX: std.ChainDomain (#2911) + +// Func returns a markdown link for the specific function with optional +// key-value arguments, for the current realm. +func Func(title string, fn string, args ...string) string { + return Realm("").Func(title, fn, args...) +} + +// FuncURL returns a URL for the specified function with optional key-value +// arguments, for the current realm. +func FuncURL(fn string, args ...string) string { + return Realm("").FuncURL(fn, args...) +} + +// Home returns the URL for the help homepage of the current realm. +func Home() string { + return Realm("").Home() +} + +// Realm represents a specific realm for generating help links. +type Realm string + +// prefix returns the URL prefix for the realm. +func (r Realm) prefix() string { + // relative + if r == "" { + return "" + } + + // local realm -> /realm + realm := string(r) + if strings.Contains(realm, chainDomain) { + return strings.TrimPrefix(realm, chainDomain) + } + + // remote realm -> https://remote.land/realm + return "https://" + string(r) +} + +// Func returns a markdown link for the specified function with optional +// key-value arguments. +func (r Realm) Func(title string, fn string, args ...string) string { + // XXX: escape title + return "[" + title + "](" + r.FuncURL(fn, args...) + ")" +} + +// FuncURL returns a URL for the specified function with optional key-value +// arguments. +func (r Realm) FuncURL(fn string, args ...string) string { + tlr := txlink.Realm(r) + return tlr.URL(fn, args...) +} + +// Home returns the base help URL for the specified realm. +func (r Realm) Home() string { + return r.prefix() + "?help" +} diff --git a/examples/gno.land/p/moul/helplink/helplink_test.gno b/examples/gno.land/p/moul/helplink/helplink_test.gno new file mode 100644 index 00000000000..07111158a98 --- /dev/null +++ b/examples/gno.land/p/moul/helplink/helplink_test.gno @@ -0,0 +1,78 @@ +package helplink + +import ( + "testing" + + "gno.land/p/demo/urequire" +) + +func TestFunc(t *testing.T) { + tests := []struct { + title string + fn string + args []string + want string + realm Realm + }{ + {"Example", "foo", []string{"bar", "1", "baz", "2"}, "[Example](?help&__func=foo&bar=1&baz=2)", ""}, + {"Realm Example", "foo", []string{"bar", "1", "baz", "2"}, "[Realm Example](/r/lorem/ipsum?help&__func=foo&bar=1&baz=2)", "gno.land/r/lorem/ipsum"}, + {"Single Arg", "testFunc", []string{"key", "value"}, "[Single Arg](?help&__func=testFunc&key=value)", ""}, + {"No Args", "noArgsFunc", []string{}, "[No Args](?help&__func=noArgsFunc)", ""}, + {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args](?help&__func=oddArgsFunc)", ""}, + } + + for _, tt := range tests { + t.Run(tt.title, func(t *testing.T) { + got := tt.realm.Func(tt.title, tt.fn, tt.args...) + urequire.Equal(t, tt.want, got) + }) + } +} + +func TestFuncURL(t *testing.T) { + tests := []struct { + fn string + args []string + want string + realm Realm + }{ + {"foo", []string{"bar", "1", "baz", "2"}, "?help&__func=foo&bar=1&baz=2", ""}, + {"testFunc", []string{"key", "value"}, "?help&__func=testFunc&key=value", ""}, + {"noArgsFunc", []string{}, "?help&__func=noArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "?help&__func=oddArgsFunc", ""}, + {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "/r/lorem/ipsum?help&__func=noArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum?help&__func=noArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + } + + for _, tt := range tests { + title := tt.fn + t.Run(title, func(t *testing.T) { + got := tt.realm.FuncURL(tt.fn, tt.args...) + urequire.Equal(t, tt.want, got) + }) + } +} + +func TestHome(t *testing.T) { + tests := []struct { + realm Realm + want string + }{ + {"", "?help"}, + {"gno.land/r/lorem/ipsum", "/r/lorem/ipsum?help"}, + {"gno.world/r/lorem/ipsum", "https://gno.world/r/lorem/ipsum?help"}, + } + + for _, tt := range tests { + t.Run(string(tt.realm), func(t *testing.T) { + got := tt.realm.Home() + urequire.Equal(t, tt.want, got) + }) + } +} diff --git a/examples/gno.land/p/moul/txlink/gno.mod b/examples/gno.land/p/moul/txlink/gno.mod new file mode 100644 index 00000000000..6110464316f --- /dev/null +++ b/examples/gno.land/p/moul/txlink/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/moul/txlink + +require gno.land/p/demo/urequire v0.0.0-latest diff --git a/examples/gno.land/p/moul/txlink/txlink.gno b/examples/gno.land/p/moul/txlink/txlink.gno new file mode 100644 index 00000000000..26656e67f29 --- /dev/null +++ b/examples/gno.land/p/moul/txlink/txlink.gno @@ -0,0 +1,74 @@ +// Package txlink provides utilities for creating transaction-related links +// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem. +// +// This package is optimized for generating lightweight transaction links with +// flexible arguments, allowing users to build dynamic links that integrate +// seamlessly with various Gno clients. +// +// The primary function, URL, is designed to produce markdown links for +// transaction functions in the current "relative realm". By specifying a custom +// Realm, you can generate links that either use the current realm path or a +// fully qualified path for another realm. +// +// This package is a streamlined alternative to helplink, providing similar +// functionality for transaction links without the full feature set of helplink. +package txlink + +import ( + "std" + "strings" +) + +const chainDomain = "gno.land" // XXX: std.ChainDomain (#2911) + +// URL returns a URL for the specified function with optional key-value +// arguments, for the current realm. +func URL(fn string, args ...string) string { + return Realm("").URL(fn, args...) +} + +// Realm represents a specific realm for generating tx links. +type Realm string + +// prefix returns the URL prefix for the realm. +func (r Realm) prefix() string { + // relative + if r == "" { + curPath := std.CurrentRealm().PkgPath() + return strings.TrimPrefix(curPath, chainDomain) + } + + // local realm -> /realm + realm := string(r) + if strings.Contains(realm, chainDomain) { + return strings.TrimPrefix(realm, chainDomain) + } + + // remote realm -> https://remote.land/realm + return "https://" + string(r) +} + +// URL returns a URL for the specified function with optional key-value +// arguments. +func (r Realm) URL(fn string, args ...string) string { + // Start with the base query + url := r.prefix() + "?help&__func=" + fn + + // Check if args length is even + if len(args)%2 != 0 { + // If not even, we can choose to handle the error here. + // For example, we can just return the URL without appending + // more args. + return url + } + + // Append key-value pairs to the URL + for i := 0; i < len(args); i += 2 { + key := args[i] + value := args[i+1] + // XXX: escape keys and args + url += "&" + key + "=" + value + } + + return url +} diff --git a/examples/gno.land/p/moul/txlink/txlink_test.gno b/examples/gno.land/p/moul/txlink/txlink_test.gno new file mode 100644 index 00000000000..7a460889148 --- /dev/null +++ b/examples/gno.land/p/moul/txlink/txlink_test.gno @@ -0,0 +1,37 @@ +package txlink + +import ( + "testing" + + "gno.land/p/demo/urequire" +) + +func TestURL(t *testing.T) { + tests := []struct { + fn string + args []string + want string + realm Realm + }{ + {"foo", []string{"bar", "1", "baz", "2"}, "?help&__func=foo&bar=1&baz=2", ""}, + {"testFunc", []string{"key", "value"}, "?help&__func=testFunc&key=value", ""}, + {"noArgsFunc", []string{}, "?help&__func=noArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "?help&__func=oddArgsFunc", ""}, + {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "/r/lorem/ipsum?help&__func=noArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum?help&__func=noArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + } + + for _, tt := range tests { + title := tt.fn + t.Run(title, func(t *testing.T) { + got := tt.realm.URL(tt.fn, tt.args...) + urequire.Equal(t, tt.want, got) + }) + } +} diff --git a/examples/gno.land/r/demo/boards/board.gno b/examples/gno.land/r/demo/boards/board.gno index a9cf56c2a91..79b27da84b2 100644 --- a/examples/gno.land/r/demo/boards/board.gno +++ b/examples/gno.land/r/demo/boards/board.gno @@ -6,6 +6,7 @@ import ( "time" "gno.land/p/demo/avl" + "gno.land/p/moul/txlink" ) //---------------------------------------- @@ -134,7 +135,5 @@ func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string } func (board *Board) GetPostFormURL() string { - return "/r/demo/boards?help&__func=CreateThread" + - "&bid=" + board.id.String() + - "&body.type=textarea" + return txlink.URL("CreateThread", "bid", board.id.String()) } diff --git a/examples/gno.land/r/demo/boards/gno.mod b/examples/gno.land/r/demo/boards/gno.mod index 434ad019883..24fea7ce853 100644 --- a/examples/gno.land/r/demo/boards/gno.mod +++ b/examples/gno.land/r/demo/boards/gno.mod @@ -2,5 +2,6 @@ module gno.land/r/demo/boards require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/moul/txlink v0.0.0-latest gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/boards/post.gno b/examples/gno.land/r/demo/boards/post.gno index f35cf23628c..95d4b2977ba 100644 --- a/examples/gno.land/r/demo/boards/post.gno +++ b/examples/gno.land/r/demo/boards/post.gno @@ -6,6 +6,7 @@ import ( "time" "gno.land/p/demo/avl" + "gno.land/p/moul/txlink" ) //---------------------------------------- @@ -155,27 +156,26 @@ func (post *Post) GetURL() string { } func (post *Post) GetReplyFormURL() string { - return "/r/demo/boards?help&__func=CreateReply" + - "&bid=" + post.board.id.String() + - "&threadid=" + post.threadID.String() + - "&postid=" + post.id.String() + - "&body.type=textarea" + return txlink.URL("CreateReply", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) } func (post *Post) GetRepostFormURL() string { - return "/r/demo/boards?help&__func=CreateRepost" + - "&bid=" + post.board.id.String() + - "&postid=" + post.id.String() + - "&title.type=textarea" + - "&body.type=textarea" + - "&dstBoardID.type=textarea" + return txlink.URL("CreateRepost", + "bid", post.board.id.String(), + "postid", post.id.String(), + ) } func (post *Post) GetDeleteFormURL() string { - return "/r/demo/boards?help&__func=DeletePost" + - "&bid=" + post.board.id.String() + - "&threadid=" + post.threadID.String() + - "&postid=" + post.id.String() + return txlink.URL("DeletePost", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) } func (post *Post) RenderSummary() string { diff --git a/examples/gno.land/r/demo/boards/z_0_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index e20964d50b7..cdf136be590 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -24,7 +24,7 @@ func main() { } // Output: -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1&body.type=textarea)] +// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1)] // // ---------------------------------------- // ## [First Post (title)](/r/demo/boards:test_board/1) diff --git a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno index 8555af0b576..cc8d188f727 100644 --- a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno @@ -35,14 +35,14 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/demo/boards/z_10_filetest.gno b/examples/gno.land/r/demo/boards/z_10_filetest.gno index 548b5865f65..0a4626003d1 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // thread does not exist with id: 1 diff --git a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno index c114e769ab1..04dd6bfb547 100644 --- a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno @@ -35,18 +35,18 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > Edited: First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/demo/boards/z_11_filetest.gno b/examples/gno.land/r/demo/boards/z_11_filetest.gno index 4cbdeeca4c3..0974991b814 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -33,10 +33,10 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // # Edited: First Post in (title) // // Edited: Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/demo/boards/z_12_filetest.gno b/examples/gno.land/r/demo/boards/z_12_filetest.gno index 4ea75b27753..8ae1c99ffbb 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -30,7 +30,7 @@ func main() { // Output: // 1 -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=2&body.type=textarea)] +// \[[post](/r/demo/boards?help&__func=CreateThread&bid=2)] // // ---------------------------------------- // Repost: Check this out diff --git a/examples/gno.land/r/demo/boards/z_2_filetest.gno b/examples/gno.land/r/demo/boards/z_2_filetest.gno index f0d53204e38..037a855eab1 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -32,7 +32,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/demo/boards/z_3_filetest.gno b/examples/gno.land/r/demo/boards/z_3_filetest.gno index 021ae10b825..79770aa1cec 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -34,7 +34,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index f0620c28c9d..61e4681f202 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -37,13 +37,13 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] // Realm: // switchrealm["gno.land/r/demo/users"] diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 176b1d89015..440daa6238d 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post (title) // // Body of the first post. (body) -// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > Reply of the first post -// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/demo/boards/z_5_filetest.gno b/examples/gno.land/r/demo/boards/z_5_filetest.gno index c326d961c91..9d30d9aaa72 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -33,11 +33,11 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/demo/boards/z_6_filetest.gno b/examples/gno.land/r/demo/boards/z_6_filetest.gno index b7de2d08bf9..fb7f9a31772 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -35,15 +35,15 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // > // > > First reply of the first reply // > > -// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/demo/boards/z_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index f1d41aa1723..7df06fd760a 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -22,7 +22,7 @@ func main() { } // Output: -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1&body.type=textarea)] +// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1)] // // ---------------------------------------- // ## [First Post (title)](/r/demo/boards:test_board/1) diff --git a/examples/gno.land/r/demo/boards/z_8_filetest.gno b/examples/gno.land/r/demo/boards/z_8_filetest.gno index 18ad64083f4..02ce2c4fcef 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -35,10 +35,10 @@ func main() { // _[see thread](/r/demo/boards:test_board/2)_ // // Reply of the second post -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // _[see all 1 replies](/r/demo/boards:test_board/2/3)_ // // > First reply of the first reply // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] diff --git a/examples/gno.land/r/demo/boards/z_9_filetest.gno b/examples/gno.land/r/demo/boards/z_9_filetest.gno index 10a1444fd35..823318a5e48 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -34,4 +34,4 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=2&threadid=1&postid=1&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=2&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=2&threadid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=2&threadid=1&postid=1)] From cfbaff2affb64bc0317916c302cf1dc3bae4d516 Mon Sep 17 00:00:00 2001 From: Morgan Date: Fri, 25 Oct 2024 16:43:08 -0500 Subject: [PATCH 02/34] ci: benchmark only BenchmarkBenchdata (#3007) A bit radical, but I'm open to other benchmarks we should include. Essentially, in an effort to have a small amount of meaningful benchmarks, I'd like for these to only be those in BenchmarkBenchdata. Yes, I recognize this is tooting my own horn, but I think they are good benchmarks that tell us, overall, if the GnoVM on a few reference programs got slower or faster, and I found them useful in the past while doing manual execution. Most other benchmarks are micro-benchmarks, which aren't likely to change often or to give us useful insight. I'm open to suggestions for others that make sense to be tracked, but I think it's for the better if we keep the number low so the CI for benchmarks can run in just a few minutes. --- .github/workflows/benchmark-master-push.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark-master-push.yml b/.github/workflows/benchmark-master-push.yml index 09978a0ae5c..bde6e623a88 100644 --- a/.github/workflows/benchmark-master-push.yml +++ b/.github/workflows/benchmark-master-push.yml @@ -34,9 +34,12 @@ jobs: go-version: "1.22.x" - name: Run benchmark + # add more benchmarks by adding additional lines for different packages; + # or modify the -bench regexp. run: | - go test -benchmem -bench=. ./... -run=^$ \ - -cpu 1,2 -timeout 50m | tee benchmarks.txt + set -xeuo pipefail && ( + go test ./gnovm/pkg/gnolang -bench='BenchmarkBenchdata' -benchmem -run='^$' -v -cpu=1,2 + ) | tee benchmarks.txt - name: Download previous benchmark data uses: actions/cache@v4 From 2838ad1a3c3b9795990257cd46f08fc04b2fb3a3 Mon Sep 17 00:00:00 2001 From: Morgan Date: Fri, 25 Oct 2024 16:45:46 -0500 Subject: [PATCH 03/34] ci: add workflow to ensure go.mod files are tidied (#3025)
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- .github/workflows/mod-tidy.yml | 26 +++++++++++++++++++++++++ tm2/pkg/amino/tests/proto3/proto/doc.go | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 .github/workflows/mod-tidy.yml create mode 100644 tm2/pkg/amino/tests/proto3/proto/doc.go diff --git a/.github/workflows/mod-tidy.yml b/.github/workflows/mod-tidy.yml new file mode 100644 index 00000000000..118761bddf9 --- /dev/null +++ b/.github/workflows/mod-tidy.yml @@ -0,0 +1,26 @@ +name: Ensure go.mods are tidied + +on: + push: + branches: + - master + workflow_dispatch: + pull_request: + +jobs: + main: + name: Ensure go.mods are tidied + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check go.mod files are up to date + working-directory: ${{ inputs.modulepath }} + run: | + make tidy VERIFY_GO_SUMS=true diff --git a/tm2/pkg/amino/tests/proto3/proto/doc.go b/tm2/pkg/amino/tests/proto3/proto/doc.go new file mode 100644 index 00000000000..909d94e7e7f --- /dev/null +++ b/tm2/pkg/amino/tests/proto3/proto/doc.go @@ -0,0 +1,3 @@ +// This file ensures there is at least one go file in this dir at all times. + +package proto3 From 534e65238a6525532e4ef5cd13b238e3416c1714 Mon Sep 17 00:00:00 2001 From: Mikael VALLENET Date: Sun, 27 Oct 2024 20:50:04 +0100 Subject: [PATCH 04/34] docs: fix tm2 broken link (#3034) --- tm2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/README.md b/tm2/README.md index 0f6e0052933..2addfe8f550 100644 --- a/tm2/README.md +++ b/tm2/README.md @@ -35,7 +35,7 @@ - MISSION: be the basis for improving the encoding standard from proto3, because proto3 length-prefixing is slow, and we need "proto4" or "amino2". - LOOK at the auto-generated proto files! - https://github.com/gnolang/gno/blob/master/pkgs/bft/consensus/types/cstypes.proto + https://github.com/gnolang/gno/blob/master/tm2/pkg/bft/consensus/types/cstypes.proto for example. - There was work to remove this from the CosmosSDK because Amino wasn't ready, but now that it is, it makes sense to incorporate it into From d03581ed748b5f04574b92ff8725957c6b41deca Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 28 Oct 2024 07:51:18 -0500 Subject: [PATCH 05/34] ci: don't test verbosely (#3026) Let's remove the noise in the tests. This will make sure the logs only show the errors when they happen. --- .github/workflows/test_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index 18911415087..38fa10e096b 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -42,7 +42,7 @@ jobs: # confusing and meticulous. There will be some improvements in Go # 1.23 regarding coverage, so we can use this as a workaround until # then. - go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} -v ./... -test.gocoverdir=$GOCOVERDIR + go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} ./... -test.gocoverdir=$GOCOVERDIR # Print results (set +x; echo 'go coverage results:') From 12bd8da50dc13361206adb916d465220506861b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 29 Oct 2024 15:18:10 +0100 Subject: [PATCH 06/34] chore: move `gnoland genesis` to `contribs/gnogenesis` (#3041) ## Description This PR migrates the `gnoland genesis` command suite under `contribs/gnogenesis`, after following discussions from #2824, and internal discussions. **BREAKING CHANGE** `gnoland genesis` will cease to exist after this PR, instead, you will need to use the binary in `contribs/gnogenesis`. It's installed by default after running `make install` from the repo root. Closes #2824
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- Makefile | 7 +- contribs/gnogenesis/Makefile | 18 ++ contribs/gnogenesis/README.md | 181 ++++++++++++++ contribs/gnogenesis/genesis.go | 32 +++ contribs/gnogenesis/go.mod | 62 +++++ contribs/gnogenesis/go.sum | 230 ++++++++++++++++++ .../gnogenesis/internal/balances/balances.go | 13 +- .../internal/balances/balances_add.go | 6 +- .../internal/balances/balances_add_test.go | 59 ++--- .../internal/balances/balances_export.go | 9 +- .../internal/balances/balances_export_test.go | 33 +-- .../internal/balances/balances_remove.go | 16 +- .../internal/balances/balances_remove_test.go | 35 ++- contribs/gnogenesis/internal/common/config.go | 35 +++ contribs/gnogenesis/internal/common/errors.go | 9 + .../gnogenesis/internal/common/helpers.go | 52 ++++ .../gnogenesis/internal/generate/generate.go | 24 +- .../internal/generate/generate_test.go | 33 +-- .../gnogenesis/internal/txs/txs.go | 45 +++- .../gnogenesis/internal/txs/txs_add.go | 2 +- .../internal/txs/txs_add_packages.go | 7 +- .../internal/txs/txs_add_packages_test.go | 21 +- .../gnogenesis/internal/txs/txs_add_sheet.go | 6 +- .../internal/txs/txs_add_sheet_test.go | 39 ++- .../gnogenesis/internal/txs/txs_export.go | 10 +- .../internal/txs/txs_export_test.go | 29 +-- .../gnogenesis/internal/txs/txs_list.go | 7 +- .../gnogenesis/internal/txs/txs_list_test.go | 15 +- .../gnogenesis/internal/txs/txs_remove.go | 6 +- .../internal/txs/txs_remove_test.go | 27 +- .../internal/validator/validator.go | 15 +- .../internal/validator/validator_add.go | 6 +- .../internal/validator/validator_add_test.go | 107 ++------ .../internal/validator/validator_remove.go | 6 +- .../validator/validator_remove_test.go | 31 +-- .../gnogenesis/internal/verify/verify.go | 17 +- .../gnogenesis/internal/verify/verify_test.go | 22 +- contribs/gnogenesis/main.go | 14 ++ docs/gno-infrastructure/validators/faq.md | 35 --- .../validators/setting-up-a-new-chain.md | 15 +- docs/gno-tooling/cli/gnoland.md | 158 ------------ gno.land/cmd/gnoland/genesis.go | 46 ---- gno.land/cmd/gnoland/root.go | 1 - gno.land/cmd/gnoland/start.go | 8 + gno.land/cmd/gnoland/types.go | 37 --- 45 files changed, 916 insertions(+), 670 deletions(-) create mode 100644 contribs/gnogenesis/Makefile create mode 100644 contribs/gnogenesis/README.md create mode 100644 contribs/gnogenesis/genesis.go create mode 100644 contribs/gnogenesis/go.mod create mode 100644 contribs/gnogenesis/go.sum rename gno.land/cmd/gnoland/genesis_balances.go => contribs/gnogenesis/internal/balances/balances.go (68%) rename gno.land/cmd/gnoland/genesis_balances_add.go => contribs/gnogenesis/internal/balances/balances_add.go (98%) rename gno.land/cmd/gnoland/genesis_balances_add_test.go => contribs/gnogenesis/internal/balances/balances_add_test.go (92%) rename gno.land/cmd/gnoland/genesis_balances_export.go => contribs/gnogenesis/internal/balances/balances_export.go (89%) rename gno.land/cmd/gnoland/genesis_balances_export_test.go => contribs/gnogenesis/internal/balances/balances_export_test.go (83%) rename gno.land/cmd/gnoland/genesis_balances_remove.go => contribs/gnogenesis/internal/balances/balances_remove.go (84%) rename gno.land/cmd/gnoland/genesis_balances_remove_test.go => contribs/gnogenesis/internal/balances/balances_remove_test.go (81%) create mode 100644 contribs/gnogenesis/internal/common/config.go create mode 100644 contribs/gnogenesis/internal/common/errors.go create mode 100644 contribs/gnogenesis/internal/common/helpers.go rename gno.land/cmd/gnoland/genesis_generate.go => contribs/gnogenesis/internal/generate/generate.go (85%) rename gno.land/cmd/gnoland/genesis_generate_test.go => contribs/gnogenesis/internal/generate/generate_test.go (89%) rename gno.land/cmd/gnoland/genesis_txs.go => contribs/gnogenesis/internal/txs/txs.go (65%) rename gno.land/cmd/gnoland/genesis_txs_add.go => contribs/gnogenesis/internal/txs/txs_add.go (97%) rename gno.land/cmd/gnoland/genesis_txs_add_packages.go => contribs/gnogenesis/internal/txs/txs_add_packages.go (92%) rename gno.land/cmd/gnoland/genesis_txs_add_packages_test.go => contribs/gnogenesis/internal/txs/txs_add_packages_test.go (88%) rename gno.land/cmd/gnoland/genesis_txs_add_sheet.go => contribs/gnogenesis/internal/txs/txs_add_sheet.go (93%) rename gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go => contribs/gnogenesis/internal/txs/txs_add_sheet_test.go (89%) rename gno.land/cmd/gnoland/genesis_txs_export.go => contribs/gnogenesis/internal/txs/txs_export.go (92%) rename gno.land/cmd/gnoland/genesis_txs_export_test.go => contribs/gnogenesis/internal/txs/txs_export_test.go (84%) rename gno.land/cmd/gnoland/genesis_txs_list.go => contribs/gnogenesis/internal/txs/txs_list.go (86%) rename gno.land/cmd/gnoland/genesis_txs_list_test.go => contribs/gnogenesis/internal/txs/txs_list_test.go (83%) rename gno.land/cmd/gnoland/genesis_txs_remove.go => contribs/gnogenesis/internal/txs/txs_remove.go (94%) rename gno.land/cmd/gnoland/genesis_txs_remove_test.go => contribs/gnogenesis/internal/txs/txs_remove_test.go (86%) rename gno.land/cmd/gnoland/genesis_validator.go => contribs/gnogenesis/internal/validator/validator.go (68%) rename gno.land/cmd/gnoland/genesis_validator_add.go => contribs/gnogenesis/internal/validator/validator_add.go (95%) rename gno.land/cmd/gnoland/genesis_validator_add_test.go => contribs/gnogenesis/internal/validator/validator_add_test.go (66%) rename gno.land/cmd/gnoland/genesis_validator_remove.go => contribs/gnogenesis/internal/validator/validator_remove.go (92%) rename gno.land/cmd/gnoland/genesis_validator_remove_test.go => contribs/gnogenesis/internal/validator/validator_remove_test.go (81%) rename gno.land/cmd/gnoland/genesis_verify.go => contribs/gnogenesis/internal/verify/verify.go (80%) rename gno.land/cmd/gnoland/genesis_verify_test.go => contribs/gnogenesis/internal/verify/verify_test.go (90%) create mode 100644 contribs/gnogenesis/main.go delete mode 100644 gno.land/cmd/gnoland/genesis.go delete mode 100644 gno.land/cmd/gnoland/types.go diff --git a/Makefile b/Makefile index fe862d52893..5cf8c8c58f9 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ VERIFY_MOD_SUMS ?= false ######################################## # Dev tools .PHONY: install -install: install.gnokey install.gno install.gnodev +install: install.gnokey install.gno install.gnodev install.gnogenesis # shortcuts to frequently used commands from sub-components. .PHONY: install.gnokey @@ -45,6 +45,11 @@ install.gno: install.gnodev: $(MAKE) --no-print-directory -C ./contribs/gnodev install @printf "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m\n" +.PHONY: install.gnogenesis +install.gnogenesis: + $(MAKE) --no-print-directory -C ./contribs/gnogenesis install + @printf "\033[0;32m[+] 'gnogenesis' has been installed. Read more in ./contribs/gnogenesis/\033[0m\n" + # old aliases .PHONY: install_gnokey diff --git a/contribs/gnogenesis/Makefile b/contribs/gnogenesis/Makefile new file mode 100644 index 00000000000..20f234e7e36 --- /dev/null +++ b/contribs/gnogenesis/Makefile @@ -0,0 +1,18 @@ +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + + +.PHONY: install +install: + go install . + +.PHONY: build +build: + go build -o build/gnogenesis . + +lint: + $(golangci_lint) --config ../../.github/golangci.yml run ./... + +test: + go test $(GOTEST_FLAGS) -v ./... + diff --git a/contribs/gnogenesis/README.md b/contribs/gnogenesis/README.md new file mode 100644 index 00000000000..ae8daa6b81c --- /dev/null +++ b/contribs/gnogenesis/README.md @@ -0,0 +1,181 @@ +## Overview + +`gnogenesis` is a CLI tool for managing the Gnoland blockchain's `genesis.json` file. It provides +subcommands for setting up and manipulating the genesis file, from generating a new genesis configuration to managing +initial validators, balances, and transactions. + +Refer to specific command help options (`--help`) for further customization options. + +## Installation + +To install gnogenesis, clone the repository and build the tool: + +```shell +git clone https://github.com/gnoland/gno.git +cd gno +make install.gnogenesis +``` + +This will compile and install `gnogenesis` to your system path, allowing you to run commands directly. + +## Features + +### Generate a `genesis.json` + +To create a new genesis.json, use the `generate` subcommand. You can specify parameters such as chain ID, block limits, +and more: + +```shell +gnogenesis generate --chain-id gno-dev --block-max-gas 100000000 --output-path ./genesis.json +``` + +This command generates a genesis.json file with custom parameters, defining the chain’s identity, block limits, and +more. By default, the genesis-time is set to the current timestamp, or you can specify a future time for scheduled chain +launches. + +Keep in mind the `genesis.json` is generated with an empty validator set, and you will need to manually add the initial +validators. + +### Manage initial validators + +The `validator` subcommands allow you to add or remove validators directly in the genesis file. + +#### Add a validator + +To add a validator, specify their `address`, `name`, and `pub-key`: + +```shell +gnogenesis validator add --address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h --name validator1 --pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplmcmggxyxyrch0zcyg684yxmerullv3l6hmau58sk4eyxskmny9h7lsnz +``` + +This command will add the validator with the specified details in the genesis file. + +The `address` and `pub-key` values need to be in bech32 format. They can be fetched using `gnoland secrets get`. + +#### Remove a validator + +If you need to remove a validator, specify their address: + +```shell +gnogenesis validator remove --address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h +``` + +This will remove the specified validator from the validator set in `genesis.json`, if it is present. + +### Verify the `genesis.json` + +The `verify` subcommand is helpful to confirm the integrity of a `genesis.json` file: + +```shell +gnogenesis verify --genesis-path ./genesis.json +``` + +This validation checks for proper structure, account balance totals, and ensures validators are correctly configured, +preventing common genesis setup issues. It is advised to always run this verification step when dealing with an external +`genesis.json`. + +### Manage account balances + +Balances can be added or removed through the balances subcommand, either individually or using a balance sheet file. + +The format for individual balance entries is `
=ugnot`. + +#### Add Account Balances + +Add a single balance directly: + +```shell +gnogenesis balances add --single g1rzuwh5frve732k4futyw45y78rzuty4626zy6h=100ugnot +``` + +Alternatively, load multiple accounts with a balance sheet file: + +```shell +gnogenesis balances add --balance-sheet ./balances.txt +``` + +The format of the balance sheet file is the same as with individual entries, for example: + +```text +# Test accounts. +g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=10000000000000ugnot # test1 +g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=10000000000000ugnot # test2 + +# Faucet accounts. +g1f4v282mwyhu29afke4vq5r2xzcm6z3ftnugcnv=1000000000000ugnot # faucet0 (jae) +g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa=1000000000000ugnot # faucet1 (moul) +g1q6jrp203fq0239pv38sdq3y3urvd6vt5azacpv=1000000000000ugnot # faucet2 (devx) +g13d7jc32adhc39erm5me38w5v7ej7lpvlnqjk73=1000000000000ugnot # faucet3 (devx) +g18l9us6trqaljw39j94wzf5ftxmd9qqkvrxghd2=1000000000000ugnot # faucet4 (adena) +``` + +This will update `genesis.json` with the provided accounts and balances. + +#### Remove account balances + +To remove an account’s balance from `genesis.json`, use: + +```shell +gnogenesis balances remove --address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h +``` + +This deletes the balance entry for the specified address, if present. + +### Handle genesis transactions + +The `txs` subcommand allows you to manage initial transactions. + +It is a bit more robust than the `balances` command suite, in the sense that it supports: + +- adding transactions from transaction sheets +- generating and adding deploy transactions from a directory (ex. like `examples`) + +The format for transactions in the transaction sheet is the following: + +- Transaction (`std.Tx`) is encoded in Amino JSON +- Transactions are saved single-line, 1 line 1 tx +- File format of the transaction sheet file is `jsonl` + +#### Add genesis transactions + +To add genesis transactions from a file: + +```shell +gnogenesis txs add sheets ./txs.json +``` + +This outputs the initial transaction count. + +An example transaction sheet: + +```json lines +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""} +``` + +To add genesis (deploy) transactions from a directory: + +```shell +gnogenesis txs add packages ./examples +``` + +This will generate `MsgAddPkg` transactions, and add them to the given `genesis.json`. + +#### Remove genesis transactions + +To clear specific transactions, use the transaction hash: + +```shell +gnogenesis txs remove "5HuU9LN8WUa2NsjiNxp8Xii9n0zlSGXc9UqzLHB+DPs=" +``` + +The transaction hash is the base64 encoding of the Amino-Binary encoded `std.Tx` transaction hash. + +The steps to get this sort of hash are: + +- get the `std.Tx` +- marshal it using `amino.Marshal` +- cast the result to `types.Tx` (`bft`) +- call `Hash` on the `types.Tx` +- encode the result into base64 diff --git a/contribs/gnogenesis/genesis.go b/contribs/gnogenesis/genesis.go new file mode 100644 index 00000000000..839e5fbe653 --- /dev/null +++ b/contribs/gnogenesis/genesis.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/gnolang/contribs/gnogenesis/internal/balances" + "github.com/gnolang/contribs/gnogenesis/internal/generate" + "github.com/gnolang/contribs/gnogenesis/internal/txs" + "github.com/gnolang/contribs/gnogenesis/internal/validator" + "github.com/gnolang/contribs/gnogenesis/internal/verify" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func newGenesisCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + ShortUsage: " [flags] [...]", + ShortHelp: "gno genesis manipulation suite", + LongHelp: "Gno genesis.json manipulation suite, for managing genesis parameters", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + generate.NewGenerateCmd(io), + validator.NewValidatorCmd(io), + verify.NewVerifyCmd(io), + balances.NewBalancesCmd(io), + txs.NewTxsCmd(io), + ) + + return cmd +} diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod new file mode 100644 index 00000000000..cdd8922fad5 --- /dev/null +++ b/contribs/gnogenesis/go.mod @@ -0,0 +1,62 @@ +module github.com/gnolang/contribs/gnogenesis + +go 1.22 + +require ( + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 +) + +replace github.com/gnolang/gno => ../.. + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.3.11 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum new file mode 100644 index 00000000000..28c509e381e --- /dev/null +++ b/contribs/gnogenesis/go.sum @@ -0,0 +1,230 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gno.land/cmd/gnoland/genesis_balances.go b/contribs/gnogenesis/internal/balances/balances.go similarity index 68% rename from gno.land/cmd/gnoland/genesis_balances.go rename to contribs/gnogenesis/internal/balances/balances.go index c8cd1c539f5..bdfa5aa38d0 100644 --- a/gno.land/cmd/gnoland/genesis_balances.go +++ b/contribs/gnogenesis/internal/balances/balances.go @@ -1,23 +1,24 @@ -package main +package balances import ( "flag" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/commands" ) type balancesCfg struct { - commonCfg + common.Cfg } -// newBalancesCmd creates the genesis balances subcommand -func newBalancesCmd(io commands.IO) *commands.Command { +// NewBalancesCmd creates the genesis balances subcommand +func NewBalancesCmd(io commands.IO) *commands.Command { cfg := &balancesCfg{} cmd := commands.NewCommand( commands.Metadata{ Name: "balances", - ShortUsage: "balances [flags]", + ShortUsage: " [flags]", ShortHelp: "manages genesis.json account balances", LongHelp: "Manipulates the initial genesis.json account balances (pre-mines)", }, @@ -35,5 +36,5 @@ func newBalancesCmd(io commands.IO) *commands.Command { } func (c *balancesCfg) RegisterFlags(fs *flag.FlagSet) { - c.commonCfg.RegisterFlags(fs) + c.Cfg.RegisterFlags(fs) } diff --git a/gno.land/cmd/gnoland/genesis_balances_add.go b/contribs/gnogenesis/internal/balances/balances_add.go similarity index 98% rename from gno.land/cmd/gnoland/genesis_balances_add.go rename to contribs/gnogenesis/internal/balances/balances_add.go index f9a898715c8..a17a13f8bc8 100644 --- a/gno.land/cmd/gnoland/genesis_balances_add.go +++ b/contribs/gnogenesis/internal/balances/balances_add.go @@ -1,4 +1,4 @@ -package main +package balances import ( "bufio" @@ -77,7 +77,7 @@ func (c *balancesAddCfg) RegisterFlags(fs *flag.FlagSet) { func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -156,7 +156,7 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) e genesis.AppState = state // Save the updated genesis - if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.rootCfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_balances_add_test.go b/contribs/gnogenesis/internal/balances/balances_add_test.go similarity index 92% rename from gno.land/cmd/gnoland/genesis_balances_add_test.go rename to contribs/gnogenesis/internal/balances/balances_add_test.go index 8f2879f9c57..29ffe19d95a 100644 --- a/gno.land/cmd/gnoland/genesis_balances_add_test.go +++ b/contribs/gnogenesis/internal/balances/balances_add_test.go @@ -1,4 +1,4 @@ -package main +package balances import ( "bytes" @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" @@ -24,10 +25,8 @@ func TestGenesis_Balances_Add(t *testing.T) { t.Run("invalid genesis", func(t *testing.T) { // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", "dummy-path", @@ -35,7 +34,7 @@ func TestGenesis_Balances_Add(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + require.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("no sources selected", func(t *testing.T) { @@ -44,14 +43,12 @@ func TestGenesis_Balances_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", tempGenesis.Name(), @@ -66,10 +63,8 @@ func TestGenesis_Balances_Add(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", "dummy-path", @@ -77,25 +72,23 @@ func TestGenesis_Balances_Add(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("balances from entries", func(t *testing.T) { t.Parallel() - dummyKeys := getDummyKeys(t, 2) + dummyKeys := common.GetDummyKeys(t, 2) tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", tempGenesis.Name(), @@ -155,10 +148,10 @@ func TestGenesis_Balances_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - dummyKeys := getDummyKeys(t, 10) + dummyKeys := common.GetDummyKeys(t, 10) amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) balances := make([]string, len(dummyKeys)) @@ -182,10 +175,8 @@ func TestGenesis_Balances_Add(t *testing.T) { require.NoError(t, err) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", tempGenesis.Name(), @@ -233,11 +224,11 @@ func TestGenesis_Balances_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) var ( - dummyKeys = getDummyKeys(t, 10) + dummyKeys = common.GetDummyKeys(t, 10) amount = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) gasFee = std.NewCoin(ugnot.Denom, 1000000) @@ -282,10 +273,8 @@ func TestGenesis_Balances_Add(t *testing.T) { require.NoError(t, err) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", tempGenesis.Name(), @@ -337,12 +326,12 @@ func TestGenesis_Balances_Add(t *testing.T) { t.Run("balances overwrite", func(t *testing.T) { t.Parallel() - dummyKeys := getDummyKeys(t, 10) + dummyKeys := common.GetDummyKeys(t, 10) tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() state := gnoland.GnoGenesisState{ // Set an initial balance value Balances: []gnoland.Balance{ @@ -356,10 +345,8 @@ func TestGenesis_Balances_Add(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "add", "--genesis-path", tempGenesis.Name(), @@ -421,7 +408,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { t.Parallel() var ( - dummyKeys = getDummyKeys(t, 10) + dummyKeys = common.GetDummyKeys(t, 10) amount = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) gasFee = std.NewCoin(ugnot.Denom, 1000000) @@ -479,7 +466,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { t.Parallel() var ( - dummyKeys = getDummyKeys(t, 10) + dummyKeys = common.GetDummyKeys(t, 10) amountCoins = std.NewCoins(std.NewCoin(ugnot.Denom, 10)) gasFee = std.NewCoin("gnos", 1) // invalid fee txs = make([]std.Tx, 0) @@ -531,7 +518,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) { t.Parallel() var ( - dummyKeys = getDummyKeys(t, 10) + dummyKeys = common.GetDummyKeys(t, 10) amountCoins = std.NewCoins(std.NewCoin("gnogno", 10)) // invalid send amount gasFee = std.NewCoin(ugnot.Denom, 1) txs = make([]std.Tx, 0) diff --git a/gno.land/cmd/gnoland/genesis_balances_export.go b/contribs/gnogenesis/internal/balances/balances_export.go similarity index 89% rename from gno.land/cmd/gnoland/genesis_balances_export.go rename to contribs/gnogenesis/internal/balances/balances_export.go index ec05d115b97..df9d6795805 100644 --- a/gno.land/cmd/gnoland/genesis_balances_export.go +++ b/contribs/gnogenesis/internal/balances/balances_export.go @@ -1,10 +1,11 @@ -package main +package balances import ( "context" "fmt" "os" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -28,14 +29,14 @@ func newBalancesExportCmd(balancesCfg *balancesCfg, io commands.IO) *commands.Co func execBalancesExport(cfg *balancesCfg, io commands.IO, args []string) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } // Load the genesis state if genesis.AppState == nil { - return errAppStateNotSet + return common.ErrAppStateNotSet } state := genesis.AppState.(gnoland.GnoGenesisState) @@ -47,7 +48,7 @@ func execBalancesExport(cfg *balancesCfg, io commands.IO, args []string) error { // Make sure the output file path is specified if len(args) == 0 { - return errNoOutputFile + return common.ErrNoOutputFile } // Open output file diff --git a/gno.land/cmd/gnoland/genesis_balances_export_test.go b/contribs/gnogenesis/internal/balances/balances_export_test.go similarity index 83% rename from gno.land/cmd/gnoland/genesis_balances_export_test.go rename to contribs/gnogenesis/internal/balances/balances_export_test.go index bd1f6152246..d4f4723df15 100644 --- a/gno.land/cmd/gnoland/genesis_balances_export_test.go +++ b/contribs/gnogenesis/internal/balances/balances_export_test.go @@ -1,10 +1,11 @@ -package main +package balances import ( "bufio" "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/commands" @@ -18,7 +19,7 @@ import ( func getDummyBalances(t *testing.T, count int) []gnoland.Balance { t.Helper() - dummyKeys := getDummyKeys(t, count) + dummyKeys := common.GetDummyKeys(t, count) amount := std.NewCoins(std.NewCoin(ugnot.Denom, 10)) balances := make([]gnoland.Balance, len(dummyKeys)) @@ -40,10 +41,8 @@ func TestGenesis_Balances_Export(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "export", "--genesis-path", "dummy-path", @@ -51,7 +50,7 @@ func TestGenesis_Balances_Export(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid genesis app state", func(t *testing.T) { @@ -60,15 +59,13 @@ func TestGenesis_Balances_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = nil // no app state require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "export", "--genesis-path", tempGenesis.Name(), @@ -76,7 +73,7 @@ func TestGenesis_Balances_Export(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + assert.ErrorContains(t, cmdErr, common.ErrAppStateNotSet.Error()) }) t.Run("no output file specified", func(t *testing.T) { @@ -85,17 +82,15 @@ func TestGenesis_Balances_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Balances: getDummyBalances(t, 1), } require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "export", "--genesis-path", tempGenesis.Name(), @@ -103,7 +98,7 @@ func TestGenesis_Balances_Export(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errNoOutputFile.Error()) + assert.ErrorContains(t, cmdErr, common.ErrNoOutputFile.Error()) }) t.Run("valid balances export", func(t *testing.T) { @@ -115,7 +110,7 @@ func TestGenesis_Balances_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Balances: balances, } @@ -126,10 +121,8 @@ func TestGenesis_Balances_Export(t *testing.T) { t.Cleanup(outputCleanup) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "export", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_balances_remove.go b/contribs/gnogenesis/internal/balances/balances_remove.go similarity index 84% rename from gno.land/cmd/gnoland/genesis_balances_remove.go rename to contribs/gnogenesis/internal/balances/balances_remove.go index 58a02319c8d..ea2aefda5cc 100644 --- a/gno.land/cmd/gnoland/genesis_balances_remove.go +++ b/contribs/gnogenesis/internal/balances/balances_remove.go @@ -1,4 +1,4 @@ -package main +package balances import ( "context" @@ -6,16 +6,14 @@ import ( "flag" "fmt" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" ) -var ( - errUnableToLoadGenesis = errors.New("unable to load genesis") - errBalanceNotFound = errors.New("genesis balances entry does not exist") -) +var errBalanceNotFound = errors.New("genesis balances entry does not exist") type balancesRemoveCfg struct { rootCfg *balancesCfg @@ -53,9 +51,9 @@ func (c *balancesRemoveCfg) RegisterFlags(fs *flag.FlagSet) { func execBalancesRemove(cfg *balancesRemoveCfg, io commands.IO) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.GenesisPath) if loadErr != nil { - return fmt.Errorf("%w, %w", errUnableToLoadGenesis, loadErr) + return fmt.Errorf("%w, %w", common.ErrUnableToLoadGenesis, loadErr) } // Validate the address @@ -66,7 +64,7 @@ func execBalancesRemove(cfg *balancesRemoveCfg, io commands.IO) error { // Check if the genesis state is set at all if genesis.AppState == nil { - return errAppStateNotSet + return common.ErrAppStateNotSet } // Construct the initial genesis balance sheet @@ -90,7 +88,7 @@ func execBalancesRemove(cfg *balancesRemoveCfg, io commands.IO) error { genesis.AppState = state // Save the updated genesis - if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.rootCfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_balances_remove_test.go b/contribs/gnogenesis/internal/balances/balances_remove_test.go similarity index 81% rename from gno.land/cmd/gnoland/genesis_balances_remove_test.go rename to contribs/gnogenesis/internal/balances/balances_remove_test.go index ed11836ba4d..ab99a31c0a9 100644 --- a/gno.land/cmd/gnoland/genesis_balances_remove_test.go +++ b/contribs/gnogenesis/internal/balances/balances_remove_test.go @@ -1,9 +1,10 @@ -package main +package balances import ( "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/bft/types" @@ -19,10 +20,8 @@ func TestGenesis_Balances_Remove(t *testing.T) { t.Run("invalid genesis", func(t *testing.T) { // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "remove", "--genesis-path", "dummy-path", @@ -30,26 +29,24 @@ func TestGenesis_Balances_Remove(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + require.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("genesis app state not set", func(t *testing.T) { t.Parallel() - dummyKey := getDummyKey(t) + dummyKey := common.GetDummyKey(t) tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = nil // not set require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "remove", "--genesis-path", tempGenesis.Name(), @@ -59,18 +56,18 @@ func TestGenesis_Balances_Remove(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - require.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + require.ErrorContains(t, cmdErr, common.ErrAppStateNotSet.Error()) }) t.Run("address is present", func(t *testing.T) { t.Parallel() - dummyKey := getDummyKey(t) + dummyKey := common.GetDummyKey(t) tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() state := gnoland.GnoGenesisState{ // Set an initial balance value Balances: []gnoland.Balance{ @@ -84,10 +81,8 @@ func TestGenesis_Balances_Remove(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "remove", "--genesis-path", tempGenesis.Name(), @@ -114,12 +109,12 @@ func TestGenesis_Balances_Remove(t *testing.T) { t.Run("address not present", func(t *testing.T) { t.Parallel() - dummyKey := getDummyKey(t) + dummyKey := common.GetDummyKey(t) tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() state := gnoland.GnoGenesisState{ Balances: []gnoland.Balance{}, // Empty initial balance } @@ -127,10 +122,8 @@ func TestGenesis_Balances_Remove(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewBalancesCmd(commands.NewTestIO()) args := []string{ - "genesis", - "balances", "remove", "--genesis-path", tempGenesis.Name(), diff --git a/contribs/gnogenesis/internal/common/config.go b/contribs/gnogenesis/internal/common/config.go new file mode 100644 index 00000000000..99278b77764 --- /dev/null +++ b/contribs/gnogenesis/internal/common/config.go @@ -0,0 +1,35 @@ +package common + +import ( + "flag" + "time" + + "github.com/gnolang/gno/tm2/pkg/bft/types" +) + +const DefaultChainID = "dev" + +// Cfg is the common +// configuration for genesis commands +// that require a genesis.json +type Cfg struct { + GenesisPath string +} + +func (c *Cfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.GenesisPath, + "genesis-path", + "./genesis.json", + "the path to the genesis.json", + ) +} + +// GetDefaultGenesis returns the default genesis config +func GetDefaultGenesis() *types.GenesisDoc { + return &types.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: DefaultChainID, + ConsensusParams: types.DefaultConsensusParams(), + } +} diff --git a/contribs/gnogenesis/internal/common/errors.go b/contribs/gnogenesis/internal/common/errors.go new file mode 100644 index 00000000000..6eff43e9dc7 --- /dev/null +++ b/contribs/gnogenesis/internal/common/errors.go @@ -0,0 +1,9 @@ +package common + +import "errors" + +var ( + ErrAppStateNotSet = errors.New("genesis app state not set") + ErrNoOutputFile = errors.New("no output file path specified") + ErrUnableToLoadGenesis = errors.New("unable to load genesis") +) diff --git a/contribs/gnogenesis/internal/common/helpers.go b/contribs/gnogenesis/internal/common/helpers.go new file mode 100644 index 00000000000..2b1f473aed1 --- /dev/null +++ b/contribs/gnogenesis/internal/common/helpers.go @@ -0,0 +1,52 @@ +package common + +import ( + "testing" + + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/bip39" + "github.com/gnolang/gno/tm2/pkg/crypto/hd" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/stretchr/testify/require" +) + +// GetDummyKey generates a random public key, +// and returns the key info +func GetDummyKey(t *testing.T) crypto.PubKey { + t.Helper() + + mnemonic, err := client.GenerateMnemonic(256) + require.NoError(t, err) + + seed := bip39.NewSeed(mnemonic, "") + + return generateKeyFromSeed(seed, 0).PubKey() +} + +// generateKeyFromSeed generates a private key from +// the provided seed and index +func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey { + pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index) + + masterPriv, ch := hd.ComputeMastersFromSeed(seed) + + //nolint:errcheck // This derivation can never error out, since the path params + // are always going to be valid + derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String()) + + return secp256k1.PrivKeySecp256k1(derivedPriv) +} + +// GetDummyKeys generates random keys for testing +func GetDummyKeys(t *testing.T, count int) []crypto.PubKey { + t.Helper() + + dummyKeys := make([]crypto.PubKey, count) + + for i := 0; i < count; i++ { + dummyKeys[i] = GetDummyKey(t) + } + + return dummyKeys +} diff --git a/gno.land/cmd/gnoland/genesis_generate.go b/contribs/gnogenesis/internal/generate/generate.go similarity index 85% rename from gno.land/cmd/gnoland/genesis_generate.go rename to contribs/gnogenesis/internal/generate/generate.go index 751ac14ae62..729b904d548 100644 --- a/gno.land/cmd/gnoland/genesis_generate.go +++ b/contribs/gnogenesis/internal/generate/generate.go @@ -1,4 +1,4 @@ -package main +package generate import ( "context" @@ -6,12 +6,11 @@ import ( "fmt" "time" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" ) -var defaultChainID = "dev" - type generateCfg struct { outputPath string chainID string @@ -22,14 +21,14 @@ type generateCfg struct { blockTimeIota int64 } -// newGenerateCmd creates the genesis generate subcommand -func newGenerateCmd(io commands.IO) *commands.Command { +// NewGenerateCmd creates the genesis generate subcommand +func NewGenerateCmd(io commands.IO) *commands.Command { cfg := &generateCfg{} return commands.NewCommand( commands.Metadata{ Name: "generate", - ShortUsage: "generate [flags]", + ShortUsage: "[flags]", ShortHelp: "generates a fresh genesis.json", LongHelp: "Generates a node's genesis.json based on specified parameters", }, @@ -58,7 +57,7 @@ func (c *generateCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.chainID, "chain-id", - defaultChainID, + common.DefaultChainID, "the ID of the chain", ) @@ -93,7 +92,7 @@ func (c *generateCfg) RegisterFlags(fs *flag.FlagSet) { func execGenerate(cfg *generateCfg, io commands.IO) error { // Start with the default configuration - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() // Set the genesis time if cfg.genesisTime > 0 { @@ -142,12 +141,3 @@ func execGenerate(cfg *generateCfg, io commands.IO) error { return nil } - -// getDefaultGenesis returns the default genesis config -func getDefaultGenesis() *types.GenesisDoc { - return &types.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: defaultChainID, - ConsensusParams: types.DefaultConsensusParams(), - } -} diff --git a/gno.land/cmd/gnoland/genesis_generate_test.go b/contribs/gnogenesis/internal/generate/generate_test.go similarity index 89% rename from gno.land/cmd/gnoland/genesis_generate_test.go rename to contribs/gnogenesis/internal/generate/generate_test.go index f078a161662..7ac02169d77 100644 --- a/gno.land/cmd/gnoland/genesis_generate_test.go +++ b/contribs/gnogenesis/internal/generate/generate_test.go @@ -1,4 +1,4 @@ -package main +package generate import ( "context" @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/testutils" @@ -25,10 +26,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--output-path", genesisPath, } @@ -42,7 +41,7 @@ func TestGenesis_Generate(t *testing.T) { require.NoError(t, readErr) // Make sure the default configuration is set - defaultGenesis := getDefaultGenesis() + defaultGenesis := common.GetDefaultGenesis() defaultGenesis.GenesisTime = genesis.GenesisTime assert.Equal(t, defaultGenesis, genesis) @@ -59,10 +58,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--chain-id", chainID, "--output-path", @@ -91,10 +88,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--block-max-tx-bytes", fmt.Sprintf("%d", blockMaxTxBytes), "--output-path", @@ -127,10 +122,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--block-max-data-bytes", fmt.Sprintf("%d", blockMaxDataBytes), "--output-path", @@ -163,10 +156,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--block-max-gas", fmt.Sprintf("%d", blockMaxGas), "--output-path", @@ -199,10 +190,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--block-time-iota", fmt.Sprintf("%d", blockTimeIota), "--output-path", @@ -235,10 +224,8 @@ func TestGenesis_Generate(t *testing.T) { genesisPath := filepath.Join(tempDir, "genesis.json") // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewGenerateCmd(commands.NewTestIO()) args := []string{ - "genesis", - "generate", "--chain-id", invalidChainID, "--output-path", diff --git a/gno.land/cmd/gnoland/genesis_txs.go b/contribs/gnogenesis/internal/txs/txs.go similarity index 65% rename from gno.land/cmd/gnoland/genesis_txs.go rename to contribs/gnogenesis/internal/txs/txs.go index 46b8d1bd29c..f8a14eafefc 100644 --- a/gno.land/cmd/gnoland/genesis_txs.go +++ b/contribs/gnogenesis/internal/txs/txs.go @@ -1,9 +1,10 @@ -package main +package txs import ( "errors" "flag" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -11,19 +12,19 @@ import ( ) type txsCfg struct { - commonCfg + common.Cfg } var errInvalidGenesisStateType = errors.New("invalid genesis state type") -// newTxsCmd creates the genesis txs subcommand -func newTxsCmd(io commands.IO) *commands.Command { +// NewTxsCmd creates the genesis txs subcommand +func NewTxsCmd(io commands.IO) *commands.Command { cfg := &txsCfg{} cmd := commands.NewCommand( commands.Metadata{ Name: "txs", - ShortUsage: "txs [flags]", + ShortUsage: " [flags]", ShortHelp: "manages the initial genesis transactions", LongHelp: "Manages genesis transactions through input files", }, @@ -42,7 +43,7 @@ func newTxsCmd(io commands.IO) *commands.Command { } func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) { - c.commonCfg.RegisterFlags(fs) + c.Cfg.RegisterFlags(fs) } // appendGenesisTxs saves the given transactions to the genesis doc @@ -74,3 +75,35 @@ func appendGenesisTxs(genesis *types.GenesisDoc, txs []std.Tx) error { return nil } + +// txStore is a wrapper for TM2 transactions +type txStore []std.Tx + +// leftMerge merges the two tx stores, with +// preference to the left +func (i *txStore) leftMerge(b txStore) error { + // Build out the tx hash map + txHashMap := make(map[string]struct{}, len(*i)) + + for _, tx := range *i { + txHash, err := getTxHash(tx) + if err != nil { + return err + } + + txHashMap[txHash] = struct{}{} + } + + for _, tx := range b { + txHash, err := getTxHash(tx) + if err != nil { + return err + } + + if _, exists := txHashMap[txHash]; !exists { + *i = append(*i, tx) + } + } + + return nil +} diff --git a/gno.land/cmd/gnoland/genesis_txs_add.go b/contribs/gnogenesis/internal/txs/txs_add.go similarity index 97% rename from gno.land/cmd/gnoland/genesis_txs_add.go rename to contribs/gnogenesis/internal/txs/txs_add.go index 7e7fd25b21e..22b3b1b966a 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add.go +++ b/contribs/gnogenesis/internal/txs/txs_add.go @@ -1,4 +1,4 @@ -package main +package txs import ( "github.com/gnolang/gno/tm2/pkg/commands" diff --git a/gno.land/cmd/gnoland/genesis_txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go similarity index 92% rename from gno.land/cmd/gnoland/genesis_txs_add_packages.go rename to contribs/gnogenesis/internal/txs/txs_add_packages.go index 56d165c070b..b07adc777a7 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -1,4 +1,4 @@ -package main +package txs import ( "context" @@ -16,6 +16,7 @@ import ( var errInvalidPackageDir = errors.New("invalid package directory") var ( + // Keep in sync with gno.land/cmd/start.go genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) ) @@ -42,7 +43,7 @@ func execTxsAddPackages( args []string, ) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -69,7 +70,7 @@ func execTxsAddPackages( } // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_txs_add_packages_test.go b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go similarity index 88% rename from gno.land/cmd/gnoland/genesis_txs_add_packages_test.go rename to contribs/gnogenesis/internal/txs/txs_add_packages_test.go index 20c4f84c9ed..c814ccde957 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_packages_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go @@ -1,4 +1,4 @@ -package main +package txs import ( "context" @@ -7,6 +7,7 @@ import ( "path/filepath" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/bft/types" @@ -23,10 +24,8 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "packages", "--genesis-path", @@ -35,7 +34,7 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid package dir", func(t *testing.T) { @@ -44,14 +43,12 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "packages", "--genesis-path", @@ -69,7 +66,7 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Prepare the package @@ -99,10 +96,8 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { ) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "packages", "--genesis-path", diff --git a/gno.land/cmd/gnoland/genesis_txs_add_sheet.go b/contribs/gnogenesis/internal/txs/txs_add_sheet.go similarity index 93% rename from gno.land/cmd/gnoland/genesis_txs_add_sheet.go rename to contribs/gnogenesis/internal/txs/txs_add_sheet.go index 261a050029c..88673bc29bd 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_sheet.go +++ b/contribs/gnogenesis/internal/txs/txs_add_sheet.go @@ -1,4 +1,4 @@ -package main +package txs import ( "context" @@ -39,7 +39,7 @@ func execTxsAddSheet( args []string, ) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -74,7 +74,7 @@ func execTxsAddSheet( } // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go b/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go similarity index 89% rename from gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go rename to contribs/gnogenesis/internal/txs/txs_add_sheet_test.go index a70446cfe6c..a174905c237 100644 --- a/gno.land/cmd/gnoland/genesis_txs_add_sheet_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go @@ -1,4 +1,4 @@ -package main +package txs import ( "context" @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" @@ -70,10 +71,8 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", @@ -82,7 +81,7 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid txs file", func(t *testing.T) { @@ -91,14 +90,12 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", @@ -117,14 +114,12 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", @@ -142,14 +137,12 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", @@ -171,7 +164,7 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Prepare the transactions file @@ -187,10 +180,8 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { require.NoError(t, err) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", @@ -226,7 +217,7 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesisState := gnoland.GnoGenesisState{ Txs: txs[0 : len(txs)/2], } @@ -247,10 +238,8 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { require.NoError(t, err) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "add", "sheets", "--genesis-path", diff --git a/gno.land/cmd/gnoland/genesis_txs_export.go b/contribs/gnogenesis/internal/txs/txs_export.go similarity index 92% rename from gno.land/cmd/gnoland/genesis_txs_export.go rename to contribs/gnogenesis/internal/txs/txs_export.go index bf54236b31f..0409f1fd0ac 100644 --- a/gno.land/cmd/gnoland/genesis_txs_export.go +++ b/contribs/gnogenesis/internal/txs/txs_export.go @@ -1,19 +1,17 @@ -package main +package txs import ( "context" - "errors" "fmt" "os" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" ) -var errNoOutputFile = errors.New("no output file path specified") - // newTxsExportCmd creates the genesis txs export subcommand func newTxsExportCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { return commands.NewCommand( @@ -32,7 +30,7 @@ func newTxsExportCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { func execTxsExport(cfg *txsCfg, io commands.IO, args []string) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -51,7 +49,7 @@ func execTxsExport(cfg *txsCfg, io commands.IO, args []string) error { // Make sure the output file path is specified if len(args) == 0 { - return errNoOutputFile + return common.ErrNoOutputFile } // Open output file diff --git a/gno.land/cmd/gnoland/genesis_txs_export_test.go b/contribs/gnogenesis/internal/txs/txs_export_test.go similarity index 84% rename from gno.land/cmd/gnoland/genesis_txs_export_test.go rename to contribs/gnogenesis/internal/txs/txs_export_test.go index 9927f671efb..47fc594d2ec 100644 --- a/gno.land/cmd/gnoland/genesis_txs_export_test.go +++ b/contribs/gnogenesis/internal/txs/txs_export_test.go @@ -1,10 +1,11 @@ -package main +package txs import ( "bufio" "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" @@ -21,10 +22,8 @@ func TestGenesis_Txs_Export(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "export", "--genesis-path", "dummy-path", @@ -32,7 +31,7 @@ func TestGenesis_Txs_Export(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid genesis app state", func(t *testing.T) { @@ -41,15 +40,13 @@ func TestGenesis_Txs_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = nil // no app state require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "export", "--genesis-path", tempGenesis.Name(), @@ -66,17 +63,15 @@ func TestGenesis_Txs_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Txs: generateDummyTxs(t, 1), } require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "export", "--genesis-path", tempGenesis.Name(), @@ -84,7 +79,7 @@ func TestGenesis_Txs_Export(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errNoOutputFile.Error()) + assert.ErrorContains(t, cmdErr, common.ErrNoOutputFile.Error()) }) t.Run("valid txs export", func(t *testing.T) { @@ -96,7 +91,7 @@ func TestGenesis_Txs_Export(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Txs: txs, } @@ -107,10 +102,8 @@ func TestGenesis_Txs_Export(t *testing.T) { t.Cleanup(outputCleanup) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "export", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_txs_list.go b/contribs/gnogenesis/internal/txs/txs_list.go similarity index 86% rename from gno.land/cmd/gnoland/genesis_txs_list.go rename to contribs/gnogenesis/internal/txs/txs_list.go index c68fbc30803..c7867da5027 100644 --- a/gno.land/cmd/gnoland/genesis_txs_list.go +++ b/contribs/gnogenesis/internal/txs/txs_list.go @@ -1,4 +1,4 @@ -package main +package txs import ( "bytes" @@ -6,6 +6,7 @@ import ( "errors" "fmt" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" @@ -33,9 +34,9 @@ func newTxsListCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { } func execTxsListCmd(io commands.IO, cfg *txsCfg) error { - genesis, err := types.GenesisDocFromFile(cfg.genesisPath) + genesis, err := types.GenesisDocFromFile(cfg.GenesisPath) if err != nil { - return fmt.Errorf("%w, %w", errUnableToLoadGenesis, err) + return fmt.Errorf("%w, %w", common.ErrUnableToLoadGenesis, err) } gs, ok := genesis.AppState.(gnoland.GnoGenesisState) diff --git a/gno.land/cmd/gnoland/genesis_txs_list_test.go b/contribs/gnogenesis/internal/txs/txs_list_test.go similarity index 83% rename from gno.land/cmd/gnoland/genesis_txs_list_test.go rename to contribs/gnogenesis/internal/txs/txs_list_test.go index d18c2f4d641..5129533dc8f 100644 --- a/gno.land/cmd/gnoland/genesis_txs_list_test.go +++ b/contribs/gnogenesis/internal/txs/txs_list_test.go @@ -1,10 +1,11 @@ -package main +package txs import ( "bytes" "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,10 +21,8 @@ func TestGenesis_List_All(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "list", "--genesis-path", "", @@ -31,7 +30,7 @@ func TestGenesis_List_All(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorIs(t, cmdErr, errUnableToLoadGenesis) + assert.ErrorIs(t, cmdErr, common.ErrUnableToLoadGenesis) }) t.Run("list all txs", func(t *testing.T) { @@ -43,7 +42,7 @@ func TestGenesis_List_All(t *testing.T) { // Generate dummy txs txs := generateDummyTxs(t, 10) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Txs: txs, } @@ -53,10 +52,8 @@ func TestGenesis_List_All(t *testing.T) { buf := bytes.NewBuffer(nil) cio.SetOut(commands.WriteNopCloser(buf)) - cmd := newRootCmd(cio) + cmd := NewTxsCmd(cio) args := []string{ - "genesis", - "txs", "list", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_txs_remove.go b/contribs/gnogenesis/internal/txs/txs_remove.go similarity index 94% rename from gno.land/cmd/gnoland/genesis_txs_remove.go rename to contribs/gnogenesis/internal/txs/txs_remove.go index 49c650f4670..f767e19bc90 100644 --- a/gno.land/cmd/gnoland/genesis_txs_remove.go +++ b/contribs/gnogenesis/internal/txs/txs_remove.go @@ -1,4 +1,4 @@ -package main +package txs import ( "context" @@ -37,7 +37,7 @@ func newTxsRemoveCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { func execTxsRemove(cfg *txsCfg, io commands.IO, args []string) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -87,7 +87,7 @@ func execTxsRemove(cfg *txsCfg, io commands.IO, args []string) error { genesis.AppState = state // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_txs_remove_test.go b/contribs/gnogenesis/internal/txs/txs_remove_test.go similarity index 86% rename from gno.land/cmd/gnoland/genesis_txs_remove_test.go rename to contribs/gnogenesis/internal/txs/txs_remove_test.go index ff5af479449..c031e0d342e 100644 --- a/gno.land/cmd/gnoland/genesis_txs_remove_test.go +++ b/contribs/gnogenesis/internal/txs/txs_remove_test.go @@ -1,9 +1,10 @@ -package main +package txs import ( "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -19,10 +20,8 @@ func TestGenesis_Txs_Remove(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "remove", "--genesis-path", "dummy-path", @@ -30,7 +29,7 @@ func TestGenesis_Txs_Remove(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid genesis app state", func(t *testing.T) { @@ -39,15 +38,13 @@ func TestGenesis_Txs_Remove(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = nil // no app state require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "remove", "--genesis-path", tempGenesis.Name(), @@ -66,17 +63,15 @@ func TestGenesis_Txs_Remove(t *testing.T) { // Generate dummy txs txs := generateDummyTxs(t, 10) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Txs: txs, } require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "remove", "--genesis-path", tempGenesis.Name(), @@ -96,7 +91,7 @@ func TestGenesis_Txs_Remove(t *testing.T) { // Generate dummy txs txs := generateDummyTxs(t, 10) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() genesis.AppState = gnoland.GnoGenesisState{ Txs: txs, } @@ -106,10 +101,8 @@ func TestGenesis_Txs_Remove(t *testing.T) { require.NoError(t, err) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewTxsCmd(commands.NewTestIO()) args := []string{ - "genesis", - "txs", "remove", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_validator.go b/contribs/gnogenesis/internal/validator/validator.go similarity index 68% rename from gno.land/cmd/gnoland/genesis_validator.go rename to contribs/gnogenesis/internal/validator/validator.go index 91d3e4af7dd..8cd84f5c9bf 100644 --- a/gno.land/cmd/gnoland/genesis_validator.go +++ b/contribs/gnogenesis/internal/validator/validator.go @@ -1,27 +1,28 @@ -package main +package validator import ( "flag" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/commands" ) type validatorCfg struct { - commonCfg + common.Cfg address string } -// newValidatorCmd creates the genesis validator subcommand -func newValidatorCmd(io commands.IO) *commands.Command { +// NewValidatorCmd creates the genesis validator subcommand +func NewValidatorCmd(io commands.IO) *commands.Command { cfg := &validatorCfg{ - commonCfg: commonCfg{}, + Cfg: common.Cfg{}, } cmd := commands.NewCommand( commands.Metadata{ Name: "validator", - ShortUsage: "validator [flags]", + ShortUsage: " [flags]", ShortHelp: "validator set management in genesis.json", LongHelp: "Manipulates the genesis.json validator set", }, @@ -38,7 +39,7 @@ func newValidatorCmd(io commands.IO) *commands.Command { } func (c *validatorCfg) RegisterFlags(fs *flag.FlagSet) { - c.commonCfg.RegisterFlags(fs) + c.Cfg.RegisterFlags(fs) fs.StringVar( &c.address, diff --git a/gno.land/cmd/gnoland/genesis_validator_add.go b/contribs/gnogenesis/internal/validator/validator_add.go similarity index 95% rename from gno.land/cmd/gnoland/genesis_validator_add.go rename to contribs/gnogenesis/internal/validator/validator_add.go index 6c44ad93f89..45744f98e82 100644 --- a/gno.land/cmd/gnoland/genesis_validator_add.go +++ b/contribs/gnogenesis/internal/validator/validator_add.go @@ -1,4 +1,4 @@ -package main +package validator import ( "context" @@ -71,7 +71,7 @@ func (c *validatorAddCfg) RegisterFlags(fs *flag.FlagSet) { func execValidatorAdd(cfg *validatorAddCfg, io commands.IO) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -124,7 +124,7 @@ func execValidatorAdd(cfg *validatorAddCfg, io commands.IO) error { genesis.Validators = append(genesis.Validators, validator) // Save the updated genesis - if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.rootCfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_validator_add_test.go b/contribs/gnogenesis/internal/validator/validator_add_test.go similarity index 66% rename from gno.land/cmd/gnoland/genesis_validator_add_test.go rename to contribs/gnogenesis/internal/validator/validator_add_test.go index 528255b3029..4e6155137a3 100644 --- a/gno.land/cmd/gnoland/genesis_validator_add_test.go +++ b/contribs/gnogenesis/internal/validator/validator_add_test.go @@ -1,61 +1,18 @@ -package main +package validator import ( "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/bip39" - "github.com/gnolang/gno/tm2/pkg/crypto/hd" - "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" - "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// getDummyKey generates a random public key, -// and returns the key info -func getDummyKey(t *testing.T) crypto.PubKey { - t.Helper() - - mnemonic, err := client.GenerateMnemonic(256) - require.NoError(t, err) - - seed := bip39.NewSeed(mnemonic, "") - - return generateKeyFromSeed(seed, 0).PubKey() -} - -// generateKeyFromSeed generates a private key from -// the provided seed and index -func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey { - pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index) - - masterPriv, ch := hd.ComputeMastersFromSeed(seed) - - //nolint:errcheck // This derivation can never error out, since the path params - // are always going to be valid - derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String()) - - return secp256k1.PrivKeySecp256k1(derivedPriv) -} - -// getDummyKeys generates random keys for testing -func getDummyKeys(t *testing.T, count int) []crypto.PubKey { - t.Helper() - - dummyKeys := make([]crypto.PubKey, count) - - for i := 0; i < count; i++ { - dummyKeys[i] = getDummyKey(t) - } - - return dummyKeys -} - func TestGenesis_Validator_Add(t *testing.T) { t.Parallel() @@ -63,10 +20,8 @@ func TestGenesis_Validator_Add(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", "dummy-path", @@ -74,7 +29,7 @@ func TestGenesis_Validator_Add(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid validator address", func(t *testing.T) { @@ -83,14 +38,12 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -109,16 +62,14 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - key := getDummyKey(t) + key := common.GetDummyKey(t) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -139,16 +90,14 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - key := getDummyKey(t) + key := common.GetDummyKey(t) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -169,16 +118,14 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - key := getDummyKey(t) + key := common.GetDummyKey(t) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -201,16 +148,14 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - dummyKeys := getDummyKeys(t, 2) + dummyKeys := common.GetDummyKeys(t, 2) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -233,8 +178,8 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - dummyKeys := getDummyKeys(t, 2) - genesis := getDefaultGenesis() + dummyKeys := common.GetDummyKeys(t, 2) + genesis := common.GetDefaultGenesis() // Set an existing validator genesis.Validators = append(genesis.Validators, types.GenesisValidator{ @@ -247,10 +192,8 @@ func TestGenesis_Validator_Add(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), @@ -273,16 +216,14 @@ func TestGenesis_Validator_Add(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - key := getDummyKey(t) - genesis := getDefaultGenesis() + key := common.GetDummyKey(t) + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "add", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_validator_remove.go b/contribs/gnogenesis/internal/validator/validator_remove.go similarity index 92% rename from gno.land/cmd/gnoland/genesis_validator_remove.go rename to contribs/gnogenesis/internal/validator/validator_remove.go index 48a15a9abaf..0206fe7d58d 100644 --- a/gno.land/cmd/gnoland/genesis_validator_remove.go +++ b/contribs/gnogenesis/internal/validator/validator_remove.go @@ -1,4 +1,4 @@ -package main +package validator import ( "context" @@ -29,7 +29,7 @@ func newValidatorRemoveCmd(rootCfg *validatorCfg, io commands.IO) *commands.Comm func execValidatorRemove(cfg *validatorCfg, io commands.IO) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -58,7 +58,7 @@ func execValidatorRemove(cfg *validatorCfg, io commands.IO) error { genesis.Validators = append(genesis.Validators[:index], genesis.Validators[index+1:]...) // Save the updated genesis - if err := genesis.SaveAs(cfg.genesisPath); err != nil { + if err := genesis.SaveAs(cfg.GenesisPath); err != nil { return fmt.Errorf("unable to save genesis.json, %w", err) } diff --git a/gno.land/cmd/gnoland/genesis_validator_remove_test.go b/contribs/gnogenesis/internal/validator/validator_remove_test.go similarity index 81% rename from gno.land/cmd/gnoland/genesis_validator_remove_test.go rename to contribs/gnogenesis/internal/validator/validator_remove_test.go index e73e867c5c3..78821f4abee 100644 --- a/gno.land/cmd/gnoland/genesis_validator_remove_test.go +++ b/contribs/gnogenesis/internal/validator/validator_remove_test.go @@ -1,9 +1,10 @@ -package main +package validator import ( "context" "testing" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/testutils" @@ -18,10 +19,8 @@ func TestGenesis_Validator_Remove(t *testing.T) { t.Parallel() // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "remove", "--genesis-path", "dummy-path", @@ -29,7 +28,7 @@ func TestGenesis_Validator_Remove(t *testing.T) { // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + assert.ErrorContains(t, cmdErr, common.ErrUnableToLoadGenesis.Error()) }) t.Run("invalid validator address", func(t *testing.T) { @@ -38,14 +37,12 @@ func TestGenesis_Validator_Remove(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "remove", "--genesis-path", tempGenesis.Name(), @@ -64,8 +61,8 @@ func TestGenesis_Validator_Remove(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - dummyKeys := getDummyKeys(t, 2) - genesis := getDefaultGenesis() + dummyKeys := common.GetDummyKeys(t, 2) + genesis := common.GetDefaultGenesis() // Set an existing validator genesis.Validators = append(genesis.Validators, types.GenesisValidator{ @@ -78,10 +75,8 @@ func TestGenesis_Validator_Remove(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "remove", "--genesis-path", tempGenesis.Name(), @@ -100,9 +95,9 @@ func TestGenesis_Validator_Remove(t *testing.T) { tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) - dummyKey := getDummyKey(t) + dummyKey := common.GetDummyKey(t) - genesis := getDefaultGenesis() + genesis := common.GetDefaultGenesis() // Set an existing validator genesis.Validators = append(genesis.Validators, types.GenesisValidator{ @@ -115,10 +110,8 @@ func TestGenesis_Validator_Remove(t *testing.T) { require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewValidatorCmd(commands.NewTestIO()) args := []string{ - "genesis", - "validator", "remove", "--genesis-path", tempGenesis.Name(), diff --git a/gno.land/cmd/gnoland/genesis_verify.go b/contribs/gnogenesis/internal/verify/verify.go similarity index 80% rename from gno.land/cmd/gnoland/genesis_verify.go rename to contribs/gnogenesis/internal/verify/verify.go index 112b075a58c..97ad14cb7f6 100644 --- a/gno.land/cmd/gnoland/genesis_verify.go +++ b/contribs/gnogenesis/internal/verify/verify.go @@ -1,4 +1,4 @@ -package main +package verify import ( "context" @@ -6,6 +6,7 @@ import ( "flag" "fmt" + "github.com/gnolang/contribs/gnogenesis/internal/common" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" @@ -14,17 +15,17 @@ import ( var errInvalidGenesisState = errors.New("invalid genesis state type") type verifyCfg struct { - commonCfg + common.Cfg } -// newVerifyCmd creates the genesis verify subcommand -func newVerifyCmd(io commands.IO) *commands.Command { +// NewVerifyCmd creates the genesis verify subcommand +func NewVerifyCmd(io commands.IO) *commands.Command { cfg := &verifyCfg{} return commands.NewCommand( commands.Metadata{ Name: "verify", - ShortUsage: "verify [flags]", + ShortUsage: "[flags]", ShortHelp: "verifies a genesis.json", LongHelp: "Verifies a node's genesis.json", }, @@ -36,12 +37,12 @@ func newVerifyCmd(io commands.IO) *commands.Command { } func (c *verifyCfg) RegisterFlags(fs *flag.FlagSet) { - c.commonCfg.RegisterFlags(fs) + c.Cfg.RegisterFlags(fs) } func execVerify(cfg *verifyCfg, io commands.IO) error { // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + genesis, loadErr := types.GenesisDocFromFile(cfg.GenesisPath) if loadErr != nil { return fmt.Errorf("unable to load genesis, %w", loadErr) } @@ -73,7 +74,7 @@ func execVerify(cfg *verifyCfg, io commands.IO) error { } } - io.Printfln("Genesis at %s is valid", cfg.genesisPath) + io.Printfln("Genesis at %s is valid", cfg.GenesisPath) return nil } diff --git a/gno.land/cmd/gnoland/genesis_verify_test.go b/contribs/gnogenesis/internal/verify/verify_test.go similarity index 90% rename from gno.land/cmd/gnoland/genesis_verify_test.go rename to contribs/gnogenesis/internal/verify/verify_test.go index 9c93519e495..76009c34c94 100644 --- a/gno.land/cmd/gnoland/genesis_verify_test.go +++ b/contribs/gnogenesis/internal/verify/verify_test.go @@ -1,4 +1,4 @@ -package main +package verify import ( "context" @@ -53,10 +53,8 @@ func TestGenesis_Verify(t *testing.T) { require.NoError(t, g.SaveAs(tempFile.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewVerifyCmd(commands.NewTestIO()) args := []string{ - "genesis", - "verify", "--genesis-path", tempFile.Name(), } @@ -84,10 +82,8 @@ func TestGenesis_Verify(t *testing.T) { require.NoError(t, g.SaveAs(tempFile.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewVerifyCmd(commands.NewTestIO()) args := []string{ - "genesis", - "verify", "--genesis-path", tempFile.Name(), } @@ -112,10 +108,8 @@ func TestGenesis_Verify(t *testing.T) { require.NoError(t, g.SaveAs(tempFile.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewVerifyCmd(commands.NewTestIO()) args := []string{ - "genesis", - "verify", "--genesis-path", tempFile.Name(), } @@ -135,10 +129,8 @@ func TestGenesis_Verify(t *testing.T) { require.NoError(t, g.SaveAs(tempFile.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewVerifyCmd(commands.NewTestIO()) args := []string{ - "genesis", - "verify", "--genesis-path", tempFile.Name(), } @@ -159,10 +151,8 @@ func TestGenesis_Verify(t *testing.T) { require.NoError(t, g.SaveAs(tempFile.Name())) // Create the command - cmd := newRootCmd(commands.NewTestIO()) + cmd := NewVerifyCmd(commands.NewTestIO()) args := []string{ - "genesis", - "verify", "--genesis-path", tempFile.Name(), } diff --git a/contribs/gnogenesis/main.go b/contribs/gnogenesis/main.go new file mode 100644 index 00000000000..a5beb2518dd --- /dev/null +++ b/contribs/gnogenesis/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "context" + "os" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func main() { + cmd := newGenesisCmd(commands.NewDefaultIO()) + + cmd.Execute(context.Background(), os.Args[1:]) +} diff --git a/docs/gno-infrastructure/validators/faq.md b/docs/gno-infrastructure/validators/faq.md index c345b49724a..1a065a5ca56 100644 --- a/docs/gno-infrastructure/validators/faq.md +++ b/docs/gno-infrastructure/validators/faq.md @@ -104,41 +104,6 @@ either a full node or a pruned node, it is important to retain enough blocks to ## Technical References -### How do I generate `genesis.json`? - -`genesis.json` is the file that is used to create the initial state of the chain. To generate `genesis.json`, use -the `gnoland genesis generate` command. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-generate-flags) for various flags that allow you to -manipulate the file. - -:::warning - -Editing generated genesis.json manually is extremely dangerous. It may corrupt chain initial state which leads chain to -not start - -::: - -### How do I add or remove validators from `genesis.json`? - -Validators inside `genesis.json` will be included in the validator set at genesis. To manipulate the genesis validator -set, use the `gnoland genesis validator` command with the `add` or `remove` subcommands. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-validator-flags) for flags that allow you to -configure the name or the voting power of the validator. - -### How do I add the balance information to the `genesis.json`? - -You may premine coins to various addresses. To modify the balances of addresses at genesis, use -the `gnoland genesis balances` command with the `add` or `remove` subcommands. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-balances-add-flags) for various flags that allow you -to update the entire balance sheet with a file or modify the balance of a single address. - -:::info - -Not only `ugnot`, but other coins are accepted. However, be aware that coins other than `ugnot` may not work(send, and -etc.) properly. - -::: - ### How do I initialize `gno secrets`? The `gno secrets init` command allows you to initialize the private information required to run the validator, including 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 0411fa3b02a..aab76eefbaf 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -19,7 +19,7 @@ Additionally, you will see the different options you can use to make your Gno in ## Installation -To install the `gnoland` binary, clone the Gno monorepo: +To install the `gnoland` and `gnogenesis` binaries, clone the Gno monorepo: ```bash git clone https://github.com/gnolang/gno.git @@ -30,7 +30,7 @@ Makefile to install the `gnoland` binary: ```bash cd gno.land -make install.gnoland +make install.gnoland install.gnogenesis ``` To verify that you've installed the binary properly and that you are able to use @@ -93,7 +93,8 @@ Let's break down the most important default settings: :::info Resetting the chain As mentioned, the working directory for the node is located in `data-dir`. To reset the chain, you need -to delete this directory and `genesis.json`, then start the node up again. If you are using the default node configuration, you can run +to delete this directory and `genesis.json`, then start the node up again. If you are using the default node +configuration, you can run `make fclean` from the `gno.land` sub-folder to delete the `gnoland-data` working directory. ::: @@ -201,7 +202,7 @@ executed. Generating an empty `genesis.json` is relatively straightforward: ```shell -gnoland genesis generate +gnogenesis generate ``` The resulting `genesis.json` is empty: @@ -232,7 +233,7 @@ This will generate a `genesis.json` in the calling directory, by default. To che generating the `genesis.json`, you can run the command using the `--help` flag: ```shell -gnoland genesis generate --help +gnogenesis generate --help USAGE generate [flags] @@ -257,7 +258,7 @@ present challenges with users who expect them to be present. The `examples` directory is located in the `$GNOROOT` location, or the local gno repository clone. ```bash -gnoland genesis txs add packages ./examples +gnogenesis txs add packages ./examples ``` ### 4. Add the initial validator set @@ -288,7 +289,7 @@ Updating the `genesis.json` is relatively simple, running the following command validator set: ```shell -gnoland genesis validator add \ +gnogenesis validator add \ --address g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl \ --pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk \ --name Cuttlas diff --git a/docs/gno-tooling/cli/gnoland.md b/docs/gno-tooling/cli/gnoland.md index 18175871d90..037a1f19d03 100644 --- a/docs/gno-tooling/cli/gnoland.md +++ b/docs/gno-tooling/cli/gnoland.md @@ -29,164 +29,6 @@ Starts the Gnoland blockchain node, with accompanying setup. | `log-level` | String | The log level for the gnoland node. (default: `debug`) | | `skip-failing-genesis-txs` | Boolean | Doesn’t panic when replaying invalid genesis txs. When starting a production-level chain, it is recommended to set this value to `true` to monitor and analyze failing transactions. (default: `false`) | -### gnoland genesis \ [flags] [\...] - -Gno `genesis.json` manipulation suite for managing genesis parameters. - -#### SUBCOMMANDS - -| Name | Description | -|-------------|---------------------------------------------| -| `generate` | Generates a fresh `genesis.json`. | -| `validator` | Validator set management in `genesis.json`. | -| `verify` | Verifies a `genesis.json`. | -| `balances` | Manages `genesis.json` account balances. | -| `txs` | Manages the initial genesis transactions. | - -### gnoland genesis generate [flags] - -Generates a node's `genesis.json` based on specified parameters. - -#### FLAGS - -| Name | Type | Description | -|------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `block-max-data-bytes` | Int | The max size of the block data.(default: `2000000`) | -| `block-max-gas` | Int | The max gas limit for the block. (default: `100000000`) | -| `block-max-tx-bytes` | Int | The max size of the block transaction. (default: `1000000`) | -| `block-time-itoa` | Int | The block time itoa (in ms). (default: `100`) | -| `chain-id` | String | The ID of the chain. (default: `dev`) | -| `genesis-time` | Int | The genesis creation time. (default: `utc now timestamp`) | -| `output-path` : | String | The output path for the `genesis.json`. If the genesis-time of the Genesis File is set to a future time, the chain will automatically start at that time if the node is online. (default: `./genesis.json`) | - -### gnoland genesis validator \ [flags] - -Manipulates the `genesis.json` validator set. - -#### SUBCOMANDS - -| Name | Description | -|----------|----------------------------------------------| -| `add` | Adds a new validator to the `genesis.json`. | -| `remove` | Removes a validator from the `genesis.json`. | - -#### FLAGS - -| Name | Type | Description | -|----------------|--------|------------------------------------------------------------| -| `address` | String | The gno bech32 address of the validator. | -| `genesis-path` | String | The path to the `genesis.json`. (default `./genesis.json`) | - -### gnoland genesis validator add [flags] - -Adds a new validator to the `genesis.json`. - -#### FLAGS - -| Name | Type | Description | -|----------------|--------|-----------------------------------------------------------------| -| `address` | String | The gno bech32 address of the validator. | -| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | -| `name` | String | The name of the validator (must be unique). | -| `power` | Uint | The voting power of the validator (must be > 0). (default: `1`) | -| `pub-key` | String | The bech32 string representation of the validator's public key. | - -```bash -gnoland genesis validator add \ --address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h \ --name test1 \ --pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplmcmggxyxyrch0zcyg684yxmerullv3l6hmau58sk4eyxskmny9h7lsnz - -Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h added to genesis file -``` - -### gnoland genesis validator remove [flags] - -Removes a validator from the `genesis.json`. - -#### FLAGS - -| Name | Type | Description | -|----------------|--------|-------------------------------------------------------------| -| `address` | String | The gno bech32 address of the validator. | -| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json)` | - -```bash -gnoland genesis validator remove \ --address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h - -Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed from genesis file -``` - -### gnoland genesis verify \ [flags] [\…] - -Verifies a `genesis.json`. - -#### FLAGS - -| Name | Type | Description | -|----------------|--------|-----------------------------------------------------------| -| `genesis-path` | String | The path to the `genesis.json`. (default: `genesis.json`) | - -### gnoland genesis balances \ [flags] [\…] - -Manages `genesis.json` account balances. - -#### SUBCOMMANDS - -| Name | Description | -|----------|--------------------------------------------------------| -| `add` | Adds the balance information. | -| `remove` | Removes the balance information of a specific account. | - -### gnoland genesis balances add [flags] - -#### FLAGS - -| Name | Type | Description | -|-----------------|--------|--------------------------------------------------------------------------------------------| -| `balance-sheet` | String | The path to the balance file containing addresses in the format `
=ugnot`. | -| `genesis-path` | String | The path to the `genesis.json` (default: `./genesis.json`) | -| `parse-export` | String | The path to the transaction export containing a list of transactions (JSONL). | -| `single` | String | The direct balance addition in the format `
=ugnot`. | - -```bash -gnoland genesis balances add \ --single g1rzuwh5frve732k4futyw45y78rzuty4626zy6h=100ugnot - -1 pre-mines saved - -g1rzuwh5frve732k4futyw45y78rzuty4626zy6h:{[24 184 235 209 35 102 125 21 90 169 226 200 234 208 158 56 197 197 146 186] [{%!d(string=ugnot) 100}]}ugnot -``` - -### gnoland balances remove [flags] - -#### FLAGS - -| Name | Type | Description | -|----------------|--------|---------------------------------------------------------------------------------------------| -| `address` | String | The address of the account whose balance information should be removed from `genesis.json`. | -| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | - -```bash -gnoland genesis balances remove \ --address=g1rzuwh5frve732k4futyw45y78rzuty4626zy6h - -Pre-mine information for address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed -``` - -### gnoland txs \ [flags] [\…] - -Manages genesis transactions through input files. - -#### SUBCOMMANDS - -| Name | Description | -|----------|---------------------------------------------------| -| `add` | Imports transactions into the `genesis.json`. | -| `remove` | Removes the transactions from the `genesis.json`. | -| `export` | Exports the transactions from the `genesis.json`. | - ### gnoland secrets \ [flags] [\…] The gno secrets manipulation suite for managing the validator key, p2p key and diff --git a/gno.land/cmd/gnoland/genesis.go b/gno.land/cmd/gnoland/genesis.go deleted file mode 100644 index 37c0f8f2926..00000000000 --- a/gno.land/cmd/gnoland/genesis.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "flag" - - "github.com/gnolang/gno/tm2/pkg/commands" -) - -func newGenesisCmd(io commands.IO) *commands.Command { - cmd := commands.NewCommand( - commands.Metadata{ - Name: "genesis", - ShortUsage: "genesis [flags] [...]", - ShortHelp: "gno genesis manipulation suite", - LongHelp: "Gno genesis.json manipulation suite, for managing genesis parameters", - }, - commands.NewEmptyConfig(), - commands.HelpExec, - ) - - cmd.AddSubCommands( - newGenerateCmd(io), - newValidatorCmd(io), - newVerifyCmd(io), - newBalancesCmd(io), - newTxsCmd(io), - ) - - return cmd -} - -// commonCfg is the common -// configuration for genesis commands -// that require a genesis.json -type commonCfg struct { - genesisPath string -} - -func (c *commonCfg) RegisterFlags(fs *flag.FlagSet) { - fs.StringVar( - &c.genesisPath, - "genesis-path", - "./genesis.json", - "the path to the genesis.json", - ) -} diff --git a/gno.land/cmd/gnoland/root.go b/gno.land/cmd/gnoland/root.go index b40a1160b0b..c6143ab9cd3 100644 --- a/gno.land/cmd/gnoland/root.go +++ b/gno.land/cmd/gnoland/root.go @@ -25,7 +25,6 @@ func newRootCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newStartCmd(io), - newGenesisCmd(io), newSecretsCmd(io), newConfigCmd(io), ) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 8d1ee81295f..77d7e20b8ef 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -14,6 +14,7 @@ import ( "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/log" "github.com/gnolang/gno/gnovm/pkg/gnoenv" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -25,6 +26,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/telemetry" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -42,6 +44,12 @@ var startGraphic = strings.ReplaceAll(` /___/ `, "'", "`") +var ( + // Keep in sync with contribs/gnogenesis/internal/txs/txs_add_packages.go + genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 + genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) +) + type startCfg struct { gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 diff --git a/gno.land/cmd/gnoland/types.go b/gno.land/cmd/gnoland/types.go deleted file mode 100644 index a48bfaf7b31..00000000000 --- a/gno.land/cmd/gnoland/types.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "github.com/gnolang/gno/tm2/pkg/std" -) - -// txStore is a wrapper for TM2 transactions -type txStore []std.Tx - -// leftMerge merges the two tx stores, with -// preference to the left -func (i *txStore) leftMerge(b txStore) error { - // Build out the tx hash map - txHashMap := make(map[string]struct{}, len(*i)) - - for _, tx := range *i { - txHash, err := getTxHash(tx) - if err != nil { - return err - } - - txHashMap[txHash] = struct{}{} - } - - for _, tx := range b { - txHash, err := getTxHash(tx) - if err != nil { - return err - } - - if _, exists := txHashMap[txHash]; !exists { - *i = append(*i, tx) - } - } - - return nil -} From 2a2be3944d52ba470b36b8cfaf164e428eb42840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 29 Oct 2024 16:56:38 +0100 Subject: [PATCH 07/34] feat: `r/gov/dao` v2 (#2581) ## Description This PR introduces an upgrade to the `r/gov/dao` system: - it makes it configurable through custom implementations - added a `p/demo/simpledao` implementation - the implementations are changeable through a govdao proposal - adds weighted voting to a govdao example implementation
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- .../p/demo/combinederr/combinederr.gno | 40 + examples/gno.land/p/demo/combinederr/gno.mod | 1 + examples/gno.land/p/demo/dao/dao.gno | 33 + examples/gno.land/p/demo/dao/doc.gno | 5 + examples/gno.land/p/demo/dao/events.gno | 56 ++ examples/gno.land/p/demo/dao/executor.gno | 9 + examples/gno.land/p/demo/dao/gno.mod | 3 + examples/gno.land/p/demo/dao/proposals.gno | 62 ++ examples/gno.land/p/demo/dao/vote.gno | 69 ++ .../{r/gov/dao => p/demo/membstore}/gno.mod | 5 +- .../gno.land/p/demo/membstore/members.gno | 38 + .../gno.land/p/demo/membstore/membstore.gno | 209 +++++ .../p/demo/membstore/membstore_test.gno | 317 +++++++ examples/gno.land/p/demo/simpledao/dao.gno | 215 +++++ .../gno.land/p/demo/simpledao/dao_test.gno | 829 ++++++++++++++++++ examples/gno.land/p/demo/simpledao/gno.mod | 12 + .../gno.land/p/demo/simpledao/mock_test.gno | 97 ++ .../gno.land/p/demo/simpledao/propstore.gno | 163 ++++ .../p/demo/simpledao/propstore_test.gno | 256 ++++++ .../gno.land/p/demo/simpledao/votestore.gno | 55 ++ examples/gno.land/p/gov/executor/callback.gno | 39 + examples/gno.land/p/gov/executor/context.gno | 75 ++ .../p/gov/{proposal => executor}/gno.mod | 2 +- .../gno.land/p/gov/executor/proposal_test.gno | 180 ++++ examples/gno.land/p/gov/proposal/proposal.gno | 106 --- .../gno.land/p/gov/proposal/proposal_test.gno | 156 ---- examples/gno.land/p/gov/proposal/types.gno | 37 - examples/gno.land/r/gnoland/blog/admin.gno | 16 +- examples/gno.land/r/gnoland/blog/gno.mod | 4 +- examples/gno.land/r/gnoland/home/home.gno | 2 +- .../gno.land/r/gnoland/home/home_filetest.gno | 2 +- .../r/gnoland/valopers/{ => v2}/gno.mod | 7 +- .../r/gnoland/valopers/{ => v2}/init.gno | 0 .../r/gnoland/valopers/{ => v2}/valopers.gno | 21 +- .../valopers/{ => v2}/valopers_test.gno | 2 + examples/gno.land/r/gov/dao/bridge/bridge.gno | 39 + .../gno.land/r/gov/dao/bridge/bridge_test.gno | 64 ++ examples/gno.land/r/gov/dao/bridge/doc.gno | 4 + examples/gno.land/r/gov/dao/bridge/gno.mod | 11 + .../gno.land/r/gov/dao/bridge/mock_test.gno | 68 ++ examples/gno.land/r/gov/dao/bridge/types.gno | 17 + examples/gno.land/r/gov/dao/bridge/v2.gno | 42 + examples/gno.land/r/gov/dao/dao.gno | 207 ----- examples/gno.land/r/gov/dao/dao_test.gno | 192 ---- examples/gno.land/r/gov/dao/memberset.gno | 40 - .../gno.land/r/gov/dao/prop2_filetest.gno | 120 --- examples/gno.land/r/gov/dao/types.gno | 32 - examples/gno.land/r/gov/dao/v2/dao.gno | 121 +++ examples/gno.land/r/gov/dao/v2/gno.mod | 10 + examples/gno.land/r/gov/dao/v2/poc.gno | 92 ++ .../r/gov/dao/{ => v2}/prop1_filetest.gno | 71 +- .../gno.land/r/gov/dao/v2/prop2_filetest.gno | 110 +++ .../gno.land/r/gov/dao/v2/prop3_filetest.gno | 120 +++ examples/gno.land/r/gov/dao/voter.gno | 91 -- .../r/sys/validators/{ => v2}/doc.gno | 0 .../r/sys/validators/{ => v2}/gno.mod | 5 +- .../r/sys/validators/{ => v2}/gnosdk.gno | 0 .../r/sys/validators/{ => v2}/init.gno | 0 .../r/sys/validators/{ => v2}/poc.gno | 24 +- .../r/sys/validators/{ => v2}/validators.gno | 0 .../validators/{ => v2}/validators_test.gno | 0 gno.land/pkg/gnoland/validators.go | 2 +- 62 files changed, 3542 insertions(+), 1063 deletions(-) create mode 100644 examples/gno.land/p/demo/combinederr/combinederr.gno create mode 100644 examples/gno.land/p/demo/combinederr/gno.mod create mode 100644 examples/gno.land/p/demo/dao/dao.gno create mode 100644 examples/gno.land/p/demo/dao/doc.gno create mode 100644 examples/gno.land/p/demo/dao/events.gno create mode 100644 examples/gno.land/p/demo/dao/executor.gno create mode 100644 examples/gno.land/p/demo/dao/gno.mod create mode 100644 examples/gno.land/p/demo/dao/proposals.gno create mode 100644 examples/gno.land/p/demo/dao/vote.gno rename examples/gno.land/{r/gov/dao => p/demo/membstore}/gno.mod (54%) create mode 100644 examples/gno.land/p/demo/membstore/members.gno create mode 100644 examples/gno.land/p/demo/membstore/membstore.gno create mode 100644 examples/gno.land/p/demo/membstore/membstore_test.gno create mode 100644 examples/gno.land/p/demo/simpledao/dao.gno create mode 100644 examples/gno.land/p/demo/simpledao/dao_test.gno create mode 100644 examples/gno.land/p/demo/simpledao/gno.mod create mode 100644 examples/gno.land/p/demo/simpledao/mock_test.gno create mode 100644 examples/gno.land/p/demo/simpledao/propstore.gno create mode 100644 examples/gno.land/p/demo/simpledao/propstore_test.gno create mode 100644 examples/gno.land/p/demo/simpledao/votestore.gno create mode 100644 examples/gno.land/p/gov/executor/callback.gno create mode 100644 examples/gno.land/p/gov/executor/context.gno rename examples/gno.land/p/gov/{proposal => executor}/gno.mod (80%) create mode 100644 examples/gno.land/p/gov/executor/proposal_test.gno delete mode 100644 examples/gno.land/p/gov/proposal/proposal.gno delete mode 100644 examples/gno.land/p/gov/proposal/proposal_test.gno delete mode 100644 examples/gno.land/p/gov/proposal/types.gno rename examples/gno.land/r/gnoland/valopers/{ => v2}/gno.mod (56%) rename examples/gno.land/r/gnoland/valopers/{ => v2}/init.gno (100%) rename examples/gno.land/r/gnoland/valopers/{ => v2}/valopers.gno (90%) rename examples/gno.land/r/gnoland/valopers/{ => v2}/valopers_test.gno (97%) create mode 100644 examples/gno.land/r/gov/dao/bridge/bridge.gno create mode 100644 examples/gno.land/r/gov/dao/bridge/bridge_test.gno create mode 100644 examples/gno.land/r/gov/dao/bridge/doc.gno create mode 100644 examples/gno.land/r/gov/dao/bridge/gno.mod create mode 100644 examples/gno.land/r/gov/dao/bridge/mock_test.gno create mode 100644 examples/gno.land/r/gov/dao/bridge/types.gno create mode 100644 examples/gno.land/r/gov/dao/bridge/v2.gno delete mode 100644 examples/gno.land/r/gov/dao/dao.gno delete mode 100644 examples/gno.land/r/gov/dao/dao_test.gno delete mode 100644 examples/gno.land/r/gov/dao/memberset.gno delete mode 100644 examples/gno.land/r/gov/dao/prop2_filetest.gno delete mode 100644 examples/gno.land/r/gov/dao/types.gno create mode 100644 examples/gno.land/r/gov/dao/v2/dao.gno create mode 100644 examples/gno.land/r/gov/dao/v2/gno.mod create mode 100644 examples/gno.land/r/gov/dao/v2/poc.gno rename examples/gno.land/r/gov/dao/{ => v2}/prop1_filetest.gno (63%) create mode 100644 examples/gno.land/r/gov/dao/v2/prop2_filetest.gno create mode 100644 examples/gno.land/r/gov/dao/v2/prop3_filetest.gno delete mode 100644 examples/gno.land/r/gov/dao/voter.gno rename examples/gno.land/r/sys/validators/{ => v2}/doc.gno (100%) rename examples/gno.land/r/sys/validators/{ => v2}/gno.mod (71%) rename examples/gno.land/r/sys/validators/{ => v2}/gnosdk.gno (100%) rename examples/gno.land/r/sys/validators/{ => v2}/init.gno (100%) rename examples/gno.land/r/sys/validators/{ => v2}/poc.gno (63%) rename examples/gno.land/r/sys/validators/{ => v2}/validators.gno (100%) rename examples/gno.land/r/sys/validators/{ => v2}/validators_test.gno (100%) diff --git a/examples/gno.land/p/demo/combinederr/combinederr.gno b/examples/gno.land/p/demo/combinederr/combinederr.gno new file mode 100644 index 00000000000..f446c7846bd --- /dev/null +++ b/examples/gno.land/p/demo/combinederr/combinederr.gno @@ -0,0 +1,40 @@ +package combinederr + +import "strings" + +// CombinedError is a combined execution error +type CombinedError struct { + errors []error +} + +// Error returns the combined execution error +func (e *CombinedError) Error() string { + if len(e.errors) == 0 { + return "" + } + + var sb strings.Builder + + for _, err := range e.errors { + sb.WriteString(err.Error() + "; ") + } + + // Remove the last semicolon and space + result := sb.String() + + return result[:len(result)-2] +} + +// Add adds a new error to the execution error +func (e *CombinedError) Add(err error) { + if err == nil { + return + } + + e.errors = append(e.errors, err) +} + +// Size returns a +func (e *CombinedError) Size() int { + return len(e.errors) +} diff --git a/examples/gno.land/p/demo/combinederr/gno.mod b/examples/gno.land/p/demo/combinederr/gno.mod new file mode 100644 index 00000000000..4c99e0ba7ef --- /dev/null +++ b/examples/gno.land/p/demo/combinederr/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/combinederr diff --git a/examples/gno.land/p/demo/dao/dao.gno b/examples/gno.land/p/demo/dao/dao.gno new file mode 100644 index 00000000000..f8ea433192f --- /dev/null +++ b/examples/gno.land/p/demo/dao/dao.gno @@ -0,0 +1,33 @@ +package dao + +const ( + ProposalAddedEvent = "ProposalAdded" // emitted when a new proposal has been added + ProposalAcceptedEvent = "ProposalAccepted" // emitted when a proposal has been accepted + ProposalNotAcceptedEvent = "ProposalNotAccepted" // emitted when a proposal has not been accepted + ProposalExecutedEvent = "ProposalExecuted" // emitted when a proposal has been executed + + ProposalEventIDKey = "proposal-id" + ProposalEventAuthorKey = "proposal-author" + ProposalEventExecutionKey = "exec-status" +) + +// ProposalRequest is a single govdao proposal request +// that contains the necessary information to +// log and generate a valid proposal +type ProposalRequest struct { + Description string // the description associated with the proposal + Executor Executor // the proposal executor +} + +// DAO defines the DAO abstraction +type DAO interface { + // PropStore is the DAO proposal storage + PropStore + + // Propose adds a new proposal to the executor-based GOVDAO. + // Returns the generated proposal ID + Propose(request ProposalRequest) (uint64, error) + + // ExecuteProposal executes the proposal with the given ID + ExecuteProposal(id uint64) error +} diff --git a/examples/gno.land/p/demo/dao/doc.gno b/examples/gno.land/p/demo/dao/doc.gno new file mode 100644 index 00000000000..3fb28204013 --- /dev/null +++ b/examples/gno.land/p/demo/dao/doc.gno @@ -0,0 +1,5 @@ +// Package dao houses common DAO building blocks (framework), which can be used or adopted by any +// specific DAO implementation. By design, the DAO should house the proposals it receives, but not the actual +// DAO members or proposal votes. These abstractions should be implemented by a separate entity, to keep the DAO +// agnostic of implementation details such as these (member / vote management). +package dao diff --git a/examples/gno.land/p/demo/dao/events.gno b/examples/gno.land/p/demo/dao/events.gno new file mode 100644 index 00000000000..97bc794e6f3 --- /dev/null +++ b/examples/gno.land/p/demo/dao/events.gno @@ -0,0 +1,56 @@ +package dao + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// EmitProposalAdded emits an event signaling that +// a given proposal was added +func EmitProposalAdded(id uint64, proposer std.Address) { + std.Emit( + ProposalAddedEvent, + ProposalEventIDKey, ufmt.Sprintf("%d", id), + ProposalEventAuthorKey, proposer.String(), + ) +} + +// EmitProposalAccepted emits an event signaling that +// a given proposal was accepted +func EmitProposalAccepted(id uint64) { + std.Emit( + ProposalAcceptedEvent, + ProposalEventIDKey, ufmt.Sprintf("%d", id), + ) +} + +// EmitProposalNotAccepted emits an event signaling that +// a given proposal was not accepted +func EmitProposalNotAccepted(id uint64) { + std.Emit( + ProposalNotAcceptedEvent, + ProposalEventIDKey, ufmt.Sprintf("%d", id), + ) +} + +// EmitProposalExecuted emits an event signaling that +// a given proposal was executed, with the given status +func EmitProposalExecuted(id uint64, status ProposalStatus) { + std.Emit( + ProposalExecutedEvent, + ProposalEventIDKey, ufmt.Sprintf("%d", id), + ProposalEventExecutionKey, status.String(), + ) +} + +// EmitVoteAdded emits an event signaling that +// a vote was cast for a given proposal +func EmitVoteAdded(id uint64, voter std.Address, option VoteOption) { + std.Emit( + VoteAddedEvent, + VoteAddedIDKey, ufmt.Sprintf("%d", id), + VoteAddedAuthorKey, voter.String(), + VoteAddedOptionKey, option.String(), + ) +} diff --git a/examples/gno.land/p/demo/dao/executor.gno b/examples/gno.land/p/demo/dao/executor.gno new file mode 100644 index 00000000000..9291c2c53c5 --- /dev/null +++ b/examples/gno.land/p/demo/dao/executor.gno @@ -0,0 +1,9 @@ +package dao + +// Executor represents a minimal closure-oriented proposal design. +// It is intended to be used by a govdao governance proposal (v1, v2, etc) +type Executor interface { + // Execute executes the given proposal, and returns any error encountered + // during the execution + Execute() error +} diff --git a/examples/gno.land/p/demo/dao/gno.mod b/examples/gno.land/p/demo/dao/gno.mod new file mode 100644 index 00000000000..ecbab2f7692 --- /dev/null +++ b/examples/gno.land/p/demo/dao/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/dao + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/dao/proposals.gno b/examples/gno.land/p/demo/dao/proposals.gno new file mode 100644 index 00000000000..5cad679d006 --- /dev/null +++ b/examples/gno.land/p/demo/dao/proposals.gno @@ -0,0 +1,62 @@ +package dao + +import "std" + +// ProposalStatus is the currently active proposal status, +// changed based on DAO functionality. +// Status transitions: +// +// ACTIVE -> ACCEPTED -> EXECUTION(SUCCEEDED/FAILED) +// +// ACTIVE -> NOT ACCEPTED +type ProposalStatus string + +var ( + Active ProposalStatus = "active" // proposal is still active + Accepted ProposalStatus = "accepted" // proposal gathered quorum + NotAccepted ProposalStatus = "not accepted" // proposal failed to gather quorum + ExecutionSuccessful ProposalStatus = "execution successful" // proposal is executed successfully + ExecutionFailed ProposalStatus = "execution failed" // proposal is failed during execution +) + +func (s ProposalStatus) String() string { + return string(s) +} + +// PropStore defines the proposal storage abstraction +type PropStore interface { + // Proposals returns the given paginated proposals + Proposals(offset, count uint64) []Proposal + + // ProposalByID returns the proposal associated with + // the given ID, if any + ProposalByID(id uint64) (Proposal, error) + + // Size returns the number of proposals in + // the proposal store + Size() int +} + +// Proposal is the single proposal abstraction +type Proposal interface { + // Author returns the author of the proposal + Author() std.Address + + // Description returns the description of the proposal + Description() string + + // Status returns the status of the proposal + Status() ProposalStatus + + // Executor returns the proposal executor + Executor() Executor + + // Stats returns the voting stats of the proposal + Stats() Stats + + // IsExpired returns a flag indicating if the proposal expired + IsExpired() bool + + // Render renders the proposal in a readable format + Render() string +} diff --git a/examples/gno.land/p/demo/dao/vote.gno b/examples/gno.land/p/demo/dao/vote.gno new file mode 100644 index 00000000000..94369f41e1b --- /dev/null +++ b/examples/gno.land/p/demo/dao/vote.gno @@ -0,0 +1,69 @@ +package dao + +// NOTE: +// This voting pods will be removed in a future version of the +// p/demo/dao package. A DAO shouldn't have to comply with or define how the voting mechanism works internally; +// it should be viewed as an entity that makes decisions +// +// The extent of "votes being enforced" in this implementation is just in the context +// of types a DAO can use (import), and in the context of "Stats", where +// there is a notion of "Yay", "Nay" and "Abstain" votes. +const ( + VoteAddedEvent = "VoteAdded" // emitted when a vote was cast for a proposal + + VoteAddedIDKey = "proposal-id" + VoteAddedAuthorKey = "author" + VoteAddedOptionKey = "option" +) + +// VoteOption is the limited voting option for a DAO proposal +type VoteOption string + +const ( + YesVote VoteOption = "YES" // Proposal should be accepted + NoVote VoteOption = "NO" // Proposal should be rejected + AbstainVote VoteOption = "ABSTAIN" // Side is not chosen +) + +func (v VoteOption) String() string { + return string(v) +} + +// Stats encompasses the proposal voting stats +type Stats struct { + YayVotes uint64 + NayVotes uint64 + AbstainVotes uint64 + + TotalVotingPower uint64 +} + +// YayPercent returns the percentage (0-100) of the yay votes +// in relation to the total voting power +func (v Stats) YayPercent() uint64 { + return v.YayVotes * 100 / v.TotalVotingPower +} + +// NayPercent returns the percentage (0-100) of the nay votes +// in relation to the total voting power +func (v Stats) NayPercent() uint64 { + return v.NayVotes * 100 / v.TotalVotingPower +} + +// AbstainPercent returns the percentage (0-100) of the abstain votes +// in relation to the total voting power +func (v Stats) AbstainPercent() uint64 { + return v.AbstainVotes * 100 / v.TotalVotingPower +} + +// MissingVotes returns the summed voting power that has not +// participated in proposal voting yet +func (v Stats) MissingVotes() uint64 { + return v.TotalVotingPower - (v.YayVotes + v.NayVotes + v.AbstainVotes) +} + +// MissingVotesPercent returns the percentage (0-100) of the missing votes +// in relation to the total voting power +func (v Stats) MissingVotesPercent() uint64 { + return v.MissingVotes() * 100 / v.TotalVotingPower +} diff --git a/examples/gno.land/r/gov/dao/gno.mod b/examples/gno.land/p/demo/membstore/gno.mod similarity index 54% rename from examples/gno.land/r/gov/dao/gno.mod rename to examples/gno.land/p/demo/membstore/gno.mod index f3c0bae990e..da22a8dcae4 100644 --- a/examples/gno.land/r/gov/dao/gno.mod +++ b/examples/gno.land/p/demo/membstore/gno.mod @@ -1,8 +1,9 @@ -module gno.land/r/gov/dao +module gno.land/p/demo/membstore require ( + gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/demo/urequire v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/membstore/members.gno b/examples/gno.land/p/demo/membstore/members.gno new file mode 100644 index 00000000000..0bbaaaa8b04 --- /dev/null +++ b/examples/gno.land/p/demo/membstore/members.gno @@ -0,0 +1,38 @@ +package membstore + +import ( + "std" +) + +// MemberStore defines the member storage abstraction +type MemberStore interface { + // Members returns all members in the store + Members(offset, count uint64) []Member + + // Size returns the current size of the store + Size() int + + // IsMember returns a flag indicating if the given address + // belongs to a member + IsMember(address std.Address) bool + + // TotalPower returns the total voting power of the member store + TotalPower() uint64 + + // Member returns the requested member + Member(address std.Address) (Member, error) + + // AddMember adds a member to the store + AddMember(member Member) error + + // UpdateMember updates the member in the store. + // If updating a member's voting power to 0, + // the member will be removed + UpdateMember(address std.Address, member Member) error +} + +// Member holds the relevant member information +type Member struct { + Address std.Address // bech32 gno address of the member (unique) + VotingPower uint64 // the voting power of the member +} diff --git a/examples/gno.land/p/demo/membstore/membstore.gno b/examples/gno.land/p/demo/membstore/membstore.gno new file mode 100644 index 00000000000..6e1932978d9 --- /dev/null +++ b/examples/gno.land/p/demo/membstore/membstore.gno @@ -0,0 +1,209 @@ +package membstore + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +var ( + ErrAlreadyMember = errors.New("address is already a member") + ErrMissingMember = errors.New("address is not a member") + ErrInvalidAddressUpdate = errors.New("invalid address update") + ErrNotGovDAO = errors.New("caller not correct govdao instance") +) + +// maxRequestMembers is the maximum number of +// paginated members that can be requested +const maxRequestMembers = 50 + +type Option func(*MembStore) + +// WithInitialMembers initializes the member store +// with an initial member list +func WithInitialMembers(members []Member) Option { + return func(store *MembStore) { + for _, m := range members { + memberAddr := m.Address.String() + + // Check if the member already exists + if store.members.Has(memberAddr) { + panic(ufmt.Errorf("%s, %s", memberAddr, ErrAlreadyMember)) + } + + store.members.Set(memberAddr, m) + store.totalVotingPower += m.VotingPower + } + } +} + +// WithDAOPkgPath initializes the member store +// with a dao package path guard +func WithDAOPkgPath(daoPkgPath string) Option { + return func(store *MembStore) { + store.daoPkgPath = daoPkgPath + } +} + +// MembStore implements the dao.MembStore abstraction +type MembStore struct { + daoPkgPath string // active dao pkg path, if any + members *avl.Tree // std.Address -> Member + totalVotingPower uint64 // cached value for quick lookups +} + +// NewMembStore creates a new member store +func NewMembStore(opts ...Option) *MembStore { + m := &MembStore{ + members: avl.NewTree(), // empty set + daoPkgPath: "", // no dao guard + totalVotingPower: 0, + } + + // Apply the options + for _, opt := range opts { + opt(m) + } + + return m +} + +// AddMember adds member to the member store `m`. +// It fails if the caller is not GovDAO or +// if the member is already present +func (m *MembStore) AddMember(member Member) error { + if !m.isCallerDAORealm() { + return ErrNotGovDAO + } + + // Check if the member exists + if m.IsMember(member.Address) { + return ErrAlreadyMember + } + + // Add the member + m.members.Set(member.Address.String(), member) + + // Update the total voting power + m.totalVotingPower += member.VotingPower + + return nil +} + +// UpdateMember updates the member with the given address. +// Updating fails if the caller is not GovDAO. +func (m *MembStore) UpdateMember(address std.Address, member Member) error { + if !m.isCallerDAORealm() { + return ErrNotGovDAO + } + + // Get the member + oldMember, err := m.Member(address) + if err != nil { + return err + } + + // Check if this is a removal request + if member.VotingPower == 0 { + m.members.Remove(address.String()) + + // Update the total voting power + m.totalVotingPower -= oldMember.VotingPower + + return nil + } + + // Check that the member wouldn't be + // overwriting an existing one + isAddressUpdate := address != member.Address + if isAddressUpdate && m.IsMember(member.Address) { + return ErrInvalidAddressUpdate + } + + // Remove the old member info + // in case the address changed + if address != member.Address { + m.members.Remove(address.String()) + } + + // Save the new member info + m.members.Set(member.Address.String(), member) + + // Update the total voting power + difference := member.VotingPower - oldMember.VotingPower + m.totalVotingPower += difference + + return nil +} + +// IsMember returns a flag indicating if the given +// address belongs to a member of the member store +func (m *MembStore) IsMember(address std.Address) bool { + _, exists := m.members.Get(address.String()) + + return exists +} + +// Member returns the member associated with the given address +func (m *MembStore) Member(address std.Address) (Member, error) { + member, exists := m.members.Get(address.String()) + if !exists { + return Member{}, ErrMissingMember + } + + return member.(Member), nil +} + +// Members returns a paginated list of members from +// the member store. If the store is empty, an empty slice +// is returned instead +func (m *MembStore) Members(offset, count uint64) []Member { + // Calculate the left and right bounds + if count < 1 || offset >= uint64(m.members.Size()) { + return []Member{} + } + + // Limit the maximum number of returned members + if count > maxRequestMembers { + count = maxRequestMembers + } + + // Gather the members + members := make([]Member, 0) + m.members.IterateByOffset( + int(offset), + int(count), + func(_ string, val interface{}) bool { + member := val.(Member) + + // Save the member + members = append(members, member) + + return false + }) + + return members +} + +// Size returns the number of active members in the member store +func (m *MembStore) Size() int { + return m.members.Size() +} + +// TotalPower returns the total voting power +// of the member store +func (m *MembStore) TotalPower() uint64 { + return m.totalVotingPower +} + +// isCallerDAORealm returns a flag indicating if the +// current caller context is the active DAO Realm. +// We need to include a dao guard, even if the +// executor guarantees it, because +// the API of the member store is public and callable +// by anyone who has a reference to the member store instance. +func (m *MembStore) isCallerDAORealm() bool { + return m.daoPkgPath == "" || std.CurrentRealm().PkgPath() == m.daoPkgPath +} diff --git a/examples/gno.land/p/demo/membstore/membstore_test.gno b/examples/gno.land/p/demo/membstore/membstore_test.gno new file mode 100644 index 00000000000..2181adde077 --- /dev/null +++ b/examples/gno.land/p/demo/membstore/membstore_test.gno @@ -0,0 +1,317 @@ +package membstore + +import ( + "testing" + + "std" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" +) + +// generateMembers generates dummy govdao members +func generateMembers(t *testing.T, count int) []Member { + t.Helper() + + members := make([]Member, 0, count) + + for i := 0; i < count; i++ { + members = append(members, Member{ + Address: testutils.TestAddress(ufmt.Sprintf("member %d", i)), + VotingPower: 10, + }) + } + + return members +} + +func TestMembStore_GetMember(t *testing.T) { + t.Parallel() + + t.Run("member not found", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore() + + _, err := m.Member(testutils.TestAddress("random")) + uassert.ErrorIs(t, err, ErrMissingMember) + }) + + t.Run("valid member fetched", func(t *testing.T) { + t.Parallel() + + // Create a non-empty store + members := generateMembers(t, 1) + + m := NewMembStore(WithInitialMembers(members)) + + _, err := m.Member(members[0].Address) + uassert.NoError(t, err) + }) +} + +func TestMembStore_GetMembers(t *testing.T) { + t.Parallel() + + t.Run("no members", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore() + + members := m.Members(0, 10) + uassert.Equal(t, 0, len(members)) + }) + + t.Run("proper pagination", func(t *testing.T) { + t.Parallel() + + var ( + numMembers = maxRequestMembers * 2 + halfRange = numMembers / 2 + + members = generateMembers(t, numMembers) + m = NewMembStore(WithInitialMembers(members)) + + verifyMembersPresent = func(members, fetchedMembers []Member) { + for _, fetchedMember := range fetchedMembers { + for _, member := range members { + if member.Address != fetchedMember.Address { + continue + } + + uassert.Equal(t, member.VotingPower, fetchedMember.VotingPower) + } + } + } + ) + + urequire.Equal(t, numMembers, m.Size()) + + fetchedMembers := m.Members(0, uint64(halfRange)) + urequire.Equal(t, halfRange, len(fetchedMembers)) + + // Verify the members + verifyMembersPresent(members, fetchedMembers) + + // Fetch the other half + fetchedMembers = m.Members(uint64(halfRange), uint64(halfRange)) + urequire.Equal(t, halfRange, len(fetchedMembers)) + + // Verify the members + verifyMembersPresent(members, fetchedMembers) + }) +} + +func TestMembStore_IsMember(t *testing.T) { + t.Parallel() + + t.Run("non-existing member", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore() + + uassert.False(t, m.IsMember(testutils.TestAddress("random"))) + }) + + t.Run("existing member", func(t *testing.T) { + t.Parallel() + + // Create a non-empty store + members := generateMembers(t, 50) + + m := NewMembStore(WithInitialMembers(members)) + + for _, member := range members { + uassert.True(t, m.IsMember(member.Address)) + } + }) +} + +func TestMembStore_AddMember(t *testing.T) { + t.Parallel() + + t.Run("caller not govdao", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore(WithDAOPkgPath("gno.land/r/gov/dao")) + + // Attempt to add a member + member := generateMembers(t, 1)[0] + uassert.ErrorIs(t, m.AddMember(member), ErrNotGovDAO) + }) + + t.Run("member already exists", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create a non-empty store + members := generateMembers(t, 1) + m := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members)) + + // Attempt to add a member + uassert.ErrorIs(t, m.AddMember(members[0]), ErrAlreadyMember) + }) + + t.Run("new member added", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create an empty store + members := generateMembers(t, 1) + m := NewMembStore(WithDAOPkgPath(daoPkgPath)) + + // Attempt to add a member + urequire.NoError(t, m.AddMember(members[0])) + + // Make sure the member is added + uassert.True(t, m.IsMember(members[0].Address)) + }) +} + +func TestMembStore_Size(t *testing.T) { + t.Parallel() + + t.Run("empty govdao", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore() + + uassert.Equal(t, 0, m.Size()) + }) + + t.Run("non-empty govdao", func(t *testing.T) { + t.Parallel() + + // Create a non-empty store + members := generateMembers(t, 50) + m := NewMembStore(WithInitialMembers(members)) + + uassert.Equal(t, len(members), m.Size()) + }) +} + +func TestMembStore_UpdateMember(t *testing.T) { + t.Parallel() + + t.Run("caller not govdao", func(t *testing.T) { + t.Parallel() + + // Create an empty store + m := NewMembStore(WithDAOPkgPath("gno.land/r/gov/dao")) + + // Attempt to update a member + member := generateMembers(t, 1)[0] + uassert.ErrorIs(t, m.UpdateMember(member.Address, member), ErrNotGovDAO) + }) + + t.Run("non-existing member", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create an empty store + members := generateMembers(t, 1) + m := NewMembStore(WithDAOPkgPath(daoPkgPath)) + + // Attempt to update a member + uassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[0]), ErrMissingMember) + }) + + t.Run("overwrite member attempt", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create a non-empty store + members := generateMembers(t, 2) + m := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members)) + + // Attempt to update a member + uassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[1]), ErrInvalidAddressUpdate) + }) + + t.Run("successful update", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create a non-empty store + members := generateMembers(t, 1) + m := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members)) + + oldVotingPower := m.totalVotingPower + urequire.Equal(t, members[0].VotingPower, oldVotingPower) + + votingPower := uint64(300) + members[0].VotingPower = votingPower + + // Attempt to update a member + uassert.NoError(t, m.UpdateMember(members[0].Address, members[0])) + uassert.Equal(t, votingPower, m.Members(0, 10)[0].VotingPower) + urequire.Equal(t, votingPower, m.totalVotingPower) + }) + + t.Run("member removed", func(t *testing.T) { + t.Parallel() + + var ( + // Execute as the /r/gov/dao caller + daoPkgPath = "gno.land/r/gov/dao" + r = std.NewCodeRealm(daoPkgPath) + ) + + std.TestSetRealm(r) + + // Create a non-empty store + members := generateMembers(t, 1) + m := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members)) + + votingPower := uint64(0) + members[0].VotingPower = votingPower + + // Attempt to update a member + uassert.NoError(t, m.UpdateMember(members[0].Address, members[0])) + + // Make sure the member was removed + uassert.False(t, m.IsMember(members[0].Address)) + }) +} diff --git a/examples/gno.land/p/demo/simpledao/dao.gno b/examples/gno.land/p/demo/simpledao/dao.gno new file mode 100644 index 00000000000..7a20237ec3f --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/dao.gno @@ -0,0 +1,215 @@ +package simpledao + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + "gno.land/p/demo/ufmt" +) + +var ( + ErrInvalidExecutor = errors.New("invalid executor provided") + ErrInsufficientProposalFunds = errors.New("insufficient funds for proposal") + ErrInsufficientExecuteFunds = errors.New("insufficient funds for executing proposal") + ErrProposalExecuted = errors.New("proposal already executed") + ErrProposalInactive = errors.New("proposal is inactive") + ErrProposalNotAccepted = errors.New("proposal is not accepted") +) + +var ( + minProposalFeeValue int64 = 100 * 1_000_000 // minimum gnot required for a govdao proposal (100 GNOT) + minExecuteFeeValue int64 = 500 * 1_000_000 // minimum gnot required for a govdao proposal (500 GNOT) + + minProposalFee = std.NewCoin("ugnot", minProposalFeeValue) + minExecuteFee = std.NewCoin("ugnot", minExecuteFeeValue) +) + +// SimpleDAO is a simple DAO implementation +type SimpleDAO struct { + proposals *avl.Tree // seqid.ID -> proposal + membStore membstore.MemberStore +} + +// New creates a new instance of the simpledao DAO +func New(membStore membstore.MemberStore) *SimpleDAO { + return &SimpleDAO{ + proposals: avl.NewTree(), + membStore: membStore, + } +} + +func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { + // Make sure the executor is set + if request.Executor == nil { + return 0, ErrInvalidExecutor + } + + var ( + caller = getDAOCaller() + sentCoins = std.GetOrigSend() // Get the sent coins, if any + canCoverFee = sentCoins.AmountOf("ugnot") >= minProposalFee.Amount + ) + + // Check if the proposal is valid + if !s.membStore.IsMember(caller) && !canCoverFee { + return 0, ErrInsufficientProposalFunds + } + + // Create the wrapped proposal + prop := &proposal{ + author: caller, + description: request.Description, + executor: request.Executor, + status: dao.Active, + tally: newTally(), + getTotalVotingPowerFn: s.membStore.TotalPower, + } + + // Add the proposal + id, err := s.addProposal(prop) + if err != nil { + return 0, ufmt.Errorf("unable to add proposal, %s", err.Error()) + } + + // Emit the proposal added event + dao.EmitProposalAdded(id, caller) + + return id, nil +} + +func (s *SimpleDAO) VoteOnProposal(id uint64, option dao.VoteOption) error { + // Verify the GOVDAO member + caller := getDAOCaller() + + member, err := s.membStore.Member(caller) + if err != nil { + return ufmt.Errorf("unable to get govdao member, %s", err.Error()) + } + + // Check if the proposal exists + propRaw, err := s.ProposalByID(id) + if err != nil { + return ufmt.Errorf("unable to get proposal %d, %s", id, err.Error()) + } + + prop := propRaw.(*proposal) + + // Check the proposal status + if prop.Status() == dao.ExecutionSuccessful || + prop.Status() == dao.ExecutionFailed { + // Proposal was already executed, nothing to vote on anymore. + // + // In fact, the proposal should stop accepting + // votes as soon as a 2/3+ majority is reached + // on either option, but leaving the ability to vote still, + // even if a proposal is accepted, or not accepted, + // leaves room for "principle" vote decisions to be recorded + return ErrProposalInactive + } + + // Cast the vote + if err = prop.tally.castVote(member, option); err != nil { + return ufmt.Errorf("unable to vote on proposal %d, %s", id, err.Error()) + } + + // Emit the vote cast event + dao.EmitVoteAdded(id, caller, option) + + // Check the votes to see if quorum is reached + var ( + totalPower = s.membStore.TotalPower() + majorityPower = (2 * totalPower) / 3 + ) + + acceptProposal := func() { + prop.status = dao.Accepted + + dao.EmitProposalAccepted(id) + } + + declineProposal := func() { + prop.status = dao.NotAccepted + + dao.EmitProposalNotAccepted(id) + } + + switch { + case prop.tally.yays > majorityPower: + // 2/3+ voted YES + acceptProposal() + case prop.tally.nays > majorityPower: + // 2/3+ voted NO + declineProposal() + case prop.tally.abstains > majorityPower: + // 2/3+ voted ABSTAIN + declineProposal() + case prop.tally.yays+prop.tally.nays+prop.tally.abstains >= totalPower: + // Everyone voted, but it's undecided, + // hence the proposal can't go through + declineProposal() + default: + // Quorum not reached + } + + return nil +} + +func (s *SimpleDAO) ExecuteProposal(id uint64) error { + var ( + caller = getDAOCaller() + sentCoins = std.GetOrigSend() // Get the sent coins, if any + canCoverFee = sentCoins.AmountOf("ugnot") >= minExecuteFee.Amount + ) + + // Check if the non-DAO member can cover the execute fee + if !s.membStore.IsMember(caller) && !canCoverFee { + return ErrInsufficientExecuteFunds + } + + // Check if the proposal exists + propRaw, err := s.ProposalByID(id) + if err != nil { + return ufmt.Errorf("unable to get proposal %d, %s", id, err.Error()) + } + + prop := propRaw.(*proposal) + + // Check if the proposal is executed + if prop.Status() == dao.ExecutionSuccessful || + prop.Status() == dao.ExecutionFailed { + // Proposal is already executed + return ErrProposalExecuted + } + + // Check the proposal status + if prop.Status() != dao.Accepted { + // Proposal is not accepted, cannot be executed + return ErrProposalNotAccepted + } + + // Emit an event when the execution finishes + defer dao.EmitProposalExecuted(id, prop.status) + + // Attempt to execute the proposal + if err = prop.executor.Execute(); err != nil { + prop.status = dao.ExecutionFailed + + return ufmt.Errorf("error during proposal %d execution, %s", id, err.Error()) + } + + // Update the proposal status + prop.status = dao.ExecutionSuccessful + + return nil +} + +// getDAOCaller returns the DAO caller. +// XXX: This is not a great way to determine the caller, and it is very unsafe. +// However, the current MsgRun context does not persist escaping the main() scope. +// Until a better solution is developed, this enables proposals to be made through a package deployment + init() +func getDAOCaller() std.Address { + return std.GetOrigCaller() +} diff --git a/examples/gno.land/p/demo/simpledao/dao_test.gno b/examples/gno.land/p/demo/simpledao/dao_test.gno new file mode 100644 index 00000000000..fb32895e72f --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/dao_test.gno @@ -0,0 +1,829 @@ +package simpledao + +import ( + "errors" + "std" + "testing" + + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" +) + +// generateMembers generates dummy govdao members +func generateMembers(t *testing.T, count int) []membstore.Member { + t.Helper() + + members := make([]membstore.Member, 0, count) + + for i := 0; i < count; i++ { + members = append(members, membstore.Member{ + Address: testutils.TestAddress(ufmt.Sprintf("member %d", i)), + VotingPower: 10, + }) + } + + return members +} + +func TestSimpleDAO_Propose(t *testing.T) { + t.Parallel() + + t.Run("invalid executor", func(t *testing.T) { + t.Parallel() + + s := New(nil) + + _, err := s.Propose(dao.ProposalRequest{}) + uassert.ErrorIs( + t, + err, + ErrInvalidExecutor, + ) + }) + + t.Run("caller cannot cover fee", func(t *testing.T) { + t.Parallel() + + var ( + called = false + cb = func() error { + called = true + + return nil + } + ex = &mockExecutor{ + executeFn: cb, + } + + sentCoins = std.NewCoins( + std.NewCoin( + "ugnot", + minProposalFeeValue-1, + ), + ) + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return false + }, + } + s = New(ms) + ) + + // Set the sent coins to be lower + // than the proposal fee + std.TestSetOrigSend(sentCoins, std.Coins{}) + + _, err := s.Propose(dao.ProposalRequest{ + Executor: ex, + }) + uassert.ErrorIs( + t, + err, + ErrInsufficientProposalFunds, + ) + + uassert.False(t, called) + }) + + t.Run("proposal added", func(t *testing.T) { + t.Parallel() + + var ( + called = false + cb = func() error { + called = true + + return nil + } + + ex = &mockExecutor{ + executeFn: cb, + } + description = "Proposal description" + + proposer = testutils.TestAddress("proposer") + sentCoins = std.NewCoins( + std.NewCoin( + "ugnot", + minProposalFeeValue, // enough to cover + ), + ) + + ms = &mockMemberStore{ + isMemberFn: func(addr std.Address) bool { + return addr == proposer + }, + } + s = New(ms) + ) + + // Set the sent coins to be enough + // to cover the fee + std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOrigCaller(proposer) + + // Make sure the proposal was added + id, err := s.Propose(dao.ProposalRequest{ + Description: description, + Executor: ex, + }) + uassert.NoError(t, err) + uassert.False(t, called) + + // Make sure the proposal exists + prop, err := s.ProposalByID(id) + uassert.NoError(t, err) + + uassert.Equal(t, proposer.String(), prop.Author().String()) + uassert.Equal(t, description, prop.Description()) + uassert.Equal(t, dao.Active.String(), prop.Status().String()) + + stats := prop.Stats() + + uassert.Equal(t, uint64(0), stats.YayVotes) + uassert.Equal(t, uint64(0), stats.NayVotes) + uassert.Equal(t, uint64(0), stats.AbstainVotes) + uassert.Equal(t, uint64(0), stats.TotalVotingPower) + }) +} + +func TestSimpleDAO_VoteOnProposal(t *testing.T) { + t.Parallel() + + t.Run("not govdao member", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + fetchErr = errors.New("fetch error") + + ms = &mockMemberStore{ + memberFn: func(_ std.Address) (membstore.Member, error) { + return membstore.Member{ + Address: voter, + }, fetchErr + }, + } + s = New(ms) + ) + + std.TestSetOrigCaller(voter) + + // Attempt to vote on the proposal + uassert.ErrorContains( + t, + s.VoteOnProposal(0, dao.YesVote), + fetchErr.Error(), + ) + }) + + t.Run("missing proposal", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + ms = &mockMemberStore{ + memberFn: func(a std.Address) (membstore.Member, error) { + if a != voter { + return membstore.Member{}, errors.New("not found") + } + + return membstore.Member{ + Address: voter, + }, nil + }, + } + + s = New(ms) + ) + + std.TestSetOrigCaller(voter) + + // Attempt to vote on the proposal + uassert.ErrorContains( + t, + s.VoteOnProposal(0, dao.YesVote), + ErrMissingProposal.Error(), + ) + }) + + t.Run("proposal executed", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + + ms = &mockMemberStore{ + memberFn: func(a std.Address) (membstore.Member, error) { + if a != voter { + return membstore.Member{}, errors.New("not found") + } + + return membstore.Member{ + Address: voter, + }, nil + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.ExecutionSuccessful, + } + ) + + std.TestSetOrigCaller(voter) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.ErrorIs( + t, + s.VoteOnProposal(id, dao.YesVote), + ErrProposalInactive, + ) + }) + + t.Run("double vote on proposal", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + member = membstore.Member{ + Address: voter, + VotingPower: 10, + } + + ms = &mockMemberStore{ + memberFn: func(a std.Address) (membstore.Member, error) { + if a != voter { + return membstore.Member{}, errors.New("not found") + } + + return member, nil + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + std.TestSetOrigCaller(voter) + + // Cast the initial vote + urequire.NoError(t, prop.tally.castVote(member, dao.YesVote)) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.ErrorContains( + t, + s.VoteOnProposal(id, dao.YesVote), + ErrAlreadyVoted.Error(), + ) + }) + + t.Run("majority accepted", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + + ms = &mockMemberStore{ + memberFn: func(address std.Address) (membstore.Member, error) { + for _, m := range members { + if m.Address == address { + return m, nil + } + } + + return membstore.Member{}, errors.New("not found") + }, + + totalPowerFn: func() uint64 { + power := uint64(0) + + for _, m := range members { + power += m.VotingPower + } + + return power + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + majorityIndex := (len(members)*2)/3 + 1 // 2/3+ + for _, m := range members[:majorityIndex] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.YesVote), + ) + } + + // Make sure the proposal was accepted + uassert.Equal(t, dao.Accepted.String(), prop.status.String()) + }) + + t.Run("majority rejected", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + + ms = &mockMemberStore{ + memberFn: func(address std.Address) (membstore.Member, error) { + for _, m := range members { + if m.Address == address { + return m, nil + } + } + + return membstore.Member{}, errors.New("member not found") + }, + + totalPowerFn: func() uint64 { + power := uint64(0) + + for _, m := range members { + power += m.VotingPower + } + + return power + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + majorityIndex := (len(members)*2)/3 + 1 // 2/3+ + for _, m := range members[:majorityIndex] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.NoVote), + ) + } + + // Make sure the proposal was not accepted + uassert.Equal(t, dao.NotAccepted.String(), prop.status.String()) + }) + + t.Run("majority abstained", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + + ms = &mockMemberStore{ + memberFn: func(address std.Address) (membstore.Member, error) { + for _, m := range members { + if m.Address == address { + return m, nil + } + } + + return membstore.Member{}, errors.New("member not found") + }, + + totalPowerFn: func() uint64 { + power := uint64(0) + + for _, m := range members { + power += m.VotingPower + } + + return power + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + majorityIndex := (len(members)*2)/3 + 1 // 2/3+ + for _, m := range members[:majorityIndex] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.AbstainVote), + ) + } + + // Make sure the proposal was not accepted + uassert.Equal(t, dao.NotAccepted.String(), prop.status.String()) + }) + + t.Run("everyone voted, undecided", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + + ms = &mockMemberStore{ + memberFn: func(address std.Address) (membstore.Member, error) { + for _, m := range members { + if m.Address == address { + return m, nil + } + } + + return membstore.Member{}, errors.New("member not found") + }, + + totalPowerFn: func() uint64 { + power := uint64(0) + + for _, m := range members { + power += m.VotingPower + } + + return power + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // The first half votes yes + for _, m := range members[:len(members)/2] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.YesVote), + ) + } + + // The other half votes no + for _, m := range members[len(members)/2:] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.NoVote), + ) + } + + // Make sure the proposal is not active, + // since everyone voted, and it was undecided + uassert.Equal(t, dao.NotAccepted.String(), prop.status.String()) + }) + + t.Run("proposal undecided", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + + ms = &mockMemberStore{ + memberFn: func(address std.Address) (membstore.Member, error) { + for _, m := range members { + if m.Address == address { + return m, nil + } + } + + return membstore.Member{}, errors.New("member not found") + }, + + totalPowerFn: func() uint64 { + power := uint64(0) + + for _, m := range members { + power += m.VotingPower + } + + return power + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.Active, + executor: &mockExecutor{}, + tally: newTally(), + } + ) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // The first quarter votes yes + for _, m := range members[:len(members)/4] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.YesVote), + ) + } + + // The second quarter votes no + for _, m := range members[len(members)/4 : len(members)/2] { + std.TestSetOrigCaller(m.Address) + + // Attempt to vote on the proposal + urequire.NoError( + t, + s.VoteOnProposal(id, dao.NoVote), + ) + } + + // Make sure the proposal is still active, + // since there wasn't quorum reached on any decision + uassert.Equal(t, dao.Active.String(), prop.status.String()) + }) +} + +func TestSimpleDAO_ExecuteProposal(t *testing.T) { + t.Parallel() + + t.Run("caller cannot cover fee", func(t *testing.T) { + t.Parallel() + + var ( + sentCoins = std.NewCoins( + std.NewCoin( + "ugnot", + minExecuteFeeValue-1, + ), + ) + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return false + }, + } + s = New(ms) + ) + + // Set the sent coins to be lower + // than the execute fee + std.TestSetOrigSend(sentCoins, std.Coins{}) + + uassert.ErrorIs( + t, + s.ExecuteProposal(0), + ErrInsufficientExecuteFunds, + ) + }) + + t.Run("missing proposal", func(t *testing.T) { + t.Parallel() + + var ( + sentCoins = std.NewCoins( + std.NewCoin( + "ugnot", + minExecuteFeeValue, + ), + ) + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return true + }, + } + + s = New(ms) + ) + + // Set the sent coins to be enough + // so the execution can take place + std.TestSetOrigSend(sentCoins, std.Coins{}) + + uassert.ErrorContains( + t, + s.ExecuteProposal(0), + ErrMissingProposal.Error(), + ) + }) + + t.Run("proposal not accepted", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return true + }, + } + s = New(ms) + + prop = &proposal{ + status: dao.NotAccepted, + } + ) + + std.TestSetOrigCaller(voter) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.ErrorIs( + t, + s.ExecuteProposal(id), + ErrProposalNotAccepted, + ) + }) + + t.Run("proposal already executed", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + status dao.ProposalStatus + }{ + { + "execution was successful", + dao.ExecutionSuccessful, + }, + { + "execution failed", + dao.ExecutionFailed, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return true + }, + } + s = New(ms) + + prop = &proposal{ + status: testCase.status, + } + ) + + std.TestSetOrigCaller(voter) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.ErrorIs( + t, + s.ExecuteProposal(id), + ErrProposalExecuted, + ) + }) + } + }) + + t.Run("execution error", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return true + }, + } + + s = New(ms) + + execError = errors.New("exec error") + + mockExecutor = &mockExecutor{ + executeFn: func() error { + return execError + }, + } + + prop = &proposal{ + status: dao.Accepted, + executor: mockExecutor, + } + ) + + std.TestSetOrigCaller(voter) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.ErrorContains( + t, + s.ExecuteProposal(id), + execError.Error(), + ) + + uassert.Equal(t, dao.ExecutionFailed.String(), prop.status.String()) + }) + + t.Run("successful execution", func(t *testing.T) { + t.Parallel() + + var ( + voter = testutils.TestAddress("voter") + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return true + }, + } + s = New(ms) + + called = false + mockExecutor = &mockExecutor{ + executeFn: func() error { + called = true + + return nil + }, + } + + prop = &proposal{ + status: dao.Accepted, + executor: mockExecutor, + } + ) + + std.TestSetOrigCaller(voter) + + // Add an initial proposal + id, err := s.addProposal(prop) + urequire.NoError(t, err) + + // Attempt to vote on the proposal + uassert.NoError(t, s.ExecuteProposal(id)) + uassert.Equal(t, dao.ExecutionSuccessful.String(), prop.status.String()) + uassert.True(t, called) + }) +} diff --git a/examples/gno.land/p/demo/simpledao/gno.mod b/examples/gno.land/p/demo/simpledao/gno.mod new file mode 100644 index 00000000000..f6f14f379ec --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/gno.mod @@ -0,0 +1,12 @@ +module gno.land/p/demo/simpledao + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/dao v0.0.0-latest + gno.land/p/demo/membstore v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/simpledao/mock_test.gno b/examples/gno.land/p/demo/simpledao/mock_test.gno new file mode 100644 index 00000000000..0cf12ccff01 --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/mock_test.gno @@ -0,0 +1,97 @@ +package simpledao + +import ( + "std" + + "gno.land/p/demo/membstore" +) + +type executeDelegate func() error + +type mockExecutor struct { + executeFn executeDelegate +} + +func (m *mockExecutor) Execute() error { + if m.executeFn != nil { + return m.executeFn() + } + + return nil +} + +type ( + membersDelegate func(uint64, uint64) []membstore.Member + sizeDelegate func() int + isMemberDelegate func(std.Address) bool + totalPowerDelegate func() uint64 + memberDelegate func(std.Address) (membstore.Member, error) + addMemberDelegate func(membstore.Member) error + updateMemberDelegate func(std.Address, membstore.Member) error +) + +type mockMemberStore struct { + membersFn membersDelegate + sizeFn sizeDelegate + isMemberFn isMemberDelegate + totalPowerFn totalPowerDelegate + memberFn memberDelegate + addMemberFn addMemberDelegate + updateMemberFn updateMemberDelegate +} + +func (m *mockMemberStore) Members(offset, count uint64) []membstore.Member { + if m.membersFn != nil { + return m.membersFn(offset, count) + } + + return nil +} + +func (m *mockMemberStore) Size() int { + if m.sizeFn != nil { + return m.sizeFn() + } + + return 0 +} + +func (m *mockMemberStore) IsMember(address std.Address) bool { + if m.isMemberFn != nil { + return m.isMemberFn(address) + } + + return false +} + +func (m *mockMemberStore) TotalPower() uint64 { + if m.totalPowerFn != nil { + return m.totalPowerFn() + } + + return 0 +} + +func (m *mockMemberStore) Member(address std.Address) (membstore.Member, error) { + if m.memberFn != nil { + return m.memberFn(address) + } + + return membstore.Member{}, nil +} + +func (m *mockMemberStore) AddMember(member membstore.Member) error { + if m.addMemberFn != nil { + return m.addMemberFn(member) + } + + return nil +} + +func (m *mockMemberStore) UpdateMember(address std.Address, member membstore.Member) error { + if m.updateMemberFn != nil { + return m.updateMemberFn(address, member) + } + + return nil +} diff --git a/examples/gno.land/p/demo/simpledao/propstore.gno b/examples/gno.land/p/demo/simpledao/propstore.gno new file mode 100644 index 00000000000..972297ff0ce --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/propstore.gno @@ -0,0 +1,163 @@ +package simpledao + +import ( + "errors" + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" +) + +var ErrMissingProposal = errors.New("proposal is missing") + +// maxRequestProposals is the maximum number of +// paginated proposals that can be requested +const maxRequestProposals = 10 + +// proposal is the internal simpledao proposal implementation +type proposal struct { + author std.Address // initiator of the proposal + description string // description of the proposal + + executor dao.Executor // executor for the proposal + status dao.ProposalStatus // status of the proposal + + tally *tally // voting tally + getTotalVotingPowerFn func() uint64 // callback for the total voting power +} + +func (p *proposal) Author() std.Address { + return p.author +} + +func (p *proposal) Description() string { + return p.description +} + +func (p *proposal) Status() dao.ProposalStatus { + return p.status +} + +func (p *proposal) Executor() dao.Executor { + return p.executor +} + +func (p *proposal) Stats() dao.Stats { + // Get the total voting power of the body + totalPower := p.getTotalVotingPowerFn() + + return dao.Stats{ + YayVotes: p.tally.yays, + NayVotes: p.tally.nays, + AbstainVotes: p.tally.abstains, + TotalVotingPower: totalPower, + } +} + +func (p *proposal) IsExpired() bool { + return false // this proposal never expires +} + +func (p *proposal) Render() string { + // Fetch the voting stats + stats := p.Stats() + + output := "" + output += ufmt.Sprintf("Author: %s", p.Author().String()) + output += "\n\n" + output += p.Description() + output += "\n\n" + output += ufmt.Sprintf("Status: %s", p.Status().String()) + output += "\n\n" + output += ufmt.Sprintf( + "Voting stats: YAY %d (%d%%), NAY %d (%d%%), ABSTAIN %d (%d%%), HAVEN'T VOTED %d (%d%%)", + stats.YayVotes, + stats.YayPercent(), + stats.NayVotes, + stats.NayPercent(), + stats.AbstainVotes, + stats.AbstainPercent(), + stats.MissingVotes(), + stats.MissingVotesPercent(), + ) + output += "\n\n" + output += ufmt.Sprintf("Threshold met: %t", stats.YayVotes > (2*stats.TotalVotingPower)/3) + + return output +} + +// addProposal adds a new simpledao proposal to the store +func (s *SimpleDAO) addProposal(proposal *proposal) (uint64, error) { + // See what the next proposal number should be + nextID := uint64(s.proposals.Size()) + + // Save the proposal + s.proposals.Set(getProposalID(nextID), proposal) + + return nextID, nil +} + +func (s *SimpleDAO) Proposals(offset, count uint64) []dao.Proposal { + // Check the requested count + if count < 1 { + return []dao.Proposal{} + } + + // Limit the maximum number of returned proposals + if count > maxRequestProposals { + count = maxRequestProposals + } + + var ( + startIndex = offset + endIndex = startIndex + count + + numProposals = uint64(s.proposals.Size()) + ) + + // Check if the current offset has any proposals + if startIndex >= numProposals { + return []dao.Proposal{} + } + + // Check if the right bound is good + if endIndex > numProposals { + endIndex = numProposals + } + + props := make([]dao.Proposal, 0) + s.proposals.Iterate( + getProposalID(startIndex), + getProposalID(endIndex), + func(_ string, val interface{}) bool { + prop := val.(*proposal) + + // Save the proposal + props = append(props, prop) + + return false + }, + ) + + return props +} + +func (s *SimpleDAO) ProposalByID(id uint64) (dao.Proposal, error) { + prop, exists := s.proposals.Get(getProposalID(id)) + if !exists { + return nil, ErrMissingProposal + } + + return prop.(*proposal), nil +} + +func (s *SimpleDAO) Size() int { + return s.proposals.Size() +} + +// getProposalID generates a sequential proposal ID +// from the given ID number +func getProposalID(id uint64) string { + return seqid.ID(id).String() +} diff --git a/examples/gno.land/p/demo/simpledao/propstore_test.gno b/examples/gno.land/p/demo/simpledao/propstore_test.gno new file mode 100644 index 00000000000..5aa6ba91a1e --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/propstore_test.gno @@ -0,0 +1,256 @@ +package simpledao + +import ( + "testing" + + "gno.land/p/demo/dao" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" +) + +// generateProposals generates dummy proposals +func generateProposals(t *testing.T, count int) []*proposal { + t.Helper() + + var ( + members = generateMembers(t, count) + proposals = make([]*proposal, 0, count) + ) + + for i := 0; i < count; i++ { + proposal := &proposal{ + author: members[i].Address, + description: ufmt.Sprintf("proposal %d", i), + status: dao.Active, + tally: newTally(), + getTotalVotingPowerFn: func() uint64 { + return 0 + }, + executor: nil, + } + + proposals = append(proposals, proposal) + } + + return proposals +} + +func equalProposals(t *testing.T, p1, p2 dao.Proposal) { + t.Helper() + + uassert.Equal( + t, + p1.Author().String(), + p2.Author().String(), + ) + + uassert.Equal( + t, + p1.Description(), + p2.Description(), + ) + + uassert.Equal( + t, + p1.Status().String(), + p2.Status().String(), + ) + + p1Stats := p1.Stats() + p2Stats := p2.Stats() + + uassert.Equal(t, p1Stats.YayVotes, p2Stats.YayVotes) + uassert.Equal(t, p1Stats.NayVotes, p2Stats.NayVotes) + uassert.Equal(t, p1Stats.AbstainVotes, p2Stats.AbstainVotes) + uassert.Equal(t, p1Stats.TotalVotingPower, p2Stats.TotalVotingPower) +} + +func TestProposal_Data(t *testing.T) { + t.Parallel() + + t.Run("author", func(t *testing.T) { + t.Parallel() + + p := &proposal{ + author: testutils.TestAddress("address"), + } + + uassert.Equal(t, p.author, p.Author()) + }) + + t.Run("description", func(t *testing.T) { + t.Parallel() + + p := &proposal{ + description: "example proposal description", + } + + uassert.Equal(t, p.description, p.Description()) + }) + + t.Run("status", func(t *testing.T) { + t.Parallel() + + p := &proposal{ + status: dao.ExecutionSuccessful, + } + + uassert.Equal(t, p.status.String(), p.Status().String()) + }) + + t.Run("executor", func(t *testing.T) { + t.Parallel() + + var ( + numCalled = 0 + cb = func() error { + numCalled++ + + return nil + } + + ex = &mockExecutor{ + executeFn: cb, + } + + p = &proposal{ + executor: ex, + } + ) + + urequire.NoError(t, p.executor.Execute()) + urequire.NoError(t, p.Executor().Execute()) + + uassert.Equal(t, 2, numCalled) + }) + + t.Run("no votes", func(t *testing.T) { + t.Parallel() + + p := &proposal{ + tally: newTally(), + getTotalVotingPowerFn: func() uint64 { + return 0 + }, + } + + stats := p.Stats() + + uassert.Equal(t, uint64(0), stats.YayVotes) + uassert.Equal(t, uint64(0), stats.NayVotes) + uassert.Equal(t, uint64(0), stats.AbstainVotes) + uassert.Equal(t, uint64(0), stats.TotalVotingPower) + }) + + t.Run("existing votes", func(t *testing.T) { + t.Parallel() + + var ( + members = generateMembers(t, 50) + totalPower = uint64(len(members)) * 10 + + p = &proposal{ + tally: newTally(), + getTotalVotingPowerFn: func() uint64 { + return totalPower + }, + } + ) + + for _, m := range members { + urequire.NoError(t, p.tally.castVote(m, dao.YesVote)) + } + + stats := p.Stats() + + uassert.Equal(t, totalPower, stats.YayVotes) + uassert.Equal(t, uint64(0), stats.NayVotes) + uassert.Equal(t, uint64(0), stats.AbstainVotes) + uassert.Equal(t, totalPower, stats.TotalVotingPower) + }) +} + +func TestSimpleDAO_GetProposals(t *testing.T) { + t.Parallel() + + t.Run("no proposals", func(t *testing.T) { + t.Parallel() + + s := New(nil) + + uassert.Equal(t, 0, s.Size()) + proposals := s.Proposals(0, 0) + + uassert.Equal(t, 0, len(proposals)) + }) + + t.Run("proper pagination", func(t *testing.T) { + t.Parallel() + + var ( + numProposals = 20 + halfRange = numProposals / 2 + + s = New(nil) + proposals = generateProposals(t, numProposals) + ) + + // Add initial proposals + for _, proposal := range proposals { + _, err := s.addProposal(proposal) + + urequire.NoError(t, err) + } + + uassert.Equal(t, numProposals, s.Size()) + + fetchedProposals := s.Proposals(0, uint64(halfRange)) + urequire.Equal(t, halfRange, len(fetchedProposals)) + + for index, fetchedProposal := range fetchedProposals { + equalProposals(t, proposals[index], fetchedProposal) + } + + // Fetch the other half + fetchedProposals = s.Proposals(uint64(halfRange), uint64(halfRange)) + urequire.Equal(t, halfRange, len(fetchedProposals)) + + for index, fetchedProposal := range fetchedProposals { + equalProposals(t, proposals[index+halfRange], fetchedProposal) + } + }) +} + +func TestSimpleDAO_GetProposalByID(t *testing.T) { + t.Parallel() + + t.Run("missing proposal", func(t *testing.T) { + t.Parallel() + + s := New(nil) + + _, err := s.ProposalByID(0) + uassert.ErrorIs(t, err, ErrMissingProposal) + }) + + t.Run("proposal found", func(t *testing.T) { + t.Parallel() + + var ( + s = New(nil) + proposal = generateProposals(t, 1)[0] + ) + + // Add the initial proposal + _, err := s.addProposal(proposal) + urequire.NoError(t, err) + + // Fetch the proposal + fetchedProposal, err := s.ProposalByID(0) + urequire.NoError(t, err) + + equalProposals(t, proposal, fetchedProposal) + }) +} diff --git a/examples/gno.land/p/demo/simpledao/votestore.gno b/examples/gno.land/p/demo/simpledao/votestore.gno new file mode 100644 index 00000000000..35a6564a1e3 --- /dev/null +++ b/examples/gno.land/p/demo/simpledao/votestore.gno @@ -0,0 +1,55 @@ +package simpledao + +import ( + "errors" + + "gno.land/p/demo/avl" + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" +) + +var ErrAlreadyVoted = errors.New("vote already cast") + +// tally is a simple vote tally system +type tally struct { + // tally cache to keep track of active + // yes / no / abstain votes + yays uint64 + nays uint64 + abstains uint64 + + voters *avl.Tree // std.Address -> dao.VoteOption +} + +// newTally creates a new tally system instance +func newTally() *tally { + return &tally{ + voters: avl.NewTree(), + } +} + +// castVote casts a single vote in the name of the given member +func (t *tally) castVote(member membstore.Member, option dao.VoteOption) error { + // Check if the member voted already + address := member.Address.String() + + _, voted := t.voters.Get(address) + if voted { + return ErrAlreadyVoted + } + + // Update the tally + switch option { + case dao.YesVote: + t.yays += member.VotingPower + case dao.AbstainVote: + t.abstains += member.VotingPower + default: + t.nays += member.VotingPower + } + + // Save the voting status + t.voters.Set(address, option) + + return nil +} diff --git a/examples/gno.land/p/gov/executor/callback.gno b/examples/gno.land/p/gov/executor/callback.gno new file mode 100644 index 00000000000..5d46a97cd69 --- /dev/null +++ b/examples/gno.land/p/gov/executor/callback.gno @@ -0,0 +1,39 @@ +package executor + +import ( + "errors" + "std" +) + +var errInvalidCaller = errors.New("invalid executor caller") + +// NewCallbackExecutor creates a new callback executor with the provided callback function +func NewCallbackExecutor(callback func() error, path string) *CallbackExecutor { + return &CallbackExecutor{ + callback: callback, + daoPkgPath: path, + } +} + +// CallbackExecutor is an implementation of the dao.Executor interface, +// based on a specific callback. +// The given callback should verify the validity of the govdao call +type CallbackExecutor struct { + callback func() error // the callback to be executed + daoPkgPath string // the active pkg path of the govdao +} + +// Execute runs the executor's callback function. +func (exec *CallbackExecutor) Execute() error { + // Verify the caller is an adequate Realm + caller := std.CurrentRealm().PkgPath() + if caller != exec.daoPkgPath { + return errInvalidCaller + } + + if exec.callback != nil { + return exec.callback() + } + + return nil +} diff --git a/examples/gno.land/p/gov/executor/context.gno b/examples/gno.land/p/gov/executor/context.gno new file mode 100644 index 00000000000..158e3b1e0be --- /dev/null +++ b/examples/gno.land/p/gov/executor/context.gno @@ -0,0 +1,75 @@ +package executor + +import ( + "errors" + "std" + + "gno.land/p/demo/context" +) + +type propContextKey string + +func (k propContextKey) String() string { return string(k) } + +const ( + statusContextKey = propContextKey("govdao-prop-status") + approvedStatus = "approved" +) + +var errNotApproved = errors.New("not approved by govdao") + +// CtxExecutor is an implementation of the dao.Executor interface, +// based on the given context. +// It utilizes the given context to assert the validity of the govdao call +type CtxExecutor struct { + callbackCtx func(ctx context.Context) error // the callback ctx fn, if any + daoPkgPath string // the active pkg path of the govdao +} + +// NewCtxExecutor creates a new executor with the provided callback function. +func NewCtxExecutor(callback func(ctx context.Context) error, path string) *CtxExecutor { + return &CtxExecutor{ + callbackCtx: callback, + daoPkgPath: path, + } +} + +// Execute runs the executor's callback function +func (exec *CtxExecutor) Execute() error { + // Verify the caller is an adequate Realm + caller := std.CurrentRealm().PkgPath() + if caller != exec.daoPkgPath { + return errInvalidCaller + } + + // Create the context + ctx := context.WithValue( + context.Empty(), + statusContextKey, + approvedStatus, + ) + + return exec.callbackCtx(ctx) +} + +// IsApprovedByGovdaoContext asserts that the govdao approved the context +func IsApprovedByGovdaoContext(ctx context.Context) bool { + v := ctx.Value(statusContextKey) + if v == nil { + return false + } + + vs, ok := v.(string) + + return ok && vs == approvedStatus +} + +// AssertContextApprovedByGovDAO asserts the given context +// was approved by GOVDAO +func AssertContextApprovedByGovDAO(ctx context.Context) { + if IsApprovedByGovdaoContext(ctx) { + return + } + + panic(errNotApproved) +} diff --git a/examples/gno.land/p/gov/proposal/gno.mod b/examples/gno.land/p/gov/executor/gno.mod similarity index 80% rename from examples/gno.land/p/gov/proposal/gno.mod rename to examples/gno.land/p/gov/executor/gno.mod index 3f6ef34a759..99f2ab3610b 100644 --- a/examples/gno.land/p/gov/proposal/gno.mod +++ b/examples/gno.land/p/gov/executor/gno.mod @@ -1,4 +1,4 @@ -module gno.land/p/gov/proposal +module gno.land/p/gov/executor require ( gno.land/p/demo/context v0.0.0-latest diff --git a/examples/gno.land/p/gov/executor/proposal_test.gno b/examples/gno.land/p/gov/executor/proposal_test.gno new file mode 100644 index 00000000000..3a70fc40596 --- /dev/null +++ b/examples/gno.land/p/gov/executor/proposal_test.gno @@ -0,0 +1,180 @@ +package executor + +import ( + "errors" + "std" + "testing" + + "gno.land/p/demo/context" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestExecutor_Callback(t *testing.T) { + t.Parallel() + + t.Run("govdao not caller", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func() error { + called = true + + return nil + } + ) + + // Create the executor + e := NewCallbackExecutor(cb, "gno.land/r/gov/dao") + + // Execute as not the /r/gov/dao caller + uassert.ErrorIs(t, e.Execute(), errInvalidCaller) + uassert.False(t, called, "expected proposal to not execute") + }) + + t.Run("execution successful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func() error { + called = true + + return nil + } + ) + + // Create the executor + daoPkgPath := "gno.land/r/gov/dao" + e := NewCallbackExecutor(cb, daoPkgPath) + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.NoError(t, e.Execute()) + uassert.True(t, called, "expected proposal to execute") + }) + + t.Run("execution unsuccessful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + expectedErr = errors.New("unexpected") + + cb = func() error { + called = true + + return expectedErr + } + ) + + // Create the executor + daoPkgPath := "gno.land/r/gov/dao" + e := NewCallbackExecutor(cb, daoPkgPath) + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.ErrorIs(t, e.Execute(), expectedErr) + uassert.True(t, called, "expected proposal to execute") + }) +} + +func TestExecutor_Context(t *testing.T) { + t.Parallel() + + t.Run("govdao not caller", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func(ctx context.Context) error { + if !IsApprovedByGovdaoContext(ctx) { + t.Fatal("not govdao caller") + } + + called = true + + return nil + } + ) + + // Create the executor + e := NewCtxExecutor(cb, "gno.land/r/gov/dao") + + // Execute as not the /r/gov/dao caller + uassert.ErrorIs(t, e.Execute(), errInvalidCaller) + uassert.False(t, called, "expected proposal to not execute") + }) + + t.Run("execution successful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + + cb = func(ctx context.Context) error { + if !IsApprovedByGovdaoContext(ctx) { + t.Fatal("not govdao caller") + } + + called = true + + return nil + } + ) + + // Create the executor + daoPkgPath := "gno.land/r/gov/dao" + e := NewCtxExecutor(cb, daoPkgPath) + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + urequire.NoError(t, e.Execute()) + uassert.True(t, called, "expected proposal to execute") + }) + + t.Run("execution unsuccessful", func(t *testing.T) { + t.Parallel() + + var ( + called = false + expectedErr = errors.New("unexpected") + + cb = func(ctx context.Context) error { + if !IsApprovedByGovdaoContext(ctx) { + t.Fatal("not govdao caller") + } + + called = true + + return expectedErr + } + ) + + // Create the executor + daoPkgPath := "gno.land/r/gov/dao" + e := NewCtxExecutor(cb, daoPkgPath) + + // Execute as the /r/gov/dao caller + r := std.NewCodeRealm(daoPkgPath) + std.TestSetRealm(r) + + uassert.NotPanics(t, func() { + err := e.Execute() + + uassert.ErrorIs(t, err, expectedErr) + }) + + uassert.True(t, called, "expected proposal to execute") + }) +} diff --git a/examples/gno.land/p/gov/proposal/proposal.gno b/examples/gno.land/p/gov/proposal/proposal.gno deleted file mode 100644 index ca1767228c9..00000000000 --- a/examples/gno.land/p/gov/proposal/proposal.gno +++ /dev/null @@ -1,106 +0,0 @@ -// Package proposal provides a structure for executing proposals. -package proposal - -import ( - "errors" - "std" - - "gno.land/p/demo/context" -) - -var errNotGovDAO = errors.New("only r/gov/dao can be the caller") - -// NewExecutor creates a new executor with the provided callback function. -func NewExecutor(callback func() error) Executor { - return &executorImpl{ - callback: callback, - done: false, - } -} - -// NewCtxExecutor creates a new executor with the provided callback function. -func NewCtxExecutor(callback func(ctx context.Context) error) Executor { - return &executorImpl{ - callbackCtx: callback, - done: false, - } -} - -// executorImpl is an implementation of the Executor interface. -type executorImpl struct { - callback func() error - callbackCtx func(ctx context.Context) error - done bool - success bool -} - -// Execute runs the executor's callback function. -func (exec *executorImpl) Execute() error { - if exec.done { - return ErrAlreadyDone - } - - // Verify the executor is r/gov/dao - assertCalledByGovdao() - - var err error - if exec.callback != nil { - err = exec.callback() - } else if exec.callbackCtx != nil { - ctx := context.WithValue(context.Empty(), statusContextKey, approvedStatus) - err = exec.callbackCtx(ctx) - } - exec.done = true - exec.success = err == nil - - return err -} - -// IsDone returns whether the executor has been executed. -func (exec *executorImpl) IsDone() bool { - return exec.done -} - -// IsSuccessful returns whether the execution was successful. -func (exec *executorImpl) IsSuccessful() bool { - return exec.success -} - -// IsExpired returns whether the execution had expired or not. -// This implementation never expires. -func (exec *executorImpl) IsExpired() bool { - return false -} - -func IsApprovedByGovdaoContext(ctx context.Context) bool { - v := ctx.Value(statusContextKey) - if v == nil { - return false - } - vs, ok := v.(string) - return ok && vs == approvedStatus -} - -func AssertContextApprovedByGovDAO(ctx context.Context) { - if !IsApprovedByGovdaoContext(ctx) { - panic("not approved by govdao") - } -} - -// assertCalledByGovdao asserts that the calling Realm is /r/gov/dao -func assertCalledByGovdao() { - caller := std.CurrentRealm().PkgPath() - - if caller != daoPkgPath { - panic(errNotGovDAO) - } -} - -type propContextKey string - -func (k propContextKey) String() string { return string(k) } - -const ( - statusContextKey = propContextKey("govdao-prop-status") - approvedStatus = "approved" -) diff --git a/examples/gno.land/p/gov/proposal/proposal_test.gno b/examples/gno.land/p/gov/proposal/proposal_test.gno deleted file mode 100644 index 536871e644d..00000000000 --- a/examples/gno.land/p/gov/proposal/proposal_test.gno +++ /dev/null @@ -1,156 +0,0 @@ -package proposal - -import ( - "errors" - "std" - "testing" - - "gno.land/p/demo/uassert" - "gno.land/p/demo/urequire" -) - -func TestExecutor(t *testing.T) { - t.Parallel() - - verifyProposalFailed := func(e Executor) { - uassert.True(t, e.IsDone(), "expected proposal to be done") - uassert.False(t, e.IsSuccessful(), "expected proposal to fail") - } - - verifyProposalSucceeded := func(e Executor) { - uassert.True(t, e.IsDone(), "expected proposal to be done") - uassert.True(t, e.IsSuccessful(), "expected proposal to be successful") - } - - t.Run("govdao not caller", func(t *testing.T) { - t.Parallel() - - var ( - called = false - - cb = func() error { - called = true - - return nil - } - ) - - // Create the executor - e := NewExecutor(cb) - - urequire.False(t, e.IsDone(), "expected status to be NotExecuted") - - // Execute as not the /r/gov/dao caller - uassert.PanicsWithMessage(t, errNotGovDAO.Error(), func() { - _ = e.Execute() - }) - - uassert.False(t, called, "expected proposal to not execute") - }) - - t.Run("execution successful", func(t *testing.T) { - t.Parallel() - - var ( - called = false - - cb = func() error { - called = true - - return nil - } - ) - - // Create the executor - e := NewExecutor(cb) - - urequire.False(t, e.IsDone(), "expected status to be NotExecuted") - - // Execute as the /r/gov/dao caller - r := std.NewCodeRealm(daoPkgPath) - std.TestSetRealm(r) - - uassert.NotPanics(t, func() { - err := e.Execute() - - uassert.NoError(t, err) - }) - - uassert.True(t, called, "expected proposal to execute") - - // Make sure the execution params are correct - verifyProposalSucceeded(e) - }) - - t.Run("execution unsuccessful", func(t *testing.T) { - t.Parallel() - - var ( - called = false - expectedErr = errors.New("unexpected") - - cb = func() error { - called = true - - return expectedErr - } - ) - - // Create the executor - e := NewExecutor(cb) - - // Execute as the /r/gov/dao caller - r := std.NewCodeRealm(daoPkgPath) - std.TestSetRealm(r) - - uassert.NotPanics(t, func() { - err := e.Execute() - - uassert.ErrorIs(t, err, expectedErr) - }) - - uassert.True(t, called, "expected proposal to execute") - - // Make sure the execution params are correct - verifyProposalFailed(e) - }) - - t.Run("proposal already executed", func(t *testing.T) { - t.Parallel() - - var ( - called = false - - cb = func() error { - called = true - - return nil - } - ) - - // Create the executor - e := NewExecutor(cb) - - urequire.False(t, e.IsDone(), "expected status to be NotExecuted") - - // Execute as the /r/gov/dao caller - r := std.NewCodeRealm(daoPkgPath) - std.TestSetRealm(r) - - uassert.NotPanics(t, func() { - uassert.NoError(t, e.Execute()) - }) - - uassert.True(t, called, "expected proposal to execute") - - // Make sure the execution params are correct - verifyProposalSucceeded(e) - - // Attempt to execute the proposal again - uassert.NotPanics(t, func() { - err := e.Execute() - - uassert.ErrorIs(t, err, ErrAlreadyDone) - }) - }) -} diff --git a/examples/gno.land/p/gov/proposal/types.gno b/examples/gno.land/p/gov/proposal/types.gno deleted file mode 100644 index 6cd2da9ccfe..00000000000 --- a/examples/gno.land/p/gov/proposal/types.gno +++ /dev/null @@ -1,37 +0,0 @@ -// Package proposal defines types for proposal execution. -package proposal - -import "errors" - -// Executor represents a minimal closure-oriented proposal design. -// It is intended to be used by a govdao governance proposal (v1, v2, etc). -type Executor interface { - // Execute executes the given proposal, and returns any error encountered - // during the execution - Execute() error - - // IsDone returns a flag indicating if the proposal was executed - IsDone() bool - - // IsSuccessful returns a flag indicating if the proposal was executed - // and is successful - IsSuccessful() bool // IsDone() && !err - - // IsExpired returns whether the execution had expired or not. - IsExpired() bool -} - -// ErrAlreadyDone is the error returned when trying to execute an already -// executed proposal. -var ErrAlreadyDone = errors.New("already executed") - -// Status enum. -type Status string - -const ( - NotExecuted Status = "not_executed" - Succeeded Status = "succeeded" - Failed Status = "failed" -) - -const daoPkgPath = "gno.land/r/gov/dao" // TODO: make sure this is configurable through r/sys/vars diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index 08b0911cf24..9c94a265fca 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -5,8 +5,8 @@ import ( "strings" "gno.land/p/demo/avl" - "gno.land/p/demo/context" - "gno.land/p/gov/proposal" + "gno.land/p/demo/dao" + "gno.land/r/gov/dao/bridge" ) var ( @@ -41,10 +41,14 @@ func AdminRemoveModerator(addr std.Address) { moderatorList.Set(addr.String(), false) // FIXME: delete instead? } -func DaoAddPost(ctx context.Context, slug, title, body, publicationDate, authors, tags string) { - proposal.AssertContextApprovedByGovDAO(ctx) - caller := std.DerivePkgAddr("gno.land/r/gov/dao") - addPost(caller, slug, title, body, publicationDate, authors, tags) +func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor { + callback := func() error { + addPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags) + + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(callback) } func ModAddPost(slug, title, body, publicationDate, authors, tags string) { diff --git a/examples/gno.land/r/gnoland/blog/gno.mod b/examples/gno.land/r/gnoland/blog/gno.mod index 17c17e0cfa6..8a4c5851b4c 100644 --- a/examples/gno.land/r/gnoland/blog/gno.mod +++ b/examples/gno.land/r/gnoland/blog/gno.mod @@ -3,6 +3,6 @@ module gno.land/r/gnoland/blog require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/blog v0.0.0-latest - gno.land/p/demo/context v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest + gno.land/p/demo/dao v0.0.0-latest + gno.land/r/gov/dao/bridge v0.0.0-latest ) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 93f9a68f39a..57570902f5a 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -193,7 +193,7 @@ func packageStaffPicks() ui.Element { ui.BulletList{ ui.Link{URL: "r/sys/names"}, ui.Link{URL: "r/sys/rewards"}, - ui.Link{URL: "r/sys/validators"}, + ui.Link{URL: "/r/sys/validators/v2"}, }, }, { ui.H4("[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)"), diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 2260dc3a409..89721fd8d08 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -117,7 +117,7 @@ func main() { // // - [r/sys/names](r/sys/names) // - [r/sys/rewards](r/sys/rewards) -// - [r/sys/validators](r/sys/validators) +// - [/r/sys/validators/v2](/r/sys/validators/v2) // // //
diff --git a/examples/gno.land/r/gnoland/valopers/gno.mod b/examples/gno.land/r/gnoland/valopers/v2/gno.mod similarity index 56% rename from examples/gno.land/r/gnoland/valopers/gno.mod rename to examples/gno.land/r/gnoland/valopers/v2/gno.mod index 2d24fb27952..099a8406db4 100644 --- a/examples/gno.land/r/gnoland/valopers/gno.mod +++ b/examples/gno.land/r/gnoland/valopers/v2/gno.mod @@ -1,11 +1,12 @@ -module gno.land/r/gnoland/valopers +module gno.land/r/gnoland/valopers/v2 require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/dao v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/validators v0.0.0-latest - gno.land/r/gov/dao v0.0.0-latest - gno.land/r/sys/validators v0.0.0-latest + gno.land/r/gov/dao/bridge v0.0.0-latest + gno.land/r/sys/validators/v2 v0.0.0-latest ) diff --git a/examples/gno.land/r/gnoland/valopers/init.gno b/examples/gno.land/r/gnoland/valopers/v2/init.gno similarity index 100% rename from examples/gno.land/r/gnoland/valopers/init.gno rename to examples/gno.land/r/gnoland/valopers/v2/init.gno diff --git a/examples/gno.land/r/gnoland/valopers/valopers.gno b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno similarity index 90% rename from examples/gno.land/r/gnoland/valopers/valopers.gno rename to examples/gno.land/r/gnoland/valopers/v2/valopers.gno index 74cec941e0d..d88ea4b872f 100644 --- a/examples/gno.land/r/gnoland/valopers/valopers.gno +++ b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno @@ -6,10 +6,11 @@ import ( "std" "gno.land/p/demo/avl" + "gno.land/p/demo/dao" "gno.land/p/demo/ufmt" pVals "gno.land/p/sys/validators" - govdao "gno.land/r/gov/dao" - "gno.land/r/sys/validators" + "gno.land/r/gov/dao/bridge" + validators "gno.land/r/sys/validators/v2" ) const ( @@ -25,6 +26,7 @@ var valopers *avl.Tree // Address -> Valoper // Valoper represents a validator operator profile type Valoper struct { Name string // the display name of the valoper + Moniker string // the moniker of the valoper Description string // the description of the valoper Address std.Address // The bech32 gno address of the validator @@ -101,7 +103,7 @@ func Render(_ string) string { // Render renders a single valoper with their information func (v Valoper) Render() string { - output := ufmt.Sprintf("## %s\n", v.Name) + output := ufmt.Sprintf("## %s (%s)\n", v.Name, v.Moniker) output += ufmt.Sprintf("%s\n\n", v.Description) output += ufmt.Sprintf("- Address: %s\n", v.Address.String()) output += ufmt.Sprintf("- PubKey: %s\n", v.PubKey) @@ -168,14 +170,19 @@ func GovDAOProposal(address std.Address) { // Create the executor executor := validators.NewPropExecutor(changesFn) - // Craft the proposal comment - comment := ufmt.Sprintf( - "Proposal to add valoper %s (Address: %s; PubKey: %s) to the valset", + // Craft the proposal description + description := ufmt.Sprintf( + "Add valoper %s (Address: %s; PubKey: %s) to the valset", valoper.Name, valoper.Address.String(), valoper.PubKey, ) + prop := dao.ProposalRequest{ + Description: description, + Executor: executor, + } + // Create the govdao proposal - govdao.Propose(comment, executor) + bridge.GovDAO().Propose(prop) } diff --git a/examples/gno.land/r/gnoland/valopers/valopers_test.gno b/examples/gno.land/r/gnoland/valopers/v2/valopers_test.gno similarity index 97% rename from examples/gno.land/r/gnoland/valopers/valopers_test.gno rename to examples/gno.land/r/gnoland/valopers/v2/valopers_test.gno index 89544c46ee5..b5940738769 100644 --- a/examples/gno.land/r/gnoland/valopers/valopers_test.gno +++ b/examples/gno.land/r/gnoland/valopers/v2/valopers_test.gno @@ -38,6 +38,7 @@ func TestValopers_Register(t *testing.T) { v := Valoper{ Address: testutils.TestAddress("valoper"), Name: "new valoper", + Moniker: "val-1", PubKey: "pub key", } @@ -50,6 +51,7 @@ func TestValopers_Register(t *testing.T) { uassert.Equal(t, v.Address, valoper.Address) uassert.Equal(t, v.Name, valoper.Name) + uassert.Equal(t, v.Moniker, valoper.Moniker) uassert.Equal(t, v.PubKey, valoper.PubKey) }) }) diff --git a/examples/gno.land/r/gov/dao/bridge/bridge.gno b/examples/gno.land/r/gov/dao/bridge/bridge.gno new file mode 100644 index 00000000000..ba47978f33f --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/bridge.gno @@ -0,0 +1,39 @@ +package bridge + +import ( + "std" + + "gno.land/p/demo/ownable" +) + +const initialOwner = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") // @moul + +var b *Bridge + +// Bridge is the active GovDAO +// implementation bridge +type Bridge struct { + *ownable.Ownable + + dao DAO +} + +// init constructs the initial GovDAO implementation +func init() { + b = &Bridge{ + Ownable: ownable.NewWithAddress(initialOwner), + dao: &govdaoV2{}, + } +} + +// SetDAO sets the currently active GovDAO implementation +func SetDAO(dao DAO) { + b.AssertCallerIsOwner() + + b.dao = dao +} + +// GovDAO returns the current GovDAO implementation +func GovDAO() DAO { + return b.dao +} diff --git a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno new file mode 100644 index 00000000000..38b5d4be257 --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno @@ -0,0 +1,64 @@ +package bridge + +import ( + "testing" + + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/ownable" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestBridge_DAO(t *testing.T) { + var ( + proposalID = uint64(10) + mockDAO = &mockDAO{ + proposeFn: func(_ dao.ProposalRequest) uint64 { + return proposalID + }, + } + ) + + b.dao = mockDAO + + uassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{})) +} + +func TestBridge_SetDAO(t *testing.T) { + t.Run("invalid owner", func(t *testing.T) { + // Attempt to set a new DAO implementation + uassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() { + SetDAO(&mockDAO{}) + }) + }) + + t.Run("valid owner", func(t *testing.T) { + var ( + addr = testutils.TestAddress("owner") + + proposalID = uint64(10) + mockDAO = &mockDAO{ + proposeFn: func(_ dao.ProposalRequest) uint64 { + return proposalID + }, + } + ) + + std.TestSetOrigCaller(addr) + + b.Ownable = ownable.NewWithAddress(addr) + + urequire.NotPanics(t, func() { + SetDAO(mockDAO) + }) + + uassert.Equal( + t, + mockDAO.Propose(dao.ProposalRequest{}), + GovDAO().Propose(dao.ProposalRequest{}), + ) + }) +} diff --git a/examples/gno.land/r/gov/dao/bridge/doc.gno b/examples/gno.land/r/gov/dao/bridge/doc.gno new file mode 100644 index 00000000000..f812b3c0787 --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/doc.gno @@ -0,0 +1,4 @@ +// Package bridge represents a GovDAO implementation wrapper, used by other Realms and Packages to +// always fetch the most active GovDAO implementation, instead of directly referencing it, and having to +// update it each time the GovDAO implementation changes +package bridge diff --git a/examples/gno.land/r/gov/dao/bridge/gno.mod b/examples/gno.land/r/gov/dao/bridge/gno.mod new file mode 100644 index 00000000000..3382557573a --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/gno.mod @@ -0,0 +1,11 @@ +module gno.land/r/gov/dao/bridge + +require ( + gno.land/p/demo/dao v0.0.0-latest + gno.land/p/demo/membstore v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/r/gov/dao/v2 v0.0.0-latest +) diff --git a/examples/gno.land/r/gov/dao/bridge/mock_test.gno b/examples/gno.land/r/gov/dao/bridge/mock_test.gno new file mode 100644 index 00000000000..05ac430b4c4 --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/mock_test.gno @@ -0,0 +1,68 @@ +package bridge + +import ( + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" +) + +type ( + proposeDelegate func(dao.ProposalRequest) uint64 + voteOnProposalDelegate func(uint64, dao.VoteOption) + executeProposalDelegate func(uint64) + getPropStoreDelegate func() dao.PropStore + getMembStoreDelegate func() membstore.MemberStore + newGovDAOExecutorDelegate func(func() error) dao.Executor +) + +type mockDAO struct { + proposeFn proposeDelegate + voteOnProposalFn voteOnProposalDelegate + executeProposalFn executeProposalDelegate + getPropStoreFn getPropStoreDelegate + getMembStoreFn getMembStoreDelegate + newGovDAOExecutorFn newGovDAOExecutorDelegate +} + +func (m *mockDAO) Propose(request dao.ProposalRequest) uint64 { + if m.proposeFn != nil { + return m.proposeFn(request) + } + + return 0 +} + +func (m *mockDAO) VoteOnProposal(id uint64, option dao.VoteOption) { + if m.voteOnProposalFn != nil { + m.voteOnProposalFn(id, option) + } +} + +func (m *mockDAO) ExecuteProposal(id uint64) { + if m.executeProposalFn != nil { + m.executeProposalFn(id) + } +} + +func (m *mockDAO) GetPropStore() dao.PropStore { + if m.getPropStoreFn != nil { + return m.getPropStoreFn() + } + + return nil +} + +func (m *mockDAO) GetMembStore() membstore.MemberStore { + if m.getMembStoreFn != nil { + return m.getMembStoreFn() + } + + return nil +} + +func (m *mockDAO) NewGovDAOExecutor(cb func() error) dao.Executor { + if m.newGovDAOExecutorFn != nil { + return m.newGovDAOExecutorFn(cb) + } + + return nil +} diff --git a/examples/gno.land/r/gov/dao/bridge/types.gno b/examples/gno.land/r/gov/dao/bridge/types.gno new file mode 100644 index 00000000000..27ea8fb62d4 --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/types.gno @@ -0,0 +1,17 @@ +package bridge + +import ( + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" +) + +// DAO abstracts the commonly used DAO interface +type DAO interface { + Propose(dao.ProposalRequest) uint64 + VoteOnProposal(uint64, dao.VoteOption) + ExecuteProposal(uint64) + GetPropStore() dao.PropStore + GetMembStore() membstore.MemberStore + + NewGovDAOExecutor(func() error) dao.Executor +} diff --git a/examples/gno.land/r/gov/dao/bridge/v2.gno b/examples/gno.land/r/gov/dao/bridge/v2.gno new file mode 100644 index 00000000000..216419cf31d --- /dev/null +++ b/examples/gno.land/r/gov/dao/bridge/v2.gno @@ -0,0 +1,42 @@ +package bridge + +import ( + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + govdao "gno.land/r/gov/dao/v2" +) + +// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm +type govdaoV2 struct{} + +func (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 { + return govdao.Propose(request) +} + +func (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) { + govdao.VoteOnProposal(id, option) +} + +func (g *govdaoV2) ExecuteProposal(id uint64) { + govdao.ExecuteProposal(id) +} + +func (g *govdaoV2) GetPropStore() dao.PropStore { + return govdao.GetPropStore() +} + +func (g *govdaoV2) GetMembStore() membstore.MemberStore { + return govdao.GetMembStore() +} + +func (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor { + return govdao.NewGovDAOExecutor(cb) +} + +func (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor { + return govdao.NewMemberPropExecutor(cb) +} + +func (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor { + return govdao.NewMembStoreImplExecutor(cb) +} diff --git a/examples/gno.land/r/gov/dao/dao.gno b/examples/gno.land/r/gov/dao/dao.gno deleted file mode 100644 index 632935dafed..00000000000 --- a/examples/gno.land/r/gov/dao/dao.gno +++ /dev/null @@ -1,207 +0,0 @@ -package govdao - -import ( - "std" - "strconv" - - "gno.land/p/demo/ufmt" - pproposal "gno.land/p/gov/proposal" -) - -var ( - proposals = make([]*proposal, 0) - members = make([]std.Address, 0) // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs -) - -const ( - msgMissingExecutor = "missing proposal executor" - msgPropExecuted = "prop already executed" - msgPropExpired = "prop is expired" - msgPropInactive = "prop is not active anymore" - msgPropActive = "prop is still active" - msgPropNotAccepted = "prop is not accepted" - - msgCallerNotAMember = "caller is not member of govdao" - msgProposalNotFound = "proposal not found" -) - -type proposal struct { - author std.Address - comment string - executor pproposal.Executor - voter Voter - executed bool - voters []std.Address // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs. -} - -func (p proposal) Status() Status { - if p.executor.IsExpired() { - return Expired - } - - if p.executor.IsDone() { - return Succeeded - } - - if !p.voter.IsFinished(members) { - return Active - } - - if p.voter.IsAccepted(members) { - return Accepted - } - - return NotAccepted -} - -// Propose is designed to be called by another contract or with -// `maketx run`, not by a `maketx call`. -func Propose(comment string, executor pproposal.Executor) int { - // XXX: require payment? - if executor == nil { - panic(msgMissingExecutor) - } - caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! - AssertIsMember(caller) - - prop := &proposal{ - comment: comment, - executor: executor, - author: caller, - voter: NewPercentageVoter(66), // at least 2/3 must say yes - } - - proposals = append(proposals, prop) - - return len(proposals) - 1 -} - -func VoteOnProposal(idx int, option string) { - assertProposalExists(idx) - caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE! - AssertIsMember(caller) - - prop := getProposal(idx) - - if prop.executed { - panic(msgPropExecuted) - } - - if prop.executor.IsExpired() { - panic(msgPropExpired) - } - - if prop.voter.IsFinished(members) { - panic(msgPropInactive) - } - - prop.voter.Vote(members, caller, option) -} - -func ExecuteProposal(idx int) { - assertProposalExists(idx) - prop := getProposal(idx) - - if prop.executed { - panic(msgPropExecuted) - } - - if prop.executor.IsExpired() { - panic(msgPropExpired) - } - - if !prop.voter.IsFinished(members) { - panic(msgPropActive) - } - - if !prop.voter.IsAccepted(members) { - panic(msgPropNotAccepted) - } - - prop.executor.Execute() - prop.voters = members - prop.executed = true -} - -func IsMember(addr std.Address) bool { - if len(members) == 0 { // special case for initial execution - return true - } - - for _, v := range members { - if v == addr { - return true - } - } - - return false -} - -func AssertIsMember(addr std.Address) { - if !IsMember(addr) { - panic(msgCallerNotAMember) - } -} - -func Render(path string) string { - if path == "" { - if len(proposals) == 0 { - return "No proposals found :(" // corner case - } - - output := "" - for idx, prop := range proposals { - output += ufmt.Sprintf("- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author) - } - - return output - } - - // else display the proposal - idx, err := strconv.Atoi(path) - if err != nil { - return "404" - } - - if !proposalExists(idx) { - return "404" - } - prop := getProposal(idx) - - vs := members - if prop.executed { - vs = prop.voters - } - - output := "" - output += ufmt.Sprintf("# Prop #%d", idx) - output += "\n\n" - output += prop.comment - output += "\n\n" - output += ufmt.Sprintf("Status: %s", string(prop.Status())) - output += "\n\n" - output += ufmt.Sprintf("Voting status: %s", prop.voter.Status(vs)) - output += "\n\n" - output += ufmt.Sprintf("Author: %s", string(prop.author)) - output += "\n\n" - - return output -} - -func getProposal(idx int) *proposal { - if idx > len(proposals)-1 { - panic(msgProposalNotFound) - } - - return proposals[idx] -} - -func proposalExists(idx int) bool { - return idx >= 0 && idx <= len(proposals) -} - -func assertProposalExists(idx int) { - if !proposalExists(idx) { - panic("invalid proposal id") - } -} diff --git a/examples/gno.land/r/gov/dao/dao_test.gno b/examples/gno.land/r/gov/dao/dao_test.gno deleted file mode 100644 index 96eaba7f5e9..00000000000 --- a/examples/gno.land/r/gov/dao/dao_test.gno +++ /dev/null @@ -1,192 +0,0 @@ -package govdao - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/urequire" - pproposal "gno.land/p/gov/proposal" -) - -func TestPackage(t *testing.T) { - u1 := testutils.TestAddress("u1") - u2 := testutils.TestAddress("u2") - u3 := testutils.TestAddress("u3") - - members = append(members, u1) - members = append(members, u2) - members = append(members, u3) - - nu1 := testutils.TestAddress("random1") - - out := Render("") - - expected := "No proposals found :(" - urequire.Equal(t, expected, out) - - var called bool - ex := pproposal.NewExecutor(func() error { - called = true - return nil - }) - - std.TestSetOrigCaller(u1) - pid := Propose("dummy proposal", ex) - - // try to vote not being a member - std.TestSetOrigCaller(nu1) - - urequire.PanicsWithMessage(t, msgCallerNotAMember, func() { - VoteOnProposal(pid, "YES") - }) - - // try to vote several times - std.TestSetOrigCaller(u1) - urequire.NotPanics(t, func() { - VoteOnProposal(pid, "YES") - }) - urequire.PanicsWithMessage(t, msgAlreadyVoted, func() { - VoteOnProposal(pid, "YES") - }) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: active - -Voting status: YES: 1, NO: 0, percent: 33, members: 3 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - std.TestSetOrigCaller(u2) - urequire.PanicsWithMessage(t, msgWrongVotingValue, func() { - VoteOnProposal(pid, "INCORRECT") - }) - urequire.NotPanics(t, func() { - VoteOnProposal(pid, "NO") - }) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: active - -Voting status: YES: 1, NO: 1, percent: 33, members: 3 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - std.TestSetOrigCaller(u3) - urequire.NotPanics(t, func() { - VoteOnProposal(pid, "YES") - }) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: accepted - -Voting status: YES: 2, NO: 1, percent: 66, members: 3 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - // Add a new member, so non-executed proposals will change the voting status - u4 := testutils.TestAddress("u4") - members = append(members, u4) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: active - -Voting status: YES: 2, NO: 1, percent: 50, members: 4 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - std.TestSetOrigCaller(u4) - urequire.NotPanics(t, func() { - VoteOnProposal(pid, "YES") - }) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: accepted - -Voting status: YES: 3, NO: 1, percent: 75, members: 4 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - ExecuteProposal(pid) - urequire.True(t, called) - - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: succeeded - -Voting status: YES: 3, NO: 1, percent: 75, members: 4 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - - // Add a new member and try to vote an already executed proposal - u5 := testutils.TestAddress("u5") - members = append(members, u5) - std.TestSetOrigCaller(u5) - urequire.PanicsWithMessage(t, msgPropExecuted, func() { - ExecuteProposal(pid) - }) - - // even if we added a new member the executed proposal is showing correctly the members that voted on it - out = Render("0") - expected = `# Prop #0 - -dummy proposal - -Status: succeeded - -Voting status: YES: 3, NO: 1, percent: 75, members: 4 - -Author: g1w5c47h6lta047h6lta047h6lta047h6ly5kscr - -` - - urequire.Equal(t, expected, out) - -} diff --git a/examples/gno.land/r/gov/dao/memberset.gno b/examples/gno.land/r/gov/dao/memberset.gno deleted file mode 100644 index 3abd52ae99d..00000000000 --- a/examples/gno.land/r/gov/dao/memberset.gno +++ /dev/null @@ -1,40 +0,0 @@ -package govdao - -import ( - "std" - - pproposal "gno.land/p/gov/proposal" -) - -const daoPkgPath = "gno.land/r/gov/dao" - -const ( - errNoChangesProposed = "no set changes proposed" - errNotGovDAO = "caller not govdao executor" -) - -func NewPropExecutor(changesFn func() []std.Address) pproposal.Executor { - if changesFn == nil { - panic(errNoChangesProposed) - } - - callback := func() error { - // Make sure the GovDAO executor runs the valset changes - assertGovDAOCaller() - - for _, addr := range changesFn() { - members = append(members, addr) - } - - return nil - } - - return pproposal.NewExecutor(callback) -} - -// assertGovDAOCaller verifies the caller is the GovDAO executor -func assertGovDAOCaller() { - if std.CurrentRealm().PkgPath() != daoPkgPath { - panic(errNotGovDAO) - } -} diff --git a/examples/gno.land/r/gov/dao/prop2_filetest.gno b/examples/gno.land/r/gov/dao/prop2_filetest.gno deleted file mode 100644 index 047709cc45f..00000000000 --- a/examples/gno.land/r/gov/dao/prop2_filetest.gno +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "std" - "time" - - "gno.land/p/demo/context" - "gno.land/p/gov/proposal" - gnoblog "gno.land/r/gnoland/blog" - govdao "gno.land/r/gov/dao" -) - -func init() { - membersFn := func() []std.Address { - return []std.Address{ - std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"), - } - } - - mExec := govdao.NewPropExecutor(membersFn) - - comment := "adding someone to vote" - - id := govdao.Propose(comment, mExec) - - govdao.ExecuteProposal(id) - - executor := proposal.NewCtxExecutor(func(ctx context.Context) error { - gnoblog.DaoAddPost( - ctx, - "hello-from-govdao", // slug - "Hello from GovDAO!", // title - "This post was published by a GovDAO proposal.", // body - time.Now().Format(time.RFC3339), // publidation date - "moul", // authors - "govdao,example", // tags - ) - return nil - }) - - // Create a proposal. - // XXX: payment - comment = "post a new blogpost about govdao" - govdao.Propose(comment, executor) -} - -func main() { - println("--") - println(govdao.Render("")) - println("--") - println(govdao.Render("1")) - println("--") - govdao.VoteOnProposal(1, "YES") - println("--") - println(govdao.Render("1")) - println("--") - println(gnoblog.Render("")) - println("--") - govdao.ExecuteProposal(1) - println("--") - println(govdao.Render("1")) - println("--") - println(gnoblog.Render("")) -} - -// Output: -// -- -// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) -// - [1](/r/gov/dao:1) - post a new blogpost about govdao (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) -// -// -- -// # Prop #1 -// -// post a new blogpost about govdao -// -// Status: active -// -// Voting status: YES: 0, NO: 0, percent: 0, members: 1 -// -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// -// -// -- -// -- -// # Prop #1 -// -// post a new blogpost about govdao -// -// Status: accepted -// -// Voting status: YES: 1, NO: 0, percent: 100, members: 1 -// -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// -// -// -- -// # Gnoland's Blog -// -// No posts. -// -- -// -- -// # Prop #1 -// -// post a new blogpost about govdao -// -// Status: succeeded -// -// Voting status: YES: 1, NO: 0, percent: 100, members: 1 -// -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// -// -// -- -// # Gnoland's Blog -// -//
-// -// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao) -// 13 Feb 2009 -//
diff --git a/examples/gno.land/r/gov/dao/types.gno b/examples/gno.land/r/gov/dao/types.gno deleted file mode 100644 index 123fc489075..00000000000 --- a/examples/gno.land/r/gov/dao/types.gno +++ /dev/null @@ -1,32 +0,0 @@ -package govdao - -import ( - "std" -) - -// Status enum. -type Status string - -var ( - Accepted Status = "accepted" - Active Status = "active" - NotAccepted Status = "not accepted" - Expired Status = "expired" - Succeeded Status = "succeeded" -) - -// Voter defines the needed methods for a voting system -type Voter interface { - - // IsAccepted indicates if the voting process had been accepted - IsAccepted(voters []std.Address) bool - - // IsFinished indicates if the voting process is finished - IsFinished(voters []std.Address) bool - - // Vote adds a new vote to the voting system - Vote(voters []std.Address, caller std.Address, flag string) - - // Status returns a human friendly string describing how the voting process is going - Status(voters []std.Address) string -} diff --git a/examples/gno.land/r/gov/dao/v2/dao.gno b/examples/gno.land/r/gov/dao/v2/dao.gno new file mode 100644 index 00000000000..c37eda80bff --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/dao.gno @@ -0,0 +1,121 @@ +package govdao + +import ( + "std" + "strconv" + + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + "gno.land/p/demo/simpledao" + "gno.land/p/demo/ufmt" +) + +var ( + d *simpledao.SimpleDAO // the current active DAO implementation + members membstore.MemberStore // the member store +) + +func init() { + var ( + // Example initial member set (just test addresses) + set = []membstore.Member{ + { + Address: std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"), + VotingPower: 10, + }, + } + ) + + // Set the member store + members = membstore.NewMembStore(membstore.WithInitialMembers(set)) + + // Set the DAO implementation + d = simpledao.New(members) +} + +// Propose is designed to be called by another contract or with +// `maketx run`, not by a `maketx call`. +func Propose(request dao.ProposalRequest) uint64 { + idx, err := d.Propose(request) + if err != nil { + panic(err) + } + + return idx +} + +// VoteOnProposal casts a vote for the given proposal +func VoteOnProposal(id uint64, option dao.VoteOption) { + if err := d.VoteOnProposal(id, option); err != nil { + panic(err) + } +} + +// ExecuteProposal executes the proposal +func ExecuteProposal(id uint64) { + if err := d.ExecuteProposal(id); err != nil { + panic(err) + } +} + +// GetPropStore returns the active proposal store +func GetPropStore() dao.PropStore { + return d +} + +// GetMembStore returns the active member store +func GetMembStore() membstore.MemberStore { + return members +} + +func Render(path string) string { + if path == "" { + numProposals := d.Size() + + if numProposals == 0 { + return "No proposals found :(" // corner case + } + + output := "" + + offset := uint64(0) + if numProposals >= 10 { + offset = uint64(numProposals) - 10 + } + + // Fetch the last 10 proposals + for idx, prop := range d.Proposals(offset, uint64(10)) { + output += ufmt.Sprintf( + "- [Proposal #%d](%s:%d) - (**%s**)(by %s)\n", + idx, + "/r/gov/dao/v2", + idx, + prop.Status().String(), + prop.Author().String(), + ) + } + + return output + } + + // Display the detailed proposal + idx, err := strconv.Atoi(path) + if err != nil { + return "404: Invalid proposal ID" + } + + // Fetch the proposal + prop, err := d.ProposalByID(uint64(idx)) + if err != nil { + return ufmt.Sprintf("unable to fetch proposal, %s", err.Error()) + } + + // Render the proposal + output := "" + output += ufmt.Sprintf("# Prop #%d", idx) + output += "\n\n" + output += prop.Render() + output += "\n\n" + + return output +} diff --git a/examples/gno.land/r/gov/dao/v2/gno.mod b/examples/gno.land/r/gov/dao/v2/gno.mod new file mode 100644 index 00000000000..bc379bf18df --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/gno.mod @@ -0,0 +1,10 @@ +module gno.land/r/gov/dao/v2 + +require ( + gno.land/p/demo/combinederr v0.0.0-latest + gno.land/p/demo/dao v0.0.0-latest + gno.land/p/demo/membstore v0.0.0-latest + gno.land/p/demo/simpledao v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/gov/executor v0.0.0-latest +) diff --git a/examples/gno.land/r/gov/dao/v2/poc.gno b/examples/gno.land/r/gov/dao/v2/poc.gno new file mode 100644 index 00000000000..30d8a403f6e --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/poc.gno @@ -0,0 +1,92 @@ +package govdao + +import ( + "errors" + "std" + + "gno.land/p/demo/combinederr" + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + "gno.land/p/gov/executor" +) + +var errNoChangesProposed = errors.New("no set changes proposed") + +// NewGovDAOExecutor creates the govdao wrapped callback executor +func NewGovDAOExecutor(cb func() error) dao.Executor { + if cb == nil { + panic(errNoChangesProposed) + } + + return executor.NewCallbackExecutor( + cb, + std.CurrentRealm().PkgPath(), + ) +} + +// NewMemberPropExecutor returns the GOVDAO member change executor +func NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { + if changesFn == nil { + panic(errNoChangesProposed) + } + + callback := func() error { + errs := &combinederr.CombinedError{} + cbMembers := changesFn() + + for _, member := range cbMembers { + switch { + case !members.IsMember(member.Address): + // Addition request + err := members.AddMember(member) + + errs.Add(err) + case member.VotingPower == 0: + // Remove request + err := members.UpdateMember(member.Address, membstore.Member{ + Address: member.Address, + VotingPower: 0, // 0 indicated removal + }) + + errs.Add(err) + default: + // Update request + err := members.UpdateMember(member.Address, member) + + errs.Add(err) + } + } + + // Check if there were any execution errors + if errs.Size() == 0 { + return nil + } + + return errs + } + + return NewGovDAOExecutor(callback) +} + +func NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor { + if changeFn == nil { + panic(errNoChangesProposed) + } + + callback := func() error { + setMembStoreImpl(changeFn()) + + return nil + } + + return NewGovDAOExecutor(callback) +} + +// setMembStoreImpl sets a new dao.MembStore implementation +func setMembStoreImpl(impl membstore.MemberStore) { + if impl == nil { + panic("invalid member store") + } + + members = impl +} diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno similarity index 63% rename from examples/gno.land/r/gov/dao/prop1_filetest.gno rename to examples/gno.land/r/gov/dao/v2/prop1_filetest.gno index 49a200fd561..69e55ef1ab6 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno @@ -10,26 +10,13 @@ package main import ( "std" + "gno.land/p/demo/dao" pVals "gno.land/p/sys/validators" - govdao "gno.land/r/gov/dao" - "gno.land/r/sys/validators" + govdao "gno.land/r/gov/dao/v2" + validators "gno.land/r/sys/validators/v2" ) -const daoPkgPath = "gno.land/r/gov/dao" - func init() { - membersFn := func() []std.Address { - return []std.Address{ - std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"), - } - } - - mExec := govdao.NewPropExecutor(membersFn) - - comment := "adding someone to vote" - id := govdao.Propose(comment, mExec) - govdao.ExecuteProposal(id) - changesFn := func() []pVals.Validator { return []pVals.Validator{ { @@ -54,74 +41,84 @@ func init() { // complete governance proposal process. executor := validators.NewPropExecutor(changesFn) - // Create a proposal. - // XXX: payment - comment = "manual valset changes proposal example" - govdao.Propose(comment, executor) + // Create a proposal + description := "manual valset changes proposal example" + + prop := dao.ProposalRequest{ + Description: description, + Executor: executor, + } + + govdao.Propose(prop) } func main() { println("--") println(govdao.Render("")) println("--") - println(govdao.Render("1")) + println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(1, "YES") + govdao.VoteOnProposal(0, dao.YesVote) println("--") - println(govdao.Render("1")) + println(govdao.Render("0")) println("--") println(validators.Render("")) println("--") - govdao.ExecuteProposal(1) + govdao.ExecuteProposal(0) println("--") - println(govdao.Render("1")) + println(govdao.Render("0")) println("--") println(validators.Render("")) } // Output: // -- -// - [0](/r/gov/dao:0) - adding someone to vote (**succeeded**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) -// - [1](/r/gov/dao:1) - manual valset changes proposal example (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) // // -- -// # Prop #1 +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm // // manual valset changes proposal example // // Status: active // -// Voting status: YES: 0, NO: 0, percent: 0, members: 1 +// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// Threshold met: false // // // -- // -- -// # Prop #1 +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm // // manual valset changes proposal example // // Status: accepted // -// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// Threshold met: true // // // -- // No valset changes to apply. // -- // -- -// # Prop #1 +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm // // manual valset changes proposal example // -// Status: succeeded +// Status: execution successful // -// Voting status: YES: 1, NO: 0, percent: 100, members: 1 +// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// Threshold met: true // // // -- diff --git a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno new file mode 100644 index 00000000000..32ddc11b67c --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno @@ -0,0 +1,110 @@ +package main + +import ( + "time" + + "gno.land/p/demo/dao" + gnoblog "gno.land/r/gnoland/blog" + govdao "gno.land/r/gov/dao/v2" +) + +func init() { + ex := gnoblog.NewPostExecutor( + "hello-from-govdao", // slug + "Hello from GovDAO!", // title + "This post was published by a GovDAO proposal.", // body + time.Now().Format(time.RFC3339), // publication date + "moul", // authors + "govdao,example", // tags + ) + + // Create a proposal + description := "post a new blogpost about govdao" + + prop := dao.ProposalRequest{ + Description: description, + Executor: ex, + } + + govdao.Propose(prop) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("0")) + println("--") + govdao.VoteOnProposal(0, "YES") + println("--") + println(govdao.Render("0")) + println("--") + println(gnoblog.Render("")) + println("--") + govdao.ExecuteProposal(0) + println("--") + println(govdao.Render("0")) + println("--") + println(gnoblog.Render("")) +} + +// Output: +// -- +// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// post a new blogpost about govdao +// +// Status: active +// +// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) +// +// Threshold met: false +// +// +// -- +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// post a new blogpost about govdao +// +// Status: accepted +// +// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// +// Threshold met: true +// +// +// -- +// # Gnoland's Blog +// +// No posts. +// -- +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// post a new blogpost about govdao +// +// Status: execution successful +// +// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// +// Threshold met: true +// +// +// -- +// # Gnoland's Blog +// +//
+// +// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao) +// 13 Feb 2009 +//
diff --git a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno new file mode 100644 index 00000000000..5aa9947c74b --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno @@ -0,0 +1,120 @@ +package main + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/membstore" + govdao "gno.land/r/gov/dao/v2" +) + +func init() { + memberFn := func() []membstore.Member { + return []membstore.Member{ + { + Address: std.Address("g123"), + VotingPower: 10, + }, + { + Address: std.Address("g456"), + VotingPower: 10, + }, + { + Address: std.Address("g789"), + VotingPower: 10, + }, + } + } + + // Create a proposal + description := "add new members to the govdao" + + prop := dao.ProposalRequest{ + Description: description, + Executor: govdao.NewMemberPropExecutor(memberFn), + } + + govdao.Propose(prop) +} + +func main() { + println("--") + println(govdao.GetMembStore().Size()) + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("0")) + println("--") + govdao.VoteOnProposal(0, "YES") + println("--") + println(govdao.Render("0")) + println("--") + println(govdao.Render("")) + println("--") + govdao.ExecuteProposal(0) + println("--") + println(govdao.Render("0")) + println("--") + println(govdao.Render("")) + println("--") + println(govdao.GetMembStore().Size()) +} + +// Output: +// -- +// 1 +// -- +// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// add new members to the govdao +// +// Status: active +// +// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) +// +// Threshold met: false +// +// +// -- +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// add new members to the govdao +// +// Status: accepted +// +// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// +// Threshold met: true +// +// +// -- +// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// -- +// # Prop #0 +// +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// +// add new members to the govdao +// +// Status: execution successful +// +// Voting stats: YAY 10 (25%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 30 (75%) +// +// Threshold met: false +// +// +// -- +// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// +// -- +// 4 diff --git a/examples/gno.land/r/gov/dao/voter.gno b/examples/gno.land/r/gov/dao/voter.gno deleted file mode 100644 index 99223210791..00000000000 --- a/examples/gno.land/r/gov/dao/voter.gno +++ /dev/null @@ -1,91 +0,0 @@ -package govdao - -import ( - "std" - - "gno.land/p/demo/ufmt" -) - -const ( - yay = "YES" - nay = "NO" - - msgNoMoreVotesAllowed = "no more votes allowed" - msgAlreadyVoted = "caller already voted" - msgWrongVotingValue = "voting values must be YES or NO" -) - -func NewPercentageVoter(percent int) *PercentageVoter { - if percent < 0 || percent > 100 { - panic("percent value must be between 0 and 100") - } - - return &PercentageVoter{ - percentage: percent, - } -} - -// PercentageVoter is a system based on the amount of received votes. -// When the specified treshold is reached, the voting process finishes. -type PercentageVoter struct { - percentage int - - voters []std.Address - yes int - no int -} - -func (pv *PercentageVoter) IsAccepted(voters []std.Address) bool { - if len(voters) == 0 { - return true // special case - } - - return pv.percent(voters) >= pv.percentage -} - -func (pv *PercentageVoter) IsFinished(voters []std.Address) bool { - return pv.yes+pv.no >= len(voters) -} - -func (pv *PercentageVoter) Status(voters []std.Address) string { - return ufmt.Sprintf("YES: %d, NO: %d, percent: %d, members: %d", pv.yes, pv.no, pv.percent(voters), len(voters)) -} - -func (pv *PercentageVoter) Vote(voters []std.Address, caller std.Address, flag string) { - if pv.IsFinished(voters) { - panic(msgNoMoreVotesAllowed) - } - - if pv.alreadyVoted(caller) { - panic(msgAlreadyVoted) - } - - switch flag { - case yay: - pv.yes++ - pv.voters = append(pv.voters, caller) - case nay: - pv.no++ - pv.voters = append(pv.voters, caller) - default: - panic(msgWrongVotingValue) - } -} - -func (pv *PercentageVoter) percent(voters []std.Address) int { - if len(voters) == 0 { - return 0 - } - - return int((float32(pv.yes) / float32(len(voters))) * 100) -} - -func (pv *PercentageVoter) alreadyVoted(addr std.Address) bool { - for _, v := range pv.voters { - if v == addr { - return true - } - } - - return false -} diff --git a/examples/gno.land/r/sys/validators/doc.gno b/examples/gno.land/r/sys/validators/v2/doc.gno similarity index 100% rename from examples/gno.land/r/sys/validators/doc.gno rename to examples/gno.land/r/sys/validators/v2/doc.gno diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/v2/gno.mod similarity index 71% rename from examples/gno.land/r/sys/validators/gno.mod rename to examples/gno.land/r/sys/validators/v2/gno.mod index d9d129dd543..db94a208902 100644 --- a/examples/gno.land/r/sys/validators/gno.mod +++ b/examples/gno.land/r/sys/validators/v2/gno.mod @@ -1,12 +1,13 @@ -module gno.land/r/sys/validators +module gno.land/r/sys/validators/v2 require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/dao v0.0.0-latest gno.land/p/demo/seqid v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest gno.land/p/nt/poa v0.0.0-latest gno.land/p/sys/validators v0.0.0-latest + gno.land/r/gov/dao/bridge v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/v2/gnosdk.gno similarity index 100% rename from examples/gno.land/r/sys/validators/gnosdk.gno rename to examples/gno.land/r/sys/validators/v2/gnosdk.gno diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/validators/v2/init.gno similarity index 100% rename from examples/gno.land/r/sys/validators/init.gno rename to examples/gno.land/r/sys/validators/v2/init.gno diff --git a/examples/gno.land/r/sys/validators/poc.gno b/examples/gno.land/r/sys/validators/v2/poc.gno similarity index 63% rename from examples/gno.land/r/sys/validators/poc.gno rename to examples/gno.land/r/sys/validators/v2/poc.gno index e088b3b4293..760edc39d1e 100644 --- a/examples/gno.land/r/sys/validators/poc.gno +++ b/examples/gno.land/r/sys/validators/v2/poc.gno @@ -3,16 +3,12 @@ package validators import ( "std" - "gno.land/p/gov/proposal" + "gno.land/p/demo/dao" "gno.land/p/sys/validators" + "gno.land/r/gov/dao/bridge" ) -const daoPkgPath = "gno.land/r/gov/dao" - -const ( - errNoChangesProposed = "no set changes proposed" - errNotGovDAO = "caller not govdao executor" -) +const errNoChangesProposed = "no set changes proposed" // NewPropExecutor creates a new executor that wraps a changes closure // proposal. This wrapper is required to ensure the GovDAO Realm actually @@ -20,15 +16,12 @@ const ( // // Concept adapted from: // https://github.com/gnolang/gno/pull/1945 -func NewPropExecutor(changesFn func() []validators.Validator) proposal.Executor { +func NewPropExecutor(changesFn func() []validators.Validator) dao.Executor { if changesFn == nil { panic(errNoChangesProposed) } callback := func() error { - // Make sure the GovDAO executor runs the valset changes - assertGovDAOCaller() - for _, change := range changesFn() { if change.VotingPower == 0 { // This change request is to remove the validator @@ -44,14 +37,7 @@ func NewPropExecutor(changesFn func() []validators.Validator) proposal.Executor return nil } - return proposal.NewExecutor(callback) -} - -// assertGovDAOCaller verifies the caller is the GovDAO executor -func assertGovDAOCaller() { - if std.PrevRealm().PkgPath() != daoPkgPath { - panic(errNotGovDAO) - } + return bridge.GovDAO().NewGovDAOExecutor(callback) } // IsValidator returns a flag indicating if the given bech32 address diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/v2/validators.gno similarity index 100% rename from examples/gno.land/r/sys/validators/validators.gno rename to examples/gno.land/r/sys/validators/v2/validators.gno diff --git a/examples/gno.land/r/sys/validators/validators_test.gno b/examples/gno.land/r/sys/validators/v2/validators_test.gno similarity index 100% rename from examples/gno.land/r/sys/validators/validators_test.gno rename to examples/gno.land/r/sys/validators/v2/validators_test.gno diff --git a/gno.land/pkg/gnoland/validators.go b/gno.land/pkg/gnoland/validators.go index 1843dff3984..339ebd9dcad 100644 --- a/gno.land/pkg/gnoland/validators.go +++ b/gno.land/pkg/gnoland/validators.go @@ -9,7 +9,7 @@ import ( ) const ( - valRealm = "gno.land/r/sys/validators" + valRealm = "gno.land/r/sys/validators/v2" // XXX: make it configurable from GovDAO valChangesFn = "GetChanges" validatorAddedEvent = "ValidatorAdded" From ed919917ed1c802448487b8b4a3602db573606aa Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 29 Oct 2024 21:34:09 +0100 Subject: [PATCH 08/34] chore: remove install.gnogenesis from root makefile (#3045) Follow-up to #3041. Not even `gnoland` is part of the core commands in the root makefile, hence it makes no sense for `gnogenesis` to be one of them. --- Makefile | 6 +----- contribs/gnogenesis/README.md | 4 ++-- .../gno-infrastructure/validators/setting-up-a-new-chain.md | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5cf8c8c58f9..2bfbe4e05e2 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ VERIFY_MOD_SUMS ?= false ######################################## # Dev tools .PHONY: install -install: install.gnokey install.gno install.gnodev install.gnogenesis +install: install.gnokey install.gno install.gnodev # shortcuts to frequently used commands from sub-components. .PHONY: install.gnokey @@ -45,10 +45,6 @@ install.gno: install.gnodev: $(MAKE) --no-print-directory -C ./contribs/gnodev install @printf "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m\n" -.PHONY: install.gnogenesis -install.gnogenesis: - $(MAKE) --no-print-directory -C ./contribs/gnogenesis install - @printf "\033[0;32m[+] 'gnogenesis' has been installed. Read more in ./contribs/gnogenesis/\033[0m\n" # old aliases diff --git a/contribs/gnogenesis/README.md b/contribs/gnogenesis/README.md index ae8daa6b81c..32cf3e6bb94 100644 --- a/contribs/gnogenesis/README.md +++ b/contribs/gnogenesis/README.md @@ -12,8 +12,8 @@ To install gnogenesis, clone the repository and build the tool: ```shell git clone https://github.com/gnoland/gno.git -cd gno -make install.gnogenesis +cd gno/contribs/gnogenesis +make install ``` This will compile and install `gnogenesis` to your system path, allowing you to run commands directly. 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 aab76eefbaf..5db8a7f1a59 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -30,7 +30,7 @@ Makefile to install the `gnoland` binary: ```bash cd gno.land -make install.gnoland install.gnogenesis +make install.gnoland && make -C contribs/gnogenesis install ``` To verify that you've installed the binary properly and that you are able to use From 9786fa366f922f04e1251ec6f1df6423b4fd2bf4 Mon Sep 17 00:00:00 2001 From: Morgan Date: Wed, 30 Oct 2024 15:12:24 +0100 Subject: [PATCH 09/34] chore: put replaces on gnolang/gno for all go.mods (#3046) Everything on this repository should default to always being on the latest master branch.
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [x] Provided any useful hints for running manual tests - [x] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--- .dockerignore | 4 +- .github/workflows/autocounterd.yml | 4 +- .github/workflows/portal-loop.yml | 1 - Dockerfile | 26 +- Dockerfile.release | 3 + contribs/gnodev/go.mod | 2 +- contribs/gnodev/go.sum | 4 +- contribs/gnofaucet/go.mod | 50 +- contribs/gnofaucet/go.sum | 109 +++-- contribs/gnogenesis/go.mod | 2 +- contribs/gnogenesis/go.sum | 4 +- contribs/gnokeykc/go.mod | 2 +- contribs/gnokeykc/go.sum | 4 +- go.mod | 4 +- go.sum | 4 +- misc/autocounterd/Dockerfile | 16 - misc/autocounterd/cmd/cmd_start.go | 24 +- misc/autocounterd/docker-compose.yml | 3 +- misc/autocounterd/go.mod | 77 +-- misc/autocounterd/go.sum | 284 ++++------- misc/devdeps/go.mod | 2 +- misc/devdeps/go.sum | 4 +- misc/docs-linter/go.mod | 10 +- misc/docs-linter/go.sum | 14 +- misc/loop/Dockerfile | 19 - misc/loop/docker-compose.yml | 2 +- misc/loop/go.mod | 57 ++- misc/loop/go.sum | 113 ++--- .../amino/tests/proto3/proto3_compat_test.go | 458 ------------------ 29 files changed, 391 insertions(+), 915 deletions(-) delete mode 100644 misc/autocounterd/Dockerfile delete mode 100644 misc/loop/Dockerfile delete mode 100644 tm2/pkg/amino/tests/proto3/proto3_compat_test.go diff --git a/.dockerignore b/.dockerignore index a45b7bafa98..3536640b4d7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,9 @@ .dockerignore build/ Dockerfile -misc/ +misc/* +!misc/loop/ +!misc/autocounterd/ docker-compose.yml tests/docker-integration/ diff --git a/.github/workflows/autocounterd.yml b/.github/workflows/autocounterd.yml index 66aced0d89c..63799960df5 100644 --- a/.github/workflows/autocounterd.yml +++ b/.github/workflows/autocounterd.yml @@ -1,6 +1,9 @@ name: autocounterd on: + pull_request: + branches: + - master push: paths: - misc/autocounterd @@ -41,7 +44,6 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - context: ./misc/autocounterd push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index b81957b22db..01135b164ac 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -45,7 +45,6 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - context: ./misc/loop target: portalloopd push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} diff --git a/Dockerfile b/Dockerfile index fa5a9e47270..b858589640f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,11 +10,20 @@ 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/gnoweb ./gno.land/cmd/gnoweb 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 +RUN go env -w GOMODCACHE=/root/.cache/go-build +WORKDIR /gnoroot +ENV GNOROOT="/gnoroot" +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build go build -C ./misc/loop -o /gnoroot/build/portalloopd ./cmd +RUN --mount=type=cache,target=/root/.cache/go-build go build -C ./misc/autocounterd -o /gnoroot/build/autocounterd ./cmd + # Base image FROM alpine:3.17 AS base WORKDIR /gnoroot ENV GNOROOT="/gnoroot" -RUN apk add ca-certificates +RUN apk add --no-cache ca-certificates CMD [ "" ] # alpine images @@ -47,6 +56,21 @@ COPY --from=build-gno /opt/gno/src/gno.land/cmd/gnoweb /opt/gno/src/gnowe EXPOSE 8888 ENTRYPOINT ["/usr/bin/gnoweb"] +# misc/loop +FROM docker AS portalloopd +WORKDIR /gnoroot +ENV GNOROOT="/gnoroot" +RUN apk add --no-cache ca-certificates bash curl jq +COPY --from=build-misc /gnoroot/build/portalloopd /usr/bin/portalloopd +ENTRYPOINT ["/usr/bin/portalloopd"] +CMD ["serve"] + +# misc/autocounterd +FROM base AS autocounterd +COPY --from=build-misc /gnoroot/build/autocounterd /usr/bin/autocounterd +ENTRYPOINT ["/usr/bin/autocounterd"] +CMD ["start"] + # all, contains everything. FROM base AS all COPY --from=build-gno /gnoroot/build/* /usr/bin/ diff --git a/Dockerfile.release b/Dockerfile.release index 4887857b5c2..481100c85c3 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,3 +1,6 @@ +# This file is similar to Dockerfile, but assumes that the binaries have +# already been created, and as such doesn't `go build` them. + FROM alpine AS base ENV GNOROOT="/gnoroot/" diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index c419f968d4a..a315d88591c 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -107,6 +107,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index af57f320257..e38c3621483 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -326,8 +326,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index c56c0b7d425..c5bb1ad0d81 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -6,15 +6,17 @@ toolchain go1.22.4 require ( github.com/gnolang/faucet v0.3.2 - github.com/gnolang/gno v0.1.1 + github.com/gnolang/gno v0.1.0-nightly.20240627 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/time v0.5.0 ) +replace github.com/gnolang/gno => ../.. + require ( - github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect @@ -23,34 +25,34 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/cors v1.11.0 // indirect - github.com/rs/xid v1.5.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 1508cdae1e6..f4bdc65d7ec 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -1,18 +1,20 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -47,8 +49,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gnolang/faucet v0.3.2 h1:3QBrdmnQszRaAZbxgO5xDDm3czNa0L/RFmhnCkbxy5I= github.com/gnolang/faucet v0.3.2/go.mod h1:/wbw9h4ooMzzyNBuM0X+ol7CiPH2OFjAFF3bYAXqA7U= -github.com/gnolang/gno v0.1.1 h1:t41S0SWIUa3syI7XpRAuCneCgRc8gOJ2g8DkUedF72U= -github.com/gnolang/gno v0.1.1/go.mod h1:BTaBNeaoY/W95NN6QA4RCoQ6Z7mi8M+Zb1I1wMWGg2w= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -77,10 +77,10 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -109,32 +109,37 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= -go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -148,22 +153,22 @@ go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -173,24 +178,24 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -199,8 +204,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index cdd8922fad5..393fed0725d 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -57,6 +57,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index 28c509e381e..f3161e47bad 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -212,8 +212,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index a8e235a5c5a..0c794afd54c 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -59,6 +59,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index b3bfadb3468..50eb5add218 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -216,8 +216,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/go.mod b/go.mod index 33f3a0f5212..24d09a87236 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/fortytw2/leaktest v1.3.0 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 - github.com/golang/protobuf v1.5.4 github.com/google/gofuzz v1.2.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 @@ -45,7 +44,7 @@ require ( golang.org/x/sync v0.8.0 golang.org/x/term v0.23.0 golang.org/x/tools v0.24.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -53,6 +52,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect diff --git a/go.sum b/go.sum index 55b5681e559..78d60eeea90 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/autocounterd/Dockerfile b/misc/autocounterd/Dockerfile deleted file mode 100644 index d860fc5f37f..00000000000 --- a/misc/autocounterd/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM golang:alpine AS builder - -COPY . /go/src/github.com/gnolang/gno/misc/autocounterd - -WORKDIR /go/src/github.com/gnolang/gno/misc/autocounterd - -RUN go build -o /build/autocounterd ./cmd - -# Final image for autocounterd -FROM alpine AS autocounterd - -COPY --from=builder /build/autocounterd /usr/bin/autocounterd - -ENTRYPOINT [ "/usr/bin/autocounterd" ] -CMD [ "start" ] - diff --git a/misc/autocounterd/cmd/cmd_start.go b/misc/autocounterd/cmd/cmd_start.go index a32d01fa324..ecf70f750be 100644 --- a/misc/autocounterd/cmd/cmd_start.go +++ b/misc/autocounterd/cmd/cmd_start.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -73,7 +74,10 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { return err } - rpcClient := rpcclient.NewHTTP(cfg.rpcURL, "/websocket") + rpcClient, err := rpcclient.NewHTTPClient(cfg.rpcURL) + if err != nil { + return err + } client := gnoclient.Client{ Signer: signer, @@ -81,14 +85,16 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { } for { - res, err := client.Call(gnoclient.CallCfg{ - PkgPath: cfg.realmPath, - FuncName: "Incr", - GasFee: "10000000ugnot", - GasWanted: 800000, - Args: nil, - }) - _ = res + _, err := client.Call( + gnoclient.BaseTxCfg{ + GasFee: "10000000ugnot", + GasWanted: 800000, + }, + vm.MsgCall{ + PkgPath: cfg.realmPath, + Func: "Incr", + Args: nil, + }) if err != nil { fmt.Printf("[ERROR] Failed to call Incr on %s, %+v\n", cfg.realmPath, err.Error()) diff --git a/misc/autocounterd/docker-compose.yml b/misc/autocounterd/docker-compose.yml index d71e6997b51..49d1ad413f3 100644 --- a/misc/autocounterd/docker-compose.yml +++ b/misc/autocounterd/docker-compose.yml @@ -3,7 +3,8 @@ services: autocounterd: image: ghcr.io/gnolang/gno/autocounterd build: - context: . + context: ../.. + target: autocounterd restart: unless-stopped environment: COUNTER_MNEMONIC: "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 12297e3c6ca..5de1d3c2974 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,54 +1,57 @@ -module loop +module autocounterd go 1.22 toolchain go1.22.4 -require github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278 +require github.com/gnolang/gno v0.0.0-00010101000000-000000000000 require ( - dario.cat/mergo v1.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/btcsuite/btcd/btcutil v1.1.3 // indirect - github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/dgraph-io/badger/v3 v3.2103.5 // indirect - github.com/dgraph-io/ristretto v0.1.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect - github.com/gnolang/goleveldb v0.0.9 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect - github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/flatbuffers v1.12.1 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/jaekwon/testify v1.6.1 // indirect - github.com/jmhodges/levigo v1.0.0 // indirect - github.com/klauspost/compress v1.12.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/linxGnu/grocksdb v1.8.11 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/cors v1.10.1 // indirect - github.com/stretchr/testify v1.8.4 // indirect - github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect - go.etcd.io/bbolt v1.3.8 // indirect - go.opencensus.io v0.22.5 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/gnolang/gno => ../.. diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index 905c884857e..b34cbde0c00 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -1,27 +1,24 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= -github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= -github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -31,17 +28,12 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -50,98 +42,59 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= -github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278 h1:CxF7gG3iqSeYVygTSYsB7Beg+Fpvka06TuTI2a0p+6s= -github.com/gnolang/gno v0.0.0-20240125181217-b6193518e278/go.mod h1:mOhpUTFaKk5CQj90qmjWfI9po2eapqziEu4D+fAtisc= -github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= -github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= -github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/linxGnu/grocksdb v1.8.11 h1:BGol9e5gB1BrsTvOxloC88pe70TCqgrfLNwkyWW0kD8= -github.com/linxGnu/grocksdb v1.8.11/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= @@ -155,7 +108,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= @@ -164,146 +116,113 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go.uber.org/zap/exp v0.1.0 h1:Ol9zQNvAEAgFHSBiR5LlwS9Xq8u5QF+7HBwNHUB8rcI= -go.uber.org/zap/exp v0.1.0/go.mod h1:z/0T3As39ttolxZGOsvk1OEvQfwwfTZpmV9YTp+VAkc= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -311,4 +230,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index 2ca693afc93..c07b82fd11d 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -8,7 +8,7 @@ require ( github.com/golangci/golangci-lint v1.59.1 // sync with github action golang.org/x/tools v0.22.1-0.20240628205440-9c895dd76b34 golang.org/x/tools/gopls v0.16.1 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.35.1 moul.io/testman v1.5.0 mvdan.cc/gofumpt v0.6.0 ) diff --git a/misc/devdeps/go.sum b/misc/devdeps/go.sum index 4c3f84b6df7..e19e47d0c56 100644 --- a/misc/devdeps/go.sum +++ b/misc/devdeps/go.sum @@ -942,8 +942,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index be771c9a952..e27b82ef2f5 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -5,17 +5,19 @@ go 1.22 toolchain go1.22.4 require ( - github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 mvdan.cc/xurls/v2 v2.5.0 ) +replace github.com/gnolang/gno => ../.. + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/misc/docs-linter/go.sum b/misc/docs-linter/go.sum index ab8c3cf7c48..4957bd0cc88 100644 --- a/misc/docs-linter/go.sum +++ b/misc/docs-linter/go.sum @@ -1,19 +1,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c h1:jtZ+oN8ZpBM0wYbcFH0B7NjFFzTFqZZmZellSSKtaCE= -github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c/go.mod h1:YcZbtNIfXVn4jS1pSG8SeG5RVHjyI7FPS3GypZaXxCI= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/misc/loop/Dockerfile b/misc/loop/Dockerfile deleted file mode 100644 index 219a47927db..00000000000 --- a/misc/loop/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM golang:alpine AS builder - -COPY . /go/src/github.com/gnolang/gno/misc/loop - -WORKDIR /go/src/github.com/gnolang/gno/misc/loop - -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/root/go/pkg/mod \ - go build -o /build/portalloopd ./cmd - -# Final image for portalloopd -FROM docker AS portalloopd - -RUN apk add bash curl jq - -COPY --from=builder /build/portalloopd /usr/bin/portalloopd - -ENTRYPOINT [ "/usr/bin/portalloopd" ] -CMD [ "serve" ] diff --git a/misc/loop/docker-compose.yml b/misc/loop/docker-compose.yml index eba0f55e787..ed2fe7192f5 100644 --- a/misc/loop/docker-compose.yml +++ b/misc/loop/docker-compose.yml @@ -68,7 +68,7 @@ services: portalloopd: build: - context: . + context: ../.. target: portalloopd restart: unless-stopped volumes: diff --git a/misc/loop/go.mod b/misc/loop/go.mod index be37c21f5c9..2d749759bfb 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -7,22 +7,23 @@ toolchain go1.22.4 require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 - github.com/gnolang/gno v0.1.0-nightly.20240707 + github.com/gnolang/gno v0.1.0-nightly.20240627 github.com/gnolang/tx-archive v0.3.0 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) +replace github.com/gnolang/gno => ../.. + require ( - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/distribution/reference v0.5.0 // indirect @@ -35,8 +36,8 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/moby/term v0.5.0 // indirect @@ -50,37 +51,35 @@ require ( github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect - github.com/rs/cors v1.11.0 // indirect - github.com/rs/xid v1.5.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/zondax/hid v0.9.2 // indirect - github.com/zondax/ledger-go v0.14.3 // indirect - go.etcd.io/bbolt v1.3.9 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.etcd.io/bbolt v1.3.11 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 2ad488a5f25..c80bfb42c2f 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -1,5 +1,5 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -9,16 +9,18 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -66,8 +68,6 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/gno v0.1.0-nightly.20240707 h1:ez1BtiwRuqRHRxvqyKDbUbNtUBYEjXwSHqRu6m347os= -github.com/gnolang/gno v0.1.0-nightly.20240707/go.mod h1:BTaBNeaoY/W95NN6QA4RCoQ6Z7mi8M+Zb1I1wMWGg2w= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/gnolang/tx-archive v0.3.0 h1:5Fr39yAT7nnAPKvcmKmBT+oPiBhMhA0aUAIEeXrYG4I= @@ -100,10 +100,10 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -160,14 +160,19 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -178,22 +183,22 @@ github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= -go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -208,14 +213,14 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -224,15 +229,15 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -244,32 +249,32 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -278,8 +283,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/tm2/pkg/amino/tests/proto3/proto3_compat_test.go b/tm2/pkg/amino/tests/proto3/proto3_compat_test.go deleted file mode 100644 index 8f9e04fc35c..00000000000 --- a/tm2/pkg/amino/tests/proto3/proto3_compat_test.go +++ /dev/null @@ -1,458 +0,0 @@ -//go:build extensive_tests - -// only built if manually enforced (via the build tag above) -package proto3 - -import ( - "bufio" - "bytes" - "encoding/binary" - "math" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes" - - p3 "github.com/gnolang/gno/tm2/pkg/amino/tests/proto3/proto" - - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/amino/tests" -) - -// This file checks basic proto3 compatibility by checking encoding of some test-vectors generated by using protoc. - -var ( - cdc = amino.NewCodec() - epoch time.Time -) - -func init() { - cdc.Seal() - epoch, _ = time.Parse("2006-01-02 15:04:05 +0000 UTC", "1970-01-01 00:00:00 +0000 UTC") -} - -func TestFixed32Roundtrip(t *testing.T) { - t.Parallel() - - // amino fixed32 (int32) <-> protbuf fixed32 (uint32) - type testi32 struct { - Int32 int32 `binary:"fixed32"` - } - ab, err := cdc.Marshal(testi32{Int32: 150}) - assert.NoError(t, err, "unexpected error") - - pb, err := proto.Marshal(&p3.TestInt32Fixed{Fixed32: 150}) - assert.NoError(t, err, "unexpected error") - - assert.Equal(t, pb, ab, "fixed32 (int32) encoding doesn't match") - - // unmarshal (from amino to proto and vice versa) - var att testi32 - var pt p3.Test32 - err = proto.Unmarshal(ab, &pt) - assert.NoError(t, err, "unexpected error") - - err = cdc.Unmarshal(pb, &att) - assert.NoError(t, err, "unexpected error") - - assert.Equal(t, uint32(att.Int32), pt.Foo) -} - -func TestVarintZigzagRoundtrip(t *testing.T) { - t.Parallel() - - t.Skip("zigzag encoding isn't default anymore for (unsigned) ints") - // amino varint (int) <-> protobuf zigzag32 (int32 in go sint32 in proto file) - type testInt32Varint struct { - Int32 int `binary:"varint"` - } - varint := testInt32Varint{Int32: 6000000} - ab, err := cdc.Marshal(varint) - assert.NoError(t, err, "unexpected error") - pb, err := proto.Marshal(&p3.TestInt32Varint{Int32: 6000000}) - assert.NoError(t, err, "unexpected error") - assert.Equal(t, pb, ab, "varint encoding doesn't match") - - var amToP3 p3.TestInt32Varint - var p3ToAm testInt32Varint - err = proto.Unmarshal(ab, &amToP3) - assert.NoError(t, err, "unexpected error") - - err = cdc.Unmarshal(pb, &p3ToAm) - assert.NoError(t, err, "unexpected error") - - assert.EqualValues(t, varint.Int32, amToP3.Int32) -} - -func TestFixedU64Roundtrip(t *testing.T) { - t.Parallel() - - type testFixed64Uint struct { - Int64 uint64 `binary:"fixed64"` - } - - pvint64 := p3.TestFixedInt64{Int64: 150} - avint64 := testFixed64Uint{Int64: 150} - ab, err := cdc.Marshal(avint64) - assert.NoError(t, err, "unexpected error") - - pb, err := proto.Marshal(&pvint64) - assert.NoError(t, err, "unexpected error") - - assert.Equal(t, pb, ab, "fixed64 encoding doesn't match") - - var amToP3 p3.TestFixedInt64 - var p3ToAm testFixed64Uint - err = proto.Unmarshal(ab, &amToP3) - assert.NoError(t, err, "unexpected error") - - err = cdc.Unmarshal(pb, &p3ToAm) - assert.NoError(t, err, "unexpected error") - - assert.EqualValues(t, p3ToAm.Int64, amToP3.Int64) -} - -func TestMultidimensionalSlices(t *testing.T) { - t.Parallel() - - s := [][]int8{ - {1, 2}, - {3, 4, 5}, - } - - _, err := cdc.Marshal(s) - assert.Error(t, err, "expected error: multidimensional slices are not allowed") -} - -func TestMultidimensionalArrays(t *testing.T) { - t.Parallel() - - arr := [2][2]int8{ - {1, 2}, - {3, 4}, - } - - _, err := cdc.Marshal(arr) - assert.Error(t, err, "expected error: multidimensional arrays are not allowed") -} - -func TestMultidimensionalByteArraysAndSlices(t *testing.T) { - t.Parallel() - - arr := [2][2]byte{ - {1, 2}, - {3, 4}, - } - - _, err := cdc.Marshal(arr) - assert.NoError(t, err, "unexpected error: multidimensional arrays are allowed, as long as they are only of bytes") - - s := [][]byte{ - {1, 2}, - {3, 4, 5}, - } - - _, err = cdc.Marshal(s) - assert.NoError(t, err, "unexpected error: multidimensional slices are allowed, as long as they are only of bytes") - - s2 := [][][]byte{{ - {1, 2}, - {3, 4, 5}, - }} - - _, err = cdc.Marshal(s2) - assert.NoError(t, err, "unexpected error: multidimensional slices are allowed, as long as they are only of bytes") -} - -func TestProto3CompatPtrsRoundtrip(t *testing.T) { - t.Parallel() - - s := p3.SomeStruct{} - - ab, err := cdc.Marshal(s) - assert.NoError(t, err) - - pb, err := proto.Marshal(&s) - assert.NoError(t, err) - // This fails as amino currently returns []byte(nil) - // while protobuf returns []byte{}: - // - // assert.Equal(t, ab, pb) - // - // Semantically, that's no problem though. Hence, we only check for zero length: - assert.Zero(t, len(ab), "expected an empty encoding for a nil pointer") - t.Log(ab) - - var amToP3 p3.SomeStruct - var p3ToAm p3.SomeStruct - err = proto.Unmarshal(ab, &amToP3) - assert.NoError(t, err, "unexpected error") - - err = cdc.Unmarshal(pb, &p3ToAm) - assert.NoError(t, err, "unexpected error") - - assert.EqualValues(t, p3ToAm, amToP3) - - s2 := p3.SomeStruct{Emb: &p3.EmbeddedStruct{}} - - ab, err = cdc.Marshal(s2) - assert.NoError(t, err) - - pb, err = proto.Marshal(&s2) - assert.NoError(t, err) - assert.Equal(t, ab, pb) - - err = proto.Unmarshal(ab, &amToP3) - assert.NoError(t, err, "unexpected error") - - err = cdc.Unmarshal(pb, &p3ToAm) - assert.NoError(t, err, "unexpected error") - - assert.EqualValues(t, p3ToAm, amToP3) - - assert.NotZero(t, len(ab), "expected a non-empty encoding for a non-nil pointer to an empty struct") - t.Log(ab) -} - -// --------------------------------------------------------------- -// ---- time.Time <-> timestamp.Timestamp (proto3 well known type) : -// --------------------------------------------------------------- - -// equivalent go struct or "type" to the proto3 message: -type goAminoGotTime struct { - T *time.Time -} - -func TestProto3CompatEmptyTimestamp(t *testing.T) { - t.Parallel() - - empty := p3.ProtoGotTime{} - // protobuf also marshals to empty bytes here: - pb, err := proto.Marshal(&empty) - assert.NoError(t, err) - assert.Len(t, pb, 0) - - // unmarshaling an empty slice behaves a bit differently in proto3 compared to amino: - res := &goAminoGotTime{} - err = cdc.Unmarshal(pb, res) - assert.NoError(t, err) - // NOTE: this behaves differently because amino defaults the time to 1970-01-01 00:00:00 +0000 UTC while - // decoding; protobuf defaults to nil here (see the following lines below): - assert.NoError(t, err) - assert.Equal(t, goAminoGotTime{T: &epoch}, *res) - pbRes := p3.ProtoGotTime{} - err = proto.Unmarshal(pb, &pbRes) - assert.NoError(t, err) - assert.Equal(t, p3.ProtoGotTime{T: nil}, pbRes) -} - -func TestProto3CompatTimestampNow(t *testing.T) { - t.Parallel() - - // test with current time: - now := time.Now() - ptts, err := ptypes.TimestampProto(now) - assert.NoError(t, err) - pt := p3.ProtoGotTime{T: ptts} - at := goAminoGotTime{T: &now} - ab1, err := cdc.Marshal(at) - assert.NoError(t, err) - ab2, err := cdc.Marshal(pt) - assert.NoError(t, err) - // amino's encoding of time.Time is the same as proto's encoding of the well known type - // timestamp.Timestamp (they can be used interchangeably): - assert.Equal(t, ab1, ab2) - pb, err := proto.Marshal(&pt) - assert.NoError(t, err) - assert.Equal(t, ab1, pb) - - pbRes := p3.ProtoGotTime{} - err = proto.Unmarshal(ab1, &pbRes) - assert.NoError(t, err) - got, err := ptypes.Timestamp(pbRes.T) - assert.NoError(t, err) - _, err = ptypes.TimestampProto(now) - assert.NoError(t, err) - err = proto.Unmarshal(pb, &pbRes) - assert.NoError(t, err) - // create time.Time from timestamp.Timestamp and check if they are the same: - got, err = ptypes.Timestamp(pbRes.T) - assert.Equal(t, got.UTC(), now.UTC()) -} - -func TestProto3EpochTime(t *testing.T) { - t.Parallel() - - pbRes := p3.ProtoGotTime{} - // amino encode epoch (1970) and decode using proto; expect the resulting time to be epoch again: - ab, err := cdc.Marshal(goAminoGotTime{T: &epoch}) - assert.NoError(t, err) - err = proto.Unmarshal(ab, &pbRes) - assert.NoError(t, err) - ts, err := ptypes.Timestamp(pbRes.T) - assert.NoError(t, err) - assert.EqualValues(t, ts, epoch) -} - -func TestProtoNegativeSeconds(t *testing.T) { - t.Parallel() - - pbRes := p3.ProtoGotTime{} - // test with negative seconds (0001-01-01 -> seconds = -62135596800, nanos = 0): - ntm, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC") - ab, err := cdc.Marshal(goAminoGotTime{T: &ntm}) - assert.NoError(t, err) - res := &goAminoGotTime{} - err = cdc.Unmarshal(ab, res) - assert.NoError(t, err) - assert.EqualValues(t, ntm, *res.T) - err = proto.Unmarshal(ab, &pbRes) - assert.NoError(t, err) - got, err := ptypes.Timestamp(pbRes.T) - assert.NoError(t, err) - assert.Equal(t, got, ntm) -} - -func TestIntVarintCompat(t *testing.T) { - t.Parallel() - - tcs := []struct { - val32 int32 - val64 int64 - }{ - {1, 1}, - {-1, -1}, - {2, 2}, - {1000, 1000}, - {math.MaxInt32, math.MaxInt64}, - {math.MinInt32, math.MinInt64}, - } - for _, tc := range tcs { - tv := p3.TestInts{Int32: tc.val32, Int64: tc.val64} - ab, err := cdc.Marshal(tv) - assert.NoError(t, err) - pb, err := proto.Marshal(&tv) - assert.NoError(t, err) - assert.Equal(t, ab, pb) - var res p3.TestInts - err = cdc.Unmarshal(pb, &res) - assert.NoError(t, err) - var res2 p3.TestInts - err = proto.Unmarshal(ab, &res2) - assert.NoError(t, err) - assert.Equal(t, res.Int32, tc.val32) - assert.Equal(t, res.Int64, tc.val64) - assert.Equal(t, res2.Int32, tc.val32) - assert.Equal(t, res2.Int64, tc.val64) - } - // special case: amino allows int as well - // test that ints are also varint encoded: - type TestInt struct { - Int int - } - tcs2 := []struct { - val int - }{ - {0}, - {-1}, - {1000}, - {-1000}, - {math.MaxInt32}, - {math.MinInt32}, - } - for _, tc := range tcs2 { - ptv := p3.TestInts{Int32: int32(tc.val)} - pb, err := proto.Marshal(&ptv) - assert.NoError(t, err) - atv := TestInt{tc.val} - ab, err := cdc.Marshal(atv) - assert.NoError(t, err) - if tc.val == 0 { - // amino results in []byte(nil) - // protobuf in []byte{} - assert.Empty(t, ab) - assert.Empty(t, pb) - } else { - assert.Equal(t, ab, pb) - } - // can we get back the int from the proto? - var res TestInt - err = cdc.Unmarshal(pb, &res) - assert.NoError(t, err) - assert.EqualValues(t, res.Int, tc.val) - } - - // purposely overflow by writing a too large value to first field (which is int32): - fieldNum := 1 - fieldNumAndType := (uint64(fieldNum) << 3) | uint64(amino.Typ3Varint) - var b bytes.Buffer - writer := bufio.NewWriter(&b) - var buf [10]byte - n := binary.PutUvarint(buf[:], fieldNumAndType) - _, err := writer.Write(buf[0:n]) - assert.NoError(t, err) - amino.EncodeUvarint(writer, math.MaxInt32+1) - err = writer.Flush() - assert.NoError(t, err) - - var res p3.TestInts - err = cdc.Unmarshal(b.Bytes(), &res) - assert.Error(t, err) -} - -// See if encoding of type def types matches the proto3 encoding -func TestTypeDefCompatibility(t *testing.T) { - t.Parallel() - - pNow := ptypes.TimestampNow() - now, err := ptypes.Timestamp(pNow) - require.NoError(t, err) - - strSl := tests.PrimitivesStructSl{ - {Int32: 1, Int64: -1, Varint: 2, String: "protobuf3", Bytes: []byte("got some bytes"), Time: now}, - {Int32: 0, Int64: 1, Varint: -2, String: "amino", Bytes: []byte("more of these bytes"), Time: now}, - } - strAr := tests.PrimitivesStructAr{strSl[0], strSl[1]} - p3StrSl := &p3.PrimitivesStructSl{ - Structs: []*p3.PrimitivesStruct{ - {Int32: 1, Int64: -1, Varint: 2, String_: "protobuf3", Bytes: []byte("got some bytes"), Time: pNow}, - {Int32: 0, Int64: 1, Varint: -2, String_: "amino", Bytes: []byte("more of these bytes"), Time: pNow}, - }, - } - - tcs := []struct { - AminoType interface{} - ProtoMsg proto.Message - }{ - // type IntDef int - 0: {tests.IntDef(0), &p3.IntDef{}}, - 1: {tests.IntDef(0), &p3.IntDef{Val: 0}}, - 2: {tests.IntDef(1), &p3.IntDef{Val: 1}}, - 3: {tests.IntDef(-1), &p3.IntDef{Val: -1}}, - - // type IntAr [4]int - 4: {tests.IntAr{1, 2, 3, 4}, &p3.IntArr{Val: []int64{1, 2, 3, 4}}}, - 5: {tests.IntAr{0, -2, 3, 4}, &p3.IntArr{Val: []int64{0, -2, 3, 4}}}, - - // type IntSl []int (protobuf doesn't really have arrays) - 6: {tests.IntSl{1, 2, 3, 4}, &p3.IntArr{Val: []int64{1, 2, 3, 4}}}, - - // type PrimitivesStructSl []PrimitivesStruct - 7: {strSl, p3StrSl}, - // type PrimitivesStructAr [2]PrimitivesStruct - 8: {strAr, p3StrSl}, - } - for i, tc := range tcs { - ab, err := amino.Marshal(tc.AminoType) - require.NoError(t, err) - - pb, err := proto.Marshal(tc.ProtoMsg) - require.NoError(t, err) - - assert.Equal(t, pb, ab, "Amino and protobuf encoding do not match %v", i) - } -} From 850182caf3040ca06a590900918fed9c15eb9028 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:15:34 +0100 Subject: [PATCH 10/34] fix(gnovm): forbid star expression when value is not a pointer (#2984) closes: #1088
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- gnovm/pkg/gnolang/preprocess.go | 7 ++++++- gnovm/tests/files/ptr9.gno | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 gnovm/tests/files/ptr9.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 2d65dfa5cb1..10c55979520 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1757,7 +1757,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { case *KeyValueExpr: // NOTE: For simplicity we just // use the *CompositeLitExpr. - + // TRANS_LEAVE ----------------------- + case *StarExpr: + xt := evalStaticTypeOf(store, last, n.X) + if xt.Kind() != PointerKind && xt.Kind() != TypeKind { + panic(fmt.Sprintf("invalid operation: cannot indirect %s (variable of type %s)", n.X.String(), xt.String())) + } // TRANS_LEAVE ----------------------- case *SelectorExpr: xt := evalStaticTypeOf(store, last, n.X) diff --git a/gnovm/tests/files/ptr9.gno b/gnovm/tests/files/ptr9.gno new file mode 100644 index 00000000000..6e104942d81 --- /dev/null +++ b/gnovm/tests/files/ptr9.gno @@ -0,0 +1,9 @@ +package main + +func main() { + v := 1 + println(*v) +} + +// Error: +// main/files/ptr9.gno:5:10: invalid operation: cannot indirect v (variable of type int) From 494976da31d8c4c9d00a5074e9254429463b6291 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Thu, 31 Oct 2024 05:46:10 +0900 Subject: [PATCH 11/34] feat(p/json): remove unnecessary code and optimize (#2939) # Description Optimized the JSON package and simplified JSON node creation using the builder pattern. - in `buffer.gno` and `escape.gno` files are modified the use of map for lookup tables to use slice array instead. - refactor the `Unquote` function in `escape.gno` file - modified the existing functions that parsed numbers to use `strconv` package, and deleted related files and functions - especially, the `eisel_lemire` and `ryu` packages were deleted since they were files that had been added to handle `ParseUint` and `ParseFloat` in `strconv` package. ## JSON Generate Example **Plain JSON** ```go node := Builder(). WithString("name", "Alice"). WithNumber("age", 30). WithBool("is_student", false). Node() value, err := Marshal(node) if err != nil { t.Errorf("unexpected error: %s", err) } Output: {"name":"Alice","age":30,"is_student":false} ``` **Nested Structure** ```go node := Builder(). WriteString("name", "Alice"). WriteObject("address", func(b *NodeBuilder) { b.WriteString("city", "New York"). WriteNumber("zipcode", 10001) }). Node() // ... Output: {"name":"Alice","address":{"city":"New York","zipcode":10001}} ``` ## Benchmark Result for Unquote **Before** ```plain BenchmarkUnquote-8 12433488 98.06 ns/op 144 B/op 2 allocs/op BenchmarkUnquoteWorstCase-8 24727736 50.46 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteBestCase-8 22542354 52.69 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteEmptyString-8 394868628 3.067 ns/op 0 B/op 0 allocs/op ``` **After** ```plain BenchmarkUnquote-8 12464704 96.61 ns/op 144 B/op 2 allocs/op BenchmarkUnquoteWorstCase-8 25084070 48.02 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteBestCase-8 23383227 52.66 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteEmptyString-8 400496838 2.968 ns/op 0 B/op 0 allocs/op ``` --- examples/gno.land/p/demo/json/buffer.gno | 65 +- examples/gno.land/p/demo/json/buffer_test.gno | 35 +- examples/gno.land/p/demo/json/builder.gno | 89 ++ .../gno.land/p/demo/json/builder_test.gno | 103 +++ examples/gno.land/p/demo/json/decode_test.gno | 2 +- .../p/demo/json/eisel_lemire/eisel_lemire.gno | 839 ------------------ .../gno.land/p/demo/json/eisel_lemire/gno.mod | 1 - examples/gno.land/p/demo/json/encode.gno | 15 +- examples/gno.land/p/demo/json/encode_test.gno | 5 +- examples/gno.land/p/demo/json/errors.gno | 34 + examples/gno.land/p/demo/json/escape.gno | 114 ++- examples/gno.land/p/demo/json/escape_test.gno | 61 +- examples/gno.land/p/demo/json/gno.mod | 6 +- examples/gno.land/p/demo/json/indent.gno | 16 +- examples/gno.land/p/demo/json/node.gno | 56 +- examples/gno.land/p/demo/json/parser.gno | 161 +--- examples/gno.land/p/demo/json/parser_test.gno | 122 --- examples/gno.land/p/demo/json/ryu/License | 21 - .../gno.land/p/demo/json/ryu/floatconv.gno | 143 --- .../p/demo/json/ryu/floatconv_test.gno | 33 - examples/gno.land/p/demo/json/ryu/gno.mod | 1 - examples/gno.land/p/demo/json/ryu/ryu64.gno | 344 ------- examples/gno.land/p/demo/json/ryu/table.gno | 678 -------------- 23 files changed, 383 insertions(+), 2561 deletions(-) create mode 100644 examples/gno.land/p/demo/json/builder.gno create mode 100644 examples/gno.land/p/demo/json/builder_test.gno delete mode 100644 examples/gno.land/p/demo/json/eisel_lemire/eisel_lemire.gno delete mode 100644 examples/gno.land/p/demo/json/eisel_lemire/gno.mod create mode 100644 examples/gno.land/p/demo/json/errors.gno delete mode 100644 examples/gno.land/p/demo/json/ryu/License delete mode 100644 examples/gno.land/p/demo/json/ryu/floatconv.gno delete mode 100644 examples/gno.land/p/demo/json/ryu/floatconv_test.gno delete mode 100644 examples/gno.land/p/demo/json/ryu/gno.mod delete mode 100644 examples/gno.land/p/demo/json/ryu/ryu64.gno delete mode 100644 examples/gno.land/p/demo/json/ryu/table.gno diff --git a/examples/gno.land/p/demo/json/buffer.gno b/examples/gno.land/p/demo/json/buffer.gno index 23fb53fb0ea..a217ee653f9 100644 --- a/examples/gno.land/p/demo/json/buffer.gno +++ b/examples/gno.land/p/demo/json/buffer.gno @@ -3,7 +3,6 @@ package json import ( "errors" "io" - "strings" "gno.land/p/demo/ufmt" ) @@ -112,28 +111,6 @@ func (b *buffer) skip(bs byte) error { return io.EOF } -// skipAny moves the index until it encounters one of the given set of bytes. -func (b *buffer) skipAny(endTokens map[byte]bool) error { - for b.index < b.length { - if _, exists := endTokens[b.data[b.index]]; exists { - return nil - } - - b.index++ - } - - // build error message - var tokens []string - for token := range endTokens { - tokens = append(tokens, string(token)) - } - - return ufmt.Errorf( - "EOF reached before encountering one of the expected tokens: %s", - strings.Join(tokens, ", "), - ) -} - // skipAndReturnIndex moves the buffer index forward by one and returns the new index. func (b *buffer) skipAndReturnIndex() (int, error) { err := b.step() @@ -165,7 +142,7 @@ func (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) { // significantTokens is a map where the keys are the significant characters in a JSON path. // The values in the map are all true, which allows us to use the map as a set for quick lookups. -var significantTokens = map[byte]bool{ +var significantTokens = [256]bool{ dot: true, // access properties of an object dollarSign: true, // root object atSign: true, // current object @@ -174,7 +151,7 @@ var significantTokens = map[byte]bool{ } // filterTokens stores the filter expression tokens. -var filterTokens = map[byte]bool{ +var filterTokens = [256]bool{ aesterisk: true, // wildcard andSign: true, orSign: true, @@ -186,7 +163,7 @@ func (b *buffer) skipToNextSignificantToken() { for b.index < b.length { current := b.data[b.index] - if _, ok := significantTokens[current]; ok { + if significantTokens[current] { break } @@ -205,7 +182,7 @@ func (b *buffer) backslash() bool { count := 0 for i := b.index - 1; ; i-- { - if i >= b.length || b.data[i] != backSlash { + if b.data[i] != backSlash { break } @@ -220,7 +197,7 @@ func (b *buffer) backslash() bool { } // numIndex holds a map of valid numeric characters -var numIndex = map[byte]bool{ +var numIndex = [256]bool{ '0': true, '1': true, '2': true, @@ -255,11 +232,11 @@ func (b *buffer) pathToken() error { } if err := b.skip(c); err != nil { - return errors.New("unmatched quote in path") + return errUnmatchedQuotePath } if b.index >= b.length { - return errors.New("unmatched quote in path") + return errUnmatchedQuotePath } case c == bracketOpen || c == parenOpen: @@ -269,7 +246,7 @@ func (b *buffer) pathToken() error { case c == bracketClose || c == parenClose: inToken = true if len(stack) == 0 || (c == bracketClose && stack[len(stack)-1] != bracketOpen) || (c == parenClose && stack[len(stack)-1] != parenOpen) { - return errors.New("mismatched bracket or parenthesis") + return errUnmatchedParenthesis } stack = stack[:len(stack)-1] @@ -284,7 +261,7 @@ func (b *buffer) pathToken() error { inToken = true inNumber = true } else if !inToken { - return errors.New("unexpected operator at start of token") + return errInvalidToken } default: @@ -300,7 +277,7 @@ func (b *buffer) pathToken() error { end: if len(stack) != 0 { - return errors.New("unclosed bracket or parenthesis at end of path") + return errUnmatchedParenthesis } if first == b.index { @@ -315,15 +292,15 @@ end: } func pathStateContainsValidPathToken(c byte) bool { - if _, ok := significantTokens[c]; ok { + if significantTokens[c] { return true } - if _, ok := filterTokens[c]; ok { + if filterTokens[c] { return true } - if _, ok := numIndex[c]; ok { + if numIndex[c] { return true } @@ -342,7 +319,7 @@ func (b *buffer) numeric(token bool) error { for ; b.index < b.length; b.index++ { b.class = b.getClasses(doubleQuote) if b.class == __ { - return errors.New("invalid token found while parsing path") + return errInvalidToken } b.state = StateTransitionTable[b.last][b.class] @@ -351,7 +328,7 @@ func (b *buffer) numeric(token bool) error { break } - return errors.New("invalid token found while parsing path") + return errInvalidToken } if b.state < __ { @@ -366,7 +343,7 @@ func (b *buffer) numeric(token bool) error { } if b.last != ZE && b.last != IN && b.last != FR && b.last != E3 { - return errors.New("invalid token found while parsing path") + return errInvalidToken } return nil @@ -407,12 +384,12 @@ func (b *buffer) string(search byte, token bool) error { b.class = b.getClasses(search) if b.class == __ { - return errors.New("invalid token found while parsing path") + return errInvalidToken } b.state = StateTransitionTable[b.last][b.class] if b.state == __ { - return errors.New("invalid token found while parsing path") + return errInvalidToken } if b.state < __ { @@ -431,11 +408,11 @@ func (b *buffer) word(bs []byte) error { max := len(bs) index := 0 - for ; b.index < b.length; b.index++ { + for ; b.index < b.length && index < max; b.index++ { c = b.data[b.index] if c != bs[index] { - return errors.New("invalid token found while parsing path") + return errInvalidToken } index++ @@ -445,7 +422,7 @@ func (b *buffer) word(bs []byte) error { } if index != max { - return errors.New("invalid token found while parsing path") + return errInvalidToken } return nil diff --git a/examples/gno.land/p/demo/json/buffer_test.gno b/examples/gno.land/p/demo/json/buffer_test.gno index b8dce390a61..f4102040be5 100644 --- a/examples/gno.land/p/demo/json/buffer_test.gno +++ b/examples/gno.land/p/demo/json/buffer_test.gno @@ -1,6 +1,8 @@ package json -import "testing" +import ( + "testing" +) func TestBufferCurrent(t *testing.T) { tests := []struct { @@ -242,37 +244,6 @@ func TestBufferSkip(t *testing.T) { } } -func TestBufferSkipAny(t *testing.T) { - tests := []struct { - name string - buffer *buffer - s map[byte]bool - wantErr bool - }{ - { - name: "Skip any valid byte", - buffer: &buffer{data: []byte("test"), length: 4, index: 0}, - s: map[byte]bool{'e': true, 'o': true}, - wantErr: false, - }, - { - name: "Skip any to EOF", - buffer: &buffer{data: []byte("test"), length: 4, index: 0}, - s: map[byte]bool{'x': true, 'y': true}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.buffer.skipAny(tt.s) - if (err != nil) != tt.wantErr { - t.Errorf("buffer.skipAny() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - func TestSkipToNextSignificantToken(t *testing.T) { tests := []struct { name string diff --git a/examples/gno.land/p/demo/json/builder.gno b/examples/gno.land/p/demo/json/builder.gno new file mode 100644 index 00000000000..4693d5ec550 --- /dev/null +++ b/examples/gno.land/p/demo/json/builder.gno @@ -0,0 +1,89 @@ +package json + +type NodeBuilder struct { + node *Node +} + +func Builder() *NodeBuilder { + return &NodeBuilder{node: ObjectNode("", nil)} +} + +func (b *NodeBuilder) WriteString(key, value string) *NodeBuilder { + b.node.AppendObject(key, StringNode("", value)) + return b +} + +func (b *NodeBuilder) WriteNumber(key string, value float64) *NodeBuilder { + b.node.AppendObject(key, NumberNode("", value)) + return b +} + +func (b *NodeBuilder) WriteBool(key string, value bool) *NodeBuilder { + b.node.AppendObject(key, BoolNode("", value)) + return b +} + +func (b *NodeBuilder) WriteNull(key string) *NodeBuilder { + b.node.AppendObject(key, NullNode("")) + return b +} + +func (b *NodeBuilder) WriteObject(key string, fn func(*NodeBuilder)) *NodeBuilder { + nestedBuilder := &NodeBuilder{node: ObjectNode("", nil)} + fn(nestedBuilder) + b.node.AppendObject(key, nestedBuilder.node) + return b +} + +func (b *NodeBuilder) WriteArray(key string, fn func(*ArrayBuilder)) *NodeBuilder { + arrayBuilder := &ArrayBuilder{nodes: []*Node{}} + fn(arrayBuilder) + b.node.AppendObject(key, ArrayNode("", arrayBuilder.nodes)) + return b +} + +func (b *NodeBuilder) Node() *Node { + return b.node +} + +type ArrayBuilder struct { + nodes []*Node +} + +func (ab *ArrayBuilder) WriteString(value string) *ArrayBuilder { + ab.nodes = append(ab.nodes, StringNode("", value)) + return ab +} + +func (ab *ArrayBuilder) WriteNumber(value float64) *ArrayBuilder { + ab.nodes = append(ab.nodes, NumberNode("", value)) + return ab +} + +func (ab *ArrayBuilder) WriteInt(value int) *ArrayBuilder { + return ab.WriteNumber(float64(value)) +} + +func (ab *ArrayBuilder) WriteBool(value bool) *ArrayBuilder { + ab.nodes = append(ab.nodes, BoolNode("", value)) + return ab +} + +func (ab *ArrayBuilder) WriteNull() *ArrayBuilder { + ab.nodes = append(ab.nodes, NullNode("")) + return ab +} + +func (ab *ArrayBuilder) WriteObject(fn func(*NodeBuilder)) *ArrayBuilder { + nestedBuilder := &NodeBuilder{node: ObjectNode("", nil)} + fn(nestedBuilder) + ab.nodes = append(ab.nodes, nestedBuilder.node) + return ab +} + +func (ab *ArrayBuilder) WriteArray(fn func(*ArrayBuilder)) *ArrayBuilder { + nestedArrayBuilder := &ArrayBuilder{nodes: []*Node{}} + fn(nestedArrayBuilder) + ab.nodes = append(ab.nodes, ArrayNode("", nestedArrayBuilder.nodes)) + return ab +} diff --git a/examples/gno.land/p/demo/json/builder_test.gno b/examples/gno.land/p/demo/json/builder_test.gno new file mode 100644 index 00000000000..4c882d0d6c8 --- /dev/null +++ b/examples/gno.land/p/demo/json/builder_test.gno @@ -0,0 +1,103 @@ +package json + +import ( + "testing" +) + +func TestNodeBuilder(t *testing.T) { + tests := []struct { + name string + build func() *Node + expected string + }{ + { + name: "plain object", + build: func() *Node { + return Builder(). + WriteString("name", "Alice"). + WriteNumber("age", 30). + WriteBool("is_student", false). + Node() + }, + expected: `{"name":"Alice","age":30,"is_student":false}`, + }, + { + name: "nested object", + build: func() *Node { + return Builder(). + WriteString("name", "Alice"). + WriteObject("address", func(b *NodeBuilder) { + b.WriteString("city", "New York"). + WriteNumber("zipcode", 10001) + }). + Node() + }, + expected: `{"name":"Alice","address":{"city":"New York","zipcode":10001}}`, + }, + { + name: "null node", + build: func() *Node { + return Builder().WriteNull("foo").Node() + }, + expected: `{"foo":null}`, + }, + { + name: "array node", + build: func() *Node { + return Builder(). + WriteArray("items", func(ab *ArrayBuilder) { + ab.WriteString("item1"). + WriteString("item2"). + WriteString("item3") + }). + Node() + }, + expected: `{"items":["item1","item2","item3"]}`, + }, + { + name: "array with objects", + build: func() *Node { + return Builder(). + WriteArray("users", func(ab *ArrayBuilder) { + ab.WriteObject(func(b *NodeBuilder) { + b.WriteString("name", "Bob"). + WriteNumber("age", 25) + }). + WriteObject(func(b *NodeBuilder) { + b.WriteString("name", "Carol"). + WriteNumber("age", 27) + }) + }). + Node() + }, + expected: `{"users":[{"name":"Bob","age":25},{"name":"Carol","age":27}]}`, + }, + { + name: "array with various types", + build: func() *Node { + return Builder(). + WriteArray("values", func(ab *ArrayBuilder) { + ab.WriteString("item1"). + WriteNumber(123). + WriteBool(true). + WriteNull() + }). + Node() + }, + expected: `{"values":["item1",123,true,null]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + node := tt.build() + value, err := Marshal(node) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if string(value) != tt.expected { + t.Errorf("expected %s, got %s", tt.expected, string(value)) + } + }) + } +} diff --git a/examples/gno.land/p/demo/json/decode_test.gno b/examples/gno.land/p/demo/json/decode_test.gno index 8aad07169f2..dc92f1f84cd 100644 --- a/examples/gno.land/p/demo/json/decode_test.gno +++ b/examples/gno.land/p/demo/json/decode_test.gno @@ -8,8 +8,8 @@ import ( type testNode struct { name string input []byte - _type ValueType value []byte + _type ValueType } func simpleValid(test *testNode, t *testing.T) { diff --git a/examples/gno.land/p/demo/json/eisel_lemire/eisel_lemire.gno b/examples/gno.land/p/demo/json/eisel_lemire/eisel_lemire.gno deleted file mode 100644 index 6a29f7f1350..00000000000 --- a/examples/gno.land/p/demo/json/eisel_lemire/eisel_lemire.gno +++ /dev/null @@ -1,839 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package eisel_lemire - -// This file implements the Eisel-Lemire ParseFloat algorithm, published in -// 2020 and discussed extensively at -// https://nigeltao.github.io/blog/2020/eisel-lemire.html -// -// The original C++ implementation is at -// https://github.com/lemire/fast_double_parser/blob/644bef4306059d3be01a04e77d3cc84b379c596f/include/fast_double_parser.h#L840 -// -// This Go re-implementation closely follows the C re-implementation at -// https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/internal/cgen/base/floatconv-submodule-code.c#L990 -// -// Additional testing (on over several million test strings) is done by -// https://github.com/nigeltao/parse-number-fxx-test-data/blob/5280dcfccf6d0b02a65ae282dad0b6d9de50e039/script/test-go-strconv.go - -import ( - "math" - "math/bits" -) - -const ( - float32ExponentBias = 127 - float64ExponentBias = 1023 -) - -// eiselLemire64 parses a floating-point number from its mantissa and exponent representation. -// This implementation is based on the Eisel-Lemire ParseFloat algorithm, which is efficient -// and precise for converting strings to floating-point numbers. -// -// Arguments: -// man (uint64): The mantissa part of the floating-point number. -// exp10 (int): The exponent part, representing the power of 10. -// neg (bool): Indicates if the number is negative. -// -// Returns: -// f (float64): The parsed floating-point number. -// ok (bool): Indicates whether the parsing was successful. -// -// The function starts by handling special cases, such as zero mantissa. -// It then checks if the exponent is within the allowed range. -// After that, it normalizes the mantissa by left-shifting it to fill -// the leading zeros. This is followed by the main algorithm logic that -// converts the normalized mantissa and exponent into a 64-bit floating-point number. -// The function returns this number along with a boolean indicating the success of the operation. -func EiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) { - // The terse comments in this function body refer to sections of the - // https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post. - - // Exp10 Range. - if man == 0 { - if neg { - f = math.Float64frombits(0x80000000_00000000) // Negative zero. - } - - return f, true - } - - if exp10 < detailedPowersOfTenMinExp10 || detailedPowersOfTenMaxExp10 < exp10 { - return 0, false - } - - // Normalization. - clz := bits.LeadingZeros64(man) - man <<= uint(clz) - retExp2 := uint64(217706*exp10>>16+64+float64ExponentBias) - uint64(clz) - - // Multiplication. - xHi, xLo := bits.Mul64(man, detailedPowersOfTen[exp10-detailedPowersOfTenMinExp10][1]) - - // Wider Approximation. - if xHi&0x1FF == 0x1FF && xLo+man < man { - yHi, yLo := bits.Mul64(man, detailedPowersOfTen[exp10-detailedPowersOfTenMinExp10][0]) - mergedHi, mergedLo := xHi, xLo+yHi - if mergedLo < xLo { - mergedHi++ - } - - if mergedHi&0x1FF == 0x1FF && mergedLo+1 == 0 && yLo+man < man { - return 0, false - } - - xHi, xLo = mergedHi, mergedLo - } - - // Shifting to 54 Bits. - msb := xHi >> 63 - retMantissa := xHi >> (msb + 9) - retExp2 -= 1 ^ msb - - // Half-way Ambiguity. - if xLo == 0 && xHi&0x1FF == 0 && retMantissa&3 == 1 { - return 0, false - } - - // From 54 to 53 Bits. - retMantissa += retMantissa & 1 - retMantissa >>= 1 - if retMantissa>>53 > 0 { - retMantissa >>= 1 - retExp2 += 1 - } - - // retExp2 is a uint64. Zero or underflow means that we're in subnormal - // float64 space. 0x7FF or above means that we're in Inf/NaN float64 space. - // - // The if block is equivalent to (but has fewer branches than): - // if retExp2 <= 0 || retExp2 >= 0x7FF { etc } - if retExp2-1 >= 0x7FF-1 { - return 0, false - } - - retBits := retExp2<<52 | retMantissa&0x000FFFFF_FFFFFFFF - if neg { - retBits |= 0x80000000_00000000 - } - - return math.Float64frombits(retBits), true -} - -// detailedPowersOfTen{Min,Max}Exp10 is the power of 10 represented by the -// first and last rows of detailedPowersOfTen. Both bounds are inclusive. -const ( - detailedPowersOfTenMinExp10 = -348 - detailedPowersOfTenMaxExp10 = +347 -) - -// detailedPowersOfTen contains 128-bit mantissa approximations (rounded down) -// to the powers of 10. For example: -// -// - 1e43 ≈ (0xE596B7B0_C643C719 * (2 ** 79)) -// - 1e43 = (0xE596B7B0_C643C719_6D9CCD05_D0000000 * (2 ** 15)) -// -// The mantissas are explicitly listed. The exponents are implied by a linear -// expression with slope 217706.0/65536.0 ≈ log(10)/log(2). -// -// The table was generated by -// https://github.com/google/wuffs/blob/ba3818cb6b473a2ed0b38ecfc07dbbd3a97e8ae7/script/print-mpb-powers-of-10.go -var detailedPowersOfTen = [...][2]uint64{ - {0x1732C869CD60E453, 0xFA8FD5A0081C0288}, // 1e-348 - {0x0E7FBD42205C8EB4, 0x9C99E58405118195}, // 1e-347 - {0x521FAC92A873B261, 0xC3C05EE50655E1FA}, // 1e-346 - {0xE6A797B752909EF9, 0xF4B0769E47EB5A78}, // 1e-345 - {0x9028BED2939A635C, 0x98EE4A22ECF3188B}, // 1e-344 - {0x7432EE873880FC33, 0xBF29DCABA82FDEAE}, // 1e-343 - {0x113FAA2906A13B3F, 0xEEF453D6923BD65A}, // 1e-342 - {0x4AC7CA59A424C507, 0x9558B4661B6565F8}, // 1e-341 - {0x5D79BCF00D2DF649, 0xBAAEE17FA23EBF76}, // 1e-340 - {0xF4D82C2C107973DC, 0xE95A99DF8ACE6F53}, // 1e-339 - {0x79071B9B8A4BE869, 0x91D8A02BB6C10594}, // 1e-338 - {0x9748E2826CDEE284, 0xB64EC836A47146F9}, // 1e-337 - {0xFD1B1B2308169B25, 0xE3E27A444D8D98B7}, // 1e-336 - {0xFE30F0F5E50E20F7, 0x8E6D8C6AB0787F72}, // 1e-335 - {0xBDBD2D335E51A935, 0xB208EF855C969F4F}, // 1e-334 - {0xAD2C788035E61382, 0xDE8B2B66B3BC4723}, // 1e-333 - {0x4C3BCB5021AFCC31, 0x8B16FB203055AC76}, // 1e-332 - {0xDF4ABE242A1BBF3D, 0xADDCB9E83C6B1793}, // 1e-331 - {0xD71D6DAD34A2AF0D, 0xD953E8624B85DD78}, // 1e-330 - {0x8672648C40E5AD68, 0x87D4713D6F33AA6B}, // 1e-329 - {0x680EFDAF511F18C2, 0xA9C98D8CCB009506}, // 1e-328 - {0x0212BD1B2566DEF2, 0xD43BF0EFFDC0BA48}, // 1e-327 - {0x014BB630F7604B57, 0x84A57695FE98746D}, // 1e-326 - {0x419EA3BD35385E2D, 0xA5CED43B7E3E9188}, // 1e-325 - {0x52064CAC828675B9, 0xCF42894A5DCE35EA}, // 1e-324 - {0x7343EFEBD1940993, 0x818995CE7AA0E1B2}, // 1e-323 - {0x1014EBE6C5F90BF8, 0xA1EBFB4219491A1F}, // 1e-322 - {0xD41A26E077774EF6, 0xCA66FA129F9B60A6}, // 1e-321 - {0x8920B098955522B4, 0xFD00B897478238D0}, // 1e-320 - {0x55B46E5F5D5535B0, 0x9E20735E8CB16382}, // 1e-319 - {0xEB2189F734AA831D, 0xC5A890362FDDBC62}, // 1e-318 - {0xA5E9EC7501D523E4, 0xF712B443BBD52B7B}, // 1e-317 - {0x47B233C92125366E, 0x9A6BB0AA55653B2D}, // 1e-316 - {0x999EC0BB696E840A, 0xC1069CD4EABE89F8}, // 1e-315 - {0xC00670EA43CA250D, 0xF148440A256E2C76}, // 1e-314 - {0x380406926A5E5728, 0x96CD2A865764DBCA}, // 1e-313 - {0xC605083704F5ECF2, 0xBC807527ED3E12BC}, // 1e-312 - {0xF7864A44C633682E, 0xEBA09271E88D976B}, // 1e-311 - {0x7AB3EE6AFBE0211D, 0x93445B8731587EA3}, // 1e-310 - {0x5960EA05BAD82964, 0xB8157268FDAE9E4C}, // 1e-309 - {0x6FB92487298E33BD, 0xE61ACF033D1A45DF}, // 1e-308 - {0xA5D3B6D479F8E056, 0x8FD0C16206306BAB}, // 1e-307 - {0x8F48A4899877186C, 0xB3C4F1BA87BC8696}, // 1e-306 - {0x331ACDABFE94DE87, 0xE0B62E2929ABA83C}, // 1e-305 - {0x9FF0C08B7F1D0B14, 0x8C71DCD9BA0B4925}, // 1e-304 - {0x07ECF0AE5EE44DD9, 0xAF8E5410288E1B6F}, // 1e-303 - {0xC9E82CD9F69D6150, 0xDB71E91432B1A24A}, // 1e-302 - {0xBE311C083A225CD2, 0x892731AC9FAF056E}, // 1e-301 - {0x6DBD630A48AAF406, 0xAB70FE17C79AC6CA}, // 1e-300 - {0x092CBBCCDAD5B108, 0xD64D3D9DB981787D}, // 1e-299 - {0x25BBF56008C58EA5, 0x85F0468293F0EB4E}, // 1e-298 - {0xAF2AF2B80AF6F24E, 0xA76C582338ED2621}, // 1e-297 - {0x1AF5AF660DB4AEE1, 0xD1476E2C07286FAA}, // 1e-296 - {0x50D98D9FC890ED4D, 0x82CCA4DB847945CA}, // 1e-295 - {0xE50FF107BAB528A0, 0xA37FCE126597973C}, // 1e-294 - {0x1E53ED49A96272C8, 0xCC5FC196FEFD7D0C}, // 1e-293 - {0x25E8E89C13BB0F7A, 0xFF77B1FCBEBCDC4F}, // 1e-292 - {0x77B191618C54E9AC, 0x9FAACF3DF73609B1}, // 1e-291 - {0xD59DF5B9EF6A2417, 0xC795830D75038C1D}, // 1e-290 - {0x4B0573286B44AD1D, 0xF97AE3D0D2446F25}, // 1e-289 - {0x4EE367F9430AEC32, 0x9BECCE62836AC577}, // 1e-288 - {0x229C41F793CDA73F, 0xC2E801FB244576D5}, // 1e-287 - {0x6B43527578C1110F, 0xF3A20279ED56D48A}, // 1e-286 - {0x830A13896B78AAA9, 0x9845418C345644D6}, // 1e-285 - {0x23CC986BC656D553, 0xBE5691EF416BD60C}, // 1e-284 - {0x2CBFBE86B7EC8AA8, 0xEDEC366B11C6CB8F}, // 1e-283 - {0x7BF7D71432F3D6A9, 0x94B3A202EB1C3F39}, // 1e-282 - {0xDAF5CCD93FB0CC53, 0xB9E08A83A5E34F07}, // 1e-281 - {0xD1B3400F8F9CFF68, 0xE858AD248F5C22C9}, // 1e-280 - {0x23100809B9C21FA1, 0x91376C36D99995BE}, // 1e-279 - {0xABD40A0C2832A78A, 0xB58547448FFFFB2D}, // 1e-278 - {0x16C90C8F323F516C, 0xE2E69915B3FFF9F9}, // 1e-277 - {0xAE3DA7D97F6792E3, 0x8DD01FAD907FFC3B}, // 1e-276 - {0x99CD11CFDF41779C, 0xB1442798F49FFB4A}, // 1e-275 - {0x40405643D711D583, 0xDD95317F31C7FA1D}, // 1e-274 - {0x482835EA666B2572, 0x8A7D3EEF7F1CFC52}, // 1e-273 - {0xDA3243650005EECF, 0xAD1C8EAB5EE43B66}, // 1e-272 - {0x90BED43E40076A82, 0xD863B256369D4A40}, // 1e-271 - {0x5A7744A6E804A291, 0x873E4F75E2224E68}, // 1e-270 - {0x711515D0A205CB36, 0xA90DE3535AAAE202}, // 1e-269 - {0x0D5A5B44CA873E03, 0xD3515C2831559A83}, // 1e-268 - {0xE858790AFE9486C2, 0x8412D9991ED58091}, // 1e-267 - {0x626E974DBE39A872, 0xA5178FFF668AE0B6}, // 1e-266 - {0xFB0A3D212DC8128F, 0xCE5D73FF402D98E3}, // 1e-265 - {0x7CE66634BC9D0B99, 0x80FA687F881C7F8E}, // 1e-264 - {0x1C1FFFC1EBC44E80, 0xA139029F6A239F72}, // 1e-263 - {0xA327FFB266B56220, 0xC987434744AC874E}, // 1e-262 - {0x4BF1FF9F0062BAA8, 0xFBE9141915D7A922}, // 1e-261 - {0x6F773FC3603DB4A9, 0x9D71AC8FADA6C9B5}, // 1e-260 - {0xCB550FB4384D21D3, 0xC4CE17B399107C22}, // 1e-259 - {0x7E2A53A146606A48, 0xF6019DA07F549B2B}, // 1e-258 - {0x2EDA7444CBFC426D, 0x99C102844F94E0FB}, // 1e-257 - {0xFA911155FEFB5308, 0xC0314325637A1939}, // 1e-256 - {0x793555AB7EBA27CA, 0xF03D93EEBC589F88}, // 1e-255 - {0x4BC1558B2F3458DE, 0x96267C7535B763B5}, // 1e-254 - {0x9EB1AAEDFB016F16, 0xBBB01B9283253CA2}, // 1e-253 - {0x465E15A979C1CADC, 0xEA9C227723EE8BCB}, // 1e-252 - {0x0BFACD89EC191EC9, 0x92A1958A7675175F}, // 1e-251 - {0xCEF980EC671F667B, 0xB749FAED14125D36}, // 1e-250 - {0x82B7E12780E7401A, 0xE51C79A85916F484}, // 1e-249 - {0xD1B2ECB8B0908810, 0x8F31CC0937AE58D2}, // 1e-248 - {0x861FA7E6DCB4AA15, 0xB2FE3F0B8599EF07}, // 1e-247 - {0x67A791E093E1D49A, 0xDFBDCECE67006AC9}, // 1e-246 - {0xE0C8BB2C5C6D24E0, 0x8BD6A141006042BD}, // 1e-245 - {0x58FAE9F773886E18, 0xAECC49914078536D}, // 1e-244 - {0xAF39A475506A899E, 0xDA7F5BF590966848}, // 1e-243 - {0x6D8406C952429603, 0x888F99797A5E012D}, // 1e-242 - {0xC8E5087BA6D33B83, 0xAAB37FD7D8F58178}, // 1e-241 - {0xFB1E4A9A90880A64, 0xD5605FCDCF32E1D6}, // 1e-240 - {0x5CF2EEA09A55067F, 0x855C3BE0A17FCD26}, // 1e-239 - {0xF42FAA48C0EA481E, 0xA6B34AD8C9DFC06F}, // 1e-238 - {0xF13B94DAF124DA26, 0xD0601D8EFC57B08B}, // 1e-237 - {0x76C53D08D6B70858, 0x823C12795DB6CE57}, // 1e-236 - {0x54768C4B0C64CA6E, 0xA2CB1717B52481ED}, // 1e-235 - {0xA9942F5DCF7DFD09, 0xCB7DDCDDA26DA268}, // 1e-234 - {0xD3F93B35435D7C4C, 0xFE5D54150B090B02}, // 1e-233 - {0xC47BC5014A1A6DAF, 0x9EFA548D26E5A6E1}, // 1e-232 - {0x359AB6419CA1091B, 0xC6B8E9B0709F109A}, // 1e-231 - {0xC30163D203C94B62, 0xF867241C8CC6D4C0}, // 1e-230 - {0x79E0DE63425DCF1D, 0x9B407691D7FC44F8}, // 1e-229 - {0x985915FC12F542E4, 0xC21094364DFB5636}, // 1e-228 - {0x3E6F5B7B17B2939D, 0xF294B943E17A2BC4}, // 1e-227 - {0xA705992CEECF9C42, 0x979CF3CA6CEC5B5A}, // 1e-226 - {0x50C6FF782A838353, 0xBD8430BD08277231}, // 1e-225 - {0xA4F8BF5635246428, 0xECE53CEC4A314EBD}, // 1e-224 - {0x871B7795E136BE99, 0x940F4613AE5ED136}, // 1e-223 - {0x28E2557B59846E3F, 0xB913179899F68584}, // 1e-222 - {0x331AEADA2FE589CF, 0xE757DD7EC07426E5}, // 1e-221 - {0x3FF0D2C85DEF7621, 0x9096EA6F3848984F}, // 1e-220 - {0x0FED077A756B53A9, 0xB4BCA50B065ABE63}, // 1e-219 - {0xD3E8495912C62894, 0xE1EBCE4DC7F16DFB}, // 1e-218 - {0x64712DD7ABBBD95C, 0x8D3360F09CF6E4BD}, // 1e-217 - {0xBD8D794D96AACFB3, 0xB080392CC4349DEC}, // 1e-216 - {0xECF0D7A0FC5583A0, 0xDCA04777F541C567}, // 1e-215 - {0xF41686C49DB57244, 0x89E42CAAF9491B60}, // 1e-214 - {0x311C2875C522CED5, 0xAC5D37D5B79B6239}, // 1e-213 - {0x7D633293366B828B, 0xD77485CB25823AC7}, // 1e-212 - {0xAE5DFF9C02033197, 0x86A8D39EF77164BC}, // 1e-211 - {0xD9F57F830283FDFC, 0xA8530886B54DBDEB}, // 1e-210 - {0xD072DF63C324FD7B, 0xD267CAA862A12D66}, // 1e-209 - {0x4247CB9E59F71E6D, 0x8380DEA93DA4BC60}, // 1e-208 - {0x52D9BE85F074E608, 0xA46116538D0DEB78}, // 1e-207 - {0x67902E276C921F8B, 0xCD795BE870516656}, // 1e-206 - {0x00BA1CD8A3DB53B6, 0x806BD9714632DFF6}, // 1e-205 - {0x80E8A40ECCD228A4, 0xA086CFCD97BF97F3}, // 1e-204 - {0x6122CD128006B2CD, 0xC8A883C0FDAF7DF0}, // 1e-203 - {0x796B805720085F81, 0xFAD2A4B13D1B5D6C}, // 1e-202 - {0xCBE3303674053BB0, 0x9CC3A6EEC6311A63}, // 1e-201 - {0xBEDBFC4411068A9C, 0xC3F490AA77BD60FC}, // 1e-200 - {0xEE92FB5515482D44, 0xF4F1B4D515ACB93B}, // 1e-199 - {0x751BDD152D4D1C4A, 0x991711052D8BF3C5}, // 1e-198 - {0xD262D45A78A0635D, 0xBF5CD54678EEF0B6}, // 1e-197 - {0x86FB897116C87C34, 0xEF340A98172AACE4}, // 1e-196 - {0xD45D35E6AE3D4DA0, 0x9580869F0E7AAC0E}, // 1e-195 - {0x8974836059CCA109, 0xBAE0A846D2195712}, // 1e-194 - {0x2BD1A438703FC94B, 0xE998D258869FACD7}, // 1e-193 - {0x7B6306A34627DDCF, 0x91FF83775423CC06}, // 1e-192 - {0x1A3BC84C17B1D542, 0xB67F6455292CBF08}, // 1e-191 - {0x20CABA5F1D9E4A93, 0xE41F3D6A7377EECA}, // 1e-190 - {0x547EB47B7282EE9C, 0x8E938662882AF53E}, // 1e-189 - {0xE99E619A4F23AA43, 0xB23867FB2A35B28D}, // 1e-188 - {0x6405FA00E2EC94D4, 0xDEC681F9F4C31F31}, // 1e-187 - {0xDE83BC408DD3DD04, 0x8B3C113C38F9F37E}, // 1e-186 - {0x9624AB50B148D445, 0xAE0B158B4738705E}, // 1e-185 - {0x3BADD624DD9B0957, 0xD98DDAEE19068C76}, // 1e-184 - {0xE54CA5D70A80E5D6, 0x87F8A8D4CFA417C9}, // 1e-183 - {0x5E9FCF4CCD211F4C, 0xA9F6D30A038D1DBC}, // 1e-182 - {0x7647C3200069671F, 0xD47487CC8470652B}, // 1e-181 - {0x29ECD9F40041E073, 0x84C8D4DFD2C63F3B}, // 1e-180 - {0xF468107100525890, 0xA5FB0A17C777CF09}, // 1e-179 - {0x7182148D4066EEB4, 0xCF79CC9DB955C2CC}, // 1e-178 - {0xC6F14CD848405530, 0x81AC1FE293D599BF}, // 1e-177 - {0xB8ADA00E5A506A7C, 0xA21727DB38CB002F}, // 1e-176 - {0xA6D90811F0E4851C, 0xCA9CF1D206FDC03B}, // 1e-175 - {0x908F4A166D1DA663, 0xFD442E4688BD304A}, // 1e-174 - {0x9A598E4E043287FE, 0x9E4A9CEC15763E2E}, // 1e-173 - {0x40EFF1E1853F29FD, 0xC5DD44271AD3CDBA}, // 1e-172 - {0xD12BEE59E68EF47C, 0xF7549530E188C128}, // 1e-171 - {0x82BB74F8301958CE, 0x9A94DD3E8CF578B9}, // 1e-170 - {0xE36A52363C1FAF01, 0xC13A148E3032D6E7}, // 1e-169 - {0xDC44E6C3CB279AC1, 0xF18899B1BC3F8CA1}, // 1e-168 - {0x29AB103A5EF8C0B9, 0x96F5600F15A7B7E5}, // 1e-167 - {0x7415D448F6B6F0E7, 0xBCB2B812DB11A5DE}, // 1e-166 - {0x111B495B3464AD21, 0xEBDF661791D60F56}, // 1e-165 - {0xCAB10DD900BEEC34, 0x936B9FCEBB25C995}, // 1e-164 - {0x3D5D514F40EEA742, 0xB84687C269EF3BFB}, // 1e-163 - {0x0CB4A5A3112A5112, 0xE65829B3046B0AFA}, // 1e-162 - {0x47F0E785EABA72AB, 0x8FF71A0FE2C2E6DC}, // 1e-161 - {0x59ED216765690F56, 0xB3F4E093DB73A093}, // 1e-160 - {0x306869C13EC3532C, 0xE0F218B8D25088B8}, // 1e-159 - {0x1E414218C73A13FB, 0x8C974F7383725573}, // 1e-158 - {0xE5D1929EF90898FA, 0xAFBD2350644EEACF}, // 1e-157 - {0xDF45F746B74ABF39, 0xDBAC6C247D62A583}, // 1e-156 - {0x6B8BBA8C328EB783, 0x894BC396CE5DA772}, // 1e-155 - {0x066EA92F3F326564, 0xAB9EB47C81F5114F}, // 1e-154 - {0xC80A537B0EFEFEBD, 0xD686619BA27255A2}, // 1e-153 - {0xBD06742CE95F5F36, 0x8613FD0145877585}, // 1e-152 - {0x2C48113823B73704, 0xA798FC4196E952E7}, // 1e-151 - {0xF75A15862CA504C5, 0xD17F3B51FCA3A7A0}, // 1e-150 - {0x9A984D73DBE722FB, 0x82EF85133DE648C4}, // 1e-149 - {0xC13E60D0D2E0EBBA, 0xA3AB66580D5FDAF5}, // 1e-148 - {0x318DF905079926A8, 0xCC963FEE10B7D1B3}, // 1e-147 - {0xFDF17746497F7052, 0xFFBBCFE994E5C61F}, // 1e-146 - {0xFEB6EA8BEDEFA633, 0x9FD561F1FD0F9BD3}, // 1e-145 - {0xFE64A52EE96B8FC0, 0xC7CABA6E7C5382C8}, // 1e-144 - {0x3DFDCE7AA3C673B0, 0xF9BD690A1B68637B}, // 1e-143 - {0x06BEA10CA65C084E, 0x9C1661A651213E2D}, // 1e-142 - {0x486E494FCFF30A62, 0xC31BFA0FE5698DB8}, // 1e-141 - {0x5A89DBA3C3EFCCFA, 0xF3E2F893DEC3F126}, // 1e-140 - {0xF89629465A75E01C, 0x986DDB5C6B3A76B7}, // 1e-139 - {0xF6BBB397F1135823, 0xBE89523386091465}, // 1e-138 - {0x746AA07DED582E2C, 0xEE2BA6C0678B597F}, // 1e-137 - {0xA8C2A44EB4571CDC, 0x94DB483840B717EF}, // 1e-136 - {0x92F34D62616CE413, 0xBA121A4650E4DDEB}, // 1e-135 - {0x77B020BAF9C81D17, 0xE896A0D7E51E1566}, // 1e-134 - {0x0ACE1474DC1D122E, 0x915E2486EF32CD60}, // 1e-133 - {0x0D819992132456BA, 0xB5B5ADA8AAFF80B8}, // 1e-132 - {0x10E1FFF697ED6C69, 0xE3231912D5BF60E6}, // 1e-131 - {0xCA8D3FFA1EF463C1, 0x8DF5EFABC5979C8F}, // 1e-130 - {0xBD308FF8A6B17CB2, 0xB1736B96B6FD83B3}, // 1e-129 - {0xAC7CB3F6D05DDBDE, 0xDDD0467C64BCE4A0}, // 1e-128 - {0x6BCDF07A423AA96B, 0x8AA22C0DBEF60EE4}, // 1e-127 - {0x86C16C98D2C953C6, 0xAD4AB7112EB3929D}, // 1e-126 - {0xE871C7BF077BA8B7, 0xD89D64D57A607744}, // 1e-125 - {0x11471CD764AD4972, 0x87625F056C7C4A8B}, // 1e-124 - {0xD598E40D3DD89BCF, 0xA93AF6C6C79B5D2D}, // 1e-123 - {0x4AFF1D108D4EC2C3, 0xD389B47879823479}, // 1e-122 - {0xCEDF722A585139BA, 0x843610CB4BF160CB}, // 1e-121 - {0xC2974EB4EE658828, 0xA54394FE1EEDB8FE}, // 1e-120 - {0x733D226229FEEA32, 0xCE947A3DA6A9273E}, // 1e-119 - {0x0806357D5A3F525F, 0x811CCC668829B887}, // 1e-118 - {0xCA07C2DCB0CF26F7, 0xA163FF802A3426A8}, // 1e-117 - {0xFC89B393DD02F0B5, 0xC9BCFF6034C13052}, // 1e-116 - {0xBBAC2078D443ACE2, 0xFC2C3F3841F17C67}, // 1e-115 - {0xD54B944B84AA4C0D, 0x9D9BA7832936EDC0}, // 1e-114 - {0x0A9E795E65D4DF11, 0xC5029163F384A931}, // 1e-113 - {0x4D4617B5FF4A16D5, 0xF64335BCF065D37D}, // 1e-112 - {0x504BCED1BF8E4E45, 0x99EA0196163FA42E}, // 1e-111 - {0xE45EC2862F71E1D6, 0xC06481FB9BCF8D39}, // 1e-110 - {0x5D767327BB4E5A4C, 0xF07DA27A82C37088}, // 1e-109 - {0x3A6A07F8D510F86F, 0x964E858C91BA2655}, // 1e-108 - {0x890489F70A55368B, 0xBBE226EFB628AFEA}, // 1e-107 - {0x2B45AC74CCEA842E, 0xEADAB0ABA3B2DBE5}, // 1e-106 - {0x3B0B8BC90012929D, 0x92C8AE6B464FC96F}, // 1e-105 - {0x09CE6EBB40173744, 0xB77ADA0617E3BBCB}, // 1e-104 - {0xCC420A6A101D0515, 0xE55990879DDCAABD}, // 1e-103 - {0x9FA946824A12232D, 0x8F57FA54C2A9EAB6}, // 1e-102 - {0x47939822DC96ABF9, 0xB32DF8E9F3546564}, // 1e-101 - {0x59787E2B93BC56F7, 0xDFF9772470297EBD}, // 1e-100 - {0x57EB4EDB3C55B65A, 0x8BFBEA76C619EF36}, // 1e-99 - {0xEDE622920B6B23F1, 0xAEFAE51477A06B03}, // 1e-98 - {0xE95FAB368E45ECED, 0xDAB99E59958885C4}, // 1e-97 - {0x11DBCB0218EBB414, 0x88B402F7FD75539B}, // 1e-96 - {0xD652BDC29F26A119, 0xAAE103B5FCD2A881}, // 1e-95 - {0x4BE76D3346F0495F, 0xD59944A37C0752A2}, // 1e-94 - {0x6F70A4400C562DDB, 0x857FCAE62D8493A5}, // 1e-93 - {0xCB4CCD500F6BB952, 0xA6DFBD9FB8E5B88E}, // 1e-92 - {0x7E2000A41346A7A7, 0xD097AD07A71F26B2}, // 1e-91 - {0x8ED400668C0C28C8, 0x825ECC24C873782F}, // 1e-90 - {0x728900802F0F32FA, 0xA2F67F2DFA90563B}, // 1e-89 - {0x4F2B40A03AD2FFB9, 0xCBB41EF979346BCA}, // 1e-88 - {0xE2F610C84987BFA8, 0xFEA126B7D78186BC}, // 1e-87 - {0x0DD9CA7D2DF4D7C9, 0x9F24B832E6B0F436}, // 1e-86 - {0x91503D1C79720DBB, 0xC6EDE63FA05D3143}, // 1e-85 - {0x75A44C6397CE912A, 0xF8A95FCF88747D94}, // 1e-84 - {0xC986AFBE3EE11ABA, 0x9B69DBE1B548CE7C}, // 1e-83 - {0xFBE85BADCE996168, 0xC24452DA229B021B}, // 1e-82 - {0xFAE27299423FB9C3, 0xF2D56790AB41C2A2}, // 1e-81 - {0xDCCD879FC967D41A, 0x97C560BA6B0919A5}, // 1e-80 - {0x5400E987BBC1C920, 0xBDB6B8E905CB600F}, // 1e-79 - {0x290123E9AAB23B68, 0xED246723473E3813}, // 1e-78 - {0xF9A0B6720AAF6521, 0x9436C0760C86E30B}, // 1e-77 - {0xF808E40E8D5B3E69, 0xB94470938FA89BCE}, // 1e-76 - {0xB60B1D1230B20E04, 0xE7958CB87392C2C2}, // 1e-75 - {0xB1C6F22B5E6F48C2, 0x90BD77F3483BB9B9}, // 1e-74 - {0x1E38AEB6360B1AF3, 0xB4ECD5F01A4AA828}, // 1e-73 - {0x25C6DA63C38DE1B0, 0xE2280B6C20DD5232}, // 1e-72 - {0x579C487E5A38AD0E, 0x8D590723948A535F}, // 1e-71 - {0x2D835A9DF0C6D851, 0xB0AF48EC79ACE837}, // 1e-70 - {0xF8E431456CF88E65, 0xDCDB1B2798182244}, // 1e-69 - {0x1B8E9ECB641B58FF, 0x8A08F0F8BF0F156B}, // 1e-68 - {0xE272467E3D222F3F, 0xAC8B2D36EED2DAC5}, // 1e-67 - {0x5B0ED81DCC6ABB0F, 0xD7ADF884AA879177}, // 1e-66 - {0x98E947129FC2B4E9, 0x86CCBB52EA94BAEA}, // 1e-65 - {0x3F2398D747B36224, 0xA87FEA27A539E9A5}, // 1e-64 - {0x8EEC7F0D19A03AAD, 0xD29FE4B18E88640E}, // 1e-63 - {0x1953CF68300424AC, 0x83A3EEEEF9153E89}, // 1e-62 - {0x5FA8C3423C052DD7, 0xA48CEAAAB75A8E2B}, // 1e-61 - {0x3792F412CB06794D, 0xCDB02555653131B6}, // 1e-60 - {0xE2BBD88BBEE40BD0, 0x808E17555F3EBF11}, // 1e-59 - {0x5B6ACEAEAE9D0EC4, 0xA0B19D2AB70E6ED6}, // 1e-58 - {0xF245825A5A445275, 0xC8DE047564D20A8B}, // 1e-57 - {0xEED6E2F0F0D56712, 0xFB158592BE068D2E}, // 1e-56 - {0x55464DD69685606B, 0x9CED737BB6C4183D}, // 1e-55 - {0xAA97E14C3C26B886, 0xC428D05AA4751E4C}, // 1e-54 - {0xD53DD99F4B3066A8, 0xF53304714D9265DF}, // 1e-53 - {0xE546A8038EFE4029, 0x993FE2C6D07B7FAB}, // 1e-52 - {0xDE98520472BDD033, 0xBF8FDB78849A5F96}, // 1e-51 - {0x963E66858F6D4440, 0xEF73D256A5C0F77C}, // 1e-50 - {0xDDE7001379A44AA8, 0x95A8637627989AAD}, // 1e-49 - {0x5560C018580D5D52, 0xBB127C53B17EC159}, // 1e-48 - {0xAAB8F01E6E10B4A6, 0xE9D71B689DDE71AF}, // 1e-47 - {0xCAB3961304CA70E8, 0x9226712162AB070D}, // 1e-46 - {0x3D607B97C5FD0D22, 0xB6B00D69BB55C8D1}, // 1e-45 - {0x8CB89A7DB77C506A, 0xE45C10C42A2B3B05}, // 1e-44 - {0x77F3608E92ADB242, 0x8EB98A7A9A5B04E3}, // 1e-43 - {0x55F038B237591ED3, 0xB267ED1940F1C61C}, // 1e-42 - {0x6B6C46DEC52F6688, 0xDF01E85F912E37A3}, // 1e-41 - {0x2323AC4B3B3DA015, 0x8B61313BBABCE2C6}, // 1e-40 - {0xABEC975E0A0D081A, 0xAE397D8AA96C1B77}, // 1e-39 - {0x96E7BD358C904A21, 0xD9C7DCED53C72255}, // 1e-38 - {0x7E50D64177DA2E54, 0x881CEA14545C7575}, // 1e-37 - {0xDDE50BD1D5D0B9E9, 0xAA242499697392D2}, // 1e-36 - {0x955E4EC64B44E864, 0xD4AD2DBFC3D07787}, // 1e-35 - {0xBD5AF13BEF0B113E, 0x84EC3C97DA624AB4}, // 1e-34 - {0xECB1AD8AEACDD58E, 0xA6274BBDD0FADD61}, // 1e-33 - {0x67DE18EDA5814AF2, 0xCFB11EAD453994BA}, // 1e-32 - {0x80EACF948770CED7, 0x81CEB32C4B43FCF4}, // 1e-31 - {0xA1258379A94D028D, 0xA2425FF75E14FC31}, // 1e-30 - {0x096EE45813A04330, 0xCAD2F7F5359A3B3E}, // 1e-29 - {0x8BCA9D6E188853FC, 0xFD87B5F28300CA0D}, // 1e-28 - {0x775EA264CF55347D, 0x9E74D1B791E07E48}, // 1e-27 - {0x95364AFE032A819D, 0xC612062576589DDA}, // 1e-26 - {0x3A83DDBD83F52204, 0xF79687AED3EEC551}, // 1e-25 - {0xC4926A9672793542, 0x9ABE14CD44753B52}, // 1e-24 - {0x75B7053C0F178293, 0xC16D9A0095928A27}, // 1e-23 - {0x5324C68B12DD6338, 0xF1C90080BAF72CB1}, // 1e-22 - {0xD3F6FC16EBCA5E03, 0x971DA05074DA7BEE}, // 1e-21 - {0x88F4BB1CA6BCF584, 0xBCE5086492111AEA}, // 1e-20 - {0x2B31E9E3D06C32E5, 0xEC1E4A7DB69561A5}, // 1e-19 - {0x3AFF322E62439FCF, 0x9392EE8E921D5D07}, // 1e-18 - {0x09BEFEB9FAD487C2, 0xB877AA3236A4B449}, // 1e-17 - {0x4C2EBE687989A9B3, 0xE69594BEC44DE15B}, // 1e-16 - {0x0F9D37014BF60A10, 0x901D7CF73AB0ACD9}, // 1e-15 - {0x538484C19EF38C94, 0xB424DC35095CD80F}, // 1e-14 - {0x2865A5F206B06FB9, 0xE12E13424BB40E13}, // 1e-13 - {0xF93F87B7442E45D3, 0x8CBCCC096F5088CB}, // 1e-12 - {0xF78F69A51539D748, 0xAFEBFF0BCB24AAFE}, // 1e-11 - {0xB573440E5A884D1B, 0xDBE6FECEBDEDD5BE}, // 1e-10 - {0x31680A88F8953030, 0x89705F4136B4A597}, // 1e-9 - {0xFDC20D2B36BA7C3D, 0xABCC77118461CEFC}, // 1e-8 - {0x3D32907604691B4C, 0xD6BF94D5E57A42BC}, // 1e-7 - {0xA63F9A49C2C1B10F, 0x8637BD05AF6C69B5}, // 1e-6 - {0x0FCF80DC33721D53, 0xA7C5AC471B478423}, // 1e-5 - {0xD3C36113404EA4A8, 0xD1B71758E219652B}, // 1e-4 - {0x645A1CAC083126E9, 0x83126E978D4FDF3B}, // 1e-3 - {0x3D70A3D70A3D70A3, 0xA3D70A3D70A3D70A}, // 1e-2 - {0xCCCCCCCCCCCCCCCC, 0xCCCCCCCCCCCCCCCC}, // 1e-1 - {0x0000000000000000, 0x8000000000000000}, // 1e0 - {0x0000000000000000, 0xA000000000000000}, // 1e1 - {0x0000000000000000, 0xC800000000000000}, // 1e2 - {0x0000000000000000, 0xFA00000000000000}, // 1e3 - {0x0000000000000000, 0x9C40000000000000}, // 1e4 - {0x0000000000000000, 0xC350000000000000}, // 1e5 - {0x0000000000000000, 0xF424000000000000}, // 1e6 - {0x0000000000000000, 0x9896800000000000}, // 1e7 - {0x0000000000000000, 0xBEBC200000000000}, // 1e8 - {0x0000000000000000, 0xEE6B280000000000}, // 1e9 - {0x0000000000000000, 0x9502F90000000000}, // 1e10 - {0x0000000000000000, 0xBA43B74000000000}, // 1e11 - {0x0000000000000000, 0xE8D4A51000000000}, // 1e12 - {0x0000000000000000, 0x9184E72A00000000}, // 1e13 - {0x0000000000000000, 0xB5E620F480000000}, // 1e14 - {0x0000000000000000, 0xE35FA931A0000000}, // 1e15 - {0x0000000000000000, 0x8E1BC9BF04000000}, // 1e16 - {0x0000000000000000, 0xB1A2BC2EC5000000}, // 1e17 - {0x0000000000000000, 0xDE0B6B3A76400000}, // 1e18 - {0x0000000000000000, 0x8AC7230489E80000}, // 1e19 - {0x0000000000000000, 0xAD78EBC5AC620000}, // 1e20 - {0x0000000000000000, 0xD8D726B7177A8000}, // 1e21 - {0x0000000000000000, 0x878678326EAC9000}, // 1e22 - {0x0000000000000000, 0xA968163F0A57B400}, // 1e23 - {0x0000000000000000, 0xD3C21BCECCEDA100}, // 1e24 - {0x0000000000000000, 0x84595161401484A0}, // 1e25 - {0x0000000000000000, 0xA56FA5B99019A5C8}, // 1e26 - {0x0000000000000000, 0xCECB8F27F4200F3A}, // 1e27 - {0x4000000000000000, 0x813F3978F8940984}, // 1e28 - {0x5000000000000000, 0xA18F07D736B90BE5}, // 1e29 - {0xA400000000000000, 0xC9F2C9CD04674EDE}, // 1e30 - {0x4D00000000000000, 0xFC6F7C4045812296}, // 1e31 - {0xF020000000000000, 0x9DC5ADA82B70B59D}, // 1e32 - {0x6C28000000000000, 0xC5371912364CE305}, // 1e33 - {0xC732000000000000, 0xF684DF56C3E01BC6}, // 1e34 - {0x3C7F400000000000, 0x9A130B963A6C115C}, // 1e35 - {0x4B9F100000000000, 0xC097CE7BC90715B3}, // 1e36 - {0x1E86D40000000000, 0xF0BDC21ABB48DB20}, // 1e37 - {0x1314448000000000, 0x96769950B50D88F4}, // 1e38 - {0x17D955A000000000, 0xBC143FA4E250EB31}, // 1e39 - {0x5DCFAB0800000000, 0xEB194F8E1AE525FD}, // 1e40 - {0x5AA1CAE500000000, 0x92EFD1B8D0CF37BE}, // 1e41 - {0xF14A3D9E40000000, 0xB7ABC627050305AD}, // 1e42 - {0x6D9CCD05D0000000, 0xE596B7B0C643C719}, // 1e43 - {0xE4820023A2000000, 0x8F7E32CE7BEA5C6F}, // 1e44 - {0xDDA2802C8A800000, 0xB35DBF821AE4F38B}, // 1e45 - {0xD50B2037AD200000, 0xE0352F62A19E306E}, // 1e46 - {0x4526F422CC340000, 0x8C213D9DA502DE45}, // 1e47 - {0x9670B12B7F410000, 0xAF298D050E4395D6}, // 1e48 - {0x3C0CDD765F114000, 0xDAF3F04651D47B4C}, // 1e49 - {0xA5880A69FB6AC800, 0x88D8762BF324CD0F}, // 1e50 - {0x8EEA0D047A457A00, 0xAB0E93B6EFEE0053}, // 1e51 - {0x72A4904598D6D880, 0xD5D238A4ABE98068}, // 1e52 - {0x47A6DA2B7F864750, 0x85A36366EB71F041}, // 1e53 - {0x999090B65F67D924, 0xA70C3C40A64E6C51}, // 1e54 - {0xFFF4B4E3F741CF6D, 0xD0CF4B50CFE20765}, // 1e55 - {0xBFF8F10E7A8921A4, 0x82818F1281ED449F}, // 1e56 - {0xAFF72D52192B6A0D, 0xA321F2D7226895C7}, // 1e57 - {0x9BF4F8A69F764490, 0xCBEA6F8CEB02BB39}, // 1e58 - {0x02F236D04753D5B4, 0xFEE50B7025C36A08}, // 1e59 - {0x01D762422C946590, 0x9F4F2726179A2245}, // 1e60 - {0x424D3AD2B7B97EF5, 0xC722F0EF9D80AAD6}, // 1e61 - {0xD2E0898765A7DEB2, 0xF8EBAD2B84E0D58B}, // 1e62 - {0x63CC55F49F88EB2F, 0x9B934C3B330C8577}, // 1e63 - {0x3CBF6B71C76B25FB, 0xC2781F49FFCFA6D5}, // 1e64 - {0x8BEF464E3945EF7A, 0xF316271C7FC3908A}, // 1e65 - {0x97758BF0E3CBB5AC, 0x97EDD871CFDA3A56}, // 1e66 - {0x3D52EEED1CBEA317, 0xBDE94E8E43D0C8EC}, // 1e67 - {0x4CA7AAA863EE4BDD, 0xED63A231D4C4FB27}, // 1e68 - {0x8FE8CAA93E74EF6A, 0x945E455F24FB1CF8}, // 1e69 - {0xB3E2FD538E122B44, 0xB975D6B6EE39E436}, // 1e70 - {0x60DBBCA87196B616, 0xE7D34C64A9C85D44}, // 1e71 - {0xBC8955E946FE31CD, 0x90E40FBEEA1D3A4A}, // 1e72 - {0x6BABAB6398BDBE41, 0xB51D13AEA4A488DD}, // 1e73 - {0xC696963C7EED2DD1, 0xE264589A4DCDAB14}, // 1e74 - {0xFC1E1DE5CF543CA2, 0x8D7EB76070A08AEC}, // 1e75 - {0x3B25A55F43294BCB, 0xB0DE65388CC8ADA8}, // 1e76 - {0x49EF0EB713F39EBE, 0xDD15FE86AFFAD912}, // 1e77 - {0x6E3569326C784337, 0x8A2DBF142DFCC7AB}, // 1e78 - {0x49C2C37F07965404, 0xACB92ED9397BF996}, // 1e79 - {0xDC33745EC97BE906, 0xD7E77A8F87DAF7FB}, // 1e80 - {0x69A028BB3DED71A3, 0x86F0AC99B4E8DAFD}, // 1e81 - {0xC40832EA0D68CE0C, 0xA8ACD7C0222311BC}, // 1e82 - {0xF50A3FA490C30190, 0xD2D80DB02AABD62B}, // 1e83 - {0x792667C6DA79E0FA, 0x83C7088E1AAB65DB}, // 1e84 - {0x577001B891185938, 0xA4B8CAB1A1563F52}, // 1e85 - {0xED4C0226B55E6F86, 0xCDE6FD5E09ABCF26}, // 1e86 - {0x544F8158315B05B4, 0x80B05E5AC60B6178}, // 1e87 - {0x696361AE3DB1C721, 0xA0DC75F1778E39D6}, // 1e88 - {0x03BC3A19CD1E38E9, 0xC913936DD571C84C}, // 1e89 - {0x04AB48A04065C723, 0xFB5878494ACE3A5F}, // 1e90 - {0x62EB0D64283F9C76, 0x9D174B2DCEC0E47B}, // 1e91 - {0x3BA5D0BD324F8394, 0xC45D1DF942711D9A}, // 1e92 - {0xCA8F44EC7EE36479, 0xF5746577930D6500}, // 1e93 - {0x7E998B13CF4E1ECB, 0x9968BF6ABBE85F20}, // 1e94 - {0x9E3FEDD8C321A67E, 0xBFC2EF456AE276E8}, // 1e95 - {0xC5CFE94EF3EA101E, 0xEFB3AB16C59B14A2}, // 1e96 - {0xBBA1F1D158724A12, 0x95D04AEE3B80ECE5}, // 1e97 - {0x2A8A6E45AE8EDC97, 0xBB445DA9CA61281F}, // 1e98 - {0xF52D09D71A3293BD, 0xEA1575143CF97226}, // 1e99 - {0x593C2626705F9C56, 0x924D692CA61BE758}, // 1e100 - {0x6F8B2FB00C77836C, 0xB6E0C377CFA2E12E}, // 1e101 - {0x0B6DFB9C0F956447, 0xE498F455C38B997A}, // 1e102 - {0x4724BD4189BD5EAC, 0x8EDF98B59A373FEC}, // 1e103 - {0x58EDEC91EC2CB657, 0xB2977EE300C50FE7}, // 1e104 - {0x2F2967B66737E3ED, 0xDF3D5E9BC0F653E1}, // 1e105 - {0xBD79E0D20082EE74, 0x8B865B215899F46C}, // 1e106 - {0xECD8590680A3AA11, 0xAE67F1E9AEC07187}, // 1e107 - {0xE80E6F4820CC9495, 0xDA01EE641A708DE9}, // 1e108 - {0x3109058D147FDCDD, 0x884134FE908658B2}, // 1e109 - {0xBD4B46F0599FD415, 0xAA51823E34A7EEDE}, // 1e110 - {0x6C9E18AC7007C91A, 0xD4E5E2CDC1D1EA96}, // 1e111 - {0x03E2CF6BC604DDB0, 0x850FADC09923329E}, // 1e112 - {0x84DB8346B786151C, 0xA6539930BF6BFF45}, // 1e113 - {0xE612641865679A63, 0xCFE87F7CEF46FF16}, // 1e114 - {0x4FCB7E8F3F60C07E, 0x81F14FAE158C5F6E}, // 1e115 - {0xE3BE5E330F38F09D, 0xA26DA3999AEF7749}, // 1e116 - {0x5CADF5BFD3072CC5, 0xCB090C8001AB551C}, // 1e117 - {0x73D9732FC7C8F7F6, 0xFDCB4FA002162A63}, // 1e118 - {0x2867E7FDDCDD9AFA, 0x9E9F11C4014DDA7E}, // 1e119 - {0xB281E1FD541501B8, 0xC646D63501A1511D}, // 1e120 - {0x1F225A7CA91A4226, 0xF7D88BC24209A565}, // 1e121 - {0x3375788DE9B06958, 0x9AE757596946075F}, // 1e122 - {0x0052D6B1641C83AE, 0xC1A12D2FC3978937}, // 1e123 - {0xC0678C5DBD23A49A, 0xF209787BB47D6B84}, // 1e124 - {0xF840B7BA963646E0, 0x9745EB4D50CE6332}, // 1e125 - {0xB650E5A93BC3D898, 0xBD176620A501FBFF}, // 1e126 - {0xA3E51F138AB4CEBE, 0xEC5D3FA8CE427AFF}, // 1e127 - {0xC66F336C36B10137, 0x93BA47C980E98CDF}, // 1e128 - {0xB80B0047445D4184, 0xB8A8D9BBE123F017}, // 1e129 - {0xA60DC059157491E5, 0xE6D3102AD96CEC1D}, // 1e130 - {0x87C89837AD68DB2F, 0x9043EA1AC7E41392}, // 1e131 - {0x29BABE4598C311FB, 0xB454E4A179DD1877}, // 1e132 - {0xF4296DD6FEF3D67A, 0xE16A1DC9D8545E94}, // 1e133 - {0x1899E4A65F58660C, 0x8CE2529E2734BB1D}, // 1e134 - {0x5EC05DCFF72E7F8F, 0xB01AE745B101E9E4}, // 1e135 - {0x76707543F4FA1F73, 0xDC21A1171D42645D}, // 1e136 - {0x6A06494A791C53A8, 0x899504AE72497EBA}, // 1e137 - {0x0487DB9D17636892, 0xABFA45DA0EDBDE69}, // 1e138 - {0x45A9D2845D3C42B6, 0xD6F8D7509292D603}, // 1e139 - {0x0B8A2392BA45A9B2, 0x865B86925B9BC5C2}, // 1e140 - {0x8E6CAC7768D7141E, 0xA7F26836F282B732}, // 1e141 - {0x3207D795430CD926, 0xD1EF0244AF2364FF}, // 1e142 - {0x7F44E6BD49E807B8, 0x8335616AED761F1F}, // 1e143 - {0x5F16206C9C6209A6, 0xA402B9C5A8D3A6E7}, // 1e144 - {0x36DBA887C37A8C0F, 0xCD036837130890A1}, // 1e145 - {0xC2494954DA2C9789, 0x802221226BE55A64}, // 1e146 - {0xF2DB9BAA10B7BD6C, 0xA02AA96B06DEB0FD}, // 1e147 - {0x6F92829494E5ACC7, 0xC83553C5C8965D3D}, // 1e148 - {0xCB772339BA1F17F9, 0xFA42A8B73ABBF48C}, // 1e149 - {0xFF2A760414536EFB, 0x9C69A97284B578D7}, // 1e150 - {0xFEF5138519684ABA, 0xC38413CF25E2D70D}, // 1e151 - {0x7EB258665FC25D69, 0xF46518C2EF5B8CD1}, // 1e152 - {0xEF2F773FFBD97A61, 0x98BF2F79D5993802}, // 1e153 - {0xAAFB550FFACFD8FA, 0xBEEEFB584AFF8603}, // 1e154 - {0x95BA2A53F983CF38, 0xEEAABA2E5DBF6784}, // 1e155 - {0xDD945A747BF26183, 0x952AB45CFA97A0B2}, // 1e156 - {0x94F971119AEEF9E4, 0xBA756174393D88DF}, // 1e157 - {0x7A37CD5601AAB85D, 0xE912B9D1478CEB17}, // 1e158 - {0xAC62E055C10AB33A, 0x91ABB422CCB812EE}, // 1e159 - {0x577B986B314D6009, 0xB616A12B7FE617AA}, // 1e160 - {0xED5A7E85FDA0B80B, 0xE39C49765FDF9D94}, // 1e161 - {0x14588F13BE847307, 0x8E41ADE9FBEBC27D}, // 1e162 - {0x596EB2D8AE258FC8, 0xB1D219647AE6B31C}, // 1e163 - {0x6FCA5F8ED9AEF3BB, 0xDE469FBD99A05FE3}, // 1e164 - {0x25DE7BB9480D5854, 0x8AEC23D680043BEE}, // 1e165 - {0xAF561AA79A10AE6A, 0xADA72CCC20054AE9}, // 1e166 - {0x1B2BA1518094DA04, 0xD910F7FF28069DA4}, // 1e167 - {0x90FB44D2F05D0842, 0x87AA9AFF79042286}, // 1e168 - {0x353A1607AC744A53, 0xA99541BF57452B28}, // 1e169 - {0x42889B8997915CE8, 0xD3FA922F2D1675F2}, // 1e170 - {0x69956135FEBADA11, 0x847C9B5D7C2E09B7}, // 1e171 - {0x43FAB9837E699095, 0xA59BC234DB398C25}, // 1e172 - {0x94F967E45E03F4BB, 0xCF02B2C21207EF2E}, // 1e173 - {0x1D1BE0EEBAC278F5, 0x8161AFB94B44F57D}, // 1e174 - {0x6462D92A69731732, 0xA1BA1BA79E1632DC}, // 1e175 - {0x7D7B8F7503CFDCFE, 0xCA28A291859BBF93}, // 1e176 - {0x5CDA735244C3D43E, 0xFCB2CB35E702AF78}, // 1e177 - {0x3A0888136AFA64A7, 0x9DEFBF01B061ADAB}, // 1e178 - {0x088AAA1845B8FDD0, 0xC56BAEC21C7A1916}, // 1e179 - {0x8AAD549E57273D45, 0xF6C69A72A3989F5B}, // 1e180 - {0x36AC54E2F678864B, 0x9A3C2087A63F6399}, // 1e181 - {0x84576A1BB416A7DD, 0xC0CB28A98FCF3C7F}, // 1e182 - {0x656D44A2A11C51D5, 0xF0FDF2D3F3C30B9F}, // 1e183 - {0x9F644AE5A4B1B325, 0x969EB7C47859E743}, // 1e184 - {0x873D5D9F0DDE1FEE, 0xBC4665B596706114}, // 1e185 - {0xA90CB506D155A7EA, 0xEB57FF22FC0C7959}, // 1e186 - {0x09A7F12442D588F2, 0x9316FF75DD87CBD8}, // 1e187 - {0x0C11ED6D538AEB2F, 0xB7DCBF5354E9BECE}, // 1e188 - {0x8F1668C8A86DA5FA, 0xE5D3EF282A242E81}, // 1e189 - {0xF96E017D694487BC, 0x8FA475791A569D10}, // 1e190 - {0x37C981DCC395A9AC, 0xB38D92D760EC4455}, // 1e191 - {0x85BBE253F47B1417, 0xE070F78D3927556A}, // 1e192 - {0x93956D7478CCEC8E, 0x8C469AB843B89562}, // 1e193 - {0x387AC8D1970027B2, 0xAF58416654A6BABB}, // 1e194 - {0x06997B05FCC0319E, 0xDB2E51BFE9D0696A}, // 1e195 - {0x441FECE3BDF81F03, 0x88FCF317F22241E2}, // 1e196 - {0xD527E81CAD7626C3, 0xAB3C2FDDEEAAD25A}, // 1e197 - {0x8A71E223D8D3B074, 0xD60B3BD56A5586F1}, // 1e198 - {0xF6872D5667844E49, 0x85C7056562757456}, // 1e199 - {0xB428F8AC016561DB, 0xA738C6BEBB12D16C}, // 1e200 - {0xE13336D701BEBA52, 0xD106F86E69D785C7}, // 1e201 - {0xECC0024661173473, 0x82A45B450226B39C}, // 1e202 - {0x27F002D7F95D0190, 0xA34D721642B06084}, // 1e203 - {0x31EC038DF7B441F4, 0xCC20CE9BD35C78A5}, // 1e204 - {0x7E67047175A15271, 0xFF290242C83396CE}, // 1e205 - {0x0F0062C6E984D386, 0x9F79A169BD203E41}, // 1e206 - {0x52C07B78A3E60868, 0xC75809C42C684DD1}, // 1e207 - {0xA7709A56CCDF8A82, 0xF92E0C3537826145}, // 1e208 - {0x88A66076400BB691, 0x9BBCC7A142B17CCB}, // 1e209 - {0x6ACFF893D00EA435, 0xC2ABF989935DDBFE}, // 1e210 - {0x0583F6B8C4124D43, 0xF356F7EBF83552FE}, // 1e211 - {0xC3727A337A8B704A, 0x98165AF37B2153DE}, // 1e212 - {0x744F18C0592E4C5C, 0xBE1BF1B059E9A8D6}, // 1e213 - {0x1162DEF06F79DF73, 0xEDA2EE1C7064130C}, // 1e214 - {0x8ADDCB5645AC2BA8, 0x9485D4D1C63E8BE7}, // 1e215 - {0x6D953E2BD7173692, 0xB9A74A0637CE2EE1}, // 1e216 - {0xC8FA8DB6CCDD0437, 0xE8111C87C5C1BA99}, // 1e217 - {0x1D9C9892400A22A2, 0x910AB1D4DB9914A0}, // 1e218 - {0x2503BEB6D00CAB4B, 0xB54D5E4A127F59C8}, // 1e219 - {0x2E44AE64840FD61D, 0xE2A0B5DC971F303A}, // 1e220 - {0x5CEAECFED289E5D2, 0x8DA471A9DE737E24}, // 1e221 - {0x7425A83E872C5F47, 0xB10D8E1456105DAD}, // 1e222 - {0xD12F124E28F77719, 0xDD50F1996B947518}, // 1e223 - {0x82BD6B70D99AAA6F, 0x8A5296FFE33CC92F}, // 1e224 - {0x636CC64D1001550B, 0xACE73CBFDC0BFB7B}, // 1e225 - {0x3C47F7E05401AA4E, 0xD8210BEFD30EFA5A}, // 1e226 - {0x65ACFAEC34810A71, 0x8714A775E3E95C78}, // 1e227 - {0x7F1839A741A14D0D, 0xA8D9D1535CE3B396}, // 1e228 - {0x1EDE48111209A050, 0xD31045A8341CA07C}, // 1e229 - {0x934AED0AAB460432, 0x83EA2B892091E44D}, // 1e230 - {0xF81DA84D5617853F, 0xA4E4B66B68B65D60}, // 1e231 - {0x36251260AB9D668E, 0xCE1DE40642E3F4B9}, // 1e232 - {0xC1D72B7C6B426019, 0x80D2AE83E9CE78F3}, // 1e233 - {0xB24CF65B8612F81F, 0xA1075A24E4421730}, // 1e234 - {0xDEE033F26797B627, 0xC94930AE1D529CFC}, // 1e235 - {0x169840EF017DA3B1, 0xFB9B7CD9A4A7443C}, // 1e236 - {0x8E1F289560EE864E, 0x9D412E0806E88AA5}, // 1e237 - {0xF1A6F2BAB92A27E2, 0xC491798A08A2AD4E}, // 1e238 - {0xAE10AF696774B1DB, 0xF5B5D7EC8ACB58A2}, // 1e239 - {0xACCA6DA1E0A8EF29, 0x9991A6F3D6BF1765}, // 1e240 - {0x17FD090A58D32AF3, 0xBFF610B0CC6EDD3F}, // 1e241 - {0xDDFC4B4CEF07F5B0, 0xEFF394DCFF8A948E}, // 1e242 - {0x4ABDAF101564F98E, 0x95F83D0A1FB69CD9}, // 1e243 - {0x9D6D1AD41ABE37F1, 0xBB764C4CA7A4440F}, // 1e244 - {0x84C86189216DC5ED, 0xEA53DF5FD18D5513}, // 1e245 - {0x32FD3CF5B4E49BB4, 0x92746B9BE2F8552C}, // 1e246 - {0x3FBC8C33221DC2A1, 0xB7118682DBB66A77}, // 1e247 - {0x0FABAF3FEAA5334A, 0xE4D5E82392A40515}, // 1e248 - {0x29CB4D87F2A7400E, 0x8F05B1163BA6832D}, // 1e249 - {0x743E20E9EF511012, 0xB2C71D5BCA9023F8}, // 1e250 - {0x914DA9246B255416, 0xDF78E4B2BD342CF6}, // 1e251 - {0x1AD089B6C2F7548E, 0x8BAB8EEFB6409C1A}, // 1e252 - {0xA184AC2473B529B1, 0xAE9672ABA3D0C320}, // 1e253 - {0xC9E5D72D90A2741E, 0xDA3C0F568CC4F3E8}, // 1e254 - {0x7E2FA67C7A658892, 0x8865899617FB1871}, // 1e255 - {0xDDBB901B98FEEAB7, 0xAA7EEBFB9DF9DE8D}, // 1e256 - {0x552A74227F3EA565, 0xD51EA6FA85785631}, // 1e257 - {0xD53A88958F87275F, 0x8533285C936B35DE}, // 1e258 - {0x8A892ABAF368F137, 0xA67FF273B8460356}, // 1e259 - {0x2D2B7569B0432D85, 0xD01FEF10A657842C}, // 1e260 - {0x9C3B29620E29FC73, 0x8213F56A67F6B29B}, // 1e261 - {0x8349F3BA91B47B8F, 0xA298F2C501F45F42}, // 1e262 - {0x241C70A936219A73, 0xCB3F2F7642717713}, // 1e263 - {0xED238CD383AA0110, 0xFE0EFB53D30DD4D7}, // 1e264 - {0xF4363804324A40AA, 0x9EC95D1463E8A506}, // 1e265 - {0xB143C6053EDCD0D5, 0xC67BB4597CE2CE48}, // 1e266 - {0xDD94B7868E94050A, 0xF81AA16FDC1B81DA}, // 1e267 - {0xCA7CF2B4191C8326, 0x9B10A4E5E9913128}, // 1e268 - {0xFD1C2F611F63A3F0, 0xC1D4CE1F63F57D72}, // 1e269 - {0xBC633B39673C8CEC, 0xF24A01A73CF2DCCF}, // 1e270 - {0xD5BE0503E085D813, 0x976E41088617CA01}, // 1e271 - {0x4B2D8644D8A74E18, 0xBD49D14AA79DBC82}, // 1e272 - {0xDDF8E7D60ED1219E, 0xEC9C459D51852BA2}, // 1e273 - {0xCABB90E5C942B503, 0x93E1AB8252F33B45}, // 1e274 - {0x3D6A751F3B936243, 0xB8DA1662E7B00A17}, // 1e275 - {0x0CC512670A783AD4, 0xE7109BFBA19C0C9D}, // 1e276 - {0x27FB2B80668B24C5, 0x906A617D450187E2}, // 1e277 - {0xB1F9F660802DEDF6, 0xB484F9DC9641E9DA}, // 1e278 - {0x5E7873F8A0396973, 0xE1A63853BBD26451}, // 1e279 - {0xDB0B487B6423E1E8, 0x8D07E33455637EB2}, // 1e280 - {0x91CE1A9A3D2CDA62, 0xB049DC016ABC5E5F}, // 1e281 - {0x7641A140CC7810FB, 0xDC5C5301C56B75F7}, // 1e282 - {0xA9E904C87FCB0A9D, 0x89B9B3E11B6329BA}, // 1e283 - {0x546345FA9FBDCD44, 0xAC2820D9623BF429}, // 1e284 - {0xA97C177947AD4095, 0xD732290FBACAF133}, // 1e285 - {0x49ED8EABCCCC485D, 0x867F59A9D4BED6C0}, // 1e286 - {0x5C68F256BFFF5A74, 0xA81F301449EE8C70}, // 1e287 - {0x73832EEC6FFF3111, 0xD226FC195C6A2F8C}, // 1e288 - {0xC831FD53C5FF7EAB, 0x83585D8FD9C25DB7}, // 1e289 - {0xBA3E7CA8B77F5E55, 0xA42E74F3D032F525}, // 1e290 - {0x28CE1BD2E55F35EB, 0xCD3A1230C43FB26F}, // 1e291 - {0x7980D163CF5B81B3, 0x80444B5E7AA7CF85}, // 1e292 - {0xD7E105BCC332621F, 0xA0555E361951C366}, // 1e293 - {0x8DD9472BF3FEFAA7, 0xC86AB5C39FA63440}, // 1e294 - {0xB14F98F6F0FEB951, 0xFA856334878FC150}, // 1e295 - {0x6ED1BF9A569F33D3, 0x9C935E00D4B9D8D2}, // 1e296 - {0x0A862F80EC4700C8, 0xC3B8358109E84F07}, // 1e297 - {0xCD27BB612758C0FA, 0xF4A642E14C6262C8}, // 1e298 - {0x8038D51CB897789C, 0x98E7E9CCCFBD7DBD}, // 1e299 - {0xE0470A63E6BD56C3, 0xBF21E44003ACDD2C}, // 1e300 - {0x1858CCFCE06CAC74, 0xEEEA5D5004981478}, // 1e301 - {0x0F37801E0C43EBC8, 0x95527A5202DF0CCB}, // 1e302 - {0xD30560258F54E6BA, 0xBAA718E68396CFFD}, // 1e303 - {0x47C6B82EF32A2069, 0xE950DF20247C83FD}, // 1e304 - {0x4CDC331D57FA5441, 0x91D28B7416CDD27E}, // 1e305 - {0xE0133FE4ADF8E952, 0xB6472E511C81471D}, // 1e306 - {0x58180FDDD97723A6, 0xE3D8F9E563A198E5}, // 1e307 - {0x570F09EAA7EA7648, 0x8E679C2F5E44FF8F}, // 1e308 - {0x2CD2CC6551E513DA, 0xB201833B35D63F73}, // 1e309 - {0xF8077F7EA65E58D1, 0xDE81E40A034BCF4F}, // 1e310 - {0xFB04AFAF27FAF782, 0x8B112E86420F6191}, // 1e311 - {0x79C5DB9AF1F9B563, 0xADD57A27D29339F6}, // 1e312 - {0x18375281AE7822BC, 0xD94AD8B1C7380874}, // 1e313 - {0x8F2293910D0B15B5, 0x87CEC76F1C830548}, // 1e314 - {0xB2EB3875504DDB22, 0xA9C2794AE3A3C69A}, // 1e315 - {0x5FA60692A46151EB, 0xD433179D9C8CB841}, // 1e316 - {0xDBC7C41BA6BCD333, 0x849FEEC281D7F328}, // 1e317 - {0x12B9B522906C0800, 0xA5C7EA73224DEFF3}, // 1e318 - {0xD768226B34870A00, 0xCF39E50FEAE16BEF}, // 1e319 - {0xE6A1158300D46640, 0x81842F29F2CCE375}, // 1e320 - {0x60495AE3C1097FD0, 0xA1E53AF46F801C53}, // 1e321 - {0x385BB19CB14BDFC4, 0xCA5E89B18B602368}, // 1e322 - {0x46729E03DD9ED7B5, 0xFCF62C1DEE382C42}, // 1e323 - {0x6C07A2C26A8346D1, 0x9E19DB92B4E31BA9}, // 1e324 - {0xC7098B7305241885, 0xC5A05277621BE293}, // 1e325 - {0xB8CBEE4FC66D1EA7, 0xF70867153AA2DB38}, // 1e326 - {0x737F74F1DC043328, 0x9A65406D44A5C903}, // 1e327 - {0x505F522E53053FF2, 0xC0FE908895CF3B44}, // 1e328 - {0x647726B9E7C68FEF, 0xF13E34AABB430A15}, // 1e329 - {0x5ECA783430DC19F5, 0x96C6E0EAB509E64D}, // 1e330 - {0xB67D16413D132072, 0xBC789925624C5FE0}, // 1e331 - {0xE41C5BD18C57E88F, 0xEB96BF6EBADF77D8}, // 1e332 - {0x8E91B962F7B6F159, 0x933E37A534CBAAE7}, // 1e333 - {0x723627BBB5A4ADB0, 0xB80DC58E81FE95A1}, // 1e334 - {0xCEC3B1AAA30DD91C, 0xE61136F2227E3B09}, // 1e335 - {0x213A4F0AA5E8A7B1, 0x8FCAC257558EE4E6}, // 1e336 - {0xA988E2CD4F62D19D, 0xB3BD72ED2AF29E1F}, // 1e337 - {0x93EB1B80A33B8605, 0xE0ACCFA875AF45A7}, // 1e338 - {0xBC72F130660533C3, 0x8C6C01C9498D8B88}, // 1e339 - {0xEB8FAD7C7F8680B4, 0xAF87023B9BF0EE6A}, // 1e340 - {0xA67398DB9F6820E1, 0xDB68C2CA82ED2A05}, // 1e341 - {0x88083F8943A1148C, 0x892179BE91D43A43}, // 1e342 - {0x6A0A4F6B948959B0, 0xAB69D82E364948D4}, // 1e343 - {0x848CE34679ABB01C, 0xD6444E39C3DB9B09}, // 1e344 - {0xF2D80E0C0C0B4E11, 0x85EAB0E41A6940E5}, // 1e345 - {0x6F8E118F0F0E2195, 0xA7655D1D2103911F}, // 1e346 - {0x4B7195F2D2D1A9FB, 0xD13EB46469447567}, // 1e347 -} diff --git a/examples/gno.land/p/demo/json/eisel_lemire/gno.mod b/examples/gno.land/p/demo/json/eisel_lemire/gno.mod deleted file mode 100644 index d6670de82e2..00000000000 --- a/examples/gno.land/p/demo/json/eisel_lemire/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/json/eisel_lemire diff --git a/examples/gno.land/p/demo/json/encode.gno b/examples/gno.land/p/demo/json/encode.gno index be90d7aa73d..55828650e22 100644 --- a/examples/gno.land/p/demo/json/encode.gno +++ b/examples/gno.land/p/demo/json/encode.gno @@ -3,10 +3,8 @@ package json import ( "bytes" "errors" - "math" "strconv" - "gno.land/p/demo/json/ryu" "gno.land/p/demo/ufmt" ) @@ -44,17 +42,8 @@ func Marshal(node *Node) ([]byte, error) { return nil, err } - // ufmt does not support %g. by doing so, we need to check if the number is an integer - // after then, apply the correct format for each float and integer numbers. - if math.Mod(nVal, 1.0) == 0 { - // must convert float to integer. otherwise it will be overflowed. - num := ufmt.Sprintf("%d", int(nVal)) - buf.WriteString(num) - } else { - // use ryu algorithm to convert float to string - num := ryu.FormatFloat64(nVal) - buf.WriteString(num) - } + num := strconv.FormatFloat(nVal, 'f', -1, 64) + buf.WriteString(num) case String: sVal, err = node.GetString() diff --git a/examples/gno.land/p/demo/json/encode_test.gno b/examples/gno.land/p/demo/json/encode_test.gno index e8e53993b5c..831a9e0e0a2 100644 --- a/examples/gno.land/p/demo/json/encode_test.gno +++ b/examples/gno.land/p/demo/json/encode_test.gno @@ -37,10 +37,9 @@ func TestMarshal_Primitive(t *testing.T) { name: "42", node: NumberNode("", 42), }, - // TODO: fix output for not to use scientific notation { - name: "1.005e+02", - node: NumberNode("", 100.5), + name: "3.14", + node: NumberNode("", 3.14), }, { name: `[1,2,3]`, diff --git a/examples/gno.land/p/demo/json/errors.gno b/examples/gno.land/p/demo/json/errors.gno new file mode 100644 index 00000000000..e0836dccdff --- /dev/null +++ b/examples/gno.land/p/demo/json/errors.gno @@ -0,0 +1,34 @@ +package json + +import "errors" + +var ( + errNilNode = errors.New("node is nil") + errNotArrayNode = errors.New("node is not array") + errNotBoolNode = errors.New("node is not boolean") + errNotNullNode = errors.New("node is not null") + errNotNumberNode = errors.New("node is not number") + errNotObjectNode = errors.New("node is not object") + errNotStringNode = errors.New("node is not string") + errInvalidToken = errors.New("invalid token") + errIndexNotFound = errors.New("index not found") + errInvalidAppend = errors.New("can't append value to non-appendable node") + errInvalidAppendCycle = errors.New("appending value to itself or its children or parents will cause a cycle") + errInvalidEscapeSequence = errors.New("invalid escape sequence") + errInvalidStringValue = errors.New("invalid string value") + errEmptyBooleanNode = errors.New("boolean node is empty") + errEmptyStringNode = errors.New("string node is empty") + errKeyRequired = errors.New("key is required for object") + errUnmatchedParenthesis = errors.New("mismatched bracket or parenthesis") + errUnmatchedQuotePath = errors.New("unmatched quote in path") +) + +var ( + errInvalidStringInput = errors.New("invalid string input") + errMalformedBooleanValue = errors.New("malformed boolean value") + errEmptyByteSlice = errors.New("empty byte slice") + errInvalidExponentValue = errors.New("invalid exponent value") + errNonDigitCharacters = errors.New("non-digit characters found") + errNumericRangeExceeded = errors.New("numeric value exceeds the range limit") + errMultipleDecimalPoints = errors.New("multiple decimal points found") +) diff --git a/examples/gno.land/p/demo/json/escape.gno b/examples/gno.land/p/demo/json/escape.gno index 5a834068127..ee3e4a79855 100644 --- a/examples/gno.land/p/demo/json/escape.gno +++ b/examples/gno.land/p/demo/json/escape.gno @@ -1,8 +1,6 @@ package json import ( - "bytes" - "errors" "unicode/utf8" ) @@ -13,6 +11,9 @@ const ( surrogateEnd = 0xDFFF basicMultilingualPlaneOffset = 0xFFFF badHex = -1 + + singleUnicodeEscapeLen = 6 + surrogatePairLen = 12 ) var hexLookupTable = [256]int{ @@ -42,48 +43,32 @@ func h2i(c byte) int { // // it returns the processed slice and any error encountered during the Unescape operation. func Unescape(input, output []byte) ([]byte, error) { - // find the index of the first backslash in the input slice. - firstBackslash := bytes.IndexByte(input, backSlash) - if firstBackslash == -1 { - return input, nil - } - - // ensure the output slice has enough capacity to hold the result. + // ensure the output slice has enough capacity to hold the input slice. inputLen := len(input) if cap(output) < inputLen { output = make([]byte, inputLen) } - output = output[:inputLen] - copy(output, input[:firstBackslash]) - - input = input[firstBackslash:] - buf := output[firstBackslash:] - - for len(input) > 0 { - inLen, bufLen, err := processEscapedUTF8(input, buf) - if err != nil { - return nil, err - } - - input = input[inLen:] // the number of bytes consumed in the input - buf = buf[bufLen:] // the number of bytes written to buf + inPos, outPos := 0, 0 - // find the next backslash in the remaining input - nextBackslash := bytes.IndexByte(input, backSlash) - if nextBackslash == -1 { - copy(buf, input) - buf = buf[len(input):] - break + for inPos < len(input) { + c := input[inPos] + if c != backSlash { + output[outPos] = c + inPos++ + outPos++ + } else { + // process escape sequence + inLen, outLen, err := processEscapedUTF8(input[inPos:], output[outPos:]) + if err != nil { + return nil, err + } + inPos += inLen + outPos += outLen } - - copy(buf, input[:nextBackslash]) - - input = input[nextBackslash:] - buf = buf[nextBackslash:] } - return output[:len(output)-len(buf)], nil + return output[:outPos], nil } // isSurrogatePair returns true if the rune is a surrogate pair. @@ -94,6 +79,16 @@ func isSurrogatePair(r rune) bool { return highSurrogateOffset <= r && r <= surrogateEnd } +// isHighSurrogate checks if the rune is a high surrogate (U+D800 to U+DBFF). +func isHighSurrogate(r rune) bool { + return r >= highSurrogateOffset && r <= 0xDBFF +} + +// isLowSurrogate checks if the rune is a low surrogate (U+DC00 to U+DFFF). +func isLowSurrogate(r rune) bool { + return r >= lowSurrogateOffset && r <= surrogateEnd +} + // combineSurrogates reconstruct the original unicode code points in the // supplemental plane by combinin the high and low surrogate. // @@ -122,28 +117,41 @@ func decodeSingleUnicodeEscape(b []byte) (rune, bool) { } // decodeUnicodeEscape decodes a Unicode escape sequence from a byte slice. +// It handles both single Unicode escape sequences and surrogate pairs. func decodeUnicodeEscape(b []byte) (rune, int) { + // decode the first Unicode escape sequence. r, ok := decodeSingleUnicodeEscape(b) if !ok { return utf8.RuneError, -1 } - // determine valid unicode escapes within the BMP + // if the rune is within the BMP and not a surrogate, return it if r <= basicMultilingualPlaneOffset && !isSurrogatePair(r) { return r, 6 } - // Decode the following escape sequence to verify a UTF-16 susergate pair. - r2, ok := decodeSingleUnicodeEscape(b[6:]) - if !ok { + if !isHighSurrogate(r) { + // invalid surrogate pair. return utf8.RuneError, -1 } - if r2 < lowSurrogateOffset { + // if the rune is a high surrogate, need to decode the next escape sequence. + + // ensure there are enough bytes for the next escape sequence. + if len(b) < surrogatePairLen { return utf8.RuneError, -1 } - - return combineSurrogates(r, r2), 12 + // decode the second Unicode escape sequence. + r2, ok := decodeSingleUnicodeEscape(b[singleUnicodeEscapeLen:]) + if !ok { + return utf8.RuneError, -1 + } + // check if the second rune is a low surrogate. + if isLowSurrogate(r2) { + combined := combineSurrogates(r, r2) + return combined, surrogatePairLen + } + return utf8.RuneError, -1 } var escapeByteSet = [256]byte{ @@ -165,7 +173,6 @@ func Unquote(s []byte, border byte) (string, bool) { } // unquoteBytes takes a byte slice and unquotes it by removing -// TODO: consider to move this function to the strconv package. func unquoteBytes(s []byte, border byte) ([]byte, bool) { if len(s) < 2 || s[0] != border || s[len(s)-1] != border { return nil, false @@ -259,21 +266,12 @@ func unquoteBytes(s []byte, border byte) ([]byte, bool) { return b[:w], true } -// processEscapedUTF8 processes the escape sequence in the given byte slice and -// and converts them to UTF-8 characters. The function returns the length of the processed input and output. -// -// The input 'in' must contain the escape sequence to be processed, -// and 'out' provides a space to store the converted characters. -// -// The function returns (input length, output length) if the escape sequence is correct. -// Unicode escape sequences (e.g. \uXXXX) are decoded to UTF-8, other default escape sequences are -// converted to their corresponding special characters (e.g. \n -> newline). -// -// If the escape sequence is invalid, or if 'in' does not completely enclose the escape sequence, -// function returns (-1, -1) to indicate an error. +// processEscapedUTF8 converts escape sequences to UTF-8 characters. +// It decodes Unicode escape sequences (\uXXXX) to UTF-8 and +// converts standard escape sequences (e.g., \n) to their corresponding special characters. func processEscapedUTF8(in, out []byte) (int, int, error) { if len(in) < 2 || in[0] != backSlash { - return -1, -1, errors.New("invalid escape sequence") + return -1, -1, errInvalidEscapeSequence } escapeSeqLen := 2 @@ -282,7 +280,7 @@ func processEscapedUTF8(in, out []byte) (int, int, error) { if escapeChar != 'u' { val := escapeByteSet[escapeChar] if val == 0 { - return -1, -1, errors.New("invalid escape sequence") + return -1, -1, errInvalidEscapeSequence } out[0] = val @@ -291,7 +289,7 @@ func processEscapedUTF8(in, out []byte) (int, int, error) { r, size := decodeUnicodeEscape(in) if size == -1 { - return -1, -1, errors.New("invalid escape sequence") + return -1, -1, errInvalidEscapeSequence } outLen := utf8.EncodeRune(out, r) diff --git a/examples/gno.land/p/demo/json/escape_test.gno b/examples/gno.land/p/demo/json/escape_test.gno index 40c118d93ce..0e2e696e83c 100644 --- a/examples/gno.land/p/demo/json/escape_test.gno +++ b/examples/gno.land/p/demo/json/escape_test.gno @@ -103,24 +103,25 @@ func TestDecodeSingleUnicodeEscape(t *testing.T) { } func TestDecodeUnicodeEscape(t *testing.T) { - testCases := []struct { - input string + tests := []struct { + input []byte expected rune size int }{ - {"\\u0041", 'A', 6}, - {"\\u03B1", 'α', 6}, - {"\\u1F600", 0x1F60, 6}, - {"\\uD830\\uDE03", 0x1C203, 12}, - {"\\uD800\\uDC00", 0x00010000, 12}, - - {"\\u004", utf8.RuneError, -1}, - {"\\uXYZW", utf8.RuneError, -1}, - {"\\uD83D\\u0041", utf8.RuneError, -1}, + {[]byte(`\u0041`), 'A', 6}, + {[]byte(`\uD83D\uDE00`), 0x1F600, 12}, // 😀 + {[]byte(`\uD834\uDD1E`), 0x1D11E, 12}, // 𝄞 + {[]byte(`\uFFFF`), '\uFFFF', 6}, + {[]byte(`\uXYZW`), utf8.RuneError, -1}, + {[]byte(`\uD800`), utf8.RuneError, -1}, // single high surrogate + {[]byte(`\uDC00`), utf8.RuneError, -1}, // single low surrogate + {[]byte(`\uD800\uDC00`), 0x10000, 12}, // First code point above U+FFFF + {[]byte(`\uDBFF\uDFFF`), 0x10FFFF, 12}, // Maximum code point + {[]byte(`\uD83D\u0041`), utf8.RuneError, -1}, // invalid surrogate pair } - for _, tc := range testCases { - r, size := decodeUnicodeEscape([]byte(tc.input)) + for _, tc := range tests { + r, size := decodeUnicodeEscape(tc.input) if r != tc.expected || size != tc.size { t.Errorf("decodeUnicodeEscape(%q) = (%U, %d); want (%U, %d)", tc.input, r, size, tc.expected, tc.size) } @@ -128,7 +129,7 @@ func TestDecodeUnicodeEscape(t *testing.T) { } func TestUnescapeToUTF8(t *testing.T) { - testCases := []struct { + tests := []struct { input []byte expectedIn int expectedOut int @@ -150,7 +151,7 @@ func TestUnescapeToUTF8(t *testing.T) { {[]byte(`\uD83D\u0041`), -1, -1, true}, // invalid unicode escape sequence } - for _, tc := range testCases { + for _, tc := range tests { input := make([]byte, len(tc.input)) copy(input, tc.input) output := make([]byte, utf8.UTFMax) @@ -166,23 +167,32 @@ func TestUnescapeToUTF8(t *testing.T) { } func TestUnescape(t *testing.T) { - testCases := []struct { + tests := []struct { name string input []byte expected []byte + isError bool }{ - {"NoEscape", []byte("hello world"), []byte("hello world")}, - {"SingleEscape", []byte("hello\\nworld"), []byte("hello\nworld")}, - {"MultipleEscapes", []byte("line1\\nline2\\r\\nline3"), []byte("line1\nline2\r\nline3")}, - {"UnicodeEscape", []byte("snowman:\\u2603"), []byte("snowman:\u2603")}, - {"Complex", []byte("tc\\n\\u2603\\r\\nend"), []byte("tc\n\u2603\r\nend")}, + {"NoEscape", []byte("hello world"), []byte("hello world"), false}, + {"SingleEscape", []byte("hello\\nworld"), []byte("hello\nworld"), false}, + {"MultipleEscapes", []byte("line1\\nline2\\r\\nline3"), []byte("line1\nline2\r\nline3"), false}, + {"UnicodeEscape", []byte("snowman:\\u2603"), []byte("snowman:\u2603"), false}, + {"SurrogatePair", []byte("emoji:\\uD83D\\uDE00"), []byte("emoji:😀"), false}, + {"InvalidEscape", []byte("hello\\xworld"), nil, true}, + {"IncompleteUnicode", []byte("incomplete:\\u123"), nil, true}, + {"InvalidSurrogatePair", []byte("invalid:\\uD83D\\u0041"), nil, true}, } - for _, tc := range testCases { + for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - output, _ := Unescape(tc.input, make([]byte, len(tc.input)+10)) - if !bytes.Equal(output, tc.expected) { - t.Errorf("unescape(%q) = %q; want %q", tc.input, output, tc.expected) + output := make([]byte, len(tc.input)*2) // Allocate extra space for possible expansion + result, err := Unescape(tc.input, output) + if (err != nil) != tc.isError { + t.Errorf("Unescape(%q) error = %v; want error = %v", tc.input, err, tc.isError) + } + + if !tc.isError && !bytes.Equal(result, tc.expected) { + t.Errorf("Unescape(%q) = %q; want %q", tc.input, result, tc.expected) } }) } @@ -206,6 +216,7 @@ func TestUnquoteBytes(t *testing.T) { {[]byte("\"\\u0041\""), '"', []byte("A"), true}, {[]byte(`"Hello, 世界"`), '"', []byte("Hello, 世界"), true}, {[]byte(`"Hello, \x80"`), '"', nil, false}, + {[]byte(`"invalid surrogate: \uD83D\u0041"`), '"', nil, false}, } for _, tc := range tests { diff --git a/examples/gno.land/p/demo/json/gno.mod b/examples/gno.land/p/demo/json/gno.mod index 8a380644acc..ef794458c56 100644 --- a/examples/gno.land/p/demo/json/gno.mod +++ b/examples/gno.land/p/demo/json/gno.mod @@ -1,7 +1,3 @@ module gno.land/p/demo/json -require ( - gno.land/p/demo/json/eisel_lemire v0.0.0-latest - gno.land/p/demo/json/ryu v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/json/indent.gno b/examples/gno.land/p/demo/json/indent.gno index cdcfd4524ee..cdf9d5e976f 100644 --- a/examples/gno.land/p/demo/json/indent.gno +++ b/examples/gno.land/p/demo/json/indent.gno @@ -9,21 +9,7 @@ import ( // A factor no higher than 2 ensures that wasted space never exceeds 50%. const indentGrowthFactor = 2 -// IndentJSON takes a JSON byte slice and a string for indentation, -// then formats the JSON according to the specified indent string. -// This function applies indentation rules as follows: -// -// 1. For top-level arrays and objects, no additional indentation is applied. -// -// 2. For nested structures like arrays within arrays or objects, indentation increases. -// -// 3. Indentation is applied after opening brackets ('[' or '{') and before closing brackets (']' or '}'). -// -// 4. Commas and colons are handled appropriately to maintain valid JSON format. -// -// 5. Nested arrays within objects or arrays receive new lines and indentation based on their depth level. -// -// The function returns the formatted JSON as a byte slice and an error if any issues occurred during formatting. +// IndentJSON formats the JSON data with the specified indentation. func Indent(data []byte, indent string) ([]byte, error) { var ( out bytes.Buffer diff --git a/examples/gno.land/p/demo/json/node.gno b/examples/gno.land/p/demo/json/node.gno index 1e71a101e62..c917150bc3d 100644 --- a/examples/gno.land/p/demo/json/node.gno +++ b/examples/gno.land/p/demo/json/node.gno @@ -44,7 +44,7 @@ func NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) prev.next[strconv.Itoa(size)] = curr } else if prev.IsObject() { if key == nil { - return nil, errors.New("key is required for object") + return nil, errKeyRequired } prev.next[**key] = curr @@ -88,7 +88,7 @@ func (n *Node) HasKey(key string) bool { // GetKey returns the value of the given key from the current object node. func (n *Node) GetKey(key string) (*Node, error) { if n == nil { - return nil, errors.New("node is nil") + return nil, errNilNode } if n.Type() != Object { @@ -174,7 +174,7 @@ func (n *Node) Value() (value interface{}, err error) { return nil, nil case Number: - value, err = ParseFloatLiteral(n.source()) + value, err = strconv.ParseFloat(string(n.source()), 64) if err != nil { return nil, err } @@ -185,14 +185,14 @@ func (n *Node) Value() (value interface{}, err error) { var ok bool value, ok = Unquote(n.source(), doubleQuote) if !ok { - return "", errors.New("invalid string value") + return "", errInvalidStringValue } n.value = value case Boolean: if len(n.source()) == 0 { - return nil, errors.New("empty boolean value") + return nil, errEmptyBooleanNode } b := n.source()[0] @@ -319,11 +319,11 @@ func (n *Node) MustIndex(expectIdx int) *Node { // if the index is negative, it returns the index is from the end of the array. func (n *Node) GetIndex(idx int) (*Node, error) { if n == nil { - return nil, errors.New("node is nil") + return nil, errNilNode } if !n.IsArray() { - return nil, errors.New("node is not array") + return nil, errNotArrayNode } if idx > n.Size() { @@ -336,7 +336,7 @@ func (n *Node) GetIndex(idx int) (*Node, error) { child, ok := n.next[strconv.Itoa(idx)] if !ok { - return nil, errors.New("index not found") + return nil, errIndexNotFound } return child, nil @@ -556,11 +556,11 @@ func (n *Node) root() *Node { // } func (n *Node) GetNull() (interface{}, error) { if n == nil { - return nil, errors.New("node is nil") + return nil, errNilNode } if !n.IsNull() { - return nil, errors.New("node is not null") + return nil, errNotNullNode } return nil, nil @@ -590,11 +590,11 @@ func (n *Node) MustNull() interface{} { // println(val) // 10.5 func (n *Node) GetNumeric() (float64, error) { if n == nil { - return 0, errors.New("node is nil") + return 0, errNilNode } if n.nodeType != Number { - return 0, errors.New("node is not number") + return 0, errNotNumberNode } val, err := n.Value() @@ -604,7 +604,7 @@ func (n *Node) GetNumeric() (float64, error) { v, ok := val.(float64) if !ok { - return 0, errors.New("node is not number") + return 0, errNotNumberNode } return v, nil @@ -639,11 +639,11 @@ func (n *Node) MustNumeric() float64 { // println(str) // "foo" func (n *Node) GetString() (string, error) { if n == nil { - return "", errors.New("string node is empty") + return "", errEmptyStringNode } if !n.IsString() { - return "", errors.New("node type is not string") + return "", errNotStringNode } val, err := n.Value() @@ -653,7 +653,7 @@ func (n *Node) GetString() (string, error) { v, ok := val.(string) if !ok { - return "", errors.New("node is not string") + return "", errNotStringNode } return v, nil @@ -683,11 +683,11 @@ func (n *Node) MustString() string { // println(val) // true func (n *Node) GetBool() (bool, error) { if n == nil { - return false, errors.New("node is nil") + return false, errNilNode } if n.nodeType != Boolean { - return false, errors.New("node is not boolean") + return false, errNotBoolNode } val, err := n.Value() @@ -697,7 +697,7 @@ func (n *Node) GetBool() (bool, error) { v, ok := val.(bool) if !ok { - return false, errors.New("node is not boolean") + return false, errNotBoolNode } return v, nil @@ -732,11 +732,11 @@ func (n *Node) MustBool() bool { // result: "foo", 1 func (n *Node) GetArray() ([]*Node, error) { if n == nil { - return nil, errors.New("node is nil") + return nil, errNilNode } if n.nodeType != Array { - return nil, errors.New("node is not array") + return nil, errNotArrayNode } val, err := n.Value() @@ -746,7 +746,7 @@ func (n *Node) GetArray() ([]*Node, error) { v, ok := val.([]*Node) if !ok { - return nil, errors.New("node is not array") + return nil, errNotArrayNode } return v, nil @@ -788,7 +788,7 @@ func (n *Node) MustArray() []*Node { // result: ["bar", "baz", 1, "foo"] func (n *Node) AppendArray(value ...*Node) error { if !n.IsArray() { - return errors.New("can't append value to non-array node") + return errInvalidAppend } for _, val := range value { @@ -836,11 +836,11 @@ func (n *Node) ArrayEach(callback func(i int, target *Node)) { // result: map[string]*Node{"key": StringNode("key", "value")} func (n *Node) GetObject() (map[string]*Node, error) { if n == nil { - return nil, errors.New("node is nil") + return nil, errNilNode } if !n.IsObject() { - return nil, errors.New("node is not object") + return nil, errNotObjectNode } val, err := n.Value() @@ -850,7 +850,7 @@ func (n *Node) GetObject() (map[string]*Node, error) { v, ok := val.(map[string]*Node) if !ok { - return nil, errors.New("node is not object") + return nil, errNotObjectNode } return v, nil @@ -873,7 +873,7 @@ func (n *Node) MustObject() map[string]*Node { // If the current node is not object type, it returns an error. func (n *Node) AppendObject(key string, value *Node) error { if !n.IsObject() { - return errors.New("can't append value to non-object node") + return errInvalidAppend } if err := n.append(&key, value); err != nil { @@ -1003,7 +1003,7 @@ func (n *Node) dropIndex(idx int) { // append is a helper function to append the given value to the current container type node. func (n *Node) append(key *string, val *Node) error { if n.isSameOrParentNode(val) { - return errors.New("can't append same or parent node") + return errInvalidAppendCycle } if val.prev != nil { diff --git a/examples/gno.land/p/demo/json/parser.gno b/examples/gno.land/p/demo/json/parser.gno index 9a2c3a8c817..bae06cb3789 100644 --- a/examples/gno.land/p/demo/json/parser.gno +++ b/examples/gno.land/p/demo/json/parser.gno @@ -2,27 +2,22 @@ package json import ( "bytes" - "errors" - "strconv" - - el "gno.land/p/demo/json/eisel_lemire" ) const ( - absMinInt64 = 1 << 63 - maxInt64 = absMinInt64 - 1 - maxUint64 = 1<<64 - 1 + unescapeStackBufSize = 64 + absMinInt64 = 1 << 63 + maxInt64 = absMinInt64 - 1 + maxUint64 = 1<<64 - 1 ) -const unescapeStackBufSize = 64 - // PaseStringLiteral parses a string from the given byte slice. func ParseStringLiteral(data []byte) (string, error) { var buf [unescapeStackBufSize]byte bf, err := Unescape(data, buf[:]) if err != nil { - return "", errors.New("invalid string input found while parsing string value") + return "", errInvalidStringInput } return string(bf), nil @@ -36,150 +31,6 @@ func ParseBoolLiteral(data []byte) (bool, error) { case bytes.Equal(data, falseLiteral): return false, nil default: - return false, errors.New("JSON Error: malformed boolean value found while parsing boolean value") - } -} - -// PaseFloatLiteral parses a float64 from the given byte slice. -// -// It utilizes double-precision (64-bit) floating-point format as defined -// by the IEEE 754 standard, providing a decimal precision of approximately 15 digits. -func ParseFloatLiteral(bytes []byte) (float64, error) { - if len(bytes) == 0 { - return -1, errors.New("JSON Error: empty byte slice found while parsing float value") - } - - neg, bytes := trimNegativeSign(bytes) - - var exponentPart []byte - for i, c := range bytes { - if lower(c) == 'e' { - exponentPart = bytes[i+1:] - bytes = bytes[:i] - break - } - } - - man, exp10, err := extractMantissaAndExp10(bytes) - if err != nil { - return -1, err - } - - if len(exponentPart) > 0 { - exp, err := strconv.Atoi(string(exponentPart)) - if err != nil { - return -1, errors.New("JSON Error: invalid exponent value found while parsing float value") - } - exp10 += exp - } - - // for fast float64 conversion - f, success := el.EiselLemire64(man, exp10, neg) - if !success { - return 0, nil - } - - return f, nil -} - -func ParseIntLiteral(bytes []byte) (int64, error) { - if len(bytes) == 0 { - return 0, errors.New("JSON Error: empty byte slice found while parsing integer value") - } - - neg, bytes := trimNegativeSign(bytes) - - var n uint64 = 0 - for _, c := range bytes { - if notDigit(c) { - return 0, errors.New("JSON Error: non-digit characters found while parsing integer value") - } - - if n > maxUint64/10 { - return 0, errors.New("JSON Error: numeric value exceeds the range limit") - } - - n *= 10 - - n1 := n + uint64(c-'0') - if n1 < n { - return 0, errors.New("JSON Error: numeric value exceeds the range limit") - } - - n = n1 - } - - if n > maxInt64 { - if neg && n == absMinInt64 { - return -absMinInt64, nil - } - - return 0, errors.New("JSON Error: numeric value exceeds the range limit") + return false, errMalformedBooleanValue } - - if neg { - return -int64(n), nil - } - - return int64(n), nil -} - -// extractMantissaAndExp10 parses a byte slice representing a decimal number and extracts the mantissa and the exponent of its base-10 representation. -// It iterates through the bytes, constructing the mantissa by treating each byte as a digit. -// If a decimal point is encountered, the function keeps track of the position of the decimal point to calculate the exponent. -// The function ensures that: -// - The number contains at most one decimal point. -// - All characters in the byte slice are digits or a single decimal point. -// - The resulting mantissa does not overflow a uint64. -func extractMantissaAndExp10(bytes []byte) (uint64, int, error) { - var ( - man uint64 - exp10 int - decimalFound bool - ) - - for _, c := range bytes { - if c == dot { - if decimalFound { - return 0, 0, errors.New("JSON Error: multiple decimal points found while parsing float value") - } - decimalFound = true - continue - } - - if notDigit(c) { - return 0, 0, errors.New("JSON Error: non-digit characters found while parsing integer value") - } - - digit := uint64(c - '0') - - if man > (maxUint64-digit)/10 { - return 0, 0, errors.New("JSON Error: numeric value exceeds the range limit") - } - - man = man*10 + digit - - if decimalFound { - exp10-- - } - } - - return man, exp10, nil -} - -func trimNegativeSign(bytes []byte) (bool, []byte) { - if bytes[0] == minus { - return true, bytes[1:] - } - - return false, bytes -} - -func notDigit(c byte) bool { - return (c & 0xF0) != 0x30 -} - -// lower converts a byte to lower case if it is an uppercase letter. -func lower(c byte) byte { - return c | 0x20 } diff --git a/examples/gno.land/p/demo/json/parser_test.gno b/examples/gno.land/p/demo/json/parser_test.gno index 078aa048a61..a05e313f67b 100644 --- a/examples/gno.land/p/demo/json/parser_test.gno +++ b/examples/gno.land/p/demo/json/parser_test.gno @@ -64,125 +64,3 @@ func TestParseBoolLiteral(t *testing.T) { } } } - -func TestParseFloatLiteral(t *testing.T) { - tests := []struct { - input string - expected float64 - }{ - {"123", 123}, - {"-123", -123}, - {"123.456", 123.456}, - {"-123.456", -123.456}, - {"12345678.1234567890", 12345678.1234567890}, - {"-12345678.09123456789", -12345678.09123456789}, - {"0.123", 0.123}, - {"-0.123", -0.123}, - {"", -1}, - {"abc", -1}, - {"123.45.6", -1}, - {"999999999999999999999", -1}, - } - - for _, tt := range tests { - t.Run(tt.input, func(t *testing.T) { - got, _ := ParseFloatLiteral([]byte(tt.input)) - if got != tt.expected { - t.Errorf("ParseFloatLiteral(%s): got %v, want %v", tt.input, got, tt.expected) - } - }) - } -} - -func TestParseFloatWithScientificNotation(t *testing.T) { - tests := []struct { - input string - expected float64 - }{ - {"1e6", 1000000}, - {"1E6", 1000000}, - {"1.23e10", 1.23e10}, - {"1.23E10", 1.23e10}, - {"-1.23e10", -1.23e10}, - {"-1.23E10", -1.23e10}, - {"2.45e-8", 2.45e-8}, - {"2.45E-8", 2.45e-8}, - {"-2.45e-8", -2.45e-8}, - {"-2.45E-8", -2.45e-8}, - {"5e0", 5}, - {"-5e0", -5}, - {"5E+0", 5}, - {"5e+1", 50}, - {"1e-1", 0.1}, - {"1E-1", 0.1}, - {"-1e-1", -0.1}, - {"-1E-1", -0.1}, - } - - for _, tt := range tests { - t.Run(tt.input, func(t *testing.T) { - got, err := ParseFloatLiteral([]byte(tt.input)) - if got != tt.expected { - t.Errorf("ParseFloatLiteral(%s): got %v, want %v", tt.input, got, tt.expected) - } - - if err != nil { - t.Errorf("ParseFloatLiteral(%s): got error %v", tt.input, err) - } - }) - } -} - -func TestParseFloat_May_Interoperability_Problem(t *testing.T) { - tests := []struct { - input string - shouldErr bool - }{ - {"3.141592653589793238462643383279", true}, - {"1E400", false}, // TODO: should error - } - - for _, tt := range tests { - t.Run(tt.input, func(t *testing.T) { - _, err := ParseFloatLiteral([]byte(tt.input)) - if tt.shouldErr && err == nil { - t.Errorf("ParseFloatLiteral(%s): expected error, but not error", tt.input) - } - }) - } -} - -func TestParseIntLiteral(t *testing.T) { - tests := []struct { - input string - expected int64 - }{ - {"0", 0}, - {"1", 1}, - {"-1", -1}, - {"12345", 12345}, - {"-12345", -12345}, - {"9223372036854775807", 9223372036854775807}, - {"-9223372036854775808", -9223372036854775808}, - {"-92233720368547758081", 0}, - {"18446744073709551616", 0}, - {"9223372036854775808", 0}, - {"-9223372036854775809", 0}, - {"", 0}, - {"abc", 0}, - {"12345x", 0}, - {"123e5", 0}, - {"9223372036854775807x", 0}, - {"27670116110564327410", 0}, - {"-27670116110564327410", 0}, - } - - for _, tt := range tests { - t.Run(tt.input, func(t *testing.T) { - got, _ := ParseIntLiteral([]byte(tt.input)) - if got != tt.expected { - t.Errorf("ParseIntLiteral(%s): got %v, want %v", tt.input, got, tt.expected) - } - }) - } -} diff --git a/examples/gno.land/p/demo/json/ryu/License b/examples/gno.land/p/demo/json/ryu/License deleted file mode 100644 index 55beeadce54..00000000000 --- a/examples/gno.land/p/demo/json/ryu/License +++ /dev/null @@ -1,21 +0,0 @@ -# Apache License - -Copyright 2018 Ulf Adams -Modifications copyright 2019 Caleb Spare - -The contents of this file may be used under the terms of the Apache License, -Version 2.0. - - (See accompanying file LICENSE or copy at - ) - -Unless required by applicable law or agreed to in writing, this software -is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. - -The code in this file is part of a Go translation of the C code originally written by -Ulf Adams, which can be found at . The original source -code is licensed under the Apache License 2.0. This code is a derivative work thereof, -adapted and modified to meet the specifications of the Gno language project. - -Please note that the modifications are also under the Apache License 2.0 unless otherwise specified. diff --git a/examples/gno.land/p/demo/json/ryu/floatconv.gno b/examples/gno.land/p/demo/json/ryu/floatconv.gno deleted file mode 100644 index 617141d2734..00000000000 --- a/examples/gno.land/p/demo/json/ryu/floatconv.gno +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2018 Ulf Adams -// Modifications copyright 2019 Caleb Spare -// -// The contents of this file may be used under the terms of the Apache License, -// Version 2.0. -// -// (See accompanying file LICENSE or copy at -// http://www.apache.org/licenses/LICENSE-2.0) -// -// Unless required by applicable law or agreed to in writing, this software -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. -// -// The code in this file is part of a Go translation of the C code originally written by -// Ulf Adams, which can be found at https://github.com/ulfjack/ryu. The original source -// code is licensed under the Apache License 2.0. This code is a derivative work thereof, -// adapted and modified to meet the specifications of the Gno language project. -// -// original Go implementation can be found at https://github.com/cespare/ryu. -// -// Please note that the modifications are also under the Apache License 2.0 unless -// otherwise specified. - -// Package ryu implements the Ryu algorithm for quickly converting floating -// point numbers into strings. -package ryu - -import ( - "math" -) - -const ( - mantBits32 = 23 - expBits32 = 8 - bias32 = 127 - - mantBits64 = 52 - expBits64 = 11 - bias64 = 1023 -) - -// FormatFloat64 converts a 64-bit floating point number f to a string. -// It behaves like strconv.FormatFloat(f, 'e', -1, 64). -func FormatFloat64(f float64) string { - b := make([]byte, 0, 24) - b = AppendFloat64(b, f) - return string(b) -} - -// AppendFloat64 appends the string form of the 64-bit floating point number f, -// as generated by FormatFloat64, to b and returns the extended buffer. -func AppendFloat64(b []byte, f float64) []byte { - // Step 1: Decode the floating-point number. - // Unify normalized and subnormal cases. - u := math.Float64bits(f) - neg := u>>(mantBits64+expBits64) != 0 - mant := u & (uint64(1)<> mantBits64) & (uint64(1)<= 0, "e >= 0") - assert(e <= 1650, "e <= 1650") - return (uint32(e) * 78913) >> 18 -} - -// log10Pow5 returns floor(log_10(5^e)). -func log10Pow5(e int32) uint32 { - // The first value this approximation fails for is 5^2621 - // which is just greater than 10^1832. - assert(e >= 0, "e >= 0") - assert(e <= 2620, "e <= 2620") - return (uint32(e) * 732923) >> 20 -} - -// pow5Bits returns ceil(log_2(5^e)), or else 1 if e==0. -func pow5Bits(e int32) int32 { - // This approximation works up to the point that the multiplication - // overflows at e = 3529. If the multiplication were done in 64 bits, - // it would fail at 5^4004 which is just greater than 2^9297. - assert(e >= 0, "e >= 0") - assert(e <= 3528, "e <= 3528") - return int32((uint32(e)*1217359)>>19 + 1) -} diff --git a/examples/gno.land/p/demo/json/ryu/floatconv_test.gno b/examples/gno.land/p/demo/json/ryu/floatconv_test.gno deleted file mode 100644 index 7f01d4034f7..00000000000 --- a/examples/gno.land/p/demo/json/ryu/floatconv_test.gno +++ /dev/null @@ -1,33 +0,0 @@ -package ryu - -import ( - "math" - "testing" -) - -func TestFormatFloat64(t *testing.T) { - tests := []struct { - name string - value float64 - expected string - }{ - {"positive infinity", math.Inf(1), "+Inf"}, - {"negative infinity", math.Inf(-1), "-Inf"}, - {"NaN", math.NaN(), "NaN"}, - {"zero", 0.0, "0e+00"}, - {"negative zero", -0.0, "0e+00"}, - {"positive number", 3.14159, "3.14159e+00"}, - {"negative number", -2.71828, "-2.71828e+00"}, - {"very small number", 1.23e-20, "1.23e-20"}, - {"very large number", 1.23e+20, "1.23e+20"}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - result := FormatFloat64(test.value) - if result != test.expected { - t.Errorf("FormatFloat64(%v) = %q, expected %q", test.value, result, test.expected) - } - }) - } -} diff --git a/examples/gno.land/p/demo/json/ryu/gno.mod b/examples/gno.land/p/demo/json/ryu/gno.mod deleted file mode 100644 index 86a1988b052..00000000000 --- a/examples/gno.land/p/demo/json/ryu/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/json/ryu diff --git a/examples/gno.land/p/demo/json/ryu/ryu64.gno b/examples/gno.land/p/demo/json/ryu/ryu64.gno deleted file mode 100644 index 249e3d0f526..00000000000 --- a/examples/gno.land/p/demo/json/ryu/ryu64.gno +++ /dev/null @@ -1,344 +0,0 @@ -package ryu - -import ( - "math/bits" -) - -type uint128 struct { - lo uint64 - hi uint64 -} - -// dec64 is a floating decimal type representing m * 10^e. -type dec64 struct { - m uint64 - e int32 -} - -func (d dec64) append(b []byte, neg bool) []byte { - // Step 5: Print the decimal representation. - if neg { - b = append(b, '-') - } - - out := d.m - outLen := decimalLen64(out) - bufLen := outLen - if bufLen > 1 { - bufLen++ // extra space for '.' - } - - // Print the decimal digits. - n := len(b) - if cap(b)-len(b) >= bufLen { - // Avoid function call in the common case. - b = b[:len(b)+bufLen] - } else { - b = append(b, make([]byte, bufLen)...) - } - - // Avoid expensive 64-bit divisions. - // We have at most 17 digits, and uint32 can store 9 digits. - // If the output doesn't fit into a uint32, cut off 8 digits - // so the rest will fit into a uint32. - var i int - if out>>32 > 0 { - var out32 uint32 - out, out32 = out/1e8, uint32(out%1e8) - for ; i < 8; i++ { - b[n+outLen-i] = '0' + byte(out32%10) - out32 /= 10 - } - } - out32 := uint32(out) - for ; i < outLen-1; i++ { - b[n+outLen-i] = '0' + byte(out32%10) - out32 /= 10 - } - b[n] = '0' + byte(out32%10) - - // Print the '.' if needed. - if outLen > 1 { - b[n+1] = '.' - } - - // Print the exponent. - b = append(b, 'e') - exp := d.e + int32(outLen) - 1 - if exp < 0 { - b = append(b, '-') - exp = -exp - } else { - // Unconditionally print a + here to match strconv's formatting. - b = append(b, '+') - } - // Always print at least two digits to match strconv's formatting. - d2 := exp % 10 - exp /= 10 - d1 := exp % 10 - d0 := exp / 10 - if d0 > 0 { - b = append(b, '0'+byte(d0)) - } - b = append(b, '0'+byte(d1), '0'+byte(d2)) - - return b -} - -func float64ToDecimalExactInt(mant, exp uint64) (d dec64, ok bool) { - e := exp - bias64 - if e > mantBits64 { - return d, false - } - shift := mantBits64 - e - mant |= 1 << mantBits64 // implicit 1 - d.m = mant >> shift - if d.m<= 0 { - // This expression is slightly faster than max(0, log10Pow2(e2) - 1). - q := log10Pow2(e2) - boolToUint32(e2 > 3) - e10 = int32(q) - k := pow5InvNumBits64 + pow5Bits(int32(q)) - 1 - i := -e2 + int32(q) + k - mul := pow5InvSplit64[q] - vr = mulShift64(4*m2, mul, i) - vp = mulShift64(4*m2+2, mul, i) - vm = mulShift64(4*m2-1-mmShift, mul, i) - if q <= 21 { - // This should use q <= 22, but I think 21 is also safe. - // Smaller values may still be safe, but it's more - // difficult to reason about them. Only one of mp, mv, - // and mm can be a multiple of 5, if any. - if mv%5 == 0 { - vrIsTrailingZeros = multipleOfPowerOfFive64(mv, q) - } else if acceptBounds { - // Same as min(e2 + (^mm & 1), pow5Factor64(mm)) >= q - // <=> e2 + (^mm & 1) >= q && pow5Factor64(mm) >= q - // <=> true && pow5Factor64(mm) >= q, since e2 >= q. - vmIsTrailingZeros = multipleOfPowerOfFive64(mv-1-mmShift, q) - } else if multipleOfPowerOfFive64(mv+2, q) { - vp-- - } - } - } else { - // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). - q := log10Pow5(-e2) - boolToUint32(-e2 > 1) - e10 = int32(q) + e2 - i := -e2 - int32(q) - k := pow5Bits(i) - pow5NumBits64 - j := int32(q) - k - mul := pow5Split64[i] - vr = mulShift64(4*m2, mul, j) - vp = mulShift64(4*m2+2, mul, j) - vm = mulShift64(4*m2-1-mmShift, mul, j) - if q <= 1 { - // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. - // mv = 4 * m2, so it always has at least two trailing 0 bits. - vrIsTrailingZeros = true - if acceptBounds { - // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. - vmIsTrailingZeros = mmShift == 1 - } else { - // mp = mv + 2, so it always has at least one trailing 0 bit. - vp-- - } - } else if q < 63 { // TODO(ulfjack/cespare): Use a tighter bound here. - // We need to compute min(ntz(mv), pow5Factor64(mv) - e2) >= q - 1 - // <=> ntz(mv) >= q - 1 && pow5Factor64(mv) - e2 >= q - 1 - // <=> ntz(mv) >= q - 1 (e2 is negative and -e2 >= q) - // <=> (mv & ((1 << (q - 1)) - 1)) == 0 - // We also need to make sure that the left shift does not overflow. - vrIsTrailingZeros = multipleOfPowerOfTwo64(mv, q-1) - } - } - - // Step 4: Find the shortest decimal representation - // in the interval of valid representations. - var removed int32 - var lastRemovedDigit uint8 - var out uint64 - // On average, we remove ~2 digits. - if vmIsTrailingZeros || vrIsTrailingZeros { - // General case, which happens rarely (~0.7%). - for { - vpDiv10 := vp / 10 - vmDiv10 := vm / 10 - if vpDiv10 <= vmDiv10 { - break - } - vmMod10 := vm % 10 - vrDiv10 := vr / 10 - vrMod10 := vr % 10 - vmIsTrailingZeros = vmIsTrailingZeros && vmMod10 == 0 - vrIsTrailingZeros = vrIsTrailingZeros && lastRemovedDigit == 0 - lastRemovedDigit = uint8(vrMod10) - vr = vrDiv10 - vp = vpDiv10 - vm = vmDiv10 - removed++ - } - if vmIsTrailingZeros { - for { - vmDiv10 := vm / 10 - vmMod10 := vm % 10 - if vmMod10 != 0 { - break - } - vpDiv10 := vp / 10 - vrDiv10 := vr / 10 - vrMod10 := vr % 10 - vrIsTrailingZeros = vrIsTrailingZeros && lastRemovedDigit == 0 - lastRemovedDigit = uint8(vrMod10) - vr = vrDiv10 - vp = vpDiv10 - vm = vmDiv10 - removed++ - } - } - if vrIsTrailingZeros && lastRemovedDigit == 5 && vr%2 == 0 { - // Round even if the exact number is .....50..0. - lastRemovedDigit = 4 - } - out = vr - // We need to take vr + 1 if vr is outside bounds - // or we need to round up. - if (vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || lastRemovedDigit >= 5 { - out++ - } - } else { - // Specialized for the common case (~99.3%). - // Percentages below are relative to this. - roundUp := false - for vp/100 > vm/100 { - // Optimization: remove two digits at a time (~86.2%). - roundUp = vr%100 >= 50 - vr /= 100 - vp /= 100 - vm /= 100 - removed += 2 - } - // Loop iterations below (approximately), without optimization above: - // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% - // Loop iterations below (approximately), with optimization above: - // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% - for vp/10 > vm/10 { - roundUp = vr%10 >= 5 - vr /= 10 - vp /= 10 - vm /= 10 - removed++ - } - // We need to take vr + 1 if vr is outside bounds - // or we need to round up. - out = vr + boolToUint64(vr == vm || roundUp) - } - - return dec64{m: out, e: e10 + removed} -} - -var powersOf10 = [...]uint64{ - 1e0, - 1e1, - 1e2, - 1e3, - 1e4, - 1e5, - 1e6, - 1e7, - 1e8, - 1e9, - 1e10, - 1e11, - 1e12, - 1e13, - 1e14, - 1e15, - 1e16, - 1e17, - // We only need to find the length of at most 17 digit numbers. -} - -func decimalLen64(u uint64) int { - // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 - log2 := 64 - bits.LeadingZeros64(u) - 1 - t := (log2 + 1) * 1233 >> 12 - return t - boolToInt(u < powersOf10[t]) + 1 -} - -func mulShift64(m uint64, mul uint128, shift int32) uint64 { - hihi, hilo := bits.Mul64(m, mul.hi) - lohi, _ := bits.Mul64(m, mul.lo) - sum := uint128{hi: hihi, lo: lohi + hilo} - if sum.lo < lohi { - sum.hi++ // overflow - } - return shiftRight128(sum, shift-64) -} - -func shiftRight128(v uint128, shift int32) uint64 { - // The shift value is always modulo 64. - // In the current implementation of the 64-bit version - // of Ryu, the shift value is always < 64. - // (It is in the range [2, 59].) - // Check this here in case a future change requires larger shift - // values. In this case this function needs to be adjusted. - assert(shift < 64, "shift < 64") - return (v.hi << uint64(64-shift)) | (v.lo >> uint(shift)) -} - -func pow5Factor64(v uint64) uint32 { - for n := uint32(0); ; n++ { - q, r := v/5, v%5 - if r != 0 { - return n - } - v = q - } -} - -func multipleOfPowerOfFive64(v uint64, p uint32) bool { - return pow5Factor64(v) >= p -} - -func multipleOfPowerOfTwo64(v uint64, p uint32) bool { - return uint32(bits.TrailingZeros64(v)) >= p -} diff --git a/examples/gno.land/p/demo/json/ryu/table.gno b/examples/gno.land/p/demo/json/ryu/table.gno deleted file mode 100644 index fe33ad90a57..00000000000 --- a/examples/gno.land/p/demo/json/ryu/table.gno +++ /dev/null @@ -1,678 +0,0 @@ -// Code generated by running "go generate". DO NOT EDIT. - -// Copyright 2018 Ulf Adams -// Modifications copyright 2019 Caleb Spare -// -// The contents of this file may be used under the terms of the Apache License, -// Version 2.0. -// -// (See accompanying file LICENSE or copy at -// http://www.apache.org/licenses/LICENSE-2.0) -// -// Unless required by applicable law or agreed to in writing, this software -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. -// -// The code in this file is part of a Go translation of the C code written by -// Ulf Adams which may be found at https://github.com/ulfjack/ryu. That source -// code is licensed under Apache 2.0 and this code is derivative work thereof. - -package ryu - -const pow5NumBits32 = 61 - -var pow5Split32 = [...]uint64{ - 1152921504606846976, 1441151880758558720, 1801439850948198400, 2251799813685248000, - 1407374883553280000, 1759218604441600000, 2199023255552000000, 1374389534720000000, - 1717986918400000000, 2147483648000000000, 1342177280000000000, 1677721600000000000, - 2097152000000000000, 1310720000000000000, 1638400000000000000, 2048000000000000000, - 1280000000000000000, 1600000000000000000, 2000000000000000000, 1250000000000000000, - 1562500000000000000, 1953125000000000000, 1220703125000000000, 1525878906250000000, - 1907348632812500000, 1192092895507812500, 1490116119384765625, 1862645149230957031, - 1164153218269348144, 1455191522836685180, 1818989403545856475, 2273736754432320594, - 1421085471520200371, 1776356839400250464, 2220446049250313080, 1387778780781445675, - 1734723475976807094, 2168404344971008868, 1355252715606880542, 1694065894508600678, - 2117582368135750847, 1323488980084844279, 1654361225106055349, 2067951531382569187, - 1292469707114105741, 1615587133892632177, 2019483917365790221, -} - -const pow5InvNumBits32 = 59 - -var pow5InvSplit32 = [...]uint64{ - 576460752303423489, 461168601842738791, 368934881474191033, 295147905179352826, - 472236648286964522, 377789318629571618, 302231454903657294, 483570327845851670, - 386856262276681336, 309485009821345069, 495176015714152110, 396140812571321688, - 316912650057057351, 507060240091291761, 405648192073033409, 324518553658426727, - 519229685853482763, 415383748682786211, 332306998946228969, 531691198313966350, - 425352958651173080, 340282366920938464, 544451787073501542, 435561429658801234, - 348449143727040987, 557518629963265579, 446014903970612463, 356811923176489971, - 570899077082383953, 456719261665907162, 365375409332725730, -} - -const pow5NumBits64 = 121 - -var pow5Split64 = [...]uint128{ - {0, 72057594037927936}, - {0, 90071992547409920}, - {0, 112589990684262400}, - {0, 140737488355328000}, - {0, 87960930222080000}, - {0, 109951162777600000}, - {0, 137438953472000000}, - {0, 85899345920000000}, - {0, 107374182400000000}, - {0, 134217728000000000}, - {0, 83886080000000000}, - {0, 104857600000000000}, - {0, 131072000000000000}, - {0, 81920000000000000}, - {0, 102400000000000000}, - {0, 128000000000000000}, - {0, 80000000000000000}, - {0, 100000000000000000}, - {0, 125000000000000000}, - {0, 78125000000000000}, - {0, 97656250000000000}, - {0, 122070312500000000}, - {0, 76293945312500000}, - {0, 95367431640625000}, - {0, 119209289550781250}, - {4611686018427387904, 74505805969238281}, - {10376293541461622784, 93132257461547851}, - {8358680908399640576, 116415321826934814}, - {612489549322387456, 72759576141834259}, - {14600669991935148032, 90949470177292823}, - {13639151471491547136, 113686837721616029}, - {3213881284082270208, 142108547152020037}, - {4314518811765112832, 88817841970012523}, - {781462496279003136, 111022302462515654}, - {10200200157203529728, 138777878078144567}, - {13292654125893287936, 86736173798840354}, - {7392445620511834112, 108420217248550443}, - {4628871007212404736, 135525271560688054}, - {16728102434789916672, 84703294725430033}, - {7075069988205232128, 105879118406787542}, - {18067209522111315968, 132348898008484427}, - {8986162942105878528, 82718061255302767}, - {6621017659204960256, 103397576569128459}, - {3664586055578812416, 129246970711410574}, - {16125424340018921472, 80779356694631608}, - {1710036351314100224, 100974195868289511}, - {15972603494424788992, 126217744835361888}, - {9982877184015493120, 78886090522101180}, - {12478596480019366400, 98607613152626475}, - {10986559581596820096, 123259516440783094}, - {2254913720070624656, 77037197775489434}, - {12042014186943056628, 96296497219361792}, - {15052517733678820785, 120370621524202240}, - {9407823583549262990, 75231638452626400}, - {11759779479436578738, 94039548065783000}, - {14699724349295723422, 117549435082228750}, - {4575641699882439235, 73468396926392969}, - {10331238143280436948, 91835496157991211}, - {8302361660673158281, 114794370197489014}, - {1154580038986672043, 143492962746861268}, - {9944984561221445835, 89683101716788292}, - {12431230701526807293, 112103877145985365}, - {1703980321626345405, 140129846432481707}, - {17205888765512323542, 87581154020301066}, - {12283988920035628619, 109476442525376333}, - {1519928094762372062, 136845553156720417}, - {12479170105294952299, 85528470722950260}, - {15598962631618690374, 106910588403687825}, - {5663645234241199255, 133638235504609782}, - {17374836326682913246, 83523897190381113}, - {7883487353071477846, 104404871487976392}, - {9854359191339347308, 130506089359970490}, - {10770660513014479971, 81566305849981556}, - {13463325641268099964, 101957882312476945}, - {2994098996302961243, 127447352890596182}, - {15706369927971514489, 79654595556622613}, - {5797904354682229399, 99568244445778267}, - {2635694424925398845, 124460305557222834}, - {6258995034005762182, 77787690973264271}, - {3212057774079814824, 97234613716580339}, - {17850130272881932242, 121543267145725423}, - {18073860448192289507, 75964541966078389}, - {8757267504958198172, 94955677457597987}, - {6334898362770359811, 118694596821997484}, - {13182683513586250689, 74184123013748427}, - {11866668373555425458, 92730153767185534}, - {5609963430089506015, 115912692208981918}, - {17341285199088104971, 72445432630613698}, - {12453234462005355406, 90556790788267123}, - {10954857059079306353, 113195988485333904}, - {13693571323849132942, 141494985606667380}, - {17781854114260483896, 88434366004167112}, - {3780573569116053255, 110542957505208891}, - {114030942967678664, 138178696881511114}, - {4682955357782187069, 86361685550944446}, - {15077066234082509644, 107952106938680557}, - {5011274737320973344, 134940133673350697}, - {14661261756894078100, 84337583545844185}, - {4491519140835433913, 105421979432305232}, - {5614398926044292391, 131777474290381540}, - {12732371365632458552, 82360921431488462}, - {6692092170185797382, 102951151789360578}, - {17588487249587022536, 128688939736700722}, - {15604490549419276989, 80430587335437951}, - {14893927168346708332, 100538234169297439}, - {14005722942005997511, 125672792711621799}, - {15671105866394830300, 78545495444763624}, - {1142138259283986260, 98181869305954531}, - {15262730879387146537, 122727336632443163}, - {7233363790403272633, 76704585395276977}, - {13653390756431478696, 95880731744096221}, - {3231680390257184658, 119850914680120277}, - {4325643253124434363, 74906821675075173}, - {10018740084832930858, 93633527093843966}, - {3300053069186387764, 117041908867304958}, - {15897591223523656064, 73151193042065598}, - {10648616992549794273, 91438991302581998}, - {4087399203832467033, 114298739128227498}, - {14332621041645359599, 142873423910284372}, - {18181260187883125557, 89295889943927732}, - {4279831161144355331, 111619862429909666}, - {14573160988285219972, 139524828037387082}, - {13719911636105650386, 87203017523366926}, - {7926517508277287175, 109003771904208658}, - {684774848491833161, 136254714880260823}, - {7345513307948477581, 85159196800163014}, - {18405263671790372785, 106448996000203767}, - {18394893571310578077, 133061245000254709}, - {13802651491282805250, 83163278125159193}, - {3418256308821342851, 103954097656448992}, - {4272820386026678563, 129942622070561240}, - {2670512741266674102, 81214138794100775}, - {17173198981865506339, 101517673492625968}, - {3019754653622331308, 126897091865782461}, - {4193189667727651020, 79310682416114038}, - {14464859121514339583, 99138353020142547}, - {13469387883465536574, 123922941275178184}, - {8418367427165960359, 77451838296986365}, - {15134645302384838353, 96814797871232956}, - {471562554271496325, 121018497339041196}, - {9518098633274461011, 75636560836900747}, - {7285937273165688360, 94545701046125934}, - {18330793628311886258, 118182126307657417}, - {4539216990053847055, 73863828942285886}, - {14897393274422084627, 92329786177857357}, - {4786683537745442072, 115412232722321697}, - {14520892257159371055, 72132645451451060}, - {18151115321449213818, 90165806814313825}, - {8853836096529353561, 112707258517892282}, - {1843923083806916143, 140884073147365353}, - {12681666973447792349, 88052545717103345}, - {2017025661527576725, 110065682146379182}, - {11744654113764246714, 137582102682973977}, - {422879793461572340, 85988814176858736}, - {528599741826965425, 107486017721073420}, - {660749677283706782, 134357522151341775}, - {7330497575943398595, 83973451344588609}, - {13774807988356636147, 104966814180735761}, - {3383451930163631472, 131208517725919702}, - {15949715511634433382, 82005323578699813}, - {6102086334260878016, 102506654473374767}, - {3015921899398709616, 128133318091718459}, - {18025852251620051174, 80083323807324036}, - {4085571240815512351, 100104154759155046}, - {14330336087874166247, 125130193448943807}, - {15873989082562435760, 78206370905589879}, - {15230800334775656796, 97757963631987349}, - {5203442363187407284, 122197454539984187}, - {946308467778435600, 76373409087490117}, - {5794571603150432404, 95466761359362646}, - {16466586540792816313, 119333451699203307}, - {7985773578781816244, 74583407312002067}, - {5370530955049882401, 93229259140002584}, - {6713163693812353001, 116536573925003230}, - {18030785363914884337, 72835358703127018}, - {13315109668038829614, 91044198378908773}, - {2808829029766373305, 113805247973635967}, - {17346094342490130344, 142256559967044958}, - {6229622945628943561, 88910349979403099}, - {3175342663608791547, 111137937474253874}, - {13192550366365765242, 138922421842817342}, - {3633657960551215372, 86826513651760839}, - {18377130505971182927, 108533142064701048}, - {4524669058754427043, 135666427580876311}, - {9745447189362598758, 84791517238047694}, - {2958436949848472639, 105989396547559618}, - {12921418224165366607, 132486745684449522}, - {12687572408530742033, 82804216052780951}, - {11247779492236039638, 103505270065976189}, - {224666310012885835, 129381587582470237}, - {2446259452971747599, 80863492239043898}, - {12281196353069460307, 101079365298804872}, - {15351495441336825384, 126349206623506090}, - {14206370669262903769, 78968254139691306}, - {8534591299723853903, 98710317674614133}, - {15279925143082205283, 123387897093267666}, - {14161639232853766206, 77117435683292291}, - {13090363022639819853, 96396794604115364}, - {16362953778299774816, 120495993255144205}, - {12532689120651053212, 75309995784465128}, - {15665861400813816515, 94137494730581410}, - {10358954714162494836, 117671868413226763}, - {4168503687137865320, 73544917758266727}, - {598943590494943747, 91931147197833409}, - {5360365506546067587, 114913933997291761}, - {11312142901609972388, 143642417496614701}, - {9375932322719926695, 89776510935384188}, - {11719915403399908368, 112220638669230235}, - {10038208235822497557, 140275798336537794}, - {10885566165816448877, 87672373960336121}, - {18218643725697949000, 109590467450420151}, - {18161618638695048346, 136988084313025189}, - {13656854658398099168, 85617552695640743}, - {12459382304570236056, 107021940869550929}, - {1739169825430631358, 133777426086938662}, - {14922039196176308311, 83610891304336663}, - {14040862976792997485, 104513614130420829}, - {3716020665709083144, 130642017663026037}, - {4628355925281870917, 81651261039391273}, - {10397130925029726550, 102064076299239091}, - {8384727637859770284, 127580095374048864}, - {5240454773662356427, 79737559608780540}, - {6550568467077945534, 99671949510975675}, - {3576524565420044014, 124589936888719594}, - {6847013871814915412, 77868710555449746}, - {17782139376623420074, 97335888194312182}, - {13004302183924499284, 121669860242890228}, - {17351060901807587860, 76043662651806392}, - {3242082053549933210, 95054578314757991}, - {17887660622219580224, 118818222893447488}, - {11179787888887237640, 74261389308404680}, - {13974734861109047050, 92826736635505850}, - {8245046539531533005, 116033420794382313}, - {16682369133275677888, 72520887996488945}, - {7017903361312433648, 90651109995611182}, - {17995751238495317868, 113313887494513977}, - {8659630992836983623, 141642359368142472}, - {5412269370523114764, 88526474605089045}, - {11377022731581281359, 110658093256361306}, - {4997906377621825891, 138322616570451633}, - {14652906532082110942, 86451635356532270}, - {9092761128247862869, 108064544195665338}, - {2142579373455052779, 135080680244581673}, - {12868327154477877747, 84425425152863545}, - {2250350887815183471, 105531781441079432}, - {2812938609768979339, 131914726801349290}, - {6369772649532999991, 82446704250843306}, - {17185587848771025797, 103058380313554132}, - {3035240737254230630, 128822975391942666}, - {6508711479211282048, 80514359619964166}, - {17359261385868878368, 100642949524955207}, - {17087390713908710056, 125803686906194009}, - {3762090168551861929, 78627304316371256}, - {4702612710689827411, 98284130395464070}, - {15101637925217060072, 122855162994330087}, - {16356052730901744401, 76784476871456304}, - {1998321839917628885, 95980596089320381}, - {7109588318324424010, 119975745111650476}, - {13666864735807540814, 74984840694781547}, - {12471894901332038114, 93731050868476934}, - {6366496589810271835, 117163813585596168}, - {3979060368631419896, 73227383490997605}, - {9585511479216662775, 91534229363747006}, - {2758517312166052660, 114417786704683758}, - {12671518677062341634, 143022233380854697}, - {1002170145522881665, 89388895863034186}, - {10476084718758377889, 111736119828792732}, - {13095105898447972362, 139670149785990915}, - {5878598177316288774, 87293843616244322}, - {16571619758500136775, 109117304520305402}, - {11491152661270395161, 136396630650381753}, - {264441385652915120, 85247894156488596}, - {330551732066143900, 106559867695610745}, - {5024875683510067779, 133199834619513431}, - {10058076329834874218, 83249896637195894}, - {3349223375438816964, 104062370796494868}, - {4186529219298521205, 130077963495618585}, - {14145795808130045513, 81298727184761615}, - {13070558741735168987, 101623408980952019}, - {11726512408741573330, 127029261226190024}, - {7329070255463483331, 79393288266368765}, - {13773023837756742068, 99241610332960956}, - {17216279797195927585, 124052012916201195}, - {8454331864033760789, 77532508072625747}, - {5956228811614813082, 96915635090782184}, - {7445286014518516353, 121144543863477730}, - {9264989777501460624, 75715339914673581}, - {16192923240304213684, 94644174893341976}, - {1794409976670715490, 118305218616677471}, - {8039035263060279037, 73940761635423419}, - {5437108060397960892, 92425952044279274}, - {16019757112352226923, 115532440055349092}, - {788976158365366019, 72207775034593183}, - {14821278253238871236, 90259718793241478}, - {9303225779693813237, 112824648491551848}, - {11629032224617266546, 141030810614439810}, - {11879831158813179495, 88144256634024881}, - {1014730893234310657, 110180320792531102}, - {10491785653397664129, 137725400990663877}, - {8863209042587234033, 86078375619164923}, - {6467325284806654637, 107597969523956154}, - {17307528642863094104, 134497461904945192}, - {10817205401789433815, 84060913690590745}, - {18133192770664180173, 105076142113238431}, - {18054804944902837312, 131345177641548039}, - {18201782118205355176, 82090736025967524}, - {4305483574047142354, 102613420032459406}, - {14605226504413703751, 128266775040574257}, - {2210737537617482988, 80166734400358911}, - {16598479977304017447, 100208418000448638}, - {11524727934775246001, 125260522500560798}, - {2591268940807140847, 78287826562850499}, - {17074144231291089770, 97859783203563123}, - {16730994270686474309, 122324729004453904}, - {10456871419179046443, 76452955627783690}, - {3847717237119032246, 95566194534729613}, - {9421332564826178211, 119457743168412016}, - {5888332853016361382, 74661089480257510}, - {16583788103125227536, 93326361850321887}, - {16118049110479146516, 116657952312902359}, - {16991309721690548428, 72911220195563974}, - {12015765115258409727, 91139025244454968}, - {15019706394073012159, 113923781555568710}, - {9551260955736489391, 142404726944460888}, - {5969538097335305869, 89002954340288055}, - {2850236603241744433, 111253692925360069}, -} - -const pow5InvNumBits64 = 122 - -var pow5InvSplit64 = [...]uint128{ - {1, 288230376151711744}, - {3689348814741910324, 230584300921369395}, - {2951479051793528259, 184467440737095516}, - {17118578500402463900, 147573952589676412}, - {12632330341676300947, 236118324143482260}, - {10105864273341040758, 188894659314785808}, - {15463389048156653253, 151115727451828646}, - {17362724847566824558, 241785163922925834}, - {17579528692795369969, 193428131138340667}, - {6684925324752475329, 154742504910672534}, - {18074578149087781173, 247588007857076054}, - {18149011334012135262, 198070406285660843}, - {3451162622983977240, 158456325028528675}, - {5521860196774363583, 253530120045645880}, - {4417488157419490867, 202824096036516704}, - {7223339340677503017, 162259276829213363}, - {7867994130342094503, 259614842926741381}, - {2605046489531765280, 207691874341393105}, - {2084037191625412224, 166153499473114484}, - {10713157136084480204, 265845599156983174}, - {12259874523609494487, 212676479325586539}, - {13497248433629505913, 170141183460469231}, - {14216899864323388813, 272225893536750770}, - {11373519891458711051, 217780714829400616}, - {5409467098425058518, 174224571863520493}, - {4965798542738183305, 278759314981632789}, - {7661987648932456967, 223007451985306231}, - {2440241304404055250, 178405961588244985}, - {3904386087046488400, 285449538541191976}, - {17880904128604832013, 228359630832953580}, - {14304723302883865611, 182687704666362864}, - {15133127457049002812, 146150163733090291}, - {16834306301794583852, 233840261972944466}, - {9778096226693756759, 187072209578355573}, - {15201174610838826053, 149657767662684458}, - {2185786488890659746, 239452428260295134}, - {5437978005854438120, 191561942608236107}, - {15418428848909281466, 153249554086588885}, - {6222742084545298729, 245199286538542217}, - {16046240111861969953, 196159429230833773}, - {1768945645263844993, 156927543384667019}, - {10209010661905972635, 251084069415467230}, - {8167208529524778108, 200867255532373784}, - {10223115638361732810, 160693804425899027}, - {1599589762411131202, 257110087081438444}, - {4969020624670815285, 205688069665150755}, - {3975216499736652228, 164550455732120604}, - {13739044029062464211, 263280729171392966}, - {7301886408508061046, 210624583337114373}, - {13220206756290269483, 168499666669691498}, - {17462981995322520850, 269599466671506397}, - {6591687966774196033, 215679573337205118}, - {12652048002903177473, 172543658669764094}, - {9175230360419352987, 276069853871622551}, - {3650835473593572067, 220855883097298041}, - {17678063637842498946, 176684706477838432}, - {13527506561580357021, 282695530364541492}, - {3443307619780464970, 226156424291633194}, - {6443994910566282300, 180925139433306555}, - {5155195928453025840, 144740111546645244}, - {15627011115008661990, 231584178474632390}, - {12501608892006929592, 185267342779705912}, - {2622589484121723027, 148213874223764730}, - {4196143174594756843, 237142198758023568}, - {10735612169159626121, 189713759006418854}, - {12277838550069611220, 151771007205135083}, - {15955192865369467629, 242833611528216133}, - {1696107848069843133, 194266889222572907}, - {12424932722681605476, 155413511378058325}, - {1433148282581017146, 248661618204893321}, - {15903913885032455010, 198929294563914656}, - {9033782293284053685, 159143435651131725}, - {14454051669254485895, 254629497041810760}, - {11563241335403588716, 203703597633448608}, - {16629290697806691620, 162962878106758886}, - {781423413297334329, 260740604970814219}, - {4314487545379777786, 208592483976651375}, - {3451590036303822229, 166873987181321100}, - {5522544058086115566, 266998379490113760}, - {4418035246468892453, 213598703592091008}, - {10913125826658934609, 170878962873672806}, - {10082303693170474728, 273406340597876490}, - {8065842954536379782, 218725072478301192}, - {17520720807854834795, 174980057982640953}, - {5897060404116273733, 279968092772225526}, - {1028299508551108663, 223974474217780421}, - {15580034865808528224, 179179579374224336}, - {17549358155809824511, 286687326998758938}, - {2971440080422128639, 229349861599007151}, - {17134547323305344204, 183479889279205720}, - {13707637858644275364, 146783911423364576}, - {14553522944347019935, 234854258277383322}, - {4264120725993795302, 187883406621906658}, - {10789994210278856888, 150306725297525326}, - {9885293106962350374, 240490760476040522}, - {529536856086059653, 192392608380832418}, - {7802327114352668369, 153914086704665934}, - {1415676938738538420, 246262538727465495}, - {1132541550990830736, 197010030981972396}, - {15663428499760305882, 157608024785577916}, - {17682787970132668764, 252172839656924666}, - {10456881561364224688, 201738271725539733}, - {15744202878575200397, 161390617380431786}, - {17812026976236499989, 258224987808690858}, - {3181575136763469022, 206579990246952687}, - {13613306553636506187, 165263992197562149}, - {10713244041592678929, 264422387516099439}, - {12259944048016053467, 211537910012879551}, - {6118606423670932450, 169230328010303641}, - {2411072648389671274, 270768524816485826}, - {16686253377679378312, 216614819853188660}, - {13349002702143502650, 173291855882550928}, - {17669055508687693916, 277266969412081485}, - {14135244406950155133, 221813575529665188}, - {240149081334393137, 177450860423732151}, - {11452284974360759988, 283921376677971441}, - {5472479164746697667, 227137101342377153}, - {11756680961281178780, 181709681073901722}, - {2026647139541122378, 145367744859121378}, - {18000030682233437097, 232588391774594204}, - {18089373360528660001, 186070713419675363}, - {3403452244197197031, 148856570735740291}, - {16513570034941246220, 238170513177184465}, - {13210856027952996976, 190536410541747572}, - {3189987192878576934, 152429128433398058}, - {1414630693863812771, 243886605493436893}, - {8510402184574870864, 195109284394749514}, - {10497670562401807014, 156087427515799611}, - {9417575270359070576, 249739884025279378}, - {14912757845771077107, 199791907220223502}, - {4551508647133041040, 159833525776178802}, - {10971762650154775986, 255733641241886083}, - {16156107749607641435, 204586912993508866}, - {9235537384944202825, 163669530394807093}, - {11087511001168814197, 261871248631691349}, - {12559357615676961681, 209496998905353079}, - {13736834907283479668, 167597599124282463}, - {18289587036911657145, 268156158598851941}, - {10942320814787415393, 214524926879081553}, - {16132554281313752961, 171619941503265242}, - {11054691591134363444, 274591906405224388}, - {16222450902391311402, 219673525124179510}, - {12977960721913049122, 175738820099343608}, - {17075388340318968271, 281182112158949773}, - {2592264228029443648, 224945689727159819}, - {5763160197165465241, 179956551781727855}, - {9221056315464744386, 287930482850764568}, - {14755542681855616155, 230344386280611654}, - {15493782960226403247, 184275509024489323}, - {1326979923955391628, 147420407219591459}, - {9501865507812447252, 235872651551346334}, - {11290841220991868125, 188698121241077067}, - {1653975347309673853, 150958496992861654}, - {10025058185179298811, 241533595188578646}, - {4330697733401528726, 193226876150862917}, - {14532604630946953951, 154581500920690333}, - {1116074521063664381, 247330401473104534}, - {4582208431592841828, 197864321178483627}, - {14733813189500004432, 158291456942786901}, - {16195403473716186445, 253266331108459042}, - {5577625149489128510, 202613064886767234}, - {8151448934333213131, 162090451909413787}, - {16731667109675051333, 259344723055062059}, - {17074682502481951390, 207475778444049647}, - {6281048372501740465, 165980622755239718}, - {6360328581260874421, 265568996408383549}, - {8777611679750609860, 212455197126706839}, - {10711438158542398211, 169964157701365471}, - {9759603424184016492, 271942652322184754}, - {11497031554089123517, 217554121857747803}, - {16576322872755119460, 174043297486198242}, - {11764721337440549842, 278469275977917188}, - {16790474699436260520, 222775420782333750}, - {13432379759549008416, 178220336625867000}, - {3045063541568861850, 285152538601387201}, - {17193446092222730773, 228122030881109760}, - {13754756873778184618, 182497624704887808}, - {18382503128506368341, 145998099763910246}, - {3586563302416817083, 233596959622256395}, - {2869250641933453667, 186877567697805116}, - {17052795772514404226, 149502054158244092}, - {12527077977055405469, 239203286653190548}, - {17400360011128145022, 191362629322552438}, - {2852241564676785048, 153090103458041951}, - {15631632947708587046, 244944165532867121}, - {8815957543424959314, 195955332426293697}, - {18120812478965698421, 156764265941034957}, - {14235904707377476180, 250822825505655932}, - {4010026136418160298, 200658260404524746}, - {17965416168102169531, 160526608323619796}, - {2919224165770098987, 256842573317791675}, - {2335379332616079190, 205474058654233340}, - {1868303466092863352, 164379246923386672}, - {6678634360490491686, 263006795077418675}, - {5342907488392393349, 210405436061934940}, - {4274325990713914679, 168324348849547952}, - {10528270399884173809, 269318958159276723}, - {15801313949391159694, 215455166527421378}, - {1573004715287196786, 172364133221937103}, - {17274202803427156150, 275782613155099364}, - {17508711057483635243, 220626090524079491}, - {10317620031244997871, 176500872419263593}, - {12818843235250086271, 282401395870821749}, - {13944423402941979340, 225921116696657399}, - {14844887537095493795, 180736893357325919}, - {15565258844418305359, 144589514685860735}, - {6457670077359736959, 231343223497377177}, - {16234182506113520537, 185074578797901741}, - {9297997190148906106, 148059663038321393}, - {11187446689496339446, 236895460861314229}, - {12639306166338981880, 189516368689051383}, - {17490142562555006151, 151613094951241106}, - {2158786396894637579, 242580951921985771}, - {16484424376483351356, 194064761537588616}, - {9498190686444770762, 155251809230070893}, - {11507756283569722895, 248402894768113429}, - {12895553841597688639, 198722315814490743}, - {17695140702761971558, 158977852651592594}, - {17244178680193423523, 254364564242548151}, - {10105994129412828495, 203491651394038521}, - {4395446488788352473, 162793321115230817}, - {10722063196803274280, 260469313784369307}, - {1198952927958798777, 208375451027495446}, - {15716557601334680315, 166700360821996356}, - {17767794532651667857, 266720577315194170}, - {14214235626121334286, 213376461852155336}, - {7682039686155157106, 170701169481724269}, - {1223217053622520399, 273121871170758831}, - {15735968901865657612, 218497496936607064}, - {16278123936234436413, 174797997549285651}, - {219556594781725998, 279676796078857043}, - {7554342905309201445, 223741436863085634}, - {9732823138989271479, 178993149490468507}, - {815121763415193074, 286389039184749612}, - {11720143854957885429, 229111231347799689}, - {13065463898708218666, 183288985078239751}, - {6763022304224664610, 146631188062591801}, - {3442138057275642729, 234609900900146882}, - {13821756890046245153, 187687920720117505}, - {11057405512036996122, 150150336576094004}, - {6623802375033462826, 240240538521750407}, - {16367088344252501231, 192192430817400325}, - {13093670675402000985, 153753944653920260}, - {2503129006933649959, 246006311446272417}, - {13070549649772650937, 196805049157017933}, - {17835137349301941396, 157444039325614346}, - {2710778055689733971, 251910462920982955}, - {2168622444551787177, 201528370336786364}, - {5424246770383340065, 161222696269429091}, - {1300097203129523457, 257956314031086546}, - {15797473021471260058, 206365051224869236}, - {8948629602435097724, 165092040979895389}, - {3249760919670425388, 264147265567832623}, - {9978506365220160957, 211317812454266098}, - {15361502721659949412, 169054249963412878}, - {2442311466204457120, 270486799941460606}, - {16711244431931206989, 216389439953168484}, - {17058344360286875914, 173111551962534787}, - {12535955717491360170, 276978483140055660}, - {10028764573993088136, 221582786512044528}, - {15401709288678291155, 177266229209635622}, - {9885339602917624555, 283625966735416996}, - {4218922867592189321, 226900773388333597}, - {14443184738299482427, 181520618710666877}, - {4175850161155765295, 145216494968533502}, - {10370709072591134795, 232346391949653603}, - {15675264887556728482, 185877113559722882}, - {5161514280561562140, 148701690847778306}, - {879725219414678777, 237922705356445290}, - {703780175531743021, 190338164285156232}, - {11631070584651125387, 152270531428124985}, - {162968861732249003, 243632850284999977}, - {11198421533611530172, 194906280227999981}, - {5269388412147313814, 155925024182399985}, - {8431021459435702103, 249480038691839976}, - {3055468352806651359, 199584030953471981}, - {17201769941212962380, 159667224762777584}, - {16454785461715008838, 255467559620444135}, - {13163828369372007071, 204374047696355308}, - {17909760324981426303, 163499238157084246}, - {2830174816776909822, 261598781051334795}, - {2264139853421527858, 209279024841067836}, - {16568707141704863579, 167423219872854268}, - {4373838538276319787, 267877151796566830}, - {3499070830621055830, 214301721437253464}, - {6488605479238754987, 171441377149802771}, - {3003071137298187333, 274306203439684434}, - {6091805724580460189, 219444962751747547}, - {15941491023890099121, 175555970201398037}, - {10748990379256517301, 280889552322236860}, - {8599192303405213841, 224711641857789488}, - {14258051472207991719, 179769313486231590}, -} From e9640ef55dc3d0bb0022a50557550bf6316b9953 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:21:43 +0100 Subject: [PATCH 12/34] test(gno.land): add unit tests for sdk/vm.vmHandler (#2459) ```console gnome$ go test -v ./sdk/vm -run TestVmHandler === RUN TestVmHandlerQuery_Eval === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.Echo("hello") === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.PubString === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.ConstString === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.pvString === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.counter === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.GetCounter() === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.Inc() === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.pvEcho("hello") === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.1337 === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.13.37 === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.float64(1337) === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst.Foo() === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.myStruct === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.Inc === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.fn()("hi") === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.sl === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.sl[1] === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.println(1234) 1234 === RUN TestVmHandlerQuery_Eval/gno.land/r/hello === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.doesnotexist === RUN TestVmHandlerQuery_Eval/gno.land/r/doesnotexist.Foo === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.Panic() === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.panic("bar") === RUN TestVmHandlerQuery_Eval/gno.land/r/hello.sl[6] --- PASS: TestVmHandlerQuery_Eval (0.03s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Echo("hello") (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.PubString (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.ConstString (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.pvString (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.counter (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.GetCounter() (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Inc() (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.pvEcho("hello") (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.1337 (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.13.37 (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.float64(1337) (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst.Foo() (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStruct (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Inc (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.fn()("hi") (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl[1] (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.println(1234) (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.doesnotexist (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/doesnotexist.Foo (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Panic() (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.panic("bar") (0.00s) --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl[6] (0.00s) === RUN TestVmHandlerQuery_Funcs === RUN TestVmHandlerQuery_Funcs/gno.land/r/hello === RUN TestVmHandlerQuery_Funcs/gno.land/r/doesnotexist === RUN TestVmHandlerQuery_Funcs/std === RUN TestVmHandlerQuery_Funcs/strings --- PASS: TestVmHandlerQuery_Funcs (0.00s) --- PASS: TestVmHandlerQuery_Funcs/gno.land/r/hello (0.00s) --- PASS: TestVmHandlerQuery_Funcs/gno.land/r/doesnotexist (0.00s) --- PASS: TestVmHandlerQuery_Funcs/std (0.00s) --- PASS: TestVmHandlerQuery_Funcs/strings (0.00s) === RUN TestVmHandlerQuery_File === RUN TestVmHandlerQuery_File/gno.land/r/hello/hello.gno === RUN TestVmHandlerQuery_File/gno.land/r/hello/README.md === RUN TestVmHandlerQuery_File/gno.land/r/hello/doesnotexist.gno === RUN TestVmHandlerQuery_File/gno.land/r/hello === RUN TestVmHandlerQuery_File/gno.land/r/doesnotexist === RUN TestVmHandlerQuery_File/gno.land/r/doesnotexist/hello.gno --- PASS: TestVmHandlerQuery_File (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/hello.gno (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/README.md (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/doesnotexist.gno (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/hello (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/doesnotexist (0.00s) --- PASS: TestVmHandlerQuery_File/gno.land/r/doesnotexist/hello.gno (0.00s) PASS ok github.com/gnolang/gno/gno.land/pkg/sdk/vm (cached) ``` --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gno.land/pkg/sdk/vm/common_test.go | 4 +- gno.land/pkg/sdk/vm/gas_test.go | 3 +- gno.land/pkg/sdk/vm/handler_test.go | 274 ++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 7380d3e0f72..8b1b7d909c1 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -23,6 +23,7 @@ type testEnv struct { vmk *VMKeeper bank bankm.BankKeeper acck authm.AccountKeeper + vmh vmHandler } func setupTestEnv() testEnv { @@ -62,6 +63,7 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { } vmk.CommitGnoTransactionStore(stdlibCtx) mcw.MultiWrite() + vmh := NewHandler(vmk) - return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck} + return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck, vmh: vmh} } diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index a199f12898a..ff924610627 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -143,7 +143,6 @@ func setupAddPkg(success bool) (sdk.Context, sdk.Tx, vmHandler) { ctx := env.ctx // conduct base gas meter tests from a non-genesis block since genesis block use infinite gas meter instead. ctx = ctx.WithBlockHeader(&bft.Header{Height: int64(1)}) - vmHandler := NewHandler(env.vmk) // Create an account with 10M ugnot (10gnot) addr := crypto.AddressFromPreimage([]byte("test1")) acc := env.acck.NewAccountWithAddress(ctx, addr) @@ -183,5 +182,5 @@ func Echo() UnknowType { fee := std.NewFee(500000, std.MustParseCoin(ugnot.ValueString(1))) tx := std.NewTx(msgs, fee, []std.Signature{}, "") - return ctx, tx, vmHandler + return ctx, tx, env.vmh } diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 38ac8fa61b9..7e029f4cacb 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -1,8 +1,13 @@ package vm import ( + "fmt" "testing" + "github.com/gnolang/gno/gnovm" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" ) @@ -48,3 +53,272 @@ func Test_parseQueryEval_panic(t *testing.T) { parseQueryEvalData("gno.land/r/demo/users") }) } + +func TestVmHandlerQuery_Eval(t *testing.T) { + tt := []struct { + input []byte + expectedResult string + expectedResultMatch string + expectedErrorMatch string + expectedPanicMatch string + // XXX: expectedEvents + }{ + // valid queries + {input: []byte(`gno.land/r/hello.Echo("hello")`), expectedResult: `("echo:hello" string)`}, + {input: []byte(`gno.land/r/hello.caller()`), expectedResult: `("" std.Address)`}, // FIXME? + {input: []byte(`gno.land/r/hello.GetHeight()`), expectedResult: `(0 int64)`}, + // {input: []byte(`gno.land/r/hello.time.RFC3339`), expectedResult: `test`}, // not working, but should we care? + {input: []byte(`gno.land/r/hello.PubString`), expectedResult: `("public string" string)`}, + {input: []byte(`gno.land/r/hello.ConstString`), expectedResult: `("const string" string)`}, + {input: []byte(`gno.land/r/hello.pvString`), expectedResult: `("private string" string)`}, + {input: []byte(`gno.land/r/hello.counter`), expectedResult: `(42 int)`}, + {input: []byte(`gno.land/r/hello.GetCounter()`), expectedResult: `(42 int)`}, + {input: []byte(`gno.land/r/hello.Inc()`), expectedResult: `(43 int)`}, + {input: []byte(`gno.land/r/hello.pvEcho("hello")`), expectedResult: `("pvecho:hello" string)`}, + {input: []byte(`gno.land/r/hello.1337`), expectedResult: `(1337 int)`}, + {input: []byte(`gno.land/r/hello.13.37`), expectedResult: `(13.37 float64)`}, + {input: []byte(`gno.land/r/hello.float64(1337)`), expectedResult: `(1337 float64)`}, + {input: []byte(`gno.land/r/hello.myStructInst`), expectedResult: `(struct{(1000 int)} gno.land/r/hello.myStruct)`}, + {input: []byte(`gno.land/r/hello.myStructInst.Foo()`), expectedResult: `("myStruct.Foo" string)`}, + {input: []byte(`gno.land/r/hello.myStruct`), expectedResultMatch: `\(typeval{gno.land/r/hello.myStruct \(0x.*\)} type{}\)`}, + {input: []byte(`gno.land/r/hello.Inc`), expectedResult: `(Inc func()( int))`}, + {input: []byte(`gno.land/r/hello.fn()("hi")`), expectedResult: `("echo:hi" string)`}, + {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? + + // panics + {input: []byte(`gno.land/r/hello`), expectedPanicMatch: `expected . syntax in query input data`}, + + // errors + {input: []byte(`gno.land/r/hello.doesnotexist`), expectedErrorMatch: `^/:0:0: name doesnotexist not declared:`}, // multiline error + {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\)$`}, + } + + for _, tc := range tt { + name := string(tc.input) + t.Run(name, func(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + vmHandler := env.vmh + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + + // Create test package. + files := []*gnovm.MemFile{ + {"hello.gno", ` +package hello + +import "std" +import "time" + +var _ = time.RFC3339 +func caller() std.Address { return std.GetOrigCaller() } +var GetHeight = std.GetHeight +var sl = []int{1,2,3,4,5} +func fn() func(string) string { return Echo } +type myStruct struct{a int} +var myStructInst = myStruct{a: 1000} +func (ms myStruct) Foo() string { return "myStruct.Foo" } +func Panic() { panic("foo") } +var counter int = 42 +var pvString = "private string" +var PubString = "public string" +const ConstString = "const string" +func Echo(msg string) string { return "echo:"+msg } +func GetCounter() int { return counter } +func Inc() int { counter += 1; return counter } +func pvEcho(msg string) string { return "pvecho:"+msg } +`}, + } + pkgPath := "gno.land/r/hello" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + assert.NoError(t, err) + env.vmk.CommitGnoTransactionStore(ctx) + + req := abci.RequestQuery{ + Path: "vm/qeval", + Data: tc.input, + } + + defer func() { + if r := recover(); r != nil { + output := fmt.Sprintf("%v", r) + assert.Regexp(t, tc.expectedPanicMatch, output) + } else { + assert.Equal(t, tc.expectedPanicMatch, "", "should not panic") + } + }() + res := vmHandler.Query(env.ctx, req) + if tc.expectedPanicMatch == "" { + if tc.expectedErrorMatch == "" { + assert.True(t, res.IsOK(), "should not have error") + if tc.expectedResult != "" { + assert.Equal(t, string(res.Data), tc.expectedResult) + } + if tc.expectedResultMatch != "" { + assert.Regexp(t, tc.expectedResultMatch, string(res.Data)) + } + } else { + assert.False(t, res.IsOK(), "should have an error") + errmsg := res.Error.Error() + assert.Regexp(t, tc.expectedErrorMatch, errmsg) + } + } + }) + } +} + +func TestVmHandlerQuery_Funcs(t *testing.T) { + tt := []struct { + input []byte + expectedResult string + expectedErrorMatch string + }{ + // valid queries + {input: []byte(`gno.land/r/hello`), expectedResult: `[{"FuncName":"Panic","Params":null,"Results":null},{"FuncName":"Echo","Params":[{"Name":"msg","Type":"string","Value":""}],"Results":[{"Name":"_","Type":"string","Value":""}]},{"FuncName":"GetCounter","Params":null,"Results":[{"Name":"_","Type":"int","Value":""}]},{"FuncName":"Inc","Params":null,"Results":[{"Name":"_","Type":"int","Value":""}]}]`}, + {input: []byte(`gno.land/r/doesnotexist`), expectedErrorMatch: `invalid package path`}, + {input: []byte(`std`), expectedErrorMatch: `invalid package path`}, + {input: []byte(`strings`), expectedErrorMatch: `invalid package path`}, + } + + for _, tc := range tt { + name := string(tc.input) + t.Run(name, func(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + vmHandler := env.vmh + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + + // Create test package. + files := []*gnovm.MemFile{ + {"hello.gno", ` +package hello + +var sl = []int{1,2,3,4,5} +func fn() func(string) string { return Echo } +type myStruct struct{a int} +var myStructInst = myStruct{a: 1000} +func (ms myStruct) Foo() string { return "myStruct.Foo" } +func Panic() { panic("foo") } +var counter int = 42 +var pvString = "private string" +var PubString = "public string" +const ConstString = "const string" +func Echo(msg string) string { return "echo:"+msg } +func GetCounter() int { return counter } +func Inc() int { counter += 1; return counter } +func pvEcho(msg string) string { return "pvecho:"+msg } +`}, + } + pkgPath := "gno.land/r/hello" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + assert.NoError(t, err) + + req := abci.RequestQuery{ + Path: "vm/qfuncs", + Data: tc.input, + } + + res := vmHandler.Query(env.ctx, req) + if tc.expectedErrorMatch == "" { + assert.True(t, res.IsOK(), "should not have error") + if tc.expectedResult != "" { + assert.Equal(t, string(res.Data), tc.expectedResult) + } + } else { + assert.False(t, res.IsOK(), "should have an error") + errmsg := res.Error.Error() + assert.Regexp(t, tc.expectedErrorMatch, errmsg) + } + }) + } +} + +func TestVmHandlerQuery_File(t *testing.T) { + tt := []struct { + input []byte + expectedResult string + expectedResultMatch string + expectedErrorMatch string + expectedPanicMatch string + // XXX: expectedEvents + }{ + // valid queries + {input: []byte(`gno.land/r/hello/hello.gno`), expectedResult: "package hello\n\nfunc Hello() string { return \"hello\" }\n"}, + {input: []byte(`gno.land/r/hello/README.md`), expectedResult: "# Hello"}, + {input: []byte(`gno.land/r/hello/doesnotexist.gno`), expectedErrorMatch: `file "gno.land/r/hello/doesnotexist.gno" is not available`}, + {input: []byte(`gno.land/r/hello`), expectedResult: "README.md\nhello.gno"}, + {input: []byte(`gno.land/r/doesnotexist`), expectedErrorMatch: `package "gno.land/r/doesnotexist" is not available`}, + {input: []byte(`gno.land/r/doesnotexist/hello.gno`), expectedErrorMatch: `file "gno.land/r/doesnotexist/hello.gno" is not available`}, + } + + for _, tc := range tt { + name := string(tc.input) + t.Run(name, func(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + vmHandler := env.vmh + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + + // Create test package. + files := []*gnovm.MemFile{ + {"README.md", "# Hello"}, + {"hello.gno", "package hello\n\nfunc Hello() string { return \"hello\" }\n"}, + } + pkgPath := "gno.land/r/hello" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + assert.NoError(t, err) + + req := abci.RequestQuery{ + Path: "vm/qfile", + Data: tc.input, + } + + defer func() { + if r := recover(); r != nil { + output := fmt.Sprintf("%v", r) + assert.Regexp(t, tc.expectedPanicMatch, output) + } else { + assert.Equal(t, "", tc.expectedPanicMatch, "should not panic") + } + }() + res := vmHandler.Query(env.ctx, req) + if tc.expectedErrorMatch == "" { + assert.True(t, res.IsOK(), "should not have error") + if tc.expectedResult != "" { + assert.Equal(t, string(res.Data), tc.expectedResult) + } + if tc.expectedResultMatch != "" { + assert.Regexp(t, tc.expectedResultMatch, string(res.Data)) + } + } else { + assert.False(t, res.IsOK(), "should have an error") + errmsg := res.Error.Error() + assert.Regexp(t, tc.expectedErrorMatch, errmsg) + } + }) + } +} From 8ec556e603ed5c21796409b5b487cbf9bf28751f Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:56:47 +0100 Subject: [PATCH 13/34] fix(gnovm): forbid star expression when value is nil (#3053) close: #3052
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- gnovm/pkg/gnolang/preprocess.go | 3 +++ gnovm/tests/files/ptr10.gno | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 gnovm/tests/files/ptr10.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 10c55979520..a7a1e15bbf3 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1760,6 +1760,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *StarExpr: xt := evalStaticTypeOf(store, last, n.X) + if xt == nil { + panic(fmt.Sprintf("invalid operation: cannot indirect nil")) + } if xt.Kind() != PointerKind && xt.Kind() != TypeKind { panic(fmt.Sprintf("invalid operation: cannot indirect %s (variable of type %s)", n.X.String(), xt.String())) } diff --git a/gnovm/tests/files/ptr10.gno b/gnovm/tests/files/ptr10.gno new file mode 100644 index 00000000000..807fc149808 --- /dev/null +++ b/gnovm/tests/files/ptr10.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(*nil) +} + +// Error: +// main/files/ptr10.gno:4:10: invalid operation: cannot indirect nil From 287335461efc8857b8a56ab0d9749bc3c6244fdd Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:12:41 +0100 Subject: [PATCH 14/34] test(gnovm): indented json on filetests's Events: directives (#3055) Closer to the current `// Realm:` output, and more git-friendly. Bigger example in #3003. Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../gno.land/r/demo/event/z1_filetest.gno | 25 ++++++++++++++++++- .../testdata/gno_test/filetest_events.txtar | 20 ++++++++++++++- gnovm/tests/file.go | 2 +- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/demo/event/z1_filetest.gno b/examples/gno.land/r/demo/event/z1_filetest.gno index 1fcfa1a0e4f..b138aa4351c 100644 --- a/examples/gno.land/r/demo/event/z1_filetest.gno +++ b/examples/gno.land/r/demo/event/z1_filetest.gno @@ -8,4 +8,27 @@ func main() { } // Events: -// [{"type":"TAG","attrs":[{"key":"key","value":"foo"}],"pkg_path":"gno.land/r/demo/event","func":"Emit"},{"type":"TAG","attrs":[{"key":"key","value":"bar"}],"pkg_path":"gno.land/r/demo/event","func":"Emit"}] +// [ +// { +// "type": "TAG", +// "attrs": [ +// { +// "key": "key", +// "value": "foo" +// } +// ], +// "pkg_path": "gno.land/r/demo/event", +// "func": "Emit" +// }, +// { +// "type": "TAG", +// "attrs": [ +// { +// "key": "key", +// "value": "bar" +// } +// ], +// "pkg_path": "gno.land/r/demo/event", +// "func": "Emit" +// } +// ] diff --git a/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar b/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar index 5e0520a2e85..0236872e78a 100644 --- a/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar @@ -30,4 +30,22 @@ func main() { // test // Events: -// [{"type":"EventA","attrs":[],"pkg_path":"","func":"main"},{"type":"EventB","attrs":[{"key":"keyA","value":"valA"}],"pkg_path":"","func":"main"}] +// [ +// { +// "type": "EventA", +// "attrs": [], +// "pkg_path": "", +// "func": "main" +// }, +// { +// "type": "EventB", +// "attrs": [ +// { +// "key": "keyA", +// "value": "valA" +// } +// ], +// "pkg_path": "", +// "func": "main" +// } +// ] diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 9df982d4fd8..98e54114af9 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -374,7 +374,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { } // check result events := m.Context.(*teststd.TestExecContext).EventLogger.Events() - evtjson, err := json.Marshal(events) + evtjson, err := json.MarshalIndent(events, "", " ") if err != nil { panic(err) } From af057801de3657c5910101b14b7996591f31cec6 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Perez Date: Thu, 31 Oct 2024 18:50:05 +0100 Subject: [PATCH 15/34] ci: publish master Docker images without `--snapshot` flag (#3057) --- .github/workflows/releaser-master.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 535cac5d965..7f81ef1ad1a 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -18,6 +18,8 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create a dummy tag to avoid goreleaser failure + run: git tag v0.0.0 master - uses: actions/setup-go@v5 with: @@ -40,7 +42,7 @@ jobs: with: distribution: goreleaser-pro version: ~> v2 - args: release --clean --snapshot --nightly --config ./.github/goreleaser.yaml + args: release --clean --nightly --config ./.github/goreleaser.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} From c776e32b08430487083de8ca2ac34c4e435878db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 4 Nov 2024 17:22:46 +0100 Subject: [PATCH 16/34] feat: support metadata for genesis txs (#2941) ## Description This PR introduces metadata support for genesis transactions (such as timestamps), in the form of a new Gno genesis state that's easily generate-able. Shoutout to @clockworkgr for sanity checking the idea, and providing insights that ultimately led to this PR materializing. **BREAKING CHANGE** The `GnoGenesisState` is now modified, and all functionality that references it (ex. `gnogenesis`, `tx-archive`) will need to adapt. ### What we wanted to accomplish The Portal Loop does not save "time" information upon restarting (from block 0). This means that any transaction that resulted in a Realm / Package calling `time.Now()` will get differing results when these same transactions are "replayed" as part of the loop (in the genesis state). We wanted to somehow preserve this timestamp information when the transactions (from a previous loop), are executed as part of the genesis building process. For example: - Portal Loop chain is on block 100 - tx A results in a call to `time.Now()`, which returns time T, and saves it somewhere (Realm state) - the Portal Loop restarts, and uses all the transactions it encountered in the past iteration as genesis txs - the genesis is generated by executing the transactions - when tx A is re-executed (this time as part of the genesis block), **it should return time T, as with the original execution context** It is worth noting that this functionality is something we want in `gnodev`, so simple helpers on the Realms to update the timestamps wouldn't work. ### What this PR does I've tried to follow a couple of principles when working on this PR: - don't break backwards compatibility - don't modify critical APIs such as the SDK ABCI, but preserve existing, working, functionality in its original form - don't add another layer to the complexity pancake - don't implement a solution that looks (and works) like a hack I'm not a huge fan of execution hooks, so the solution proposed in this PR doesn't rely on any hook mechanism. Not going with the hook approach also significantly decreases the complexity and preserves readability. The base of this solution is enabling execution context modification, with minimal / no API changes. Having functionality like this, we can tailor the context during critical segments such as genesis generation, and we're not just limited to timestamps (which is the primary use-case). We also introduce a new type of `AppState`, called `MetadataGenesisState`, where metadata is associated with the transactions. We hide the actual `AppState` implementation behind an interface, so existing tools and flows don't break, and work as normal. ### What this PR doesn't do There is more work to be done if this PR is merged: - we need to add support to `tx-archive` for supporting exporting txs with metadata. Should be straightforward to do - the portal loop also needs to be restarted with this new "mode" enabled - we need to add support to existing `gnoland genesis` commands to support the new `MetadataGenesisState`. It is also straightforward, but definitely a bit of work - if we want support for something like this in gnodev, the export / import code of gnodev also needs to be modified to support the new genesis state type (cc @gfanton) - https://github.com/gnolang/gno/pull/2943 Related PRs and issues: - #2751 - #2744 cc @moul @thehowl @jeronimoalbi @ilgooz
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- contribs/gnodev/cmd/gnodev/setup_node.go | 10 +- contribs/gnodev/cmd/gnodev/txs.go | 23 -- contribs/gnodev/pkg/dev/node.go | 26 ++- contribs/gnodev/pkg/dev/node_state.go | 5 +- contribs/gnodev/pkg/dev/packages.go | 24 +- contribs/gnogenesis/internal/txs/txs.go | 9 +- .../internal/txs/txs_add_packages.go | 2 +- .../internal/txs/txs_add_packages_test.go | 4 +- .../gnogenesis/internal/txs/txs_add_sheet.go | 21 +- .../internal/txs/txs_add_sheet_test.go | 33 +-- .../internal/txs/txs_export_test.go | 5 +- .../gnogenesis/internal/txs/txs_list_test.go | 2 +- .../gnogenesis/internal/txs/txs_remove.go | 2 +- .../internal/txs/txs_remove_test.go | 4 +- contribs/gnogenesis/internal/verify/verify.go | 2 +- .../gnogenesis/internal/verify/verify_test.go | 7 +- gno.land/genesis/genesis_txs.jsonl | 34 +-- gno.land/pkg/gnoland/app.go | 27 ++- gno.land/pkg/gnoland/app_test.go | 205 +++++++++++++++++- gno.land/pkg/gnoland/balance_test.go | 23 +- gno.land/pkg/gnoland/genesis.go | 15 +- gno.land/pkg/gnoland/node_inmemory.go | 3 +- gno.land/pkg/gnoland/package.go | 2 + gno.land/pkg/gnoland/types.go | 62 +++++- gno.land/pkg/gnoland/types_test.go | 131 +++++++++++ .../pkg/integration/testing_integration.go | 10 +- gno.land/pkg/integration/testing_node.go | 10 +- tm2/pkg/sdk/baseapp.go | 26 ++- tm2/pkg/sdk/helpers.go | 27 ++- 29 files changed, 581 insertions(+), 173 deletions(-) delete mode 100644 contribs/gnodev/cmd/gnodev/txs.go create mode 100644 gno.land/pkg/gnoland/types_test.go diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go index 578cf525751..4b3619b4a7d 100644 --- a/contribs/gnodev/cmd/gnodev/setup_node.go +++ b/contribs/gnodev/cmd/gnodev/setup_node.go @@ -23,7 +23,7 @@ func setupDevNode( if devCfg.txsFile != "" { // Load txs files var err error - nodeConfig.InitialTxs, err = parseTxs(devCfg.txsFile) + nodeConfig.InitialTxs, err = gnoland.ReadGenesisTxs(ctx, devCfg.txsFile) if err != nil { return nil, fmt.Errorf("unable to load transactions: %w", err) } @@ -35,7 +35,13 @@ func setupDevNode( // Override balances and txs nodeConfig.BalancesList = state.Balances - nodeConfig.InitialTxs = state.Txs + + stateTxs := state.Txs + nodeConfig.InitialTxs = make([]gnoland.TxWithMetadata, len(stateTxs)) + + for index, nodeTx := range stateTxs { + nodeConfig.InitialTxs[index] = nodeTx + } logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(nodeConfig.InitialTxs)) } diff --git a/contribs/gnodev/cmd/gnodev/txs.go b/contribs/gnodev/cmd/gnodev/txs.go deleted file mode 100644 index 0be33b68702..00000000000 --- a/contribs/gnodev/cmd/gnodev/txs.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/gnolang/gno/tm2/pkg/std" -) - -func parseTxs(txFile string) ([]std.Tx, error) { - if txFile == "" { - return nil, nil - } - - file, loadErr := os.Open(txFile) - if loadErr != nil { - return nil, fmt.Errorf("unable to open tx file %s: %w", txFile, loadErr) - } - defer file.Close() - - return std.ParseTxs(context.Background(), file) -} diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 0e1099eef88..54baa2ea774 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -35,7 +35,7 @@ type NodeConfig struct { BalancesList []gnoland.Balance PackagesPathList []PackagePath Emitter emitter.Emitter - InitialTxs []std.Tx + InitialTxs []gnoland.TxWithMetadata TMConfig *tmcfg.Config SkipFailingGenesisTxs bool NoReplay bool @@ -84,7 +84,7 @@ type Node struct { loadedPackages int // state - initialState, state []std.Tx + initialState, state []gnoland.TxWithMetadata currentStateIndex int } @@ -154,7 +154,7 @@ func (n *Node) GetRemoteAddress() string { // GetBlockTransactions returns the transactions contained // within the specified block, if any -func (n *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { +func (n *Node) GetBlockTransactions(blockNum uint64) ([]gnoland.TxWithMetadata, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -163,21 +163,26 @@ func (n *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { // GetBlockTransactions returns the transactions contained // within the specified block, if any -func (n *Node) getBlockTransactions(blockNum uint64) ([]std.Tx, error) { +func (n *Node) getBlockTransactions(blockNum uint64) ([]gnoland.TxWithMetadata, error) { int64BlockNum := int64(blockNum) b, err := n.client.Block(&int64BlockNum) if err != nil { - return []std.Tx{}, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here + return []gnoland.TxWithMetadata{}, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here } - txs := make([]std.Tx, len(b.Block.Data.Txs)) + txs := make([]gnoland.TxWithMetadata, len(b.Block.Data.Txs)) for i, encodedTx := range b.Block.Data.Txs { var tx std.Tx if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil { return nil, fmt.Errorf("unable to unmarshal amino tx, %w", unmarshalErr) } - txs[i] = tx + txs[i] = gnoland.TxWithMetadata{ + Tx: tx, + Metadata: &gnoland.GnoTxMetadata{ + Timestamp: b.BlockMeta.Header.Time.Unix(), + }, + } } return txs, nil @@ -347,11 +352,14 @@ func (n *Node) SendTransaction(tx *std.Tx) error { return nil } -func (n *Node) getBlockStoreState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) getBlockStoreState(ctx context.Context) ([]gnoland.TxWithMetadata, error) { // get current genesis state genesis := n.GenesisDoc().AppState.(gnoland.GnoGenesisState) - state := genesis.Txs[n.loadedPackages:] // ignore previously loaded packages + initialTxs := genesis.Txs[n.loadedPackages:] // ignore previously loaded packages + + state := append([]gnoland.TxWithMetadata{}, initialTxs...) + lastBlock := n.getLatestBlockNumber() var blocnum uint64 = 1 for ; blocnum <= lastBlock; blocnum++ { diff --git a/contribs/gnodev/pkg/dev/node_state.go b/contribs/gnodev/pkg/dev/node_state.go index 846c4857784..7504580b333 100644 --- a/contribs/gnodev/pkg/dev/node_state.go +++ b/contribs/gnodev/pkg/dev/node_state.go @@ -8,7 +8,6 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/events" "github.com/gnolang/gno/gno.land/pkg/gnoland" bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/std" ) var ErrEmptyState = errors.New("empty state") @@ -29,7 +28,7 @@ func (n *Node) SaveCurrentState(ctx context.Context) error { } // Export the current state as list of txs -func (n *Node) ExportCurrentState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) ExportCurrentState(ctx context.Context) ([]gnoland.TxWithMetadata, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -42,7 +41,7 @@ func (n *Node) ExportCurrentState(ctx context.Context) ([]std.Tx, error) { return state[:n.currentStateIndex], nil } -func (n *Node) getState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) getState(ctx context.Context) ([]gnoland.TxWithMetadata, error) { if n.state == nil { var err error n.state, err = n.getBlockStoreState(ctx) diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 7b560c21e09..7ee628ce39e 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/gnolang/gno/contribs/gnodev/pkg/address" + "github.com/gnolang/gno/gno.land/pkg/gnoland" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" @@ -118,7 +119,7 @@ func (pm PackagesMap) toList() gnomod.PkgList { return list } -func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { +func (pm PackagesMap) Load(fee std.Fee) ([]gnoland.TxWithMetadata, error) { pkgs := pm.toList() sorted, err := pkgs.Sort() @@ -127,7 +128,8 @@ func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { } nonDraft := sorted.GetNonDraftPkgs() - txs := []std.Tx{} + txs := make([]gnoland.TxWithMetadata, 0, len(nonDraft)) + for _, modPkg := range nonDraft { pkg := pm[modPkg.Dir] if pkg.Creator.IsZero() { @@ -141,18 +143,20 @@ func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { } // Create transaction - tx := std.Tx{ - Fee: fee, - Msgs: []std.Msg{ - vmm.MsgAddPackage{ - Creator: pkg.Creator, - Deposit: pkg.Deposit, - Package: memPkg, + tx := gnoland.TxWithMetadata{ + Tx: std.Tx{ + Fee: fee, + Msgs: []std.Msg{ + vmm.MsgAddPackage{ + Creator: pkg.Creator, + Deposit: pkg.Deposit, + Package: memPkg, + }, }, }, } - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + tx.Tx.Signatures = make([]std.Signature, len(tx.Tx.GetSigners())) txs = append(txs, tx) } diff --git a/contribs/gnogenesis/internal/txs/txs.go b/contribs/gnogenesis/internal/txs/txs.go index f8a14eafefc..fbf4c6ea3c7 100644 --- a/contribs/gnogenesis/internal/txs/txs.go +++ b/contribs/gnogenesis/internal/txs/txs.go @@ -8,7 +8,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/std" ) type txsCfg struct { @@ -47,7 +46,7 @@ func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) { } // appendGenesisTxs saves the given transactions to the genesis doc -func appendGenesisTxs(genesis *types.GenesisDoc, txs []std.Tx) error { +func appendGenesisTxs(genesis *types.GenesisDoc, txs []gnoland.TxWithMetadata) error { // Initialize the app state if it's not present if genesis.AppState == nil { genesis.AppState = gnoland.GnoGenesisState{} @@ -77,7 +76,7 @@ func appendGenesisTxs(genesis *types.GenesisDoc, txs []std.Tx) error { } // txStore is a wrapper for TM2 transactions -type txStore []std.Tx +type txStore []gnoland.TxWithMetadata // leftMerge merges the two tx stores, with // preference to the left @@ -86,7 +85,7 @@ func (i *txStore) leftMerge(b txStore) error { txHashMap := make(map[string]struct{}, len(*i)) for _, tx := range *i { - txHash, err := getTxHash(tx) + txHash, err := getTxHash(tx.Tx) if err != nil { return err } @@ -95,7 +94,7 @@ func (i *txStore) leftMerge(b txStore) error { } for _, tx := range b { - txHash, err := getTxHash(tx) + txHash, err := getTxHash(tx.Tx) if err != nil { return err } diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index b07adc777a7..1b4e6e7cffb 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -53,7 +53,7 @@ func execTxsAddPackages( return errInvalidPackageDir } - parsedTxs := make([]std.Tx, 0) + parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, path := range args { // Generate transactions from the packages (recursively) txs, err := gnoland.LoadPackagesFromDir(path, genesisDeployAddress, genesisDeployFee) diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go index c814ccde957..12a9287f171 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go @@ -118,9 +118,9 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { state := updatedGenesis.AppState.(gnoland.GnoGenesisState) require.Equal(t, 1, len(state.Txs)) - require.Equal(t, 1, len(state.Txs[0].Msgs)) + require.Equal(t, 1, len(state.Txs[0].Tx.Msgs)) - msgAddPkg, ok := state.Txs[0].Msgs[0].(vmm.MsgAddPackage) + msgAddPkg, ok := state.Txs[0].Tx.Msgs[0].(vmm.MsgAddPackage) require.True(t, ok) assert.Equal(t, packagePath, msgAddPkg.Package.Path) diff --git a/contribs/gnogenesis/internal/txs/txs_add_sheet.go b/contribs/gnogenesis/internal/txs/txs_add_sheet.go index 88673bc29bd..0bbd4b578cc 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_sheet.go +++ b/contribs/gnogenesis/internal/txs/txs_add_sheet.go @@ -4,17 +4,13 @@ import ( "context" "errors" "fmt" - "os" + "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/std" ) -var ( - errInvalidTxsFile = errors.New("unable to open transactions file") - errNoTxsFileSpecified = errors.New("no txs file specified") -) +var errNoTxsFileSpecified = errors.New("no txs file specified") // newTxsAddSheetCmd creates the genesis txs add sheet subcommand func newTxsAddSheetCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { @@ -49,22 +45,13 @@ func execTxsAddSheet( return errNoTxsFileSpecified } - parsedTxs := make([]std.Tx, 0) + parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, file := range args { - file, loadErr := os.Open(file) - if loadErr != nil { - return fmt.Errorf("%w, %w", errInvalidTxsFile, loadErr) - } - - txs, err := std.ParseTxs(ctx, file) + txs, err := gnoland.ReadGenesisTxs(ctx, file) if err != nil { return fmt.Errorf("unable to parse file, %w", err) } - if err = file.Close(); err != nil { - return fmt.Errorf("unable to gracefully close file, %w", err) - } - parsedTxs = append(parsedTxs, txs...) } diff --git a/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go b/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go index a174905c237..6da3faea6ed 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_sheet_test.go @@ -21,25 +21,27 @@ import ( ) // generateDummyTxs generates dummy transactions -func generateDummyTxs(t *testing.T, count int) []std.Tx { +func generateDummyTxs(t *testing.T, count int) []gnoland.TxWithMetadata { t.Helper() - txs := make([]std.Tx, count) + txs := make([]gnoland.TxWithMetadata, count) for i := 0; i < count; i++ { - txs[i] = std.Tx{ - Msgs: []std.Msg{ - bank.MsgSend{ - FromAddress: crypto.Address{byte(i)}, - ToAddress: crypto.Address{byte((i + 1) % count)}, - Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 1)), + txs[i] = gnoland.TxWithMetadata{ + Tx: std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: crypto.Address{byte(i)}, + ToAddress: crypto.Address{byte((i + 1) % count)}, + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 1)), + }, }, + Fee: std.Fee{ + GasWanted: 1, + GasFee: std.NewCoin(ugnot.Denom, 1000000), + }, + Memo: fmt.Sprintf("tx %d", i), }, - Fee: std.Fee{ - GasWanted: 1, - GasFee: std.NewCoin(ugnot.Denom, 1000000), - }, - Memo: fmt.Sprintf("tx %d", i), } } @@ -47,7 +49,7 @@ func generateDummyTxs(t *testing.T, count int) []std.Tx { } // encodeDummyTxs encodes the transactions into amino JSON -func encodeDummyTxs(t *testing.T, txs []std.Tx) []string { +func encodeDummyTxs(t *testing.T, txs []gnoland.TxWithMetadata) []string { t.Helper() encodedTxs := make([]string, 0, len(txs)) @@ -104,8 +106,7 @@ func TestGenesis_Txs_Add_Sheets(t *testing.T) { } // Run the command - cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorContains(t, cmdErr, errInvalidTxsFile.Error()) + assert.Error(t, cmd.ParseAndRun(context.Background(), args)) }) t.Run("no txs file", func(t *testing.T) { diff --git a/contribs/gnogenesis/internal/txs/txs_export_test.go b/contribs/gnogenesis/internal/txs/txs_export_test.go index 47fc594d2ec..ad738cd95f7 100644 --- a/contribs/gnogenesis/internal/txs/txs_export_test.go +++ b/contribs/gnogenesis/internal/txs/txs_export_test.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -117,9 +116,9 @@ func TestGenesis_Txs_Export(t *testing.T) { // Validate the transactions were written down scanner := bufio.NewScanner(outputFile) - outputTxs := make([]std.Tx, 0) + outputTxs := make([]gnoland.TxWithMetadata, 0) for scanner.Scan() { - var tx std.Tx + var tx gnoland.TxWithMetadata require.NoError(t, amino.UnmarshalJSON(scanner.Bytes(), &tx)) diff --git a/contribs/gnogenesis/internal/txs/txs_list_test.go b/contribs/gnogenesis/internal/txs/txs_list_test.go index 5129533dc8f..d4d9f4d7ba8 100644 --- a/contribs/gnogenesis/internal/txs/txs_list_test.go +++ b/contribs/gnogenesis/internal/txs/txs_list_test.go @@ -63,6 +63,6 @@ func TestGenesis_List_All(t *testing.T) { cmdErr := cmd.ParseAndRun(context.Background(), args) require.NoError(t, cmdErr) - require.Len(t, buf.String(), 4442) + require.Len(t, buf.String(), 5262) }) } diff --git a/contribs/gnogenesis/internal/txs/txs_remove.go b/contribs/gnogenesis/internal/txs/txs_remove.go index f767e19bc90..dbfc90fb1dc 100644 --- a/contribs/gnogenesis/internal/txs/txs_remove.go +++ b/contribs/gnogenesis/internal/txs/txs_remove.go @@ -59,7 +59,7 @@ func execTxsRemove(cfg *txsCfg, io commands.IO, args []string) error { for indx, tx := range state.Txs { // Find the hash of the transaction - hash, err := getTxHash(tx) + hash, err := getTxHash(tx.Tx) if err != nil { return fmt.Errorf("unable to generate tx hash, %w", err) } diff --git a/contribs/gnogenesis/internal/txs/txs_remove_test.go b/contribs/gnogenesis/internal/txs/txs_remove_test.go index c031e0d342e..16edbb83e3c 100644 --- a/contribs/gnogenesis/internal/txs/txs_remove_test.go +++ b/contribs/gnogenesis/internal/txs/txs_remove_test.go @@ -97,7 +97,7 @@ func TestGenesis_Txs_Remove(t *testing.T) { } require.NoError(t, genesis.SaveAs(tempGenesis.Name())) - txHash, err := getTxHash(txs[0]) + txHash, err := getTxHash(txs[0].Tx) require.NoError(t, err) // Create the command @@ -124,7 +124,7 @@ func TestGenesis_Txs_Remove(t *testing.T) { assert.Len(t, state.Txs, len(txs)-1) for _, tx := range state.Txs { - genesisTxHash, err := getTxHash(tx) + genesisTxHash, err := getTxHash(tx.Tx) require.NoError(t, err) assert.NotEqual(t, txHash, genesisTxHash) diff --git a/contribs/gnogenesis/internal/verify/verify.go b/contribs/gnogenesis/internal/verify/verify.go index 97ad14cb7f6..9022711ce49 100644 --- a/contribs/gnogenesis/internal/verify/verify.go +++ b/contribs/gnogenesis/internal/verify/verify.go @@ -61,7 +61,7 @@ func execVerify(cfg *verifyCfg, io commands.IO) error { // Validate the initial transactions for _, tx := range state.Txs { - if validateErr := tx.ValidateBasic(); validateErr != nil { + if validateErr := tx.Tx.ValidateBasic(); validateErr != nil { return fmt.Errorf("invalid transacton, %w", validateErr) } } diff --git a/contribs/gnogenesis/internal/verify/verify_test.go b/contribs/gnogenesis/internal/verify/verify_test.go index 76009c34c94..130bd5e09bc 100644 --- a/contribs/gnogenesis/internal/verify/verify_test.go +++ b/contribs/gnogenesis/internal/verify/verify_test.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/mock" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/require" ) @@ -45,7 +44,7 @@ func TestGenesis_Verify(t *testing.T) { g.AppState = gnoland.GnoGenesisState{ Balances: []gnoland.Balance{}, - Txs: []std.Tx{ + Txs: []gnoland.TxWithMetadata{ {}, }, } @@ -76,7 +75,7 @@ func TestGenesis_Verify(t *testing.T) { Balances: []gnoland.Balance{ {}, }, - Txs: []std.Tx{}, + Txs: []gnoland.TxWithMetadata{}, } require.NoError(t, g.SaveAs(tempFile.Name())) @@ -102,7 +101,7 @@ func TestGenesis_Verify(t *testing.T) { g := getValidTestGenesis() g.AppState = gnoland.GnoGenesisState{ Balances: []gnoland.Balance{}, - Txs: []std.Tx{}, + Txs: []gnoland.TxWithMetadata{}, } require.NoError(t, g.SaveAs(tempFile.Name())) diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index daf9fbdc5d4..43453dcd2fc 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -1,17 +1,17 @@ -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","administrator","g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"AqCqe0cS55Ym7/BvPDoCDyPP5q8284gecVQ2PMOlq/4lJpO9Q18SOWKI15dMEBY1pT0AYyhCeTirlsM1I3Y4Cg=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","zo_oma","Love is the encryption key\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":"GGp+bVL2eEvKecPqgcULSABYOSnSMnJzfIsR8ZIRER1GGX/fOiCReX4WKMrGLVROJVfbLQkDRwvhS4TLHlSoSQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","manfred","https://github.com/moul"]}],"fee":{"gas_wanted":"2000000","gas_fee":"200000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"9CWeNbKx+hEL+RdHplAVAFntcrAVx5mK9tMqoywuHVoreH844n3yOxddQrGfBk6T2tMBmNWakERRqWZfS+bYAQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","piupiu","@piux2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":"pTUpP0d/XlfVe3TH1hlaoLhKadzIKG1gtQ/Ueuat72p+659RWRea58Z0mk6GgPE/EeTbhMEY45zufevBdGJVoQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""} -{"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":""} -{"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":""} -{"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":""} -{"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":""} -{"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":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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\u0026__func=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\u0026__func=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\u0026__func=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":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","administrator","g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"AqCqe0cS55Ym7/BvPDoCDyPP5q8284gecVQ2PMOlq/4lJpO9Q18SOWKI15dMEBY1pT0AYyhCeTirlsM1I3Y4Cg=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","zo_oma","Love is the encryption key\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":"GGp+bVL2eEvKecPqgcULSABYOSnSMnJzfIsR8ZIRER1GGX/fOiCReX4WKMrGLVROJVfbLQkDRwvhS4TLHlSoSQ=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","manfred","https://github.com/moul"]}],"fee":{"gas_wanted":"2000000","gas_fee":"200000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"9CWeNbKx+hEL+RdHplAVAFntcrAVx5mK9tMqoywuHVoreH844n3yOxddQrGfBk6T2tMBmNWakERRqWZfS+bYAQ=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","piupiu","@piux2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":"pTUpP0d/XlfVe3TH1hlaoLhKadzIKG1gtQ/Ueuat72p+659RWRea58Z0mk6GgPE/EeTbhMEY45zufevBdGJVoQ=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""}} +{"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","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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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\u0026__func=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\u0026__func=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\u0026__func=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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"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/gnoland/app.go b/gno.land/pkg/gnoland/app.go index e784f2148aa..ff1b5a88fea 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/config" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/events" @@ -301,9 +302,31 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci } txResponses := make([]abci.ResponseDeliverTx, 0, len(state.Txs)) + // Run genesis txs for _, tx := range state.Txs { - res := cfg.baseApp.Deliver(tx) + var ( + stdTx = tx.Tx + metadata = tx.Metadata + + ctxFn sdk.ContextFn + ) + + // Check if there is metadata associated with the tx + if metadata != nil { + // Create a custom context modifier + ctxFn = func(ctx sdk.Context) sdk.Context { + // Create a copy of the header, in + // which only the timestamp information is modified + header := ctx.BlockHeader().(*bft.Header).Copy() + header.Time = time.Unix(metadata.Timestamp, 0) + + // Save the modified header + return ctx.WithBlockHeader(header) + } + } + + res := cfg.baseApp.Deliver(stdTx, ctxFn) if res.IsErr() { ctx.Logger().Error( "Unable to deliver genesis tx", @@ -319,7 +342,7 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci GasUsed: res.GasUsed, }) - cfg.GenesisTxResultHandler(ctx, tx, res) + cfg.GenesisTxResultHandler(ctx, stdTx, res) } return txResponses, nil } diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 87b624280c6..5e4dcb2b449 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -52,16 +52,18 @@ func TestNewAppWithOptions(t *testing.T) { Amount: []std.Coin{{Amount: 1e15, Denom: "ugnot"}}, }, }, - Txs: []std.Tx{ + Txs: []TxWithMetadata{ { - Msgs: []std.Msg{vm.NewMsgAddPackage(addr, "gno.land/r/demo", []*gnovm.MemFile{ - { - Name: "demo.gno", - Body: "package demo; func Hello() string { return `hello`; }", - }, - })}, - Fee: std.Fee{GasWanted: 1e6, GasFee: std.Coin{Amount: 1e6, Denom: "ugnot"}}, - Signatures: []std.Signature{{}}, // one empty signature + Tx: std.Tx{ + Msgs: []std.Msg{vm.NewMsgAddPackage(addr, "gno.land/r/demo", []*gnovm.MemFile{ + { + Name: "demo.gno", + Body: "package demo; func Hello() string { return `hello`; }", + }, + })}, + Fee: std.Fee{GasWanted: 1e6, GasFee: std.Coin{Amount: 1e6, Denom: "ugnot"}}, + Signatures: []std.Signature{{}}, // one empty signature + }, }, }, }, @@ -206,7 +208,7 @@ func generateValidatorUpdates(t *testing.T, count int) []abci.ValidatorUpdate { for i := 0; i < count; i++ { // Generate a random private key - key := getDummyKey(t) + key := getDummyKey(t).PubKey() validator := abci.ValidatorUpdate{ Address: key.Address(), @@ -220,6 +222,189 @@ func generateValidatorUpdates(t *testing.T, count int) []abci.ValidatorUpdate { return validators } +func createAndSignTx( + t *testing.T, + msgs []std.Msg, + chainID string, + key crypto.PrivKey, +) std.Tx { + t.Helper() + + tx := std.Tx{ + Msgs: msgs, + Fee: std.Fee{ + GasFee: std.NewCoin("ugnot", 2000000), + GasWanted: 10000000, + }, + } + + signBytes, err := tx.GetSignBytes(chainID, 0, 0) + require.NoError(t, err) + + // Sign the tx + signedTx, err := key.Sign(signBytes) + require.NoError(t, err) + + tx.Signatures = []std.Signature{ + { + PubKey: key.PubKey(), + Signature: signedTx, + }, + } + + return tx +} + +func TestInitChainer_MetadataTxs(t *testing.T) { + var ( + currentTimestamp = time.Now() + laterTimestamp = currentTimestamp.Add(10 * 24 * time.Hour) // 10 days + + getMetadataState = func(tx std.Tx, balances []Balance) GnoGenesisState { + return GnoGenesisState{ + // Set the package deployment as the genesis tx + Txs: []TxWithMetadata{ + { + Tx: tx, + Metadata: &GnoTxMetadata{ + Timestamp: laterTimestamp.Unix(), + }, + }, + }, + // Make sure the deployer account has a balance + Balances: balances, + } + } + + getNonMetadataState = func(tx std.Tx, balances []Balance) GnoGenesisState { + return GnoGenesisState{ + Txs: []TxWithMetadata{ + { + Tx: tx, + }, + }, + Balances: balances, + } + } + ) + + testTable := []struct { + name string + genesisTime time.Time + expectedTime time.Time + stateFn func(std.Tx, []Balance) GnoGenesisState + }{ + { + "non-metadata transaction", + currentTimestamp, + currentTimestamp, + getNonMetadataState, + }, + { + "metadata transaction", + currentTimestamp, + laterTimestamp, + getMetadataState, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + var ( + db = memdb.NewMemDB() + + key = getDummyKey(t) // user account, and genesis deployer + chainID = "test" + + path = "gno.land/r/demo/metadatatx" + body = `package metadatatx + + import "time" + + // Time is initialized on deployment (genesis) + var t time.Time = time.Now() + + // GetT returns the time that was saved from genesis + func GetT() int64 { return t.Unix() } +` + ) + + // Create a fresh app instance + app, err := NewAppWithOptions(TestAppOptions(db)) + require.NoError(t, err) + + // Prepare the deploy transaction + msg := vm.MsgAddPackage{ + Creator: key.PubKey().Address(), + Package: &gnovm.MemPackage{ + Name: "metadatatx", + Path: path, + Files: []*gnovm.MemFile{ + { + Name: "file.gno", + Body: body, + }, + }, + }, + Deposit: nil, + } + + // Create the initial genesis tx + tx := createAndSignTx(t, []std.Msg{msg}, chainID, key) + + // Run the top-level init chain process + app.InitChain(abci.RequestInitChain{ + ChainID: chainID, + Time: testCase.genesisTime, + ConsensusParams: &abci.ConsensusParams{ + Block: defaultBlockParams(), + Validator: &abci.ValidatorParams{ + PubKeyTypeURLs: []string{}, + }, + }, + // Set the package deployment as the genesis tx, + // and make sure the deployer account has a balance + AppState: testCase.stateFn(tx, []Balance{ + { + // Make sure the deployer account has a balance + Address: key.PubKey().Address(), + Amount: std.NewCoins(std.NewCoin("ugnot", 20_000_000)), + }, + }), + }) + + // Prepare the call transaction + callMsg := vm.MsgCall{ + Caller: key.PubKey().Address(), + PkgPath: path, + Func: "GetT", + } + + tx = createAndSignTx(t, []std.Msg{callMsg}, chainID, key) + + // Marshal the transaction to Amino binary + marshalledTx, err := amino.Marshal(tx) + require.NoError(t, err) + + // Execute the call to the "GetT" method + // on the deployed Realm + resp := app.DeliverTx(abci.RequestDeliverTx{ + Tx: marshalledTx, + }) + + require.True(t, resp.IsOK()) + + // Make sure the initialized Realm state is + // the injected context timestamp from the tx metadata + assert.Contains( + t, + string(resp.Data), + fmt.Sprintf("(%d int64)", testCase.expectedTime.Unix()), + ) + }) + } +} + func TestEndBlocker(t *testing.T) { t.Parallel() diff --git a/gno.land/pkg/gnoland/balance_test.go b/gno.land/pkg/gnoland/balance_test.go index 99a348e9f2f..489384196ad 100644 --- a/gno.land/pkg/gnoland/balance_test.go +++ b/gno.land/pkg/gnoland/balance_test.go @@ -120,7 +120,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { for index, key := range dummyKeys { entries[index] = fmt.Sprintf( "%s=%s", - key.Address().String(), + key.PubKey().Address().String(), ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ) } @@ -131,7 +131,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { // Validate the balance map assert.Len(t, balanceMap, len(dummyKeys)) for _, key := range dummyKeys { - assert.Equal(t, amount, balanceMap[key.Address()].Amount) + assert.Equal(t, amount, balanceMap[key.PubKey().Address()].Amount) } }) @@ -162,7 +162,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) { t.Run("malformed balance, invalid amount", func(t *testing.T) { t.Parallel() - dummyKey := getDummyKey(t) + dummyKey := getDummyKey(t).PubKey() balances := []string{ fmt.Sprintf( @@ -194,7 +194,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) { for index, key := range dummyKeys { balances[index] = fmt.Sprintf( "%s=%s", - key.Address().String(), + key.PubKey().Address().String(), ugnot.ValueString(amount.AmountOf(ugnot.Denom)), ) } @@ -206,14 +206,14 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) { // Validate the balance map assert.Len(t, balanceMap, len(dummyKeys)) for _, key := range dummyKeys { - assert.Equal(t, amount, balanceMap[key.Address()].Amount) + assert.Equal(t, amount, balanceMap[key.PubKey().Address()].Amount) } }) t.Run("malformed balance, invalid amount", func(t *testing.T) { t.Parallel() - dummyKey := getDummyKey(t) + dummyKey := getDummyKey(t).PubKey() balances := []string{ fmt.Sprintf( @@ -236,9 +236,8 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) { // XXX: this function should probably be exposed somewhere as it's duplicate of // cmd/genesis/... -// getDummyKey generates a random public key, -// and returns the key info -func getDummyKey(t *testing.T) crypto.PubKey { +// getDummyKey generates a random private key +func getDummyKey(t *testing.T) crypto.PrivKey { t.Helper() mnemonic, err := client.GenerateMnemonic(256) @@ -246,14 +245,14 @@ func getDummyKey(t *testing.T) crypto.PubKey { seed := bip39.NewSeed(mnemonic, "") - return generateKeyFromSeed(seed, 0).PubKey() + return generateKeyFromSeed(seed, 0) } // getDummyKeys generates random keys for testing -func getDummyKeys(t *testing.T, count int) []crypto.PubKey { +func getDummyKeys(t *testing.T, count int) []crypto.PrivKey { t.Helper() - dummyKeys := make([]crypto.PubKey, count) + dummyKeys := make([]crypto.PrivKey, count) for i := 0; i < count; i++ { dummyKeys[i] = getDummyKey(t) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index f5f0aa56758..613fba51c37 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -60,8 +60,9 @@ func LoadGenesisBalancesFile(path string) ([]Balance, error) { // LoadGenesisTxsFile loads genesis transactions from the provided file path. // XXX: Improve the way we generate and load this file -func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]std.Tx, error) { - txs := []std.Tx{} +func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]TxWithMetadata, error) { + txs := make([]TxWithMetadata, 0) + txsBz := osm.MustReadFile(path) txsLines := strings.Split(string(txsBz), "\n") for _, txLine := range txsLines { @@ -73,7 +74,7 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]st txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) - var tx std.Tx + var tx TxWithMetadata if err := amino.UnmarshalJSON([]byte(txLine), &tx); err != nil { return nil, fmt.Errorf("unable to Unmarshall txs file: %w", err) } @@ -86,7 +87,7 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]st // LoadPackagesFromDir loads gno packages from a directory. // It creates and returns a list of transactions based on these packages. -func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]std.Tx, error) { +func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]TxWithMetadata, error) { // list all packages from target path pkgs, err := gnomod.ListPkgs(dir) if err != nil { @@ -101,14 +102,16 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]std.Tx // Filter out draft packages. nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - txs := []std.Tx{} + txs := make([]TxWithMetadata, 0, len(nonDraftPkgs)) for _, pkg := range nonDraftPkgs { tx, err := LoadPackage(pkg, creator, fee, nil) if err != nil { return nil, fmt.Errorf("unable to load package %q: %w", pkg.Dir, err) } - txs = append(txs, tx) + txs = append(txs, TxWithMetadata{ + Tx: tx, + }) } return txs, nil diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index f81838e1eb3..9dccbbfac8d 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -16,7 +16,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/std" ) type InMemoryNodeConfig struct { @@ -44,7 +43,7 @@ func NewDefaultGenesisConfig(chainid string) *bft.GenesisDoc { }, AppState: &GnoGenesisState{ Balances: []Balance{}, - Txs: []std.Tx{}, + Txs: []TxWithMetadata{}, }, } } diff --git a/gno.land/pkg/gnoland/package.go b/gno.land/pkg/gnoland/package.go index fd1afbde136..e4b2449c972 100644 --- a/gno.land/pkg/gnoland/package.go +++ b/gno.land/pkg/gnoland/package.go @@ -11,4 +11,6 @@ var Package = amino.RegisterPackage(amino.NewPackage( ).WithDependencies().WithTypes( &GnoAccount{}, "Account", GnoGenesisState{}, "GenesisState", + TxWithMetadata{}, "TxWithMetadata", + GnoTxMetadata{}, "GnoTxMetadata", )) diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index 016f3279dbd..b1bedb541aa 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -1,8 +1,13 @@ package gnoland import ( + "bufio" + "context" "errors" + "fmt" + "os" + "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -20,6 +25,59 @@ func ProtoGnoAccount() std.Account { } type GnoGenesisState struct { - Balances []Balance `json:"balances"` - Txs []std.Tx `json:"txs"` + Balances []Balance `json:"balances"` + Txs []TxWithMetadata `json:"txs"` +} + +type TxWithMetadata struct { + Tx std.Tx `json:"tx"` + Metadata *GnoTxMetadata `json:"metadata,omitempty"` +} + +type GnoTxMetadata struct { + Timestamp int64 `json:"timestamp"` +} + +// ReadGenesisTxs reads the genesis txs from the given file path +func ReadGenesisTxs(ctx context.Context, path string) ([]TxWithMetadata, error) { + // Open the txs file + file, loadErr := os.Open(path) + if loadErr != nil { + return nil, fmt.Errorf("unable to open tx file %s: %w", path, loadErr) + } + defer file.Close() + + var ( + txs []TxWithMetadata + + scanner = bufio.NewScanner(file) + ) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // Parse the amino JSON + var tx TxWithMetadata + if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { + return nil, fmt.Errorf( + "unable to unmarshal amino JSON, %w", + err, + ) + } + + txs = append(txs, tx) + } + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return txs, nil } diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go new file mode 100644 index 00000000000..b4625d6d7d6 --- /dev/null +++ b/gno.land/pkg/gnoland/types_test.go @@ -0,0 +1,131 @@ +package gnoland + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateTxs generates dummy transactions +func generateTxs(t *testing.T, count int) []TxWithMetadata { + t.Helper() + + txs := make([]TxWithMetadata, count) + + for i := 0; i < count; i++ { + txs[i] = TxWithMetadata{ + Tx: std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: crypto.Address{byte(i)}, + ToAddress: crypto.Address{byte(i)}, + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 1)), + }, + }, + Fee: std.Fee{ + GasWanted: 10, + GasFee: std.NewCoin(ugnot.Denom, 1000000), + }, + Memo: fmt.Sprintf("tx %d", i), + }, + } + } + + return txs +} + +func TestReadGenesisTxs(t *testing.T) { + t.Parallel() + + createFile := func(path, data string) { + file, err := os.Create(path) + require.NoError(t, err) + + _, err = file.WriteString(data) + require.NoError(t, err) + } + + t.Run("invalid path", func(t *testing.T) { + t.Parallel() + + path := "" // invalid + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + txs, err := ReadGenesisTxs(ctx, path) + assert.Nil(t, txs) + + assert.Error(t, err) + }) + + t.Run("invalid tx format", func(t *testing.T) { + t.Parallel() + + var ( + dir = t.TempDir() + path = filepath.Join(dir, "txs.jsonl") + ) + + // Create the file + createFile( + path, + "random data", + ) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + txs, err := ReadGenesisTxs(ctx, path) + assert.Nil(t, txs) + + assert.Error(t, err) + }) + + t.Run("valid txs", func(t *testing.T) { + t.Parallel() + + var ( + dir = t.TempDir() + path = filepath.Join(dir, "txs.jsonl") + txs = generateTxs(t, 1000) + ) + + // Create the file + file, err := os.Create(path) + require.NoError(t, err) + + // Write the transactions + for _, tx := range txs { + encodedTx, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + _, err = file.WriteString(fmt.Sprintf("%s\n", encodedTx)) + require.NoError(t, err) + } + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + // Load the transactions + readTxs, err := ReadGenesisTxs(ctx, path) + require.NoError(t, err) + + require.Len(t, readTxs, len(txs)) + + for index, readTx := range readTxs { + assert.Equal(t, txs[index], readTx) + } + }) +} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index d3f55cfadf7..56b298e4541 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -134,7 +134,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // This genesis will be use when node is started. genesis := &gnoland.GnoGenesisState{ Balances: LoadDefaultGenesisBalanceFile(t, gnoRootDir), - Txs: []std.Tx{}, + Txs: []gnoland.TxWithMetadata{}, } // test1 must be created outside of the loop below because it is already included in genesis so @@ -660,13 +660,13 @@ func (pl *pkgsLoader) SetPatch(replace, with string) { pl.patchs[replace] = with } -func (pl *pkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { +func (pl *pkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std.Coins) ([]gnoland.TxWithMetadata, error) { pkgslist, err := pl.List().Sort() // sorts packages by their dependencies. if err != nil { return nil, fmt.Errorf("unable to sort packages: %w", err) } - txs := make([]std.Tx, len(pkgslist)) + txs := make([]gnoland.TxWithMetadata, len(pkgslist)) for i, pkg := range pkgslist { tx, err := gnoland.LoadPackage(pkg, creator, fee, deposit) if err != nil { @@ -693,7 +693,9 @@ func (pl *pkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std } } - txs[i] = tx + txs[i] = gnoland.TxWithMetadata{ + Tx: tx, + } } return txs, nil diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 5e9e2272049..ef7e05d0787 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -54,13 +54,13 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem // TestingNodeConfig constructs an in-memory node configuration // with default packages and genesis transactions already loaded. // It will return the default creator address of the loaded packages. -func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...std.Tx) (*gnoland.InMemoryNodeConfig, bft.Address) { +func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...gnoland.TxWithMetadata) (*gnoland.InMemoryNodeConfig, bft.Address) { cfg := TestingMinimalNodeConfig(t, gnoroot) creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 balances := LoadDefaultGenesisBalanceFile(t, gnoroot) - txs := []std.Tx{} + txs := make([]gnoland.TxWithMetadata, 0) txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) txs = append(txs, additionalTxs...) @@ -121,13 +121,13 @@ func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey Amount: std.MustParseCoins(ugnot.ValueString(10000000000000)), }, }, - Txs: []std.Tx{}, + Txs: []gnoland.TxWithMetadata{}, }, } } // LoadDefaultPackages loads the default packages for testing using a given creator address and gnoroot directory. -func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std.Tx { +func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []gnoland.TxWithMetadata { examplesDir := filepath.Join(gnoroot, "examples") defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) @@ -148,7 +148,7 @@ func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balanc } // LoadDefaultGenesisTXsFile loads the default genesis transactions file for testing. -func LoadDefaultGenesisTXsFile(t TestingTS, chainid string, gnoroot string) []std.Tx { +func LoadDefaultGenesisTXsFile(t TestingTS, chainid string, gnoroot string) []gnoland.TxWithMetadata { txsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.jsonl") // NOTE: We dont care about giving a correct address here, as it's only for display diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 3cabc9df336..c11f81d852a 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -336,6 +336,7 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC app.deliverState.ctx = app.deliverState.ctx. WithBlockGasMeter(store.NewInfiniteGasMeter()) + // Run the set chain initializer res = app.initChainer(app.deliverState.ctx, req) // sanity check @@ -557,7 +558,9 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckTx) res.Error = ABCIError(std.ErrTxDecode(err.Error())) return } else { - result := app.runTx(RunTxModeCheck, req.Tx, tx) + ctx := app.getContextForTx(RunTxModeCheck, req.Tx) + + result := app.runTx(ctx, tx) res.ResponseBase = result.ResponseBase res.GasWanted = result.GasWanted res.GasUsed = result.GasUsed @@ -573,7 +576,9 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv res.Error = ABCIError(std.ErrTxDecode(err.Error())) return } else { - result := app.runTx(RunTxModeDeliver, req.Tx, tx) + ctx := app.getContextForTx(RunTxModeDeliver, req.Tx) + + result := app.runTx(ctx, tx) res.ResponseBase = result.ResponseBase res.GasWanted = result.GasWanted res.GasUsed = result.GasUsed @@ -701,14 +706,17 @@ func (app *BaseApp) cacheTxContext(ctx Context) (Context, store.MultiStore) { // anteHandler. The provided txBytes may be nil in some cases, eg. in tests. For // further details on transaction execution, reference the BaseApp SDK // documentation. -func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) { - // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is - // determined by the GasMeter. We need access to the context to get the gas - // meter so we initialize upfront. - var gasWanted int64 +func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { + var ( + // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is + // determined by the GasMeter. We need access to the context to get the gas + // meter so we initialize upfront. + gasWanted int64 + + ms = ctx.MultiStore() + mode = ctx.Mode() + ) - ctx := app.getContextForTx(mode, txBytes) - ms := ctx.MultiStore() if mode == RunTxModeDeliver { gasleft := ctx.BlockGasMeter().Remaining() ctx = ctx.WithGasMeter(store.NewPassthroughGasMeter( diff --git a/tm2/pkg/sdk/helpers.go b/tm2/pkg/sdk/helpers.go index d61e8115671..ea5cdfea2da 100644 --- a/tm2/pkg/sdk/helpers.go +++ b/tm2/pkg/sdk/helpers.go @@ -10,17 +10,36 @@ import ( var isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString func (app *BaseApp) Check(tx Tx) (result Result) { - return app.runTx(RunTxModeCheck, nil, tx) + ctx := app.getContextForTx(RunTxModeCheck, nil) + + return app.runTx(ctx, tx) } func (app *BaseApp) Simulate(txBytes []byte, tx Tx) (result Result) { - return app.runTx(RunTxModeSimulate, txBytes, tx) + ctx := app.getContextForTx(RunTxModeSimulate, txBytes) + + return app.runTx(ctx, tx) } -func (app *BaseApp) Deliver(tx Tx) (result Result) { - return app.runTx(RunTxModeDeliver, nil, tx) +func (app *BaseApp) Deliver(tx Tx, ctxFns ...ContextFn) (result Result) { + ctx := app.getContextForTx(RunTxModeDeliver, nil) + + for _, ctxFn := range ctxFns { + if ctxFn == nil { + continue + } + + ctx = ctxFn(ctx) + } + + return app.runTx(ctx, tx) } +// ContextFn is the custom execution context builder. +// It can be used to add custom metadata when replaying transactions +// during InitChainer or in the context of a unit test. +type ContextFn func(ctx Context) Context + // Context with current {check, deliver}State of the app // used by tests func (app *BaseApp) NewContext(mode RunTxMode, header abci.Header) Context { From 879041fcbfdbdb3bf56ebd27ca9c0ee581e56795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 4 Nov 2024 21:58:06 +0100 Subject: [PATCH 17/34] feat: add `contribs/gnomigrate` (#3063) ## Description This PR introduces a small tool called `gnomigrate`, which will serve as a helper for migrating Gno data. Currently, it only supports transaction sheet migration from `std.Tx` to `gnoland.TxWithMetadata`.
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- contribs/gnomigrate/Makefile | 18 ++ contribs/gnomigrate/README.md | 59 +++++ contribs/gnomigrate/go.mod | 57 +++++ contribs/gnomigrate/go.sum | 230 +++++++++++++++++++ contribs/gnomigrate/internal/txs/txs.go | 199 ++++++++++++++++ contribs/gnomigrate/internal/txs/txs_test.go | 157 +++++++++++++ contribs/gnomigrate/main.go | 14 ++ contribs/gnomigrate/migrate.go | 24 ++ 8 files changed, 758 insertions(+) create mode 100644 contribs/gnomigrate/Makefile create mode 100644 contribs/gnomigrate/README.md create mode 100644 contribs/gnomigrate/go.mod create mode 100644 contribs/gnomigrate/go.sum create mode 100644 contribs/gnomigrate/internal/txs/txs.go create mode 100644 contribs/gnomigrate/internal/txs/txs_test.go create mode 100644 contribs/gnomigrate/main.go create mode 100644 contribs/gnomigrate/migrate.go diff --git a/contribs/gnomigrate/Makefile b/contribs/gnomigrate/Makefile new file mode 100644 index 00000000000..155fc997012 --- /dev/null +++ b/contribs/gnomigrate/Makefile @@ -0,0 +1,18 @@ +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + + +.PHONY: install +install: + go install . + +.PHONY: build +build: + go build -o build/gnomigrate . + +lint: + $(golangci_lint) --config ../../.github/golangci.yml run ./... + +test: + go test $(GOTEST_FLAGS) -v ./... + diff --git a/contribs/gnomigrate/README.md b/contribs/gnomigrate/README.md new file mode 100644 index 00000000000..2b4f5ecf831 --- /dev/null +++ b/contribs/gnomigrate/README.md @@ -0,0 +1,59 @@ +## Overview + +`gnomigrate` is a CLI tool designed to migrate Gno legacy data formats to the new standard formats used in Gno +blockchain. + +## Features + +- **Transaction Migration**: Converts legacy `std.Tx` transactions to the new `gnoland.TxWithMetadata` format. + +## Installation + +### Clone the repository + +```shell +git clone https://github.com/gnolang/gno.git +``` + +### Navigate to the project directory + +```shell +cd contribs/gnomigrate +``` + +### Build the binary + +```shell +make build +``` + +### Install the binary + +```shell +make install +``` + +## Migrating legacy transactions + +The `gnomigrate` tool provides the `txs` subcommand to manage the migration of legacy transaction sheets. + +```shell +gnomigrate txs --input-dir --output-dir +``` + +### Flags + +- `--input-dir`: Specifies the directory containing the legacy transaction sheets to migrate. +- `--output-dir`: Specifies the directory where the migrated transaction sheets will be saved. + +### Example + +```shell +gnomigrate txs --input-dir ./legacy_txs --output-dir ./migrated_txs +``` + +This command will: + +- Read all `.jsonl` files from the ./legacy_txs directory, that are Amino-JSON encoded `std.Tx`s. +- Migrate each transaction from `std.Tx` to `gnoland.TxWithMetadata` (no metadata). +- Save the migrated transactions to the `./migrated_txs` directory, preserving the original directory structure. diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod new file mode 100644 index 00000000000..0499853969f --- /dev/null +++ b/contribs/gnomigrate/go.mod @@ -0,0 +1,57 @@ +module github.com/gnolang/gnomigrate + +go 1.23 + +require ( + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 +) + +replace github.com/gnolang/gno => ../.. + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + go.etcd.io/bbolt v1.3.11 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum new file mode 100644 index 00000000000..f3161e47bad --- /dev/null +++ b/contribs/gnomigrate/go.sum @@ -0,0 +1,230 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/contribs/gnomigrate/internal/txs/txs.go b/contribs/gnomigrate/internal/txs/txs.go new file mode 100644 index 00000000000..4c65ca6ef0b --- /dev/null +++ b/contribs/gnomigrate/internal/txs/txs.go @@ -0,0 +1,199 @@ +package txs + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + errInvalidInputDir = errors.New("invalid input directory") + errInvalidOutputDir = errors.New("invalid output directory") +) + +type txsCfg struct { + inputDir string + outputDir string +} + +// NewTxsCmd creates the migrate txs subcommand +func NewTxsCmd(io commands.IO) *commands.Command { + cfg := &txsCfg{} + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "txs", + ShortUsage: " [flags]", + ShortHelp: "manages the legacy transaction sheet migrations", + LongHelp: "Manages legacy transaction migrations through sheet input files", + }, + cfg, + func(ctx context.Context, _ []string) error { + return cfg.execMigrate(ctx, io) + }, + ) + + return cmd +} + +func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.inputDir, + "input-dir", + "", + "the input directory for the legacy transaction sheets", + ) + + fs.StringVar( + &c.outputDir, + "output-dir", + "", + "the output directory for the standard transaction sheets", + ) +} + +func (c *txsCfg) execMigrate(ctx context.Context, io commands.IO) error { + // Make sure the dirs are set + if c.inputDir == "" { + return errInvalidInputDir + } + + if c.outputDir == "" { + return errInvalidOutputDir + } + + // Make sure the output dir is present + if err := os.MkdirAll(c.outputDir, os.ModePerm); err != nil { + return fmt.Errorf("unable to create output dir, %w", err) + } + + return migrateDir(ctx, io, c.inputDir, c.outputDir) +} + +// migrateDir migrates the transaction sheet directory +func migrateDir( + ctx context.Context, + io commands.IO, + sourceDir string, + outputDir string, +) error { + // Read the sheet directory + entries, err := os.ReadDir(sourceDir) + if err != nil { + return fmt.Errorf("error reading directory %s, %w", sourceDir, err) + } + + for _, entry := range entries { + select { + case <-ctx.Done(): + return nil + default: + var ( + srcPath = filepath.Join(sourceDir, entry.Name()) + destPath = filepath.Join(outputDir, entry.Name()) + ) + + // Check if a dir is encountered + if !entry.IsDir() { + // Make sure the file type is valid + if !strings.HasSuffix(entry.Name(), ".jsonl") { + continue + } + + // Process the tx sheet + io.Printfln("Migrating %s -> %s", srcPath, destPath) + + if err := processFile(ctx, io, srcPath, destPath); err != nil { + io.ErrPrintfln("unable to process file %s, %w", srcPath, err) + } + + continue + } + + // Ensure destination directory exists + if err = os.MkdirAll(destPath, os.ModePerm); err != nil { + return fmt.Errorf("error creating directory %s, %w", destPath, err) + } + + // Recursively process the directory + if err = migrateDir(ctx, io, srcPath, destPath); err != nil { + io.ErrPrintfln("unable migrate directory %s, %w", srcPath, err) + } + } + } + + return nil +} + +// processFile processes the old legacy std.Tx sheet into the new standard gnoland.TxWithMetadata +func processFile(ctx context.Context, io commands.IO, source, destination string) error { + file, err := os.Open(source) + if err != nil { + return fmt.Errorf("unable to open file, %w", err) + } + defer file.Close() + + // Create the destination file + outputFile, err := os.Create(destination) + if err != nil { + return fmt.Errorf("unable to create file, %w", err) + } + defer outputFile.Close() + + scanner := bufio.NewScanner(file) + + scanner.Buffer(make([]byte, 1_000_000), 2_000_000) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil + default: + var ( + tx std.Tx + txWithMetadata gnoland.TxWithMetadata + ) + + if err = amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { + io.ErrPrintfln("unable to read line, %s", err) + + continue + } + + // Convert the std.Tx -> gnoland.TxWithMetadata + txWithMetadata = gnoland.TxWithMetadata{ + Tx: tx, + Metadata: nil, // not set + } + + // Save the new transaction with metadata + marshaledData, err := amino.MarshalJSON(txWithMetadata) + if err != nil { + io.ErrPrintfln("unable to marshal tx, %s", err) + + continue + } + + if _, err = outputFile.WriteString(fmt.Sprintf("%s\n", string(marshaledData))); err != nil { + io.ErrPrintfln("unable to save to output file, %s", err) + } + } + } + + // Check if there were any scanner errors + if err := scanner.Err(); err != nil { + return fmt.Errorf("error encountered during scan, %w", err) + } + + return nil +} diff --git a/contribs/gnomigrate/internal/txs/txs_test.go b/contribs/gnomigrate/internal/txs/txs_test.go new file mode 100644 index 00000000000..edc8addf213 --- /dev/null +++ b/contribs/gnomigrate/internal/txs/txs_test.go @@ -0,0 +1,157 @@ +package txs + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateDummyTxs generates dummy transactions +func generateDummyTxs(t *testing.T, count int) []std.Tx { + t.Helper() + + txs := make([]std.Tx, count) + + for i := 0; i < count; i++ { + txs[i] = std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: crypto.Address{byte(i)}, + ToAddress: crypto.Address{byte((i + 1) % count)}, + Amount: std.NewCoins(std.NewCoin(ugnot.Denom, 1)), + }, + }, + Fee: std.Fee{ + GasWanted: 1, + GasFee: std.NewCoin(ugnot.Denom, 1000000), + }, + Memo: fmt.Sprintf("tx %d", i), + } + } + + return txs +} + +func TestMigrate_Txs(t *testing.T) { + t.Parallel() + + t.Run("invalid input dir", func(t *testing.T) { + t.Parallel() + + // Perform the migration + cmd := NewTxsCmd(commands.NewTestIO()) + args := []string{ + "--input-dir", + "", + "--output-dir", + t.TempDir(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errInvalidInputDir) + }) + + t.Run("invalid output dir", func(t *testing.T) { + t.Parallel() + + // Perform the migration + cmd := NewTxsCmd(commands.NewTestIO()) + args := []string{ + "--input-dir", + t.TempDir(), + "--output-dir", + "", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errInvalidOutputDir) + }) + + t.Run("valid tx sheet migration", func(t *testing.T) { + t.Parallel() + + var ( + inputDir = t.TempDir() + outputDir = t.TempDir() + + txs = generateDummyTxs(t, 10000) + + chunks = 4 + chunkSize = len(txs) / chunks + ) + + getSheetPath := func(dir string, index int) string { + return filepath.Join(dir, fmt.Sprintf("transactions-sheet-%d.jsonl", index)) + } + + // Generate the initial sheet files + files := make([]*os.File, 0, chunks) + for i := 0; i < chunks; i++ { + f, err := os.Create(getSheetPath(inputDir, i)) + require.NoError(t, err) + + files = append(files, f) + } + + for i := 0; i < chunks; i++ { + var ( + start = i * chunkSize + end = start + chunkSize + ) + + if end > len(txs) { + end = len(txs) + } + + tx := txs[start:end] + + f := files[i] + + jsonData, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + _, err = f.WriteString(fmt.Sprintf("%s\n", jsonData)) + require.NoError(t, err) + } + + // Perform the migration + cmd := NewTxsCmd(commands.NewTestIO()) + args := []string{ + "--input-dir", + inputDir, + "--output-dir", + outputDir, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + metadataTxs := make([]gnoland.TxWithMetadata, 0, len(txs)) + for i := 0; i < chunks; i++ { + readTxs, err := gnoland.ReadGenesisTxs(context.Background(), getSheetPath(outputDir, i)) + require.NoError(t, err) + + metadataTxs = append(metadataTxs, readTxs...) + } + + // Make sure the metadata txs match + for index, tx := range metadataTxs { + assert.Equal(t, txs[index], tx.Tx) + } + }) +} diff --git a/contribs/gnomigrate/main.go b/contribs/gnomigrate/main.go new file mode 100644 index 00000000000..ea7e2561e8b --- /dev/null +++ b/contribs/gnomigrate/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "context" + "os" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func main() { + cmd := newMigrateCmd(commands.NewDefaultIO()) + + cmd.Execute(context.Background(), os.Args[1:]) +} diff --git a/contribs/gnomigrate/migrate.go b/contribs/gnomigrate/migrate.go new file mode 100644 index 00000000000..6c8667a5f58 --- /dev/null +++ b/contribs/gnomigrate/migrate.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gnomigrate/internal/txs" +) + +func newMigrateCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + ShortUsage: " [flags] [...]", + ShortHelp: "gno migration suite", + LongHelp: "Gno state migration suite, for managing legacy headaches", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + txs.NewTxsCmd(io), + ) + + return cmd +} From e3995b9745992ffea88589536b9adac78a876d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 4 Nov 2024 22:12:30 +0100 Subject: [PATCH 18/34] chore: update `tx-archive` in portal loop (#3064) ## Description This PR updates the `tx-archive` version in portal loop to use the new standard `gnoland.TxWithMetadata` format for transaction sheets. **BREAKING CHANGE** The portal loop will now save transactions with the new metadata format
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- misc/loop/cmd/snapshotter.go | 4 ++-- misc/loop/go.mod | 2 +- misc/loop/go.sum | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/misc/loop/cmd/snapshotter.go b/misc/loop/cmd/snapshotter.go index 8ace34c7ba6..06562e2c5c5 100644 --- a/misc/loop/cmd/snapshotter.go +++ b/misc/loop/cmd/snapshotter.go @@ -19,7 +19,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/gnolang/tx-archive/backup" "github.com/gnolang/tx-archive/backup/client/http" - "github.com/gnolang/tx-archive/backup/writer/legacy" + "github.com/gnolang/tx-archive/backup/writer/standard" ) const ( @@ -204,7 +204,7 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { } defer instanceBackupFile.Close() - w := legacy.NewWriter(instanceBackupFile) + w := standard.NewWriter(instanceBackupFile) // Create the tx-archive backup service c, err := http.NewClient(rpcURL) diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 2d749759bfb..9fc5bfb2d57 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/gnolang/tx-archive v0.3.0 + github.com/gnolang/tx-archive v0.4.0 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index c80bfb42c2f..f6a75e3740a 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -72,6 +72,8 @@ github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1G github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= github.com/gnolang/tx-archive v0.3.0 h1:5Fr39yAT7nnAPKvcmKmBT+oPiBhMhA0aUAIEeXrYG4I= github.com/gnolang/tx-archive v0.3.0/go.mod h1:WDgxSZibE7LkGdiVjkU/lhA35xyXjrSkZp6kwuTvSSw= +github.com/gnolang/tx-archive v0.4.0 h1:+1Rgo0U0HjLQLq/xqeGdJwtAzo9xWj09t1oZLvrL3bU= +github.com/gnolang/tx-archive v0.4.0/go.mod h1:seKHGnvxUnDgH/mSsCEdwG0dHY/FrpbUm6Hd0+KMd9w= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From 95df7b0c42414f987e8e5bb2696c7443ae7666d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 5 Nov 2024 07:19:03 +0100 Subject: [PATCH 19/34] chore: tidy `misc/loop` mod (#3065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR tidies the `misc/loop` `go.mod`. I'm not sure how the CI didn't catch this, or why it's green 🤷‍♂️
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- .github/workflows/mod-tidy.yml | 2 +- contribs/gnomigrate/go.mod | 2 +- gno.land/pkg/gnoland/types.go | 2 ++ misc/loop/go.sum | 2 -- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mod-tidy.yml b/.github/workflows/mod-tidy.yml index 118761bddf9..24eab553d19 100644 --- a/.github/workflows/mod-tidy.yml +++ b/.github/workflows/mod-tidy.yml @@ -23,4 +23,4 @@ jobs: - name: Check go.mod files are up to date working-directory: ${{ inputs.modulepath }} run: | - make tidy VERIFY_GO_SUMS=true + make tidy VERIFY_MOD_SUMS=true diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index 0499853969f..c492ae7c818 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gnomigrate -go 1.23 +go 1.22 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index b1bedb541aa..6bc4417d8e0 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -53,6 +53,8 @@ func ReadGenesisTxs(ctx context.Context, path string) ([]TxWithMetadata, error) scanner = bufio.NewScanner(file) ) + scanner.Buffer(make([]byte, 1_000_000), 2_000_000) + for scanner.Scan() { select { case <-ctx.Done(): diff --git a/misc/loop/go.sum b/misc/loop/go.sum index f6a75e3740a..27ed94fecae 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -70,8 +70,6 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= -github.com/gnolang/tx-archive v0.3.0 h1:5Fr39yAT7nnAPKvcmKmBT+oPiBhMhA0aUAIEeXrYG4I= -github.com/gnolang/tx-archive v0.3.0/go.mod h1:WDgxSZibE7LkGdiVjkU/lhA35xyXjrSkZp6kwuTvSSw= github.com/gnolang/tx-archive v0.4.0 h1:+1Rgo0U0HjLQLq/xqeGdJwtAzo9xWj09t1oZLvrL3bU= github.com/gnolang/tx-archive v0.4.0/go.mod h1:seKHGnvxUnDgH/mSsCEdwG0dHY/FrpbUm6Hd0+KMd9w= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= From 9096ef4ed45c9bbe97a201d81b03cc7cc88c928b Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 5 Nov 2024 09:52:51 +0100 Subject: [PATCH 20/34] fix(tm2): rename methods to avoid conflicts with (un)marshaler interfaces (#3000) Amino `codec.MarshallJSON` has the same method name as Go standard library `json.Marshaler` interface but with a different signature. This is rejected by `go vet`. The same applies for `codec.UnmarshallJSON` vs `json.Unmarshaler`. To fix that, we rename `codec.MarshalJSON` to `codec.JSONMarshal` and `codec.UnmarshalJSON` to `codec.JSONUnmarshal`. Now `go vet ./...' pass on the full mono-repo. Fixes #2954. BREAKING CHANGE: rename tm2 amino exported methods.
Contributors' checklist... - [*] Added new tests, or not needed, or not feasible - [*] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [*] Updated the official documentation or not needed - [*] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [*] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- .github/golangci.yml | 1 + tm2/pkg/amino/amino.go | 14 +++--- tm2/pkg/amino/benchmark_test.go | 4 +- tm2/pkg/amino/json_test.go | 52 ++++++++++----------- tm2/pkg/amino/reflect_test.go | 6 +-- tm2/pkg/amino/repr_test.go | 4 +- tm2/pkg/amino/tests/fuzz/json/debug/main.go | 2 +- tm2/pkg/amino/tests/fuzz/json/json.go | 2 +- 8 files changed, 43 insertions(+), 42 deletions(-) diff --git a/.github/golangci.yml b/.github/golangci.yml index e78d09a582e..43cea27a791 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -28,6 +28,7 @@ linters: - misspell # Misspelled English words in comments - makezero # Finds slice declarations with non-zero initial length - importas # Enforces consistent import aliases + - govet # same as 'go vet' - gosec # Security problems - gofmt # Whether the code was gofmt-ed - goimports # Unused imports diff --git a/tm2/pkg/amino/amino.go b/tm2/pkg/amino/amino.go index e402c74f4fd..262f5d9a54e 100644 --- a/tm2/pkg/amino/amino.go +++ b/tm2/pkg/amino/amino.go @@ -130,7 +130,7 @@ func UnmarshalAnySized(bz []byte, ptr interface{}) error { } func MarshalJSON(o interface{}) ([]byte, error) { - return gcdc.MarshalJSON(o) + return gcdc.JSONMarshal(o) } func MarshalJSONAny(o interface{}) ([]byte, error) { @@ -146,7 +146,7 @@ func MustMarshalJSONAny(o interface{}) []byte { } func UnmarshalJSON(bz []byte, ptr interface{}) error { - return gcdc.UnmarshalJSON(bz, ptr) + return gcdc.JSONUnmarshal(bz, ptr) } func MustUnmarshalJSON(bz []byte, ptr interface{}) { @@ -756,7 +756,7 @@ func (cdc *Codec) MustUnmarshalAny(bz []byte, ptr interface{}) { return } -func (cdc *Codec) MarshalJSON(o interface{}) ([]byte, error) { +func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) { cdc.doAutoseal() rv := reflect.ValueOf(o) @@ -814,7 +814,7 @@ func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) { // MustMarshalJSON panics if an error occurs. Besides tha behaves exactly like MarshalJSON. func (cdc *Codec) MustMarshalJSON(o interface{}) []byte { - bz, err := cdc.MarshalJSON(o) + bz, err := cdc.JSONMarshal(o) if err != nil { panic(err) } @@ -830,7 +830,7 @@ func (cdc *Codec) MustMarshalJSONAny(o interface{}) []byte { return bz } -func (cdc *Codec) UnmarshalJSON(bz []byte, ptr interface{}) error { +func (cdc *Codec) JSONUnmarshal(bz []byte, ptr interface{}) error { cdc.doAutoseal() if len(bz) == 0 { return errors.New("cannot decode empty bytes") @@ -851,7 +851,7 @@ func (cdc *Codec) UnmarshalJSON(bz []byte, ptr interface{}) error { // MustUnmarshalJSON panics if an error occurs. Besides tha behaves exactly like UnmarshalJSON. func (cdc *Codec) MustUnmarshalJSON(bz []byte, ptr interface{}) { - if err := cdc.UnmarshalJSON(bz, ptr); err != nil { + if err := cdc.JSONUnmarshal(bz, ptr); err != nil { panic(err) } } @@ -859,7 +859,7 @@ func (cdc *Codec) MustUnmarshalJSON(bz []byte, ptr interface{}) { // MarshalJSONIndent calls json.Indent on the output of cdc.MarshalJSON // using the given prefix and indent string. func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byte, error) { - bz, err := cdc.MarshalJSON(o) + bz, err := cdc.JSONMarshal(o) if err != nil { return nil, err } diff --git a/tm2/pkg/amino/benchmark_test.go b/tm2/pkg/amino/benchmark_test.go index 1edeb8d6fa9..a1489b36b44 100644 --- a/tm2/pkg/amino/benchmark_test.go +++ b/tm2/pkg/amino/benchmark_test.go @@ -103,7 +103,7 @@ func _benchmarkBinary(b *testing.B, cdc *amino.Codec, rt reflect.Type, codecType case "binary": bz, err = cdc.Marshal(ptr) case "json": - bz, err = cdc.MarshalJSON(ptr) + bz, err = cdc.JSONMarshal(ptr) case "binary_pb": bz, err = pbcdc.Marshal(ptr) case "binary_pb_translate_only": @@ -129,7 +129,7 @@ func _benchmarkBinary(b *testing.B, cdc *amino.Codec, rt reflect.Type, codecType case "binary": err = cdc.Unmarshal(bz, ptr2) case "json": - err = cdc.UnmarshalJSON(bz, ptr2) + err = cdc.JSONUnmarshal(bz, ptr2) case "binary_pb": err = pbcdc.Unmarshal(bz, ptr2) case "binary_pb_translate_only": diff --git a/tm2/pkg/amino/json_test.go b/tm2/pkg/amino/json_test.go index 9b5cd9cff09..e749522218a 100644 --- a/tm2/pkg/amino/json_test.go +++ b/tm2/pkg/amino/json_test.go @@ -114,7 +114,7 @@ func TestMarshalJSON(t *testing.T) { for i, tt := range cases { t.Logf("Trying case #%v", i) - blob, err := cdc.MarshalJSON(tt.in) + blob, err := cdc.JSONMarshal(tt.in) if tt.wantErr != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("#%d:\ngot:\n\t%v\nwant non-nil error containing\n\t%q", i, @@ -151,11 +151,11 @@ func TestMarshalJSONTime(t *testing.T) { Time: time.Now().Round(0).UTC(), // strip monotonic. } - b, err := cdc.MarshalJSON(s) + b, err := cdc.JSONMarshal(s) assert.Nil(t, err) var s2 SimpleStruct - err = cdc.UnmarshalJSON(b, &s2) + err = cdc.JSONUnmarshal(b, &s2) assert.Nil(t, err) assert.Equal(t, s, s2) } @@ -176,11 +176,11 @@ func TestMarshalJSONPBTime(t *testing.T) { Duration: &durationpb.Duration{Seconds: 100}, } - b, err := cdc.MarshalJSON(s) + b, err := cdc.JSONMarshal(s) assert.Nil(t, err) var s2 SimpleStruct - err = cdc.UnmarshalJSON(b, &s2) + err = cdc.JSONUnmarshal(b, &s2) assert.Nil(t, err) assert.Equal(t, s, s2) } @@ -217,15 +217,15 @@ func TestUnmarshalMap(t *testing.T) { obj := new(map[string]int) cdc := amino.NewCodec() assert.Panics(t, func() { - err := cdc.UnmarshalJSON(jsonBytes, &obj) + err := cdc.JSONUnmarshal(jsonBytes, &obj) assert.Fail(t, "should have panicked but got err: %v", err) }) assert.Panics(t, func() { - err := cdc.UnmarshalJSON(jsonBytes, obj) + err := cdc.JSONUnmarshal(jsonBytes, obj) assert.Fail(t, "should have panicked but got err: %v", err) }) assert.Panics(t, func() { - bz, err := cdc.MarshalJSON(obj) + bz, err := cdc.JSONMarshal(obj) assert.Fail(t, "should have panicked but got bz: %X err: %v", bz, err) }) } @@ -237,22 +237,22 @@ func TestUnmarshalFunc(t *testing.T) { obj := func() {} cdc := amino.NewCodec() assert.Panics(t, func() { - err := cdc.UnmarshalJSON(jsonBytes, &obj) + err := cdc.JSONUnmarshal(jsonBytes, &obj) assert.Fail(t, "should have panicked but got err: %v", err) }) - err := cdc.UnmarshalJSON(jsonBytes, obj) - // UnmarshalJSON expects a pointer + err := cdc.JSONUnmarshal(jsonBytes, obj) + // JSONUnmarshal expects a pointer assert.Error(t, err) // ... nor encoding it. assert.Panics(t, func() { - bz, err := cdc.MarshalJSON(obj) + bz, err := cdc.JSONMarshal(obj) assert.Fail(t, "should have panicked but got bz: %X err: %v", bz, err) }) } -func TestUnmarshalJSON(t *testing.T) { +func TestJSONUnmarshal(t *testing.T) { t.Parallel() cdc := amino.NewCodec() @@ -323,7 +323,7 @@ func TestUnmarshalJSON(t *testing.T) { } for i, tt := range cases { - err := cdc.UnmarshalJSON([]byte(tt.blob), tt.in) + err := cdc.JSONUnmarshal([]byte(tt.blob), tt.in) if tt.wantErr != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("#%d:\ngot:\n\t%q\nwant non-nil error containing\n\t%q", i, @@ -390,7 +390,7 @@ func TestJSONCodecRoundTrip(t *testing.T) { } for i, tt := range cases { - mBlob, err := cdc.MarshalJSON(tt.in) + mBlob, err := cdc.JSONMarshal(tt.in) if tt.wantErr != "" { if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("#%d:\ngot:\n\t%q\nwant non-nil error containing\n\t%q", i, @@ -400,27 +400,27 @@ func TestJSONCodecRoundTrip(t *testing.T) { } if err != nil { - t.Errorf("#%d: unexpected error after MarshalJSON: %v", i, err) + t.Errorf("#%d: unexpected error after JSONMarshal: %v", i, err) continue } - if err = cdc.UnmarshalJSON(mBlob, tt.out); err != nil { - t.Errorf("#%d: unexpected error after UnmarshalJSON: %v\nmBlob: %s", i, err, mBlob) + if err = cdc.JSONUnmarshal(mBlob, tt.out); err != nil { + t.Errorf("#%d: unexpected error after JSONUnmarshal: %v\nmBlob: %s", i, err, mBlob) continue } // Now check that the input is exactly equal to the output - uBlob, err := cdc.MarshalJSON(tt.out) + uBlob, err := cdc.JSONMarshal(tt.out) assert.NoError(t, err) - if err := cdc.UnmarshalJSON(mBlob, tt.out); err != nil { - t.Errorf("#%d: unexpected error after second MarshalJSON: %v", i, err) + if err := cdc.JSONUnmarshal(mBlob, tt.out); err != nil { + t.Errorf("#%d: unexpected error after second JSONMarshal: %v", i, err) continue } if !reflect.DeepEqual(tt.want, tt.out) { - t.Errorf("#%d: After roundtrip UnmarshalJSON\ngot: \t%v\nwant:\t%v", i, tt.out, tt.want) + t.Errorf("#%d: After roundtrip JSONUnmarshal\ngot: \t%v\nwant:\t%v", i, tt.out, tt.want) } if !bytes.Equal(mBlob, uBlob) { - t.Errorf("#%d: After roundtrip MarshalJSON\ngot: \t%s\nwant:\t%s", i, uBlob, mBlob) + t.Errorf("#%d: After roundtrip JSONMarshal\ngot: \t%s\nwant:\t%s", i, uBlob, mBlob) } } } @@ -526,10 +526,10 @@ func TestAminoJSONTimeEncodeDecodeRoundTrip(t *testing.T) { din := time.Date(2008, 9, 15, 14, 13, 12, 11109876, loc).Round(time.Millisecond).UTC() cdc := amino.NewCodec() - blobAmino, err := cdc.MarshalJSON(din) - require.Nil(t, err, "amino.Codec.MarshalJSON should succeed") + blobAmino, err := cdc.JSONMarshal(din) + require.Nil(t, err, "amino.Codec.JSONMarshal should succeed") var tAminoOut time.Time - require.Nil(t, cdc.UnmarshalJSON(blobAmino, &tAminoOut), "amino.Codec.UnmarshalJSON should succeed") + require.Nil(t, cdc.JSONUnmarshal(blobAmino, &tAminoOut), "amino.Codec.JSONUnmarshal should succeed") require.NotEqual(t, tAminoOut, time.Time{}, "amino.marshaled definitely isn't equal to zero time") require.Equal(t, tAminoOut, din, "expecting marshaled in to be equal to marshaled out") diff --git a/tm2/pkg/amino/reflect_test.go b/tm2/pkg/amino/reflect_test.go index bd815c8b9a6..475930e540c 100644 --- a/tm2/pkg/amino/reflect_test.go +++ b/tm2/pkg/amino/reflect_test.go @@ -120,7 +120,7 @@ func _testCodec(t *testing.T, rt reflect.Type, codecType string) { case "binary": bz, err = cdc.Marshal(ptr) case "json": - bz, err = cdc.MarshalJSON(ptr) + bz, err = cdc.JSONMarshal(ptr) default: panic("should not happen") } @@ -133,7 +133,7 @@ func _testCodec(t *testing.T, rt reflect.Type, codecType string) { case "binary": err = cdc.Unmarshal(bz, ptr2) case "json": - err = cdc.UnmarshalJSON(bz, ptr2) + err = cdc.JSONUnmarshal(bz, ptr2) default: panic("should not happen") } @@ -427,7 +427,7 @@ func TestCodecJSONRoundtripNonNilRegisteredTypeDef(t *testing.T) { "ConcreteTypeDef incorrectly serialized") var i1 tests.Interface1 - err = cdc.UnmarshalJSON(bz, &i1) + err = cdc.JSONUnmarshal(bz, &i1) assert.Nil(t, err) assert.Equal(t, c3, i1) } diff --git a/tm2/pkg/amino/repr_test.go b/tm2/pkg/amino/repr_test.go index 4be50b5d93d..e9162d2883e 100644 --- a/tm2/pkg/amino/repr_test.go +++ b/tm2/pkg/amino/repr_test.go @@ -91,13 +91,13 @@ func TestMarshalAminoJSON(t *testing.T) { c: []*Foo{nil, nil, nil}, D: "J", } - bz, err := cdc.MarshalJSON(f) + bz, err := cdc.JSONMarshal(f) assert.Nil(t, err) t.Logf("bz %X", bz) var f2 Foo - err = cdc.UnmarshalJSON(bz, &f2) + err = cdc.JSONUnmarshal(bz, &f2) assert.Nil(t, err) assert.Equal(t, f, f2) diff --git a/tm2/pkg/amino/tests/fuzz/json/debug/main.go b/tm2/pkg/amino/tests/fuzz/json/debug/main.go index 892ff943381..8eb41cd0b30 100644 --- a/tm2/pkg/amino/tests/fuzz/json/debug/main.go +++ b/tm2/pkg/amino/tests/fuzz/json/debug/main.go @@ -13,6 +13,6 @@ func main() { bz := []byte("TODO") cdc := amino.NewCodec() cst := tests.ComplexSt{} - err := cdc.UnmarshalJSON(bz, &cst) + err := cdc.JSONUnmarshal(bz, &cst) fmt.Printf("Expected a panic but did not. (err: %v)", err) } diff --git a/tm2/pkg/amino/tests/fuzz/json/json.go b/tm2/pkg/amino/tests/fuzz/json/json.go index b214bd7498b..4c3afe16fd3 100644 --- a/tm2/pkg/amino/tests/fuzz/json/json.go +++ b/tm2/pkg/amino/tests/fuzz/json/json.go @@ -13,7 +13,7 @@ import ( func Fuzz(data []byte) int { cdc := amino.NewCodec() cst := tests.ComplexSt{} - err := cdc.UnmarshalJSON(data, &cst) + err := cdc.JSONUnmarshal(data, &cst) if err != nil { return 0 } From 367408af5c633abd1e828a69983a3501c35602dc Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 5 Nov 2024 10:52:53 +0100 Subject: [PATCH 21/34] chore: fix lint issues from `go vet` (#3069) After merge of #3000, a few missing field names in struct literal were reported. Fix it. No functional change.
Contributors' checklist... - [*] Added new tests, or not needed, or not feasible - [*] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [*] Updated the official documentation or not needed - [*] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- gno.land/pkg/sdk/vm/handler_test.go | 8 ++++---- gno.land/pkg/sdk/vm/keeper_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 7e029f4cacb..0d238deed1f 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -113,7 +113,7 @@ func TestVmHandlerQuery_Eval(t *testing.T) { // Create test package. files := []*gnovm.MemFile{ - {"hello.gno", ` + {Name: "hello.gno", Body: ` package hello import "std" @@ -206,7 +206,7 @@ func TestVmHandlerQuery_Funcs(t *testing.T) { // Create test package. files := []*gnovm.MemFile{ - {"hello.gno", ` + {Name: "hello.gno", Body: ` package hello var sl = []int{1,2,3,4,5} @@ -284,8 +284,8 @@ func TestVmHandlerQuery_File(t *testing.T) { // Create test package. files := []*gnovm.MemFile{ - {"README.md", "# Hello"}, - {"hello.gno", "package hello\n\nfunc Hello() string { return \"hello\" }\n"}, + {Name: "README.md", Body: "# Hello"}, + {Name: "hello.gno", Body: "package hello\n\nfunc Hello() string { return \"hello\" }\n"}, } pkgPath := "gno.land/r/hello" msg1 := NewMsgAddPackage(addr, pkgPath, files) diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 3aba53d4490..9afbb3de551 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -314,7 +314,7 @@ func TestVMKeeperParams(t *testing.T) { // Create test package. files := []*gnovm.MemFile{ - {"init.gno", ` + {Name: "init.gno", Body: ` package test import "std" From 538ebff9d05ddadaacc8ef780692d326f8500d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jer=C3=B3nimo=20Albi?= Date: Wed, 6 Nov 2024 13:07:06 +0100 Subject: [PATCH 22/34] feat: change `gnoweb` to add URL query string to render path (#2876) ### Description This PR introduces a change for _gnoweb_ to also pass the URL query string as part of the render path. Passing the URL query string is required to support arguments when rendering realms, which is the case of the AVL pager implementation in #2584 that uses "page" and "size". The PR changes the behavior of realm rendering calls by adding URL query arguments as suffix of the `path` argument, so URLs like `https://gno.land/r/demo/foo:bar?baz=42` would call `Render(path)` with "bar?baz=42" as value of `path`. It also changes how special _gnoweb_ arguments like `help` of `func` are specified which now must be done by using the `$` character, for example: - `https://gno.land/r/demo/foo$help&func=Bar&name=Baz` - `https://gno.land/r/demo/foo:example$tz=Europe/Paris` - `https://gno.land/r/demo/foo:example?value=42$tz=Europe/Paris` Note that `__func` is now `func`, without the underscore prefix. ### Possible Issues The change could potentially affect realm or package implementations that rely on the render path in cases where the `?` is not expected, for example when parsing or splitting it. Because of that there might be changes required to handle the case where a caller sends query string arguments. Realms and packages should be able to handle render paths which could look like `render/path?arg=1&arg=N`. Links that still use `?`, `help` and `__func` won't work as expected, they must be changed to use `$` instead of `?`, and `func` instead of `__func`. Packages `gno.land/p/moul/txlink` and `gno.land/p/moul/helplink` has to be refactored to properly generate links. --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: leohhhn Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- .../gno.land/p/moul/helplink/helplink.gno | 2 +- .../p/moul/helplink/helplink_test.gno | 40 ++++----- examples/gno.land/p/moul/txlink/txlink.gno | 2 +- .../gno.land/p/moul/txlink/txlink_test.gno | 24 ++--- examples/gno.land/r/demo/boards/README.md | 8 +- .../gno.land/r/demo/boards/z_0_filetest.gno | 6 +- .../r/demo/boards/z_10_c_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_10_filetest.gno | 2 +- .../r/demo/boards/z_11_d_filetest.gno | 8 +- .../gno.land/r/demo/boards/z_11_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_12_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_2_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_3_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_4_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_5_c_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_5_filetest.gno | 6 +- .../gno.land/r/demo/boards/z_6_filetest.gno | 8 +- .../gno.land/r/demo/boards/z_7_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_8_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_9_filetest.gno | 2 +- .../r/demo/games/dice_roller/dice_roller.gno | 4 +- .../gno.land/r/demo/games/shifumi/shifumi.gno | 4 +- examples/gno.land/r/demo/profile/render.gno | 6 +- examples/gno.land/r/demo/tamagotchi/realm.gno | 8 +- .../r/demo/tamagotchi/z0_filetest.gno | 8 +- .../gno.land/r/gnoland/events/rendering.gno | 4 +- .../gno.land/r/morgan/guestbook/guestbook.gno | 2 +- gno.land/genesis/genesis_txs.jsonl | 2 +- gno.land/pkg/gnoweb/gnoweb.go | 88 ++++++++++++++++--- gno.land/pkg/gnoweb/gnoweb_test.go | 11 ++- gno.land/pkg/gnoweb/views/realm_help.html | 2 +- gno.land/pkg/gnoweb/views/realm_render.html | 2 +- misc/deployments/test4.gno.land/genesis.json | 40 ++++----- 33 files changed, 201 insertions(+), 128 deletions(-) diff --git a/examples/gno.land/p/moul/helplink/helplink.gno b/examples/gno.land/p/moul/helplink/helplink.gno index b9ac8e102b9..0c18f5d0360 100644 --- a/examples/gno.land/p/moul/helplink/helplink.gno +++ b/examples/gno.land/p/moul/helplink/helplink.gno @@ -75,5 +75,5 @@ func (r Realm) FuncURL(fn string, args ...string) string { // Home returns the base help URL for the specified realm. func (r Realm) Home() string { - return r.prefix() + "?help" + return r.prefix() + "$help" } diff --git a/examples/gno.land/p/moul/helplink/helplink_test.gno b/examples/gno.land/p/moul/helplink/helplink_test.gno index 07111158a98..29cfd02eb67 100644 --- a/examples/gno.land/p/moul/helplink/helplink_test.gno +++ b/examples/gno.land/p/moul/helplink/helplink_test.gno @@ -14,11 +14,11 @@ func TestFunc(t *testing.T) { want string realm Realm }{ - {"Example", "foo", []string{"bar", "1", "baz", "2"}, "[Example](?help&__func=foo&bar=1&baz=2)", ""}, - {"Realm Example", "foo", []string{"bar", "1", "baz", "2"}, "[Realm Example](/r/lorem/ipsum?help&__func=foo&bar=1&baz=2)", "gno.land/r/lorem/ipsum"}, - {"Single Arg", "testFunc", []string{"key", "value"}, "[Single Arg](?help&__func=testFunc&key=value)", ""}, - {"No Args", "noArgsFunc", []string{}, "[No Args](?help&__func=noArgsFunc)", ""}, - {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args](?help&__func=oddArgsFunc)", ""}, + {"Example", "foo", []string{"bar", "1", "baz", "2"}, "[Example]($help&func=foo&bar=1&baz=2)", ""}, + {"Realm Example", "foo", []string{"bar", "1", "baz", "2"}, "[Realm Example](/r/lorem/ipsum$help&func=foo&bar=1&baz=2)", "gno.land/r/lorem/ipsum"}, + {"Single Arg", "testFunc", []string{"key", "value"}, "[Single Arg]($help&func=testFunc&key=value)", ""}, + {"No Args", "noArgsFunc", []string{}, "[No Args]($help&func=noArgsFunc)", ""}, + {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args]($help&func=oddArgsFunc)", ""}, } for _, tt := range tests { @@ -36,18 +36,18 @@ func TestFuncURL(t *testing.T) { want string realm Realm }{ - {"foo", []string{"bar", "1", "baz", "2"}, "?help&__func=foo&bar=1&baz=2", ""}, - {"testFunc", []string{"key", "value"}, "?help&__func=testFunc&key=value", ""}, - {"noArgsFunc", []string{}, "?help&__func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "?help&__func=oddArgsFunc", ""}, - {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, - {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, - {"noArgsFunc", []string{}, "/r/lorem/ipsum?help&__func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, - {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, - {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, - {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum?help&__func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, + {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, + {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, } for _, tt := range tests { @@ -64,9 +64,9 @@ func TestHome(t *testing.T) { realm Realm want string }{ - {"", "?help"}, - {"gno.land/r/lorem/ipsum", "/r/lorem/ipsum?help"}, - {"gno.world/r/lorem/ipsum", "https://gno.world/r/lorem/ipsum?help"}, + {"", "$help"}, + {"gno.land/r/lorem/ipsum", "/r/lorem/ipsum$help"}, + {"gno.world/r/lorem/ipsum", "https://gno.world/r/lorem/ipsum$help"}, } for _, tt := range tests { diff --git a/examples/gno.land/p/moul/txlink/txlink.gno b/examples/gno.land/p/moul/txlink/txlink.gno index 26656e67f29..4705161578c 100644 --- a/examples/gno.land/p/moul/txlink/txlink.gno +++ b/examples/gno.land/p/moul/txlink/txlink.gno @@ -52,7 +52,7 @@ func (r Realm) prefix() string { // arguments. func (r Realm) URL(fn string, args ...string) string { // Start with the base query - url := r.prefix() + "?help&__func=" + fn + url := r.prefix() + "$help&func=" + fn // Check if args length is even if len(args)%2 != 0 { diff --git a/examples/gno.land/p/moul/txlink/txlink_test.gno b/examples/gno.land/p/moul/txlink/txlink_test.gno index 7a460889148..a598a06b154 100644 --- a/examples/gno.land/p/moul/txlink/txlink_test.gno +++ b/examples/gno.land/p/moul/txlink/txlink_test.gno @@ -13,18 +13,18 @@ func TestURL(t *testing.T) { want string realm Realm }{ - {"foo", []string{"bar", "1", "baz", "2"}, "?help&__func=foo&bar=1&baz=2", ""}, - {"testFunc", []string{"key", "value"}, "?help&__func=testFunc&key=value", ""}, - {"noArgsFunc", []string{}, "?help&__func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "?help&__func=oddArgsFunc", ""}, - {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, - {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, - {"noArgsFunc", []string{}, "/r/lorem/ipsum?help&__func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, - {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum?help&__func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, - {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum?help&__func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, - {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum?help&__func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum?help&__func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, + {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, + {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, + {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, + {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, } for _, tt := range tests { diff --git a/examples/gno.land/r/demo/boards/README.md b/examples/gno.land/r/demo/boards/README.md index 3aa765df25a..174e1c242fc 100644 --- a/examples/gno.land/r/demo/boards/README.md +++ b/examples/gno.land/r/demo/boards/README.md @@ -85,7 +85,7 @@ The `USERNAME` for posting can different than your `KEYNAME`. It is internally l ./build/gnokey maketx call -pkgpath "gno.land/r/demo/users" -func "Register" -args "" -args "USERNAME" -args "Profile description" -gas-fee "10000000ugnot" -gas-wanted "2000000" -send "200000000ugnot" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME ``` -Interactive documentation: https://gno.land/r/demo/users?help&__func=Register +Interactive documentation: https://gno.land/r/demo/users$help&func=Register ### Create a board with a smart contract call. @@ -93,7 +93,7 @@ Interactive documentation: https://gno.land/r/demo/users?help&__func=Register ./build/gnokey maketx call -pkgpath "gno.land/r/demo/boards" -func "CreateBoard" -args "BOARDNAME" -gas-fee "1000000ugnot" -gas-wanted "10000000" -broadcast -chainid dev -remote localhost:26657 KEYNAME ``` -Interactive documentation: https://gno.land/r/demo/boards?help&__func=CreateBoard +Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateBoard Next, query for the permanent board ID by querying (you need this to create a new post): @@ -109,7 +109,7 @@ NOTE: If a board was created successfully, your SEQUENCE_NUMBER would have incre ./build/gnokey maketx call -pkgpath "gno.land/r/demo/boards" -func "CreateThread" -args BOARD_ID -args "Hello gno.land" -args "Text of the post" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME ``` -Interactive documentation: https://gno.land/r/demo/boards?help&__func=CreateThread +Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateThread ### Create a comment to a post. @@ -117,7 +117,7 @@ Interactive documentation: https://gno.land/r/demo/boards?help&__func=CreateThre ./build/gnokey maketx call -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 -chainid dev -remote localhost:26657 KEYNAME ``` -Interactive documentation: https://gno.land/r/demo/boards?help&__func=CreateReply +Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateReply ```bash ./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:BOARDNAME/1" -remote localhost:26657 diff --git a/examples/gno.land/r/demo/boards/z_0_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index cdf136be590..4fc224da9b2 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -24,16 +24,16 @@ func main() { } // Output: -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1)] +// \[[post](/r/demo/boards$help&func=CreateThread&bid=1)] // // ---------------------------------------- // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) // // ---------------------------------------- // ## [Second Post (title)](/r/demo/boards:test_board/2) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) (0 reposts) diff --git a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno index cc8d188f727..f746877b5c7 100644 --- a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno @@ -35,14 +35,14 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/demo/boards/z_10_filetest.gno b/examples/gno.land/r/demo/boards/z_10_filetest.gno index 0a4626003d1..8a6d11c79cf 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // thread does not exist with id: 1 diff --git a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno index 04dd6bfb547..de2b6aa463b 100644 --- a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno @@ -35,18 +35,18 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // > Edited: First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/demo/boards/z_11_filetest.gno b/examples/gno.land/r/demo/boards/z_11_filetest.gno index 0974991b814..49ee0ee0273 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -33,10 +33,10 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // # Edited: First Post in (title) // // Edited: Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/demo/boards/z_12_filetest.gno b/examples/gno.land/r/demo/boards/z_12_filetest.gno index 8ae1c99ffbb..02953352dd2 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -30,11 +30,11 @@ func main() { // Output: // 1 -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=2)] +// \[[post](/r/demo/boards$help&func=CreateThread&bid=2)] // // ---------------------------------------- // Repost: Check this out // ## [First Post (title)](/r/demo/boards:test_board1/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (1 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (1 reposts) diff --git a/examples/gno.land/r/demo/boards/z_2_filetest.gno b/examples/gno.land/r/demo/boards/z_2_filetest.gno index 037a855eab1..53c0a1965da 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -32,7 +32,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/demo/boards/z_3_filetest.gno b/examples/gno.land/r/demo/boards/z_3_filetest.gno index 79770aa1cec..89e5a3f12ff 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -34,7 +34,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index 61e4681f202..fa0b9e20be5 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -37,13 +37,13 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] // Realm: // switchrealm["gno.land/r/demo/users"] diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 440daa6238d..b20d8cdfed8 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post (title) // // Body of the first post. (body) -// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] // // > Reply of the first post -// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/demo/boards/z_5_filetest.gno b/examples/gno.land/r/demo/boards/z_5_filetest.gno index 9d30d9aaa72..c0614bb9da3 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -33,11 +33,11 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/demo/boards/z_6_filetest.gno b/examples/gno.land/r/demo/boards/z_6_filetest.gno index fb7f9a31772..6ddd8b9cf3f 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -35,15 +35,15 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] // > // > > First reply of the first reply // > > -// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/demo/boards/z_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index 7df06fd760a..534095b99cf 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -22,10 +22,10 @@ func main() { } // Output: -// \[[post](/r/demo/boards?help&__func=CreateThread&bid=1)] +// \[[post](/r/demo/boards$help&func=CreateThread&bid=1)] // // ---------------------------------------- // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) diff --git a/examples/gno.land/r/demo/boards/z_8_filetest.gno b/examples/gno.land/r/demo/boards/z_8_filetest.gno index 02ce2c4fcef..f5477026805 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -35,10 +35,10 @@ func main() { // _[see thread](/r/demo/boards:test_board/2)_ // // Reply of the second post -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] // // _[see all 1 replies](/r/demo/boards:test_board/2/3)_ // // > First reply of the first reply // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] diff --git a/examples/gno.land/r/demo/boards/z_9_filetest.gno b/examples/gno.land/r/demo/boards/z_9_filetest.gno index 823318a5e48..4be9d2bdfa6 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -34,4 +34,4 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=2&threadid=1&postid=1)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=2&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=2&threadid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=2&threadid=1&postid=1)] diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno index 9dcd67f0dcb..4dbbd6c7682 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno @@ -162,8 +162,8 @@ Welcome to Dice Roller! Challenge your friends to a simple yet exciting dice rol --- ## **How to Play**: -1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller?help&__func=NewGame) -2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller?help&__func=Play) +1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help&func=NewGame) +2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help&func=Play) --- diff --git a/examples/gno.land/r/demo/games/shifumi/shifumi.gno b/examples/gno.land/r/demo/games/shifumi/shifumi.gno index 9094cb8fd69..3de09196da1 100644 --- a/examples/gno.land/r/demo/games/shifumi/shifumi.gno +++ b/examples/gno.land/r/demo/games/shifumi/shifumi.gno @@ -86,8 +86,8 @@ func Render(path string) string { output := `# 👊 ✋ ✌️ Shifumi Actions: -* [NewGame](shifumi?help&__func=NewGame) opponentAddress -* [Play](shifumi?help&__func=Play) gameIndex move (1=rock, 2=paper, 3=scissors) +* [NewGame](shifumi$help&func=NewGame) opponentAddress +* [Play](shifumi$help&func=Play) gameIndex move (1=rock, 2=paper, 3=scissors) game | player1 | | player2 | | win --- | --- | --- | --- | --- | --- diff --git a/examples/gno.land/r/demo/profile/render.gno b/examples/gno.land/r/demo/profile/render.gno index 79d1078a997..223839851dd 100644 --- a/examples/gno.land/r/demo/profile/render.gno +++ b/examples/gno.land/r/demo/profile/render.gno @@ -11,9 +11,9 @@ import ( const ( BaseURL = "/r/demo/profile" - SetStringFieldURL = BaseURL + "?help&__func=SetStringField&field=%s" - SetIntFieldURL = BaseURL + "?help&__func=SetIntField&field=%s" - SetBoolFieldURL = BaseURL + "?help&__func=SetBoolField&field=%s" + SetStringFieldURL = BaseURL + "$help&func=SetStringField&field=%s" + SetIntFieldURL = BaseURL + "$help&func=SetIntField&field=%s" + SetBoolFieldURL = BaseURL + "$help&func=SetBoolField&field=%s" ViewAllFieldsURL = BaseURL + ":u/%s" ViewFieldURL = BaseURL + ":f/%s/%s" ) diff --git a/examples/gno.land/r/demo/tamagotchi/realm.gno b/examples/gno.land/r/demo/tamagotchi/realm.gno index f8f62c9fc7a..f6d648180ed 100644 --- a/examples/gno.land/r/demo/tamagotchi/realm.gno +++ b/examples/gno.land/r/demo/tamagotchi/realm.gno @@ -43,10 +43,10 @@ func Heal() string { func Render(path string) string { tama := t.Markdown() links := `Actions: -* [Feed](/r/demo/tamagotchi?help&__func=Feed) -* [Play](/r/demo/tamagotchi?help&__func=Play) -* [Heal](/r/demo/tamagotchi?help&__func=Heal) -* [Reset](/r/demo/tamagotchi?help&__func=Reset) +* [Feed](/r/demo/tamagotchi$help&func=Feed) +* [Play](/r/demo/tamagotchi$help&func=Play) +* [Heal](/r/demo/tamagotchi$help&func=Heal) +* [Reset](/r/demo/tamagotchi$help&func=Reset) ` return tama + "\n\n" + links diff --git a/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno index e494ec5cbc8..1ea56b4a3f9 100644 --- a/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno +++ b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno @@ -19,7 +19,7 @@ func main() { // * sleepy: 0 // // Actions: -// * [Feed](/r/demo/tamagotchi?help&__func=Feed) -// * [Play](/r/demo/tamagotchi?help&__func=Play) -// * [Heal](/r/demo/tamagotchi?help&__func=Heal) -// * [Reset](/r/demo/tamagotchi?help&__func=Reset) +// * [Feed](/r/demo/tamagotchi$help&func=Feed) +// * [Play](/r/demo/tamagotchi$help&func=Play) +// * [Heal](/r/demo/tamagotchi$help&func=Heal) +// * [Reset](/r/demo/tamagotchi$help&func=Reset) diff --git a/examples/gno.land/r/gnoland/events/rendering.gno b/examples/gno.land/r/gnoland/events/rendering.gno index d98879c68f6..89f9a69cc8a 100644 --- a/examples/gno.land/r/gnoland/events/rendering.gno +++ b/examples/gno.land/r/gnoland/events/rendering.gno @@ -122,8 +122,8 @@ func (e Event) Render(admin bool) string { buf.WriteString(ufmt.Sprintf("**Ends:** %s UTC%s%d\n\n", e.endTime.Format("02 Jan 2006, 03:04 PM"), sign, hoursOffset)) if admin { - buf.WriteString(ufmt.Sprintf("[EDIT](/r/gnoland/events?help&__func=EditEvent&id=%s)\n\n", e.id)) - buf.WriteString(ufmt.Sprintf("[DELETE](/r/gnoland/events?help&__func=DeleteEvent&id=%s)\n\n", e.id)) + buf.WriteString(ufmt.Sprintf("[EDIT](/r/gnoland/events$help&func=EditEvent&id=%s)\n\n", e.id)) + buf.WriteString(ufmt.Sprintf("[DELETE](/r/gnoland/events$help&func=DeleteEvent&id=%s)\n\n", e.id)) } if e.link != "" { diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno index b3a56d88397..be9e9db6133 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -83,7 +83,7 @@ func validateMessage(msg string) string { func Render(maxID string) string { var bld strings.Builder - bld.WriteString("# Guestbook 📝\n\n[Come sign the guestbook!](./guestbook?help&__func=Sign)\n\n---\n\n") + bld.WriteString("# Guestbook 📝\n\n[Come sign the guestbook!](./guestbook$help&func=Sign)\n\n---\n\n") var maxIDBinary string if maxID != "" { diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 43453dcd2fc..fa2c9e83fbd 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -12,6 +12,6 @@ {"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","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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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\u0026__func=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\u0026__func=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\u0026__func=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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","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":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"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/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go index ed6271f5afe..40d027d84b9 100644 --- a/gno.land/pkg/gnoweb/gnoweb.go +++ b/gno.land/pkg/gnoweb/gnoweb.go @@ -32,7 +32,9 @@ import ( ) const ( - qFileStr = "vm/qfile" + qFileStr = "vm/qfile" + gnowebArgsSeparator = "$" + urlQuerySeparator = "?" ) //go:embed views/* @@ -93,8 +95,8 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { // realm routes // NOTE: see rePathPart. app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:(?:.*\\.(?:gno|md|txt|mod)$)|(?:LICENSE$))?}", handlerRealmFile(logger, app, &cfg)) - app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(logger, app, &cfg)) - app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}{args:(?:\\$.*)?}", handlerRealmMain(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:[^$]*}{args:(?:\\$.*)?}", handlerRealmRender(logger, app, &cfg)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(logger, app, &cfg)) // other @@ -260,15 +262,20 @@ func handlerRedirect(logger *slog.Logger, app gotuna.App, cfg *Config, to string func handlerRealmMain(logger *slog.Logger, app gotuna.App, cfg *Config) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + args, err := parseGnowebArgs(r.RequestURI) + if err != nil { + writeError(logger, w, err) + return + } + vars := mux.Vars(r) rlmname := vars["rlmname"] rlmpath := "gno.land/r/" + rlmname - query := r.URL.Query() logger.Info("handling", "name", rlmname, "path", rlmpath) - if query.Has("help") { + if args.Has("help") { // Render function helper. - funcName := query.Get("__func") + funcName := args.Get("func") qpath := "vm/qfuncs" data := []byte(rlmpath) res, err := makeRequest(logger, cfg, qpath, data) @@ -283,7 +290,7 @@ func handlerRealmMain(logger *slog.Logger, app gotuna.App, cfg *Config) http.Han fsig := &(fsigs[i]) for j := range fsig.Params { param := &(fsig.Params[j]) - value := query.Get(param.Name) + value := args.Get(param.Name) param.Value = value } } @@ -322,17 +329,38 @@ func handlerRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config) http.H } func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http.ResponseWriter, r *http.Request) { + gnowebArgs, err := parseGnowebArgs(r.RequestURI) + if err != nil { + writeError(logger, w, err) + return + } + + queryArgs, err := parseQueryArgs(r.RequestURI) + if err != nil { + writeError(logger, w, err) + return + } + + var urlQuery, gnowebQuery string + if len(queryArgs) > 0 { + urlQuery = urlQuerySeparator + queryArgs.Encode() + } + if len(gnowebArgs) > 0 { + gnowebQuery = gnowebArgsSeparator + gnowebArgs.Encode() + } + vars := mux.Vars(r) rlmname := vars["rlmname"] rlmpath := "gno.land/r/" + rlmname querystr := vars["querystr"] if r.URL.Path == "/r/"+rlmname+":" { // Redirect to /r/REALM if querypath is empty. - http.Redirect(w, r, "/r/"+rlmname, http.StatusFound) + http.Redirect(w, r, "/r/"+rlmname+urlQuery+gnowebQuery, http.StatusFound) return } + qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s:%s", rlmpath, querystr)) + data := []byte(fmt.Sprintf("%s:%s", rlmpath, querystr+urlQuery)) res, err := makeRequest(logger, cfg, qpath, data) if err != nil { // XXX hack @@ -357,11 +385,19 @@ func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http. queryParts := strings.Split(querystr, "/") pathLinks := []pathLink{} for i, part := range queryParts { + rlmpath := strings.Join(queryParts[:i+1], "/") + + // Add URL query arguments to the last breadcrumb item's URL + if i+1 == len(queryParts) { + rlmpath = rlmpath + urlQuery + gnowebQuery + } + pathLinks = append(pathLinks, pathLink{ - URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"), + URL: "/r/" + rlmname + ":" + rlmpath, Text: part, }) } + // Render template. tmpl := app.NewTemplatingEngine() // XXX: extract title from realm's output @@ -538,3 +574,35 @@ func pathOf(diruri string) string { panic(fmt.Sprintf("invalid dir-URI %q", diruri)) } + +// parseQueryArgs parses URL query arguments that are not specific to gnoweb. +// These are the standard arguments that comes after the "?" symbol and before +// the special "$" symbol. The "$" symbol can be used within public query +// arguments by using its encoded representation "%24". +func parseQueryArgs(rawURL string) (url.Values, error) { + if i := strings.Index(rawURL, gnowebArgsSeparator); i != -1 { + rawURL = rawURL[:i] + } + + u, err := url.Parse(rawURL) + if err != nil { + return url.Values{}, fmt.Errorf("invalid query arguments: %w", err) + } + return u.Query(), nil +} + +// parseGnowebArgs parses URL query arguments that are specific to gnoweb. +// These arguments are indicated by using the "$" symbol followed by a query +// string with the arguments. +func parseGnowebArgs(rawURL string) (url.Values, error) { + i := strings.Index(rawURL, gnowebArgsSeparator) + if i == -1 { + return url.Values{}, nil + } + + values, err := url.ParseQuery(rawURL[i+1:]) + if err != nil { + return url.Values{}, fmt.Errorf("invalid gnoweb arguments: %w", err) + } + return values, nil +} diff --git a/gno.land/pkg/gnoweb/gnoweb_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go index 18df5ec2356..8c8bcca48f5 100644 --- a/gno.land/pkg/gnoweb/gnoweb_test.go +++ b/gno.land/pkg/gnoweb/gnoweb_test.go @@ -27,15 +27,20 @@ func TestRoutes(t *testing.T) { {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". {"/about", ok, "blockchain"}, {"/r/gnoland/blog", ok, ""}, // whatever content - {"/r/gnoland/blog?help", ok, "exposed"}, + {"/r/gnoland/blog$help", ok, "exposed"}, {"/r/gnoland/blog/", ok, "admin.gno"}, {"/r/gnoland/blog/admin.gno", ok, "func "}, + {"/r/gnoland/blog$help&func=Render", ok, "Render(...)"}, + {"/r/gnoland/blog$help&func=Render&path=foo/bar", ok, `input type="text" value="foo/bar"`}, + {"/r/gnoland/blog$help&func=NonExisting", ok, "NonExisting not found"}, {"/r/demo/users:administrator", ok, "address"}, {"/r/demo/users", ok, "manfred"}, {"/r/demo/users/users.gno", ok, "// State"}, {"/r/demo/deep/very/deep", ok, "it works!"}, + {"/r/demo/deep/very/deep?arg1=val1&arg2=val2", ok, "hi ?arg1=val1&arg2=val2"}, {"/r/demo/deep/very/deep:bob", ok, "hi bob"}, - {"/r/demo/deep/very/deep?help", ok, "exposed"}, + {"/r/demo/deep/very/deep:bob?arg1=val1&arg2=val2", ok, "hi bob?arg1=val1&arg2=val2"}, + {"/r/demo/deep/very/deep$help", ok, "exposed"}, {"/r/demo/deep/very/deep/", ok, "render.gno"}, {"/r/demo/deep/very/deep/render.gno", ok, "func Render("}, {"/contribute", ok, "Game of Realms"}, @@ -93,7 +98,7 @@ func TestAnalytics(t *testing.T) { "/r/gnoland/blog", "/r/gnoland/blog/admin.gno", "/r/demo/users:administrator", - "/r/gnoland/blog?help", + "/r/gnoland/blog$help", // special pages "/404-not-found", diff --git a/gno.land/pkg/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html index 0a93f786c0d..7bde8fef7fa 100644 --- a/gno.land/pkg/gnoweb/views/realm_help.html +++ b/gno.land/pkg/gnoweb/views/realm_help.html @@ -10,7 +10,7 @@
diff --git a/gno.land/pkg/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html index 924ef2b414f..1b5842cba1f 100644 --- a/gno.land/pkg/gnoweb/views/realm_render.html +++ b/gno.land/pkg/gnoweb/views/realm_render.html @@ -20,7 +20,7 @@ [readme] {{ end }} [source] - [help] + [help]
diff --git a/misc/deployments/test4.gno.land/genesis.json b/misc/deployments/test4.gno.land/genesis.json index 0c64b18ed42..c0721d55bdf 100644 --- a/misc/deployments/test4.gno.land/genesis.json +++ b/misc/deployments/test4.gno.land/genesis.json @@ -1474,11 +1474,11 @@ "files": [ { "name": "README.md", - "body": "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 localhost:26657` here, that flag can be replaced\nwith `-remote test3.gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid test3` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\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 -recover KEYNAME\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\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/users?help\u0026__func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=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 localhost:26657\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 -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -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 -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\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## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n" + "body": "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 localhost:26657` here, that flag can be replaced\nwith `-remote test3.gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid test3` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\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 -recover KEYNAME\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\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.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 localhost:26657\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 -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -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 -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://test3.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 localhost:26657\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## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n" }, { "name": "board.gno", - "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateThread\" +\n\t\t\"\u0026bid=\" + board.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n" + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn \"/r/demo/boards$help\u0026func=CreateThread\" +\n\t\t\"\u0026bid=\" + board.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n" }, { "name": "boards.gno", @@ -1490,7 +1490,7 @@ }, { "name": "post.gno", - "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateReply\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=CreateRepost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026title.type=textarea\" +\n\t\t\"\u0026body.type=textarea\" +\n\t\t\"\u0026dstBoardID.type=textarea\"\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn \"/r/demo/boards?help\u0026__func=DeletePost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String()\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n" + "body": "package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn \"/r/demo/boards$help\u0026func=CreateReply\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026body.type=textarea\"\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn \"/r/demo/boards$help\u0026func=CreateRepost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026postid=\" + post.id.String() +\n\t\t\"\u0026title.type=textarea\" +\n\t\t\"\u0026body.type=textarea\" +\n\t\t\"\u0026dstBoardID.type=textarea\"\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn \"/r/demo/boards$help\u0026func=DeletePost\" +\n\t\t\"\u0026bid=\" + post.board.id.String() +\n\t\t\"\u0026threadid=\" + post.threadID.String() +\n\t\t\"\u0026postid=\" + post.id.String()\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n" }, { "name": "public.gno", @@ -1526,7 +1526,7 @@ }, { "name": "z_0_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n" }, { "name": "z_10_a_filetest.gno", @@ -1538,11 +1538,11 @@ }, { "name": "z_10_c_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" }, { "name": "z_10_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n" }, { "name": "z_11_a_filetest.gno", @@ -1558,11 +1558,11 @@ }, { "name": "z_11_d_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" }, { "name": "z_11_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n" }, { "name": "z_12_a_filetest.gno", @@ -1582,7 +1582,7 @@ }, { "name": "z_12_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=2\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n" }, { "name": "z_1_filetest.gno", @@ -1590,15 +1590,15 @@ }, { "name": "z_2_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" }, { "name": "z_3_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n" }, { "name": "z_4_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n" }, { "name": "z_5_b_filetest.gno", @@ -1606,7 +1606,7 @@ }, { "name": "z_5_c_filetest.gno", - "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" + "body": "package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n" }, { "name": "z_5_d_filetest.gno", @@ -1614,19 +1614,19 @@ }, { "name": "z_5_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" }, { "name": "z_6_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards?help\u0026__func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2\u0026body.type=textarea)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2\u0026title.type=textarea\u0026body.type=textarea\u0026dstBoardID.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n" }, { "name": "z_7_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards?help\u0026__func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1\u0026body.type=textarea)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n" }, { "name": "z_8_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n" }, { "name": "z_9_a_filetest.gno", @@ -1638,7 +1638,7 @@ }, { "name": "z_9_filetest.gno", - "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards?help\u0026__func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[x](/r/demo/boards?help\u0026__func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\n" + "body": "// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1\u0026body.type=textarea)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\n" } ] }, @@ -3590,11 +3590,11 @@ "files": [ { "name": "realm.gno", - "body": "package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi?help\u0026__func=Feed)\n* [Play](/r/demo/tamagotchi?help\u0026__func=Play)\n* [Heal](/r/demo/tamagotchi?help\u0026__func=Heal)\n* [Reset](/r/demo/tamagotchi?help\u0026__func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n" + "body": "package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n" }, { "name": "z0_filetest.gno", - "body": "package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi?help\u0026__func=Feed)\n// * [Play](/r/demo/tamagotchi?help\u0026__func=Play)\n// * [Heal](/r/demo/tamagotchi?help\u0026__func=Heal)\n// * [Reset](/r/demo/tamagotchi?help\u0026__func=Reset)\n" + "body": "package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n" } ] }, From f2928f1c40776bda909f6e4a397c5e8fb409d66f Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Wed, 6 Nov 2024 17:17:26 +0100 Subject: [PATCH 23/34] test(cmd/gno): prevent nil deref in testMainCaseRun (#3071) In `gnovm/cmd/gno/main_test.go`, when an error is expected but none happen, the test helper tries to dereference nil, causing a confusing test result Before: ``` === RUN TestModApp/mod_tidy..~..~tests~integ~invalid_module_version1 main_test.go:92: recover runtime error: invalid memory address or nil pointer dereference main_test.go:93: Error Trace: /Users/norman/Code/gno/gnovm/cmd/gno/main_test.go:93 /Users/norman/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.darwin-arm64/src/runtime/panic.go:770 /Users/norman/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.darwin-arm64/src/runtime/panic.go:261 /Users/norman/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.darwin-arm64/src/runtime/signal_unix.go:881 /Users/norman/Code/gno/gnovm/cmd/gno/main_test.go:131 Error: Should be false Test: TestModApp/mod_tidy..~..~tests~integ~invalid_module_version1 Messages: should not panic ``` After: ``` === RUN TestModApp/mod_tidy..~..~tests~integ~invalid_module_version1 main_test.go:131: err main_test.go:132: Error Trace: /Users/norman/Code/gno/gnovm/cmd/gno/main_test.go:132 Error: Expected value not to be nil. Test: TestModApp/mod_tidy..~..~tests~integ~invalid_module_version1 Messages: err shouldn't be nil ```
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
Signed-off-by: Norman Meier --- gnovm/cmd/gno/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/main_test.go b/gnovm/cmd/gno/main_test.go index 1797d0aede9..069c42db379 100644 --- a/gnovm/cmd/gno/main_test.go +++ b/gnovm/cmd/gno/main_test.go @@ -128,7 +128,7 @@ func testMainCaseRun(t *testing.T, tc []testMainCase) { if errShouldBeEmpty { require.Nil(t, err, "err should be nil") } else { - t.Log("err", err.Error()) + t.Log("err", fmt.Sprintf("%v", err)) require.NotNil(t, err, "err shouldn't be nil") if test.errShouldContain != "" { require.Contains(t, err.Error(), test.errShouldContain, "err should contain") From 724ffc99d058588dc912173dad4546641dd8beca Mon Sep 17 00:00:00 2001 From: Antoine Eddi <5222525+aeddi@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:40:33 +0100 Subject: [PATCH 24/34] ci: add a stale bot for PRs (#2804) Closes https://github.com/gnolang/gno/issues/1445
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> Co-authored-by: Morgan --- .github/workflows/stale-bot.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/stale-bot.yml diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml new file mode 100644 index 00000000000..55a17ac60a8 --- /dev/null +++ b/.github/workflows/stale-bot.yml @@ -0,0 +1,23 @@ +name: "Close stale PRs" +on: + schedule: + - cron: "30 1 * * *" +permissions: + pull-requests: write + issues: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + exempt-all-milestones: true + stale-pr-message: "This PR is stale because it has been open 3 months with no activity. Remove stale label or comment or this will be closed in 3 months." + close-pr-message: "This PR was closed because it has been stalled for 3 months with no activity." + days-before-pr-stale: 90 + days-before-pr-close: 90 + stale-issue-message: "This issue is stale because it has been open 6 months with no activity. Remove stale label or comment or this will be closed in 3 months." + close-issue-message: "This issue was closed because it has been stalled for 3 months with no activity." + days-before-issue-stale: 180 + days-before-issue-close: 90 From 9dad8f18b04f57ab5d0898544aa96410933c8bd5 Mon Sep 17 00:00:00 2001 From: Morgan Date: Wed, 6 Nov 2024 17:56:29 +0100 Subject: [PATCH 25/34] fix(simpledao): reject invalid voting options (#3077)
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- examples/gno.land/p/demo/simpledao/propstore.gno | 2 +- examples/gno.land/p/demo/simpledao/votestore.gno | 8 +++++++- examples/gno.land/r/gov/dao/v2/prop1_filetest.gno | 6 +++--- examples/gno.land/r/gov/dao/v2/prop2_filetest.gno | 6 +++--- examples/gno.land/r/gov/dao/v2/prop3_filetest.gno | 6 +++--- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/demo/simpledao/propstore.gno b/examples/gno.land/p/demo/simpledao/propstore.gno index 972297ff0ce..06741d397cb 100644 --- a/examples/gno.land/p/demo/simpledao/propstore.gno +++ b/examples/gno.land/p/demo/simpledao/propstore.gno @@ -71,7 +71,7 @@ func (p *proposal) Render() string { output += ufmt.Sprintf("Status: %s", p.Status().String()) output += "\n\n" output += ufmt.Sprintf( - "Voting stats: YAY %d (%d%%), NAY %d (%d%%), ABSTAIN %d (%d%%), HAVEN'T VOTED %d (%d%%)", + "Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)", stats.YayVotes, stats.YayPercent(), stats.NayVotes, diff --git a/examples/gno.land/p/demo/simpledao/votestore.gno b/examples/gno.land/p/demo/simpledao/votestore.gno index 35a6564a1e3..489fcaf2c0f 100644 --- a/examples/gno.land/p/demo/simpledao/votestore.gno +++ b/examples/gno.land/p/demo/simpledao/votestore.gno @@ -2,6 +2,7 @@ package simpledao import ( "errors" + "strings" "gno.land/p/demo/avl" "gno.land/p/demo/dao" @@ -38,14 +39,19 @@ func (t *tally) castVote(member membstore.Member, option dao.VoteOption) error { return ErrAlreadyVoted } + // convert option to upper-case, like the constants are. + option = dao.VoteOption(strings.ToUpper(string(option))) + // Update the tally switch option { case dao.YesVote: t.yays += member.VotingPower case dao.AbstainVote: t.abstains += member.VotingPower - default: + case dao.NoVote: t.nays += member.VotingPower + default: + panic("invalid voting option: " + option) } // Save the voting status diff --git a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno index 69e55ef1ab6..07d06bfe74d 100644 --- a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno @@ -84,7 +84,7 @@ func main() { // // Status: active // -// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) +// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) // // Threshold met: false // @@ -99,7 +99,7 @@ func main() { // // Status: accepted // -// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) // // Threshold met: true // @@ -116,7 +116,7 @@ func main() { // // Status: execution successful // -// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) // // Threshold met: true // diff --git a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno index 32ddc11b67c..76e744a6fc8 100644 --- a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno @@ -61,7 +61,7 @@ func main() { // // Status: active // -// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) +// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) // // Threshold met: false // @@ -76,7 +76,7 @@ func main() { // // Status: accepted // -// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) // // Threshold met: true // @@ -95,7 +95,7 @@ func main() { // // Status: execution successful // -// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) // // Threshold met: true // diff --git a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno index 5aa9947c74b..0cd3bce6f83 100644 --- a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno @@ -75,7 +75,7 @@ func main() { // // Status: active // -// Voting stats: YAY 0 (0%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 10 (100%) +// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) // // Threshold met: false // @@ -90,7 +90,7 @@ func main() { // // Status: accepted // -// Voting stats: YAY 10 (100%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 0 (0%) +// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) // // Threshold met: true // @@ -108,7 +108,7 @@ func main() { // // Status: execution successful // -// Voting stats: YAY 10 (25%), NAY 0 (0%), ABSTAIN 0 (0%), HAVEN'T VOTED 30 (75%) +// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%) // // Threshold met: false // From 2173b49d709a7ca68e8227176001dec469fb7c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Wed, 6 Nov 2024 17:57:29 +0100 Subject: [PATCH 26/34] feat(examples): add `r/demo/daoweb` realm (#3074) ## Description This PR adds a simple JSON adapter for `r/gov/dao/v2`, used by [govdao-web](https://govdao.gnoteam.com/).
Contributors' checklist... - x ] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- examples/gno.land/r/demo/daoweb/daoweb.gno | 116 +++++++++++++++++++++ examples/gno.land/r/demo/daoweb/gno.mod | 7 ++ 2 files changed, 123 insertions(+) create mode 100644 examples/gno.land/r/demo/daoweb/daoweb.gno create mode 100644 examples/gno.land/r/demo/daoweb/gno.mod diff --git a/examples/gno.land/r/demo/daoweb/daoweb.gno b/examples/gno.land/r/demo/daoweb/daoweb.gno new file mode 100644 index 00000000000..d753a1ed32a --- /dev/null +++ b/examples/gno.land/r/demo/daoweb/daoweb.gno @@ -0,0 +1,116 @@ +package daoweb + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/json" + "gno.land/r/gov/dao/bridge" +) + +// Proposals returns the paginated GovDAO proposals +func Proposals(offset, count uint64) string { + var ( + propStore = bridge.GovDAO().GetPropStore() + size = propStore.Size() + ) + + // Get the props + props := propStore.Proposals(offset, count) + + resp := ProposalsResponse{ + Proposals: make([]Proposal, 0, count), + Total: uint64(size), + } + + for _, p := range props { + prop := Proposal{ + Author: p.Author(), + Description: p.Description(), + Status: p.Status(), + Stats: p.Stats(), + IsExpired: p.IsExpired(), + } + + resp.Proposals = append(resp.Proposals, prop) + } + + // Encode the response into JSON + encodedProps, err := json.Marshal(encodeProposalsResponse(resp)) + if err != nil { + panic(err) + } + + return string(encodedProps) +} + +// ProposalByID fetches the proposal using the given ID +func ProposalByID(id uint64) string { + propStore := bridge.GovDAO().GetPropStore() + + p, err := propStore.ProposalByID(id) + if err != nil { + panic(err) + } + + // Encode the response into JSON + prop := Proposal{ + Author: p.Author(), + Description: p.Description(), + Status: p.Status(), + Stats: p.Stats(), + IsExpired: p.IsExpired(), + } + + encodedProp, err := json.Marshal(encodeProposal(prop)) + if err != nil { + panic(err) + } + + return string(encodedProp) +} + +// encodeProposal encodes a proposal into a json node +func encodeProposal(p Proposal) *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "author": json.StringNode("author", p.Author.String()), + "description": json.StringNode("description", p.Description), + "status": json.StringNode("status", p.Status.String()), + "stats": json.ObjectNode("stats", map[string]*json.Node{ + "yay_votes": json.NumberNode("yay_votes", float64(p.Stats.YayVotes)), + "nay_votes": json.NumberNode("nay_votes", float64(p.Stats.NayVotes)), + "abstain_votes": json.NumberNode("abstain_votes", float64(p.Stats.AbstainVotes)), + "total_voting_power": json.NumberNode("total_voting_power", float64(p.Stats.TotalVotingPower)), + }), + "is_expired": json.BoolNode("is_expired", p.IsExpired), + }) +} + +// encodeProposalsResponse encodes a proposal response into a JSON node +func encodeProposalsResponse(props ProposalsResponse) *json.Node { + proposals := make([]*json.Node, 0, len(props.Proposals)) + + for _, p := range props.Proposals { + proposals = append(proposals, encodeProposal(p)) + } + + return json.ObjectNode("", map[string]*json.Node{ + "proposals": json.ArrayNode("proposals", proposals), + "total": json.NumberNode("total", float64(props.Total)), + }) +} + +// ProposalsResponse is a paginated proposal response +type ProposalsResponse struct { + Proposals []Proposal `json:"proposals"` + Total uint64 `json:"total"` +} + +// Proposal is a single GovDAO proposal +type Proposal struct { + Author std.Address `json:"author"` + Description string `json:"description"` + Status dao.ProposalStatus `json:"status"` + Stats dao.Stats `json:"stats"` + IsExpired bool `json:"is_expired"` +} diff --git a/examples/gno.land/r/demo/daoweb/gno.mod b/examples/gno.land/r/demo/daoweb/gno.mod new file mode 100644 index 00000000000..bc781b311dc --- /dev/null +++ b/examples/gno.land/r/demo/daoweb/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/daoweb + +require ( + gno.land/p/demo/dao v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest + gno.land/r/gov/dao/bridge v0.0.0-latest +) From e2e943536c8e705a143f11bd6b451b309bbb87a7 Mon Sep 17 00:00:00 2001 From: Morgan Date: Wed, 6 Nov 2024 19:59:54 +0100 Subject: [PATCH 27/34] chore: `s/Gno.land/gno.land/g` (#3047) some locations were missed, most importantly on gnoweb. changes also involve changing the hiring link, which was pointing to lever which we don't use anymore, and prettifying the html pages. this was done automatically by my text editor; I can revert it if we want a cleaner diff. --- README.md | 2 +- docs/concepts/namespaces.md | 6 +- docs/concepts/testnets.md | 36 +- .../local-setup/installation.md | 2 +- docs/getting-started/playground-start.md | 22 +- docs/gno-infrastructure/validators/faq.md | 8 +- .../gno-infrastructure/validators/overview.md | 10 +- docs/how-to-guides/connecting-from-go.md | 38 +- docs/overview.md | 14 +- examples/gno.land/r/gnoland/home/home.gno | 2 +- .../gno.land/r/gnoland/home/home_filetest.gno | 2 +- .../gno.land/r/gnoland/pages/page_about.gno | 24 +- .../r/gnoland/pages/page_ecosystem.gno | 20 +- .../gno.land/r/gnoland/pages/page_gnolang.gno | 2 +- .../r/gnoland/pages/page_testnets.gno | 4 +- .../r/gnoland/pages/page_tokenomics.gno | 2 +- .../gno.land/r/gnoland/pages/pages_test.gno | 4 +- examples/gno.land/r/stefann/home/home.gno | 4 +- gno.land/README.md | 6 +- gno.land/genesis/README.md | 2 +- gno.land/pkg/gnoclient/README.md | 3 +- gno.land/pkg/gnoweb/views/faucet.html | 202 +++++--- gno.land/pkg/gnoweb/views/funcs.html | 471 +++++++++++------- gno.land/pkg/gnoweb/views/generic.html | 35 +- gno.land/pkg/gnoweb/views/package_dir.html | 58 ++- gno.land/pkg/gnoweb/views/package_file.html | 41 +- gno.land/pkg/gnoweb/views/realm_help.html | 161 +++--- gno.land/pkg/gnoweb/views/realm_render.html | 65 +-- gnovm/pkg/gnolang/store.go | 2 +- 29 files changed, 716 insertions(+), 532 deletions(-) diff --git a/README.md b/README.md index eeffc9adefc..89bfd96d74f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ repository offers more resources to dig into. We are eager to see your first PR! * [examples](./examples) - Smart-contract examples and guides for new Gno developers. * [gnovm](./gnovm) - GnoVM and Gnolang. -* [gno.land](./gno.land) - Gno.land blockchain and tools. +* [gno.land](./gno.land) - gno.land blockchain and tools. * [tm2](./tm2) - Tendermint2. * [docs](./docs) - Official documentation, deployed under [docs.gno.land](https://docs.gno.land). * [contribs](./contribs) - Collection of enhanced tools for Gno. diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md index 0f9176bcbf1..c7f03ec1f0a 100644 --- a/docs/concepts/namespaces.md +++ b/docs/concepts/namespaces.md @@ -28,7 +28,7 @@ Here's a breakdown of the structure of a package path: - `p/`: [Package](packages.md) - `r/`: [Realm](realms.md) - Namespace: A namespace can be included after the type (e.g., user or organization name). Namespaces are a - way to group related packages or realms, but currently ownership cannot be claimed. (see + way to group related packages or realms, but currently ownership cannot be claimed. (see [Issue#1107](https://github.com/gnolang/gno/issues/1107) for more info) - Remaining Path: The remaining part of the path. - Can only contain alphanumeric characters (letters and numbers) and underscores. @@ -74,8 +74,8 @@ After successful registration, you can add a package under the registered namesp ## Anonymous Namespace -Gno.land offers the ability to add a package without having a registered namespace. -You can do this by using your own address as a namespace. This is formatted as `{p,r}/{std.Address}/**`. +gno.land offers the ability to add a package without having a registered namespace. +You can do this by using your own address as a namespace. This is formatted as `{p,r}/{std.Address}/**`. > ex: with `test1` user adding a package `microblog` using his own address as namespace ```bash diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md index 730795d3742..4df8e3a4b86 100644 --- a/docs/concepts/testnets.md +++ b/docs/concepts/testnets.md @@ -5,10 +5,10 @@ id: testnets # Gno Testnets This page documents all gno.land testnets, what their properties are, and how -they are meant to be used. For testnet configuration, visit the +they are meant to be used. For testnet configuration, visit the [reference section](../reference/network-config.md). -Gno.land testnets are categorized by 4 main points: +gno.land testnets are categorized by 4 main points: - **Persistence of state** - Is the state and transaction history persisted? - **Timeliness of code** @@ -21,25 +21,25 @@ Gno.land testnets are categorized by 4 main points: Below you can find a breakdown of each existing testnet by these categories. ## Portal Loop -Portal Loop is an always up-to-date rolling testnet. It is meant to be used as +Portal Loop is an always up-to-date rolling testnet. It is meant to be used as a nightly build of the Gno tech stack. The home page of [gno.land](https://gno.land) -is the `gnoweb` render of the Portal Loop testnet. +is the `gnoweb` render of the Portal Loop testnet. - **Persistence of state:** - - State is kept on a best-effort basis + - State is kept on a best-effort basis - Transactions that are affected by breaking changes will be discarded - **Timeliness of code:** - Packages & realms which are available in the `examples/` folder on the [Gno -monorepo](https://github.com/gnolang/gno) exist on the Portal Loop in matching +monorepo](https://github.com/gnolang/gno) exist on the Portal Loop in matching state - they are refreshed with every new commit to the `master` branch. - **Intended purpose** - Providing access the latest version of Gno for fast development & demoing - **Versioning strategy**: - Portal Loop infrastructure is managed within the -[`misc/loop`](https://github.com/gnolang/gno/tree/master/misc/loop) folder in the +[`misc/loop`](https://github.com/gnolang/gno/tree/master/misc/loop) folder in the monorepo -For more information on the Portal Loop, and how it can be best utilized, +For more information on the Portal Loop, and how it can be best utilized, check out the [Portal Loop concept page](./portal-loop.md). Also, you can find the Portal Loop faucet on [`gno.land/faucet`](https://gno.land/faucet). @@ -76,7 +76,7 @@ Staging is a testnet that is reset once every 60 minutes. These testnets are deprecated and currently serve as archives of previous progress. ### Test3 -Test3 is the most recent persistent Gno testnet. It is still being used, but +Test3 is the most recent persistent Gno testnet. It is still being used, but most packages, such as the AVL package, are outdated. - **Persistence of state:** @@ -85,28 +85,28 @@ most packages, such as the AVL package, are outdated. - Test3 is at commit [1ca2d97](https://github.com/gnolang/gno/commit/1ca2d973817b174b5b06eb9da011e1fcd2cca575) of Gno, and it can contain new on-chain code - **Intended purpose** - - Running a full node, building an indexer, showing demos, persisting history + - Running a full node, building an indexer, showing demos, persisting history - **Versioning strategy**: - There is no versioning strategy for test3. It will stay the way it is, until the team chooses to shut it down. -Since gno.land is designed with open-source in mind, anyone can see currently -available code by browsing the [test3 homepage](https://test3.gno.land/). +Since gno.land is designed with open-source in mind, anyone can see currently +available code by browsing the [test3 homepage](https://test3.gno.land/). -Test3 is a single-node testnet, ran by the Gno core team. There is no plan to -upgrade test3 to a multi-node testnet. +Test3 is a single-node testnet, ran by the Gno core team. There is no plan to +upgrade test3 to a multi-node testnet. -Launch date: November 4th 2022 +Launch date: November 4th 2022 Release commit: [1ca2d97](https://github.com/gnolang/gno/commit/1ca2d973817b174b5b06eb9da011e1fcd2cca575) ### Test2 (archive) The second Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test2.gno.land). -Launch date: July 10th 2022 -Release commit: [652dc7a](https://github.com/gnolang/gno/commit/652dc7a3a62ee0438093d598d123a8c357bf2499) +Launch date: July 10th 2022 +Release commit: [652dc7a](https://github.com/gnolang/gno/commit/652dc7a3a62ee0438093d598d123a8c357bf2499) ### Test1 (archive) The first Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test1.gno.land). -Launch date: May 6th 2022 +Launch date: May 6th 2022 Release commit: [797c7a1](https://github.com/gnolang/gno/commit/797c7a132d65534df373c63b837cf94b7831ac6e) diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index 272d0069ee5..e05c2f9b205 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -69,7 +69,7 @@ go run ./cmd/gno --help ### `gnodev` `gnodev` is the go-to Gno development helper tool - it comes with a built in -Gno.land node, a `gnoweb` server to display the state of your smart contracts +gno.land node, a `gnoweb` server to display the state of your smart contracts (realms), and a watcher system to actively track changes in your code. Read more about `gnodev` [here](../../gno-tooling/cli/gnodev.md). diff --git a/docs/getting-started/playground-start.md b/docs/getting-started/playground-start.md index f62e2748efe..0da950b69c0 100644 --- a/docs/getting-started/playground-start.md +++ b/docs/getting-started/playground-start.md @@ -6,17 +6,17 @@ id: playground-start ## Overview -The Gno Playground is an innovative web-based editor and sandbox that enables developers to +The Gno Playground is an innovative web-based editor and sandbox that enables developers to interactively work with the Gno language. It makes coding, testing, and deploying simple with its diverse set of tools and features. Users can -share code, run tests, and deploy projects to gno.land networks, +share code, run tests, and deploy projects to gno.land networks, making it the perfect tool to get started with Gno development. ## Prerequisites - **A gno.land compatible wallet** - Currently, [Adena](https://www.adena.app/) is the preferred wallet for -Gno.land, with more wallets being introduced in the future. +gno.land, with more wallets being introduced in the future. ## Playground Features @@ -44,25 +44,25 @@ ensuring the shared code remains accessible over an extended period. ### Deploy -The **Deploy** feature allows users to seamlessly deploy their Gno code to the -chain. After connecting a gno.land wallet, users can select their desired +The **Deploy** feature allows users to seamlessly deploy their Gno code to the +chain. After connecting a gno.land wallet, users can select their desired package path and network for deployment. ![default_deploy](../assets/getting-started/playground/default_deploy.png) -After inputting your desired package path, you can select the network you would +After inputting your desired package path, you can select the network you would like to deploy to, such as [Portal Loop](../concepts/portal-loop.md) or local, and click deploy. :::info -The Playground will automatically provide enough test tokens to cover the gas +The Playground will automatically provide enough test tokens to cover the gas cost at the time of deployment, removing the need for using a faucet. ::: ### Format The **Format** feature utilizes the Monaco editor and -[`gofmt`](https://pkg.go.dev/cmd/gofmt) to automatically refine and standardize +[`gofmt`](https://pkg.go.dev/cmd/gofmt) to automatically refine and standardize your Gno code's syntax. ### Run @@ -82,7 +82,7 @@ View the code [here](https://play.gno.land/p/nBq2W8drjMy). ### Test -The **Test** feature will look for `_test.gno` files in your playground and run +The **Test** feature will look for `_test.gno` files in your playground and run the`gno test -v` command on them. Testing your code will open a terminal that will show you the output of the test. Read more about how Gno tests work [here](../concepts/gno-test.md). @@ -95,10 +95,10 @@ It provides a command-line interface for hands-on learning, iterative testing, a ## Learning about gno.land & writing Gno code If you're new here, don't worry—content is regularly produced to breakdown -Gno.land to explain its features. Dive into the essentials of gno.land by +gno.land to explain its features. Dive into the essentials of gno.land by exploring the [Concepts](../concepts/concepts.md) section. To get started writing Gno code, check out the [How-to](../how-to-guides/how-to-guides.md) section, the `examples/` folder on -the [Gno monorepo](https://github.com/gnolang/gno), or one of many community projects and tutorials found in the +the [Gno monorepo](https://github.com/gnolang/gno), or one of many community projects and tutorials found in the [awesome-gno](https://github.com/gnolang/awesome-gno/blob/main/README.md) repo on GitHub. diff --git a/docs/gno-infrastructure/validators/faq.md b/docs/gno-infrastructure/validators/faq.md index 1a065a5ca56..940d3abe7a1 100644 --- a/docs/gno-infrastructure/validators/faq.md +++ b/docs/gno-infrastructure/validators/faq.md @@ -8,7 +8,7 @@ id: validators-faq ### What is a gno.land validator? -Gno.land is based on [Tendermint2](https://docs.gno.land/concepts/tendermint2) that relies on a set of validators +gno.land is based on [Tendermint2](https://docs.gno.land/concepts/tendermint2) that relies on a set of validators selected based on [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC) to secure the network. Validators are tasked with participating in consensus by committing new blocks and broadcasting votes. Validators are compensated with a portion of transaction fees generated in the network. In gno.land, the voting power of @@ -45,7 +45,7 @@ network. ### What stage is the gno.land project in? -Gno.land is currently in Testnet 3, the single-node testnet stage. The next version, Testnet 4, is scheduled to go live +gno.land is currently in Testnet 3, the single-node testnet stage. The next version, Testnet 4, is scheduled to go live in Q3 2024, which will include a validator set implementation for a multinode environment. ## Becoming a Validator @@ -69,11 +69,11 @@ validators for their work. All validators fairly receive an equal amount of rewa The exact plans for mainnet are still TBD. Based on the latest discussions between contributors, the mainnet will likely have an inital validator set size of 20~50, which will gradually scale with the development and decentralization of the -Gno.land project. +gno.land project. ### How do I make my first contribution? -Gno.land is in active development and external contributions are always welcome! If you’re looking for tasks to begin +gno.land is in active development and external contributions are always welcome! If you’re looking for tasks to begin with, we suggest you visit the [Bounties &](https://github.com/orgs/gnolang/projects/35/views/3) [Worx](https://github.com/orgs/gnolang/projects/35/views/3) board and search for open tasks up for grabs. Start from small challenges and work your way up to the bigger ones. Every diff --git a/docs/gno-infrastructure/validators/overview.md b/docs/gno-infrastructure/validators/overview.md index 918bd218f50..e0973ad22d1 100644 --- a/docs/gno-infrastructure/validators/overview.md +++ b/docs/gno-infrastructure/validators/overview.md @@ -6,7 +6,7 @@ id: validators-overview ## Introduction -Gno.land is a blockchain powered by the Gno tech stack, which consists of +gno.land is a blockchain powered by the Gno tech stack, which consists of the [Gno Language](https://docs.gno.land/concepts/gno-language/) (Gno), [Tendermint2](https://docs.gno.land/concepts/tendermint2/) (TM2), and [GnoVM](https://docs.gno.land/concepts/gnovm/). Unlike @@ -17,7 +17,7 @@ selected via governance based on their contribution to the project and technical network is equally distributed across all validators to achieve a high nakamoto coefficient. A portion of all transaction fees paid to the network are evenly shared between all validators to provide a fair incentive structure. -| **Blockchain** | Cosmos | Gno.land | +| **Blockchain** | Cosmos | gno.land | |--------------------------------------|-------------------------|-------------------------------| | **Consensus Protocol** | Comet BFT | Tendermint2 | | **Consensus Mechanism** | Proof of Stake | Proof of Contribution | @@ -78,9 +78,9 @@ be expected from a good, reliable validator. Join the official gno.land community in various channels to receive the latest updates about the project and actively communicate with other validators and contributors. -- [Gno.land Blog](https://gno.land/r/gnoland/blog) -- [Gno.land Discord](https://discord.gg/YFtMjWwUN7) -- [Gno.land Twitter](https://x.com/_gnoland) +- [gno.land Blog](https://gno.land/r/gnoland/blog) +- [gno.land Discord](https://discord.gg/YFtMjWwUN7) +- [gno.land Twitter](https://x.com/_gnoland) :::info diff --git a/docs/how-to-guides/connecting-from-go.md b/docs/how-to-guides/connecting-from-go.md index 971007e5cef..1c0478234fc 100644 --- a/docs/how-to-guides/connecting-from-go.md +++ b/docs/how-to-guides/connecting-from-go.md @@ -2,7 +2,7 @@ id: connect-from-go --- -# How to connect a Go app to gno.land +# How to connect a Go app to gno.land This guide will show you how to connect to a gno.land network from your Go application, using the [gnoclient](../reference/gnoclient/gnoclient.md) package. @@ -46,7 +46,7 @@ go get github.com/gnolang/gno/gno.land/pkg/gnoclient ## Main components -The `gnoclient` package exposes a `Client` struct containing a `Signer` and +The `gnoclient` package exposes a `Client` struct containing a `Signer` and `RPCClient` connector. `Client` exposes all available functionality for talking to a gno.land chain. @@ -60,11 +60,11 @@ type Client struct { ### Signer The `Signer` provides functionality to sign transactions with a gno.land keypair. -The keypair can be accessed from a local keybase, or it can be generated +The keypair can be accessed from a local keybase, or it can be generated in-memory from a BIP39 mnemonic. :::info -The keybase directory path is set with the `gnokey --home` flag. +The keybase directory path is set with the `gnokey --home` flag. ::: ### RPCClient @@ -74,7 +74,7 @@ The `RPCCLient` provides connectivity to a gno.land network via HTTP or WebSocke ## Initialize the Signer -For this example, we will initialize the `Signer` from a local keybase: +For this example, we will initialize the `Signer` from a local keybase: ```go package main @@ -92,14 +92,14 @@ func main() { signer := gnoclient.SignerFromKeybase{ Keybase: keybase, Account: "", // Name of your keypair in keybase - Password: "", // Password to decrypt your keypair + Password: "", // Password to decrypt your keypair ChainID: "", // id of gno.land chain } } ``` A few things to note: -- You can view keys in your local keybase by running `gnokey list`. +- You can view keys in your local keybase by running `gnokey list`. - You can get the password from a user input using the IO package. - `Signer` can also be initialized in-memory from a BIP39 mnemonic, using the [`SignerFromBip39`](https://gnolang.github.io/gno/github.com/gnolang/gno@v0.0.0/gno.land/pkg/gnoclient.html#SignerFromBip39) @@ -116,10 +116,10 @@ if err != nil { } ``` -A list of gno.land network endpoints & chain IDs can be found in the +A list of gno.land network endpoints & chain IDs can be found in the [Gno RPC endpoints](../reference/network-config.md) page. -With this, we can initialize the `gnoclient.Client` struct: +With this, we can initialize the `gnoclient.Client` struct: ```go package main @@ -138,7 +138,7 @@ func main() { signer := gnoclient.SignerFromKeybase{ Keybase: keybase, Account: "", // Name of your keypair in keybase - Password: "", // Password to decrypt your keypair + Password: "", // Password to decrypt your keypair ChainID: "", // id of gno.land chain } @@ -147,7 +147,7 @@ func main() { if err != nil { panic(err) } - + // Initialize the gnoclient client := gnoclient.Client{ Signer: signer, @@ -161,7 +161,7 @@ We can now communicate with the gno.land chain. Let's explore some of the functi ## Query account info from a chain -To send transactions to the chain, we need to know the account number (ID) and +To send transactions to the chain, we need to know the account number (ID) and sequence (nonce). We can get this information by querying the chain with the `QueryAccount` function: @@ -219,7 +219,7 @@ txCfg := gnoclient.BaseTxCfg{ ``` For calling an exported (public) function in a Gno realm, we can use the `MsgCall` -message type. We will use the wrapped ugnot realm for this example, wrapping +message type. We will use the wrapped ugnot realm for this example, wrapping `1000000ugnot` (1 $GNOT) for demonstration purposes. ```go @@ -250,11 +250,11 @@ if err != nil { } ``` -Before running your code, make sure your keypair has enough funds to send the -transaction. +Before running your code, make sure your keypair has enough funds to send the +transaction. -If everything went well, you've just sent a state-changing transaction to a -Gno.land chain! +If everything went well, you've just sent a state-changing transaction to a +gno.land chain! ## Reading on-chain state @@ -288,9 +288,7 @@ Congratulations 🎉 You've just built a small demo app in Go that connects to a gno.land chain to query account info, send a transaction, and read on-chain state. -Check out the full example app code [here](https://github.com/leohhhn/connect-gno/blob/master/main.go). +Check out the full example app code [here](https://github.com/leohhhn/connect-gno/blob/master/main.go). To see a real-world example CLI tool use `gnoclient`, check out [gnoblog-cli](https://github.com/gnolang/blog/tree/main/cmd/gnoblog-cli). - - diff --git a/docs/overview.md b/docs/overview.md index 3619e507dba..a687c878dde 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,7 +1,7 @@ --- id: overview slug: / -description: "Gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted +description: "gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted version of the Go programming language called Gno." --- @@ -9,17 +9,17 @@ version of the Go programming language called Gno." ## What is gno.land? -Gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted +gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted version of the Go programming language called Gno. ### Key Features and Technology -1. **Interpreted Gno**: Gno.land utilizes the Gno programming language, which is based on Go. It is executed +1. **Interpreted Gno**: gno.land utilizes the Gno programming language, which is based on Go. It is executed through a specialized virtual machine called the GnoVM, purpose-built for blockchain development with built-in determinism and a modified standard library. While Gno shares similarities with Go in terms of syntax, it currently lacks go routine support. However, this feature is planned for future development, ensuring deterministic GnoVM executions. -2. **Consensus Protocol - Tendermint2**: Gno.land achieves consensus between blockchain nodes using the Tendermint2 +2. **Consensus Protocol - Tendermint2**: gno.land achieves consensus between blockchain nodes using the Tendermint2 consensus protocol. This approach ensures secure and reliable network operation. 3. **Inter-Blockchain Communication (IBC)**: In the future, gno.land will be able to communicate and exchange data with other blockchain networks within the Cosmos ecosystem through the Inter-Blockchain Communication (IBC) protocol. @@ -37,19 +37,19 @@ The decision to base gno.land's language on Go was influenced by the following f In comparison to Ethereum, gno.land offers distinct advantages: -1. **Transparent and Auditable Smart Contracts**: Gno.land Smart Contracts are fully transparent and auditable by users +1. **Transparent and Auditable Smart Contracts**: gno.land Smart Contracts are fully transparent and auditable by users because the actual source code is uploaded to the blockchain. In contrast, Ethereum requires contracts to be precompiled into bytecode, leading to less transparency as bytecode is stored on the blockchain, not the human-readable source code. Smart contracts in gno.land can be used as libraries with a simple import statement, making gno.land a defacto source-code repository for the ecosystem. -2. **General-Purpose Language**: Gno.land's Gno is a general-purpose language, similar to Go, extending its +2. **General-Purpose Language**: gno.land's Gno is a general-purpose language, similar to Go, extending its usability beyond the context of blockchain. In contrast, Solidity is designed specifically for Smart Contracts on the Ethereum platform. ## Using the gno.land Documentation -Gno.land's documentation adopts the [Diataxis](https://diataxis.fr/) framework, ensuring structured and predictable content. It includes: +gno.land's documentation adopts the [Diataxis](https://diataxis.fr/) framework, ensuring structured and predictable content. It includes: - A [Getting Started](getting-started/local-setup/local-setup.md) section, covering simple instructions on how to begin your journey into gno.land. - Concise how-to guides for specific technical tasks. - Conceptual explanations, offering context and usage insights. diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 57570902f5a..c6b3929a16c 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -246,7 +246,7 @@ func discoverLinks() ui.Element { - Tokenomics (soon) - [Partners, Fund, Grants](/partners) - [Explore the Ecosystem](/ecosystem) -- [Careers](https://jobs.lever.co/allinbits?department=Gno.land) +- [Careers](https://jobs.ashbyhq.com/allinbits)
diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 89721fd8d08..b22c22567b3 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -33,7 +33,7 @@ func main() { // - Tokenomics (soon) // - [Partners, Fund, Grants](/partners) // - [Explore the Ecosystem](/ecosystem) -// - [Careers](https://jobs.lever.co/allinbits?department=Gno.land) +// - [Careers](https://jobs.ashbyhq.com/allinbits) // //
// diff --git a/examples/gno.land/r/gnoland/pages/page_about.gno b/examples/gno.land/r/gnoland/pages/page_about.gno index 6b1f5a6c556..99a879b4ba3 100644 --- a/examples/gno.land/r/gnoland/pages/page_about.gno +++ b/examples/gno.land/r/gnoland/pages/page_about.gno @@ -2,28 +2,28 @@ package gnopages func init() { path := "about" - title := "Gno.land Is A Platform To Write Smart Contracts In Gno" + title := "gno.land Is A Platform To Write Smart Contracts In Gno" // XXX: description := "On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem." body := ` -Gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go +gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go programming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code, -making it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code -libraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent, +making it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code +libraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse. -Gno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of -smart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive +gno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of +smart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive to a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build new ones from scratch, making web3 vastly more accessible. -Secured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes -fairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that -often corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and -alignment. +Secured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes +fairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that +often corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and +alignment. One of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years. -By observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for -future generations with censorship-resistant tools that improve their understanding of the world. +By observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for +future generations with censorship-resistant tools that improve their understanding of the world. ` _ = b.NewPost("", path, title, body, "2022-05-20T13:17:22Z", nil, nil) } diff --git a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno index c6e7c22ae48..514ea7b2a98 100644 --- a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -3,48 +3,48 @@ package gnopages func init() { var ( path = "ecosystem" - title = "Discover Gno.land Ecosystem Projects & Initiatives" + title = "Discover gno.land Ecosystem Projects & Initiatives" // XXX: description = "Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." body = ` ### [Gno Playground](https://play.gno.land) -Gno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your +Gno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your understanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute -functions in your code using the repo. +functions in your code using the repo. Visit the playground at [play.gno.land](https://play.gno.land)! ### [Gno Studio Connect](https://gno.studio/connect) -Gno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage -with gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact -with any realm’s exposed function(s) on gno.land. +Gno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage +with gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact +with any realm’s exposed function(s) on gno.land. See your realm interactions in [Gno Studio Connect](https://gno.studio/connect) ### [Gnoscan](https://gnoscan.io) Developed by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find -information that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. +information that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. Gnoscan makes our on-chain data easy to read and intuitive to discover. Explore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)! ### Adena -Adena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to +Adena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to interact easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a high-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/) ### Gnoswap -Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an +Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an automated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform. ### Flippando Flippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles -on to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player +on to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player must memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later be assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip) diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno index 13fc4072b1a..ac7bd9025b0 100644 --- a/examples/gno.land/r/gnoland/pages/page_gnolang.gno +++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno @@ -3,7 +3,7 @@ package gnopages func init() { var ( path = "gnolang" - title = "About the Gno, the Language for Gno.land" + title = "About the Gno, the Language for gno.land" // TODO fix broken images body = ` diff --git a/examples/gno.land/r/gnoland/pages/page_testnets.gno b/examples/gno.land/r/gnoland/pages/page_testnets.gno index 900ee2e3bf7..0811cd68e6d 100644 --- a/examples/gno.land/r/gnoland/pages/page_testnets.gno +++ b/examples/gno.land/r/gnoland/pages/page_testnets.gno @@ -2,9 +2,9 @@ package gnopages func init() { path := "testnets" - title := "Gno.land Testnet List" + title := "gno.land Testnet List" body := ` -- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet +- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet - [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master - _[test4.gno.land](https://test4.gno.land) (latest)_ diff --git a/examples/gno.land/r/gnoland/pages/page_tokenomics.gno b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno index f51364c36e6..3070e58cc6f 100644 --- a/examples/gno.land/r/gnoland/pages/page_tokenomics.gno +++ b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno @@ -3,7 +3,7 @@ package gnopages func init() { var ( path = "tokenomics" - title = "Gno.land Tokenomics" + title = "gno.land Tokenomics" // XXX: description = """ body = `Lorem Ipsum` ) diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index 074e80e1892..16984a1c7ff 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -30,8 +30,8 @@ func TestAbout(t *testing.T) { printedOnce := false got := Render("p/about") expectedSubtrings := []string{ - "Gno.land Is A Platform To Write Smart Contracts In Gno", - "Gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language.", + "gno.land Is A Platform To Write Smart Contracts In Gno", + "gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language.", } for _, substring := range expectedSubtrings { if !strings.Contains(got, substring) { diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index f40329ebf7e..9586f377311 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -57,7 +57,7 @@ func init() { `Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`, `### Contributions`, - `I'm just getting started, but you can follow my journey through Gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`, + `I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`, }, } @@ -282,7 +282,7 @@ func renderSponsors() string { out += ufmt.Sprintf( `
  • - %d. %s + %d. %s %s
  • `, padding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(), diff --git a/gno.land/README.md b/gno.land/README.md index 7da2a8574de..8f7f9c32945 100644 --- a/gno.land/README.md +++ b/gno.land/README.md @@ -1,6 +1,6 @@ -# Gno.land +# gno.land -Gno.land is a layer-1 blockchain that integrates various cutting-edge technologies, including [Tendermint2](../tm2), [GnoVM](../gnovm), Proof-of-Contributions consensus mechanism, on-chain governance through a new DAO framework with support for sub-DAOs, and a unique licensing model that allows open-source code to be monetized by default. +gno.land is a layer-1 blockchain that integrates various cutting-edge technologies, including [Tendermint2](../tm2), [GnoVM](../gnovm), Proof-of-Contributions consensus mechanism, on-chain governance through a new DAO framework with support for sub-DAOs, and a unique licensing model that allows open-source code to be monetized by default. ## Getting started @@ -12,7 +12,7 @@ To add a web interface and faucet to your localnet, use [`gnoweb`](./cmd/gnoweb) ## Interchain -Gno.land aims to offer security, high-quality contract libraries, and scalability to other Gnolang chains, while also prioritizing interoperability with existing and emerging chains. +gno.land aims to offer security, high-quality contract libraries, and scalability to other Gnolang chains, while also prioritizing interoperability with existing and emerging chains. Post mainnet launch, gno.land aims to integrate IBCv1 to connect with existing Cosmos chains and implement ICS1 for security through the existing chains. Afterwards, the platform plans to improve IBC by adding new capabilities for interchain smart-contracts. diff --git a/gno.land/genesis/README.md b/gno.land/genesis/README.md index 55fdb3d0dfd..4fb81baaaa0 100644 --- a/gno.land/genesis/README.md +++ b/gno.land/genesis/README.md @@ -1,3 +1,3 @@ -# Gno.land genesis +# gno.land genesis **WIP: see https://github.com/gnolang/independence-day** diff --git a/gno.land/pkg/gnoclient/README.md b/gno.land/pkg/gnoclient/README.md index a2f00895dbd..4b3854b1bcc 100644 --- a/gno.land/pkg/gnoclient/README.md +++ b/gno.land/pkg/gnoclient/README.md @@ -1,4 +1,4 @@ -# Gno.land Go Client +# gno.land Go Client The gno.land Go client is a dedicated library for interacting seamlessly with the gno.land RPC API. This library simplifies the process of querying or sending transactions to the gno.land RPC API and interpreting the responses. @@ -18,4 +18,3 @@ The roadmap for the gno.land Go client includes: - **Initial Development:** Kickstart the development specifically for gno.land. Subsequently, transition the generic functionalities to other modules like `tm2`, `gnovm`, `gnosdk`. - **Integration:** Begin incorporating this library within various components such as `gno.land/cmd/*` and other external clients, including `gnoblog-client`, the Discord community faucet bot, and [GnoMobile](https://github.com/gnolang/gnomobile). - **Enhancements:** Once the generic client establishes a robust foundation, we aim to utilize code generation for contracts. This will streamline the creation of type-safe, contract-specific clients. - diff --git a/gno.land/pkg/gnoweb/views/faucet.html b/gno.land/pkg/gnoweb/views/faucet.html index 1c9ca1de53c..938b8993e0e 100644 --- a/gno.land/pkg/gnoweb/views/faucet.html +++ b/gno.land/pkg/gnoweb/views/faucet.html @@ -1,97 +1,139 @@ {{- define "app" -}} - + - - {{ template "html_head" . }} - Gno.land - - -
    - -
    - This is the gno.land (test) {{ if .Data.Config.CaptchaSite }} - - {{ end }} - + + {{ template "html_head" . }} + gno.land + + +
    + +
    + This is the gno.land (test) {{ if .Data.Config.CaptchaSite }} + + {{ end }} + - + // Reset the captcha + grecaptcha.reset(); + }); + }; + -
    -
    -
    - {{ if .Data.Config.CaptchaSite }} -
    - {{ end }} -
    -
    -
    - -
    - {{ template "footer" }} -
    - {{ template "js" }} - +
    +
    +
    + {{ if .Data.Config.CaptchaSite }} +
    + {{ end }} +
    + +
    +
    +
    + +
    + {{ template "footer" }} +
    + {{ template "js" }} + {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/funcs.html b/gno.land/pkg/gnoweb/views/funcs.html index 626d01d8448..5a3a086e155 100644 --- a/gno.land/pkg/gnoweb/views/funcs.html +++ b/gno.land/pkg/gnoweb/views/funcs.html @@ -1,220 +1,337 @@ {{- define "header_buttons" -}}
    - +
    -{{- end -}} - -{{- define "html_head" -}} +{{- end -}} {{- define "html_head" -}} -{{if .Data.Description}}{{end}} +{{if .Data.Description}}{{end}} - - - - + + + + - + - + -{{- end -}} - -{{- define "logo" -}} +{{- end -}} {{- define "logo" -}} -{{- end -}} - -{{- define "footer" -}} -
    - {{ template "logo" }} -
    -{{- end -}} - -{{- define "js" -}} +{{- end -}} {{- define "footer" -}} +
    {{ template "logo" }}
    +{{- end -}} {{- define "js" -}} -{{ template "analytics" .}} -{{- end -}} - -{{- define "analytics" -}} -{{- if .Data.Config.WithAnalytics -}} +{{ template "analytics" .}} {{- end -}} {{- define "analytics" -}} {{- if +.Data.Config.WithAnalytics -}} - -{{- end -}} -{{- end -}} - -{{- define "subscribe" -}} + +{{- end -}} {{- end -}} {{- define "subscribe" -}}
    -
    - -
    -
    - - - -
    -
    - - -
    - -
    -
    +
    + +
    +
    + + + +
    +
    + + +
    + +
    +
    {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/generic.html b/gno.land/pkg/gnoweb/views/generic.html index 5bcd14c3a46..a917ad34c03 100644 --- a/gno.land/pkg/gnoweb/views/generic.html +++ b/gno.land/pkg/gnoweb/views/generic.html @@ -1,21 +1,24 @@ {{- define "app" -}} - + - - Gno.land - {{ .Data.Title }} - {{ template "html_head" . }} - - -
    - -
    -
    +    
    +        gno.land - {{ .Data.Title }}
    +        {{ template "html_head" . }}
    +    
    +    
    +        
    + +
    +
               {{- .Data.MainContent -}}
    -        
    -
    - {{ template "footer" }} -
    - {{ template "js" .}} - +
    +
    + {{ template "footer" }} +
    + {{ template "js" .}} + {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/package_dir.html b/gno.land/pkg/gnoweb/views/package_dir.html index 793ebd40b84..ed7cd9a8347 100644 --- a/gno.land/pkg/gnoweb/views/package_dir.html +++ b/gno.land/pkg/gnoweb/views/package_dir.html @@ -1,33 +1,37 @@ {{- define "app" -}} - + - - {{ template "html_head" . }} - Gno.land - {{.Data.DirPath}} - - -
    - - -
    {{ template "dir_contents" . }}
    - {{ template "footer" }} -
    - {{ template "js" . }} - + + {{ template "html_head" . }} + gno.land - {{.Data.DirPath}} + + +
    + + +
    + {{ template "dir_contents" . }} +
    + {{ template "footer" }} +
    + {{ template "js" . }} + -{{- end -}} - -{{- define "dir_contents" -}} +{{- end -}} {{- define "dir_contents" -}}
    - {{ $dirPath := .Data.DirPath }} -
      - {{ range .Data.Files }} -
    • - {{ . }} -
    • - {{ end }} -
    + {{ $dirPath := .Data.DirPath }} +
      + {{ range .Data.Files }} +
    • + {{ . }} +
    • + {{ end }} +
    {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/package_file.html b/gno.land/pkg/gnoweb/views/package_file.html index 43e7820b29f..32d8af9e174 100644 --- a/gno.land/pkg/gnoweb/views/package_file.html +++ b/gno.land/pkg/gnoweb/views/package_file.html @@ -1,23 +1,28 @@ {{- define "app" -}} - + - - {{ template "html_head" . }} - Gno.land - {{.Data.DirPath}}/{{.Data.FileName}} - - -
    - -
    - {{ .Data.DirPath }}/{{ .Data.FileName }} -
    -
    - {{ .Data.FileContents }} -
    + + {{ template "html_head" . }} + gno.land - {{.Data.DirPath}}/{{.Data.FileName}} + + +
    + +
    + + {{ .Data.DirPath }}/{{ + .Data.FileName }} + +
    +
    + {{ .Data.FileContents }} +
    - {{ template "footer" }} -
    - {{ template "js" .}} - + {{ template "footer" }} +
    + {{ template "js" .}} + {{- end -}} diff --git a/gno.land/pkg/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html index 7bde8fef7fa..f33bb50e9e6 100644 --- a/gno.land/pkg/gnoweb/views/realm_help.html +++ b/gno.land/pkg/gnoweb/views/realm_help.html @@ -1,89 +1,100 @@ {{- define "app" -}} - + - - {{ template "html_head" . }} - Gno.land - {{.Data.DirPath}} - - -
    -
    - - + + {{ template "html_head" . }} + gno.land - {{.Data.DirPath}} + + +
    +
    + +
    + + {{ .Data.DirPath }}$help + +
    -
    -
    - These are the realm's exposed functions ("public smart contracts").
    -
    - My address: (see `gnokey list`)
    -
    -
    - {{ template "func_specs" . }} -
    +
    +
    + These are the realm's exposed functions ("public smart + contracts").
    +
    + My address: + (see + `gnokey list`)
    +
    +
    + {{ template "func_specs" . }} +
    - {{ template "footer" }} -
    - {{ template "js" . }} - - + {{ template "footer" }} +
    + {{ template "js" . }} + + -{{- end -}} - -{{- define "func_specs" -}} +{{- end -}} {{- define "func_specs" -}}
    - {{ $funcName := .Data.FuncName }} {{ $found := false }} {{ if eq $funcName "" }} {{ range .Data.FunctionSignatures }} {{ template "func_spec" . }} {{ end }} {{ else }} {{ range - .Data.FunctionSignatures }} {{ if eq .FuncName $funcName }} {{ $found = true }} {{ template "func_spec" . }} {{ end }} {{ end }} {{ if not $found }} {{ $funcName }} not found. {{ end }} {{ end }} + {{ $funcName := .Data.FuncName }} {{ $found := false }} {{ if eq $funcName + "" }} {{ range .Data.FunctionSignatures }} {{ template "func_spec" . }} {{ + end }} {{ else }} {{ range .Data.FunctionSignatures }} {{ if eq .FuncName + $funcName }} {{ $found = true }} {{ template "func_spec" . }} {{ end }} {{ + end }} {{ if not $found }} {{ $funcName }} not found. {{ end }} {{ end }}
    -{{- end -}} - -{{- define "func_spec" -}} +{{- end -}} {{- define "func_spec" -}}
    - - - - - - - - - - - - - - - - - -
    contract{{ .FuncName }}(...)
    params - - {{ range .Params }}{{ template "func_param" . }}{{ end }} -
    -
    results - - {{ range .Results }}{{ template "func_result" . }}{{ end }} -
    -
    command -
    -
    + + + + + + + + + + + + + + + + + +
    contract{{ .FuncName }}(...)
    params + + {{ range .Params }}{{ template "func_param" . }}{{ end }} +
    +
    results + + {{ range .Results }}{{ template "func_result" . }}{{ end }} +
    +
    command +
    +
    -{{- end -}} - -{{- define "func_param" -}} +{{- end -}} {{- define "func_param" -}} - {{ .Name }} - - - - {{ .Type }} + {{ .Name }} + + + + {{ .Type }} -{{- end -}} - -{{- define "func_result" -}} +{{- end -}} {{- define "func_result" -}} - {{ .Name }} - {{ .Type }} + {{ .Name }} + {{ .Type }} {{ end }} diff --git a/gno.land/pkg/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html index 1b5842cba1f..978b6afed46 100644 --- a/gno.land/pkg/gnoweb/views/realm_render.html +++ b/gno.land/pkg/gnoweb/views/realm_render.html @@ -1,35 +1,40 @@ {{- define "app" -}} - + - - {{ template "html_head" . }} - Gno.land - {{.Data.RealmName}} - - -
    - -
    - /r/{{ .Data.RealmName }} - {{- if .Data.Query -}}:{{- end -}} {{- range $index, $link := .Data.PathLinks -}} {{- if (gt $index 0) }}/{{ end -}} - {{ $link.Text }} - {{- end -}} - - - {{ if .Data.HasReadme }} - [readme] - {{ end }} - [source] - [help] - -
    + + {{ template "html_head" . }} + gno.land - {{.Data.RealmName}} + + +
    + +
    + /r/{{ .Data.RealmName }} + {{- if .Data.Query -}}:{{- end -}} {{- range $index, $link + := .Data.PathLinks -}} {{- if (gt $index 0) }}/{{ end -}} + {{ $link.Text }} + {{- end -}} + + + {{ if .Data.HasReadme }} + [readme] + {{ end }} + [source] + [help] + +
    -
    -
    {{ .Data.Contents }}
    -
    - {{ template "footer" }} -
    - {{ template "js" .}} - +
    +
    {{ .Data.Contents }}
    +
    + {{ template "footer" }} +
    + {{ template "js" .}} + {{- end -}} diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index dfb1e9f114c..9410eede29e 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -29,7 +29,7 @@ type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValu type NativeStore func(pkgName string, name Name) func(m *Machine) // Store is the central interface that specifies the communications between the -// GnoVM and the underlying data store; currently, generally the Gno.land +// GnoVM and the underlying data store; currently, generally the gno.land // blockchain, or the file system. type Store interface { // STABLE From 81a88a2976ba9f2f9127ebbe7fb7d1e1f7fa4bd4 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:06:30 +0100 Subject: [PATCH 28/34] feat: add p/avl/pager (#2584) - add `p/demo/avl/pager` - update `r/demo/users` Hey reviewers, in addition to what you wanted to review, I'm specifically curious if you have any better API/usage ideas. Example: https://github.com/gnolang/gno/pull/2584/files#diff-8d5cbbe072737a7f288f74adcaaace11cacc3d31264e6a001515fcae824394e2R33 Related with #447, #599, #868 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Antonio Navarro Perez Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/demo/avl/pager/gno.mod | 8 + examples/gno.land/p/demo/avl/pager/pager.gno | 213 ++++++++++++++++++ .../gno.land/p/demo/avl/pager/pager_test.gno | 191 ++++++++++++++++ .../gno.land/p/demo/avl/pager/z_filetest.gno | 101 +++++++++ examples/gno.land/r/demo/users/gno.mod | 1 + examples/gno.land/r/demo/users/users.gno | 30 ++- .../gno.land/r/demo/users/z_5_filetest.gno | 6 + 7 files changed, 543 insertions(+), 7 deletions(-) create mode 100644 examples/gno.land/p/demo/avl/pager/gno.mod create mode 100644 examples/gno.land/p/demo/avl/pager/pager.gno create mode 100644 examples/gno.land/p/demo/avl/pager/pager_test.gno create mode 100644 examples/gno.land/p/demo/avl/pager/z_filetest.gno diff --git a/examples/gno.land/p/demo/avl/pager/gno.mod b/examples/gno.land/p/demo/avl/pager/gno.mod new file mode 100644 index 00000000000..59c961d73f2 --- /dev/null +++ b/examples/gno.land/p/demo/avl/pager/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/avl/pager + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/avl/pager/pager.gno b/examples/gno.land/p/demo/avl/pager/pager.gno new file mode 100644 index 00000000000..60bb44d97b6 --- /dev/null +++ b/examples/gno.land/p/demo/avl/pager/pager.gno @@ -0,0 +1,213 @@ +package pager + +import ( + "math" + "net/url" + "strconv" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +// Pager is a struct that holds the AVL tree and pagination parameters. +type Pager struct { + Tree *avl.Tree + PageQueryParam string + SizeQueryParam string + DefaultPageSize int +} + +// Page represents a single page of results. +type Page struct { + Items []Item + PageNumber int + PageSize int + TotalItems int + TotalPages int + HasPrev bool + HasNext bool + Pager *Pager // Reference to the parent Pager +} + +// Item represents a key-value pair in the AVL tree. +type Item struct { + Key string + Value interface{} +} + +// NewPager creates a new Pager with default values. +func NewPager(tree *avl.Tree, defaultPageSize int) *Pager { + return &Pager{ + Tree: tree, + PageQueryParam: "page", + SizeQueryParam: "size", + DefaultPageSize: defaultPageSize, + } +} + +// GetPage retrieves a page of results from the AVL tree. +func (p *Pager) GetPage(pageNumber int) *Page { + return p.GetPageWithSize(pageNumber, p.DefaultPageSize) +} + +func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page { + totalItems := p.Tree.Size() + totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize))) + + page := &Page{ + TotalItems: totalItems, + TotalPages: totalPages, + PageSize: pageSize, + Pager: p, + } + + // pages without content + if pageSize < 1 { + return page + } + + // page number provided is not available + if pageNumber < 1 { + page.HasNext = totalPages > 0 + return page + } + + // page number provided is outside the range of total pages + if pageNumber > totalPages { + page.PageNumber = pageNumber + page.HasPrev = pageNumber > 0 + return page + } + + startIndex := (pageNumber - 1) * pageSize + endIndex := startIndex + pageSize + if endIndex > totalItems { + endIndex = totalItems + } + + items := []Item{} + p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { + items = append(items, Item{Key: key, Value: value}) + return false + }) + + page.Items = items + page.PageNumber = pageNumber + page.HasPrev = pageNumber > 1 + page.HasNext = pageNumber < totalPages + return page +} + +func (p *Pager) MustGetPageByPath(rawURL string) *Page { + page, err := p.GetPageByPath(rawURL) + if err != nil { + panic("invalid path") + } + return page +} + +// GetPageByPath retrieves a page of results based on the query parameters in the URL path. +func (p *Pager) GetPageByPath(rawURL string) (*Page, error) { + pageNumber, pageSize, err := p.ParseQuery(rawURL) + if err != nil { + return nil, err + } + return p.GetPageWithSize(pageNumber, pageSize), nil +} + +// UI generates the Markdown UI for the page selector. +func (p *Page) Selector() string { + pageNumber := p.PageNumber + pageNumber = max(pageNumber, 1) + + if p.TotalPages <= 1 { + return "" + } + + md := "" + + if p.HasPrev { + // Always show the first page link + md += ufmt.Sprintf("[%d](?%s=%d) | ", 1, p.Pager.PageQueryParam, 1) + + // Before + if p.PageNumber > 4 { + md += "… | " + } + + if p.PageNumber > 3 { + md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber-2, p.Pager.PageQueryParam, p.PageNumber-2) + } + + if p.PageNumber > 2 { + md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber-1, p.Pager.PageQueryParam, p.PageNumber-1) + } + } + + if p.PageNumber > 0 && p.PageNumber <= p.TotalPages { + // Current page + md += ufmt.Sprintf("**%d**", p.PageNumber) + } else { + md += ufmt.Sprintf("_%d_", p.PageNumber) + } + + if p.HasNext { + md += " | " + + if p.PageNumber < p.TotalPages-1 { + md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber+1, p.Pager.PageQueryParam, p.PageNumber+1) + } + + if p.PageNumber < p.TotalPages-2 { + md += ufmt.Sprintf("[%d](?%s=%d) | ", p.PageNumber+2, p.Pager.PageQueryParam, p.PageNumber+2) + } + + if p.PageNumber < p.TotalPages-3 { + md += "… | " + } + + // Always show the last page link + md += ufmt.Sprintf("[%d](?%s=%d)", p.TotalPages, p.Pager.PageQueryParam, p.TotalPages) + } + + return md +} + +// ParseQuery parses the URL to extract the page number and page size. +func (p *Pager) ParseQuery(rawURL string) (int, int, error) { + u, err := url.Parse(rawURL) + if err != nil { + return 1, p.DefaultPageSize, err + } + + query := u.Query() + pageNumber := 1 + pageSize := p.DefaultPageSize + + if p.PageQueryParam != "" { + if pageStr := query.Get(p.PageQueryParam); pageStr != "" { + pageNumber, err = strconv.Atoi(pageStr) + if err != nil || pageNumber < 1 { + pageNumber = 1 + } + } + } + + if p.SizeQueryParam != "" { + if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" { + pageSize, err = strconv.Atoi(sizeStr) + if err != nil || pageSize < 1 { + pageSize = p.DefaultPageSize + } + } + } + + return pageNumber, pageSize, nil +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/examples/gno.land/p/demo/avl/pager/pager_test.gno b/examples/gno.land/p/demo/avl/pager/pager_test.gno new file mode 100644 index 00000000000..da4680db8c7 --- /dev/null +++ b/examples/gno.land/p/demo/avl/pager/pager_test.gno @@ -0,0 +1,191 @@ +package pager + +import ( + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/urequire" +) + +func TestPager_GetPage(t *testing.T) { + // Create a new AVL tree and populate it with some key-value pairs. + tree := avl.NewTree() + tree.Set("a", 1) + tree.Set("b", 2) + tree.Set("c", 3) + tree.Set("d", 4) + tree.Set("e", 5) + + // Create a new pager. + pager := NewPager(tree, 10) + + // Define test cases. + tests := []struct { + pageNumber int + pageSize int + expected []Item + }{ + {1, 2, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}}}, + {2, 2, []Item{{Key: "c", Value: 3}, {Key: "d", Value: 4}}}, + {3, 2, []Item{{Key: "e", Value: 5}}}, + {1, 3, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}}}, + {2, 3, []Item{{Key: "d", Value: 4}, {Key: "e", Value: 5}}}, + {1, 5, []Item{{Key: "a", Value: 1}, {Key: "b", Value: 2}, {Key: "c", Value: 3}, {Key: "d", Value: 4}, {Key: "e", Value: 5}}}, + {2, 5, []Item{}}, + } + + for _, tt := range tests { + page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize) + + uassert.Equal(t, len(tt.expected), len(page.Items)) + + for i, item := range page.Items { + uassert.Equal(t, tt.expected[i].Key, item.Key) + uassert.Equal(t, tt.expected[i].Value, item.Value) + } + } +} + +func TestPager_GetPageByPath(t *testing.T) { + // Create a new AVL tree and populate it with some key-value pairs. + tree := avl.NewTree() + for i := 0; i < 50; i++ { + tree.Set(ufmt.Sprintf("key%d", i), i) + } + + // Create a new pager. + pager := NewPager(tree, 10) + + // Define test cases. + tests := []struct { + rawURL string + expectedPage int + expectedSize int + }{ + {"/r/foo:bar/baz?size=10&page=1", 1, 10}, + {"/r/foo:bar/baz?size=10&page=2", 2, 10}, + {"/r/foo:bar/baz?page=3", 3, pager.DefaultPageSize}, + {"/r/foo:bar/baz?size=20", 1, 20}, + {"/r/foo:bar/baz", 1, pager.DefaultPageSize}, + } + + for _, tt := range tests { + page, err := pager.GetPageByPath(tt.rawURL) + urequire.NoError(t, err, ufmt.Sprintf("GetPageByPath(%s) returned error: %v", tt.rawURL, err)) + + uassert.Equal(t, tt.expectedPage, page.PageNumber) + uassert.Equal(t, tt.expectedSize, page.PageSize) + } +} + +func TestPage_Selector(t *testing.T) { + // Create a new AVL tree and populate it with some key-value pairs. + tree := avl.NewTree() + tree.Set("a", 1) + tree.Set("b", 2) + tree.Set("c", 3) + tree.Set("d", 4) + tree.Set("e", 5) + + // Create a new pager. + pager := NewPager(tree, 10) + + // Define test cases. + tests := []struct { + pageNumber int + pageSize int + expected string + }{ + {1, 2, "**1** | [2](?page=2) | [3](?page=3)"}, + {2, 2, "[1](?page=1) | **2** | [3](?page=3)"}, + {3, 2, "[1](?page=1) | [2](?page=2) | **3**"}, + } + + for _, tt := range tests { + page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize) + + ui := page.Selector() + uassert.Equal(t, tt.expected, ui) + } +} + +func TestPager_UI_WithManyPages(t *testing.T) { + // Create a new AVL tree and populate it with many key-value pairs. + tree := avl.NewTree() + for i := 0; i < 100; i++ { + tree.Set(ufmt.Sprintf("key%d", i), i) + } + + // Create a new pager. + pager := NewPager(tree, 10) + + // Define test cases for a large number of pages. + tests := []struct { + pageNumber int + pageSize int + expected string + }{ + // XXX: -1 + // XXX: 0 + {1, 10, "**1** | [2](?page=2) | [3](?page=3) | … | [10](?page=10)"}, + {2, 10, "[1](?page=1) | **2** | [3](?page=3) | [4](?page=4) | … | [10](?page=10)"}, + {3, 10, "[1](?page=1) | [2](?page=2) | **3** | [4](?page=4) | [5](?page=5) | … | [10](?page=10)"}, + {4, 10, "[1](?page=1) | [2](?page=2) | [3](?page=3) | **4** | [5](?page=5) | [6](?page=6) | … | [10](?page=10)"}, + {5, 10, "[1](?page=1) | … | [3](?page=3) | [4](?page=4) | **5** | [6](?page=6) | [7](?page=7) | … | [10](?page=10)"}, + {6, 10, "[1](?page=1) | … | [4](?page=4) | [5](?page=5) | **6** | [7](?page=7) | [8](?page=8) | … | [10](?page=10)"}, + {7, 10, "[1](?page=1) | … | [5](?page=5) | [6](?page=6) | **7** | [8](?page=8) | [9](?page=9) | [10](?page=10)"}, + {8, 10, "[1](?page=1) | … | [6](?page=6) | [7](?page=7) | **8** | [9](?page=9) | [10](?page=10)"}, + {9, 10, "[1](?page=1) | … | [7](?page=7) | [8](?page=8) | **9** | [10](?page=10)"}, + {10, 10, "[1](?page=1) | … | [8](?page=8) | [9](?page=9) | **10**"}, + // XXX: 11 + } + + for _, tt := range tests { + page := pager.GetPageWithSize(tt.pageNumber, tt.pageSize) + + ui := page.Selector() + uassert.Equal(t, tt.expected, ui) + } +} + +func TestPager_ParseQuery(t *testing.T) { + // Create a new AVL tree and populate it with some key-value pairs. + tree := avl.NewTree() + tree.Set("a", 1) + tree.Set("b", 2) + tree.Set("c", 3) + tree.Set("d", 4) + tree.Set("e", 5) + + // Create a new pager. + pager := NewPager(tree, 10) + + // Define test cases. + tests := []struct { + rawURL string + expectedPage int + expectedSize int + expectedError bool + }{ + {"/r/foo:bar/baz?size=2&page=1", 1, 2, false}, + {"/r/foo:bar/baz?size=3&page=2", 2, 3, false}, + {"/r/foo:bar/baz?size=5&page=3", 3, 5, false}, + {"/r/foo:bar/baz?page=2", 2, pager.DefaultPageSize, false}, + {"/r/foo:bar/baz?size=3", 1, 3, false}, + {"/r/foo:bar/baz", 1, pager.DefaultPageSize, false}, + {"/r/foo:bar/baz?size=0&page=0", 1, pager.DefaultPageSize, false}, + } + + for _, tt := range tests { + page, size, err := pager.ParseQuery(tt.rawURL) + if tt.expectedError { + uassert.Error(t, err, ufmt.Sprintf("ParseQuery(%s) expected error but got none", tt.rawURL)) + } else { + urequire.NoError(t, err, ufmt.Sprintf("ParseQuery(%s) returned error: %v", tt.rawURL, err)) + uassert.Equal(t, tt.expectedPage, page, ufmt.Sprintf("ParseQuery(%s) returned page %d, expected %d", tt.rawURL, page, tt.expectedPage)) + uassert.Equal(t, tt.expectedSize, size, ufmt.Sprintf("ParseQuery(%s) returned size %d, expected %d", tt.rawURL, size, tt.expectedSize)) + } + } +} diff --git a/examples/gno.land/p/demo/avl/pager/z_filetest.gno b/examples/gno.land/p/demo/avl/pager/z_filetest.gno new file mode 100644 index 00000000000..91c20115469 --- /dev/null +++ b/examples/gno.land/p/demo/avl/pager/z_filetest.gno @@ -0,0 +1,101 @@ +package main + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" +) + +func main() { + // Create a new AVL tree and populate it with some key-value pairs. + var id seqid.ID + tree := avl.NewTree() + for i := 0; i < 42; i++ { + tree.Set(id.Next().String(), i) + } + + // Create a new pager. + pager := pager.NewPager(tree, 7) + + for pn := -1; pn < 8; pn++ { + page := pager.GetPage(pn) + + println(ufmt.Sprintf("## Page %d of %d", page.PageNumber, page.TotalPages)) + for idx, item := range page.Items { + println(ufmt.Sprintf("- idx=%d key=%s value=%d", idx, item.Key, item.Value)) + } + println(page.Selector()) + println() + } +} + +// Output: +// ## Page 0 of 6 +// _0_ | [1](?page=1) | [2](?page=2) | … | [6](?page=6) +// +// ## Page 0 of 6 +// _0_ | [1](?page=1) | [2](?page=2) | … | [6](?page=6) +// +// ## Page 1 of 6 +// - idx=0 key=0000001 value=0 +// - idx=1 key=0000002 value=1 +// - idx=2 key=0000003 value=2 +// - idx=3 key=0000004 value=3 +// - idx=4 key=0000005 value=4 +// - idx=5 key=0000006 value=5 +// - idx=6 key=0000007 value=6 +// **1** | [2](?page=2) | [3](?page=3) | … | [6](?page=6) +// +// ## Page 2 of 6 +// - idx=0 key=0000008 value=7 +// - idx=1 key=0000009 value=8 +// - idx=2 key=000000a value=9 +// - idx=3 key=000000b value=10 +// - idx=4 key=000000c value=11 +// - idx=5 key=000000d value=12 +// - idx=6 key=000000e value=13 +// [1](?page=1) | **2** | [3](?page=3) | [4](?page=4) | … | [6](?page=6) +// +// ## Page 3 of 6 +// - idx=0 key=000000f value=14 +// - idx=1 key=000000g value=15 +// - idx=2 key=000000h value=16 +// - idx=3 key=000000j value=17 +// - idx=4 key=000000k value=18 +// - idx=5 key=000000m value=19 +// - idx=6 key=000000n value=20 +// [1](?page=1) | [2](?page=2) | **3** | [4](?page=4) | [5](?page=5) | [6](?page=6) +// +// ## Page 4 of 6 +// - idx=0 key=000000p value=21 +// - idx=1 key=000000q value=22 +// - idx=2 key=000000r value=23 +// - idx=3 key=000000s value=24 +// - idx=4 key=000000t value=25 +// - idx=5 key=000000v value=26 +// - idx=6 key=000000w value=27 +// [1](?page=1) | [2](?page=2) | [3](?page=3) | **4** | [5](?page=5) | [6](?page=6) +// +// ## Page 5 of 6 +// - idx=0 key=000000x value=28 +// - idx=1 key=000000y value=29 +// - idx=2 key=000000z value=30 +// - idx=3 key=0000010 value=31 +// - idx=4 key=0000011 value=32 +// - idx=5 key=0000012 value=33 +// - idx=6 key=0000013 value=34 +// [1](?page=1) | … | [3](?page=3) | [4](?page=4) | **5** | [6](?page=6) +// +// ## Page 6 of 6 +// - idx=0 key=0000014 value=35 +// - idx=1 key=0000015 value=36 +// - idx=2 key=0000016 value=37 +// - idx=3 key=0000017 value=38 +// - idx=4 key=0000018 value=39 +// - idx=5 key=0000019 value=40 +// - idx=6 key=000001a value=41 +// [1](?page=1) | … | [4](?page=4) | [5](?page=5) | **6** +// +// ## Page 7 of 6 +// [1](?page=1) | … | [5](?page=5) | [6](?page=6) | _7_ diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod index cdef52b6952..f2f88a0f993 100644 --- a/examples/gno.land/r/demo/users/gno.mod +++ b/examples/gno.land/r/demo/users/gno.mod @@ -2,6 +2,7 @@ module gno.land/r/demo/users require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/avl/pager v0.0.0-latest gno.land/p/demo/avlhelpers v0.0.0-latest gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/users v0.0.0-latest diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 4a0b9c1caf7..daad2e7fc18 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -7,6 +7,7 @@ import ( "strings" "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" "gno.land/p/demo/avlhelpers" "gno.land/p/demo/users" ) @@ -301,9 +302,10 @@ var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`) //---------------------------------------- // Render main page -func Render(path string) string { +func Render(fullPath string) string { + path, _ := splitPathAndQuery(fullPath) if path == "" { - return renderHome() + return renderHome(fullPath) } else if len(path) >= 38 { // 39? 40? if path[:2] != "g1" { return "invalid address " + path @@ -323,12 +325,26 @@ func Render(path string) string { } } -func renderHome() string { +func renderHome(path string) string { doc := "" - name2User.Iterate("", "", func(key string, value interface{}) bool { - user := value.(*users.User) + + page := pager.NewPager(&name2User, 50).MustGetPageByPath(path) + + for _, item := range page.Items { + user := item.Value.(*users.User) doc += " * [" + user.Name + "](/r/demo/users:" + user.Name + ")\n" - return false - }) + } + doc += "\n" + doc += page.Selector() return doc } + +func splitPathAndQuery(fullPath string) (string, string) { + parts := strings.SplitN(fullPath, "?", 2) + path := parts[0] + queryString := "" + if len(parts) > 1 { + queryString = "?" + parts[1] + } + return path, queryString +} diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno index 31e482b7388..2b3e1b17b5c 100644 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ b/examples/gno.land/r/demo/users/z_5_filetest.gno @@ -28,6 +28,8 @@ func main() { users.Register(caller, "satoshi", "my other profile") println(users.Render("")) println("========================================") + println(users.Render("?page=2")) + println("========================================") println(users.Render("gnouser")) println("========================================") println(users.Render("satoshi")) @@ -49,6 +51,10 @@ func main() { // * [test1](/r/demo/users:test1) // * [x](/r/demo/users:x) // +// +// ======================================== +// +// // ======================================== // ## user gnouser // From 9129e4e973220c401be70e179b9eb3acad09b48f Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 7 Nov 2024 15:56:54 +0100 Subject: [PATCH 29/34] fix(gnovm): don't print to stdout by default (#3076) Attempt to fix #3075 cc/ @zivkovicmilos @sw360cab --- contribs/gnodev/pkg/dev/node.go | 2 ++ gno.land/pkg/gnoland/app.go | 5 ++++- gno.land/pkg/gnoland/node_inmemory.go | 3 +++ gno.land/pkg/sdk/vm/keeper.go | 28 ++++++++++++++++----------- gnovm/pkg/gnolang/machine.go | 3 +-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 54baa2ea774..9b3f838b8a0 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "os" "path/filepath" "strings" "sync" @@ -576,5 +577,6 @@ func newNodeConfig(tmc *tmcfg.Config, chainid string, appstate gnoland.GnoGenesi PrivValidator: pv, TMConfig: tmc, Genesis: genesis, + VMOutput: os.Stdout, } } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index ff1b5a88fea..d29ae3fd181 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -3,6 +3,7 @@ package gnoland import ( "fmt" + "io" "log/slog" "path/filepath" "strconv" @@ -36,10 +37,11 @@ type AppOptions struct { DB dbm.DB // required Logger *slog.Logger // required EventSwitch events.EventSwitch // required + VMOutput io.Writer // optional InitChainerConfig // options related to InitChainer } -// DefaultAppOptions provides a "ready" default [AppOptions] for use with +// TestAppOptions provides a "ready" default [AppOptions] for use with // [NewAppWithOptions], using the provided db. func TestAppOptions(db dbm.DB) *AppOptions { return &AppOptions{ @@ -91,6 +93,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { bankKpr := bank.NewBankKeeper(acctKpr) paramsKpr := params.NewParamsKeeper(mainKey, "vm") vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr) + vmk.Output = cfg.VMOutput // Set InitChainer icc := cfg.InitChainerConfig diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 9dccbbfac8d..426a8c780c7 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -2,6 +2,7 @@ package gnoland import ( "fmt" + "io" "log/slog" "path/filepath" "time" @@ -23,6 +24,7 @@ type InMemoryNodeConfig struct { Genesis *bft.GenesisDoc TMConfig *tmcfg.Config DB *memdb.MemDB // will be initialized if nil + VMOutput io.Writer // optional // If StdlibDir not set, then it's filepath.Join(TMConfig.RootDir, "gnovm", "stdlibs") InitChainerConfig @@ -107,6 +109,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, DB: cfg.DB, EventSwitch: evsw, InitChainerConfig: cfg.InitChainerConfig, + VMOutput: cfg.VMOutput, }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 7f5216a4f03..5921e3eb3bb 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -6,8 +6,8 @@ import ( "bytes" "context" "fmt" + "io" "log/slog" - "os" "path/filepath" "regexp" "strings" @@ -57,6 +57,9 @@ var _ VMKeeperI = &VMKeeper{} // VMKeeper holds all package code and store state. type VMKeeper struct { + // Needs to be explicitly set, like in the case of gnodev. + Output io.Writer + baseKey store.StoreKey iavlKey store.StoreKey acck auth.AccountKeeper @@ -108,7 +111,7 @@ func (vm *VMKeeper) Initialize( m2 := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: os.Stdout, // XXX + Output: vm.Output, Store: vm.gnoStore, }) defer m2.Release() @@ -191,8 +194,7 @@ func loadStdlibPackage(pkgPath, stdlibDir string, store gno.Store) { m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, XXX why? - Output: os.Stdout, - Store: store, + Store: store, }) defer m.Release() m.RunMemPackage(memPkg, true) @@ -275,7 +277,7 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add m := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: os.Stdout, // XXX + Output: vm.Output, Store: store, Context: msgCtx, Alloc: store.GetAllocator(), @@ -376,7 +378,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { m2 := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: os.Stdout, // XXX + Output: vm.Output, Store: gnostore, Alloc: gnostore.GetAllocator(), Context: msgCtx, @@ -477,7 +479,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { m := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: os.Stdout, // XXX + Output: vm.Output, Store: gnostore, Context: msgCtx, Alloc: gnostore.GetAllocator(), @@ -574,10 +576,14 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Parse and run the files, construct *PV. buf := new(bytes.Buffer) + output := io.Writer(buf) + if vm.Output != nil { + output = io.MultiWriter(buf, vm.Output) + } m := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: buf, + Output: output, Store: gnostore, Alloc: gnostore.GetAllocator(), Context: msgCtx, @@ -603,7 +609,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { m2 := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: "", - Output: buf, + Output: output, Store: gnostore, Alloc: gnostore.GetAllocator(), Context: msgCtx, @@ -735,7 +741,7 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res m := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: pkgPath, - Output: os.Stdout, // XXX + Output: vm.Output, Store: gnostore, Context: msgCtx, Alloc: alloc, @@ -802,7 +808,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string m := gno.NewMachineWithOptions( gno.MachineOptions{ PkgPath: pkgPath, - Output: os.Stdout, // XXX + Output: vm.Output, Store: gnostore, Context: msgCtx, Alloc: alloc, diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 09be71682a5..33bf32730c5 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "os" "reflect" "slices" "strconv" @@ -144,7 +143,7 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { output := opts.Output if output == nil { - output = os.Stdout + output = io.Discard } alloc := opts.Alloc if alloc == nil { From 7ef606ce42b175d74b16ba3dd631fdb8926ab9d7 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Fri, 8 Nov 2024 00:40:17 +0100 Subject: [PATCH 30/34] fix(gnovm): prevent assignment to non-assignable expressions (#2896) closes: #2889
    Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
    --- gnovm/pkg/gnolang/preprocess.go | 3 +++ gnovm/pkg/gnolang/type_check.go | 35 +++++++++++++++++++++++++++++++++ gnovm/tests/files/assign29.gno | 8 ++++++++ gnovm/tests/files/assign30.gno | 8 ++++++++ gnovm/tests/files/assign31.gno | 9 +++++++++ gnovm/tests/files/var31.gno | 8 ++++++++ gnovm/tests/files/var32.gno | 8 ++++++++ gnovm/tests/files/var33.gno | 9 +++++++++ 8 files changed, 88 insertions(+) create mode 100644 gnovm/tests/files/assign29.gno create mode 100644 gnovm/tests/files/assign30.gno create mode 100644 gnovm/tests/files/assign31.gno create mode 100644 gnovm/tests/files/var31.gno create mode 100644 gnovm/tests/files/var32.gno create mode 100644 gnovm/tests/files/var33.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index a7a1e15bbf3..78e4488b2a0 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1961,6 +1961,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *AssignStmt: n.AssertCompatible(store, last) + // NOTE: keep DEFINE and ASSIGN in sync. if n.Op == DEFINE { // Rhs consts become default *ConstExprs. @@ -2291,6 +2292,8 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *ValueDecl: + assertValidAssignRhs(store, last, n) + // evaluate value if const expr. if n.Const { // NOTE: may or may not be a *ConstExpr, diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index f86c44e7921..c62d67375ee 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -837,6 +837,7 @@ func (x *RangeStmt) AssertCompatible(store Store, last BlockNode) { func (x *AssignStmt) AssertCompatible(store Store, last BlockNode) { if x.Op == ASSIGN || x.Op == DEFINE { + assertValidAssignRhs(store, last, x) if len(x.Lhs) > len(x.Rhs) { if len(x.Rhs) != 1 { panic(fmt.Sprintf("assignment mismatch: %d variables but %d values", len(x.Lhs), len(x.Rhs))) @@ -997,6 +998,40 @@ func assertValidAssignLhs(store Store, last BlockNode, lx Expr) { } } +func assertValidAssignRhs(store Store, last BlockNode, n Node) { + var exps []Expr + switch x := n.(type) { + case *ValueDecl: + exps = x.Values + case *AssignStmt: + exps = x.Rhs + default: + panic(fmt.Sprintf("unexpected node type %T", n)) + } + + for _, exp := range exps { + tt := evalStaticTypeOfRaw(store, last, exp) + if tt == nil { + switch x := n.(type) { + case *ValueDecl: + if x.Type != nil { + continue + } + panic("use of untyped nil in variable declaration") + case *AssignStmt: + if x.Op != DEFINE { + continue + } + panic("use of untyped nil in assignment") + } + } + if _, ok := tt.(*TypeType); ok { + tt = evalStaticType(store, last, exp) + panic(fmt.Sprintf("%s (type) is not an expression", tt.String())) + } + } +} + func kindString(xt Type) string { if xt != nil { return xt.Kind().String() diff --git a/gnovm/tests/files/assign29.gno b/gnovm/tests/files/assign29.gno new file mode 100644 index 00000000000..ca605f5ecbe --- /dev/null +++ b/gnovm/tests/files/assign29.gno @@ -0,0 +1,8 @@ +package main + +func main() { + t := struct{} +} + +// Error: +// main/files/assign29.gno:4:2: struct{} (type) is not an expression diff --git a/gnovm/tests/files/assign30.gno b/gnovm/tests/files/assign30.gno new file mode 100644 index 00000000000..ad72f880f27 --- /dev/null +++ b/gnovm/tests/files/assign30.gno @@ -0,0 +1,8 @@ +package main + +func main() { + t := *struct{} +} + +// Error: +// main/files/assign30.gno:4:2: *struct{} (type) is not an expression diff --git a/gnovm/tests/files/assign31.gno b/gnovm/tests/files/assign31.gno new file mode 100644 index 00000000000..8c96c04501e --- /dev/null +++ b/gnovm/tests/files/assign31.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := nil + println(a) +} + +// Error: +// main/files/assign31.gno:4:2: use of untyped nil in assignment diff --git a/gnovm/tests/files/var31.gno b/gnovm/tests/files/var31.gno new file mode 100644 index 00000000000..813e6ff9e22 --- /dev/null +++ b/gnovm/tests/files/var31.gno @@ -0,0 +1,8 @@ +package main + +func main() { + var t = struct{} +} + +// Error: +// main/files/var31.gno:4:6: struct{} (type) is not an expression diff --git a/gnovm/tests/files/var32.gno b/gnovm/tests/files/var32.gno new file mode 100644 index 00000000000..827c3951f94 --- /dev/null +++ b/gnovm/tests/files/var32.gno @@ -0,0 +1,8 @@ +package main + +func main() { + var t = nil +} + +// Error: +// main/files/var32.gno:4:6: use of untyped nil in variable declaration diff --git a/gnovm/tests/files/var33.gno b/gnovm/tests/files/var33.gno new file mode 100644 index 00000000000..ce883dce47c --- /dev/null +++ b/gnovm/tests/files/var33.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var t *int = nil + println("pass") +} + +// Output: +// pass From d73b6c6d5b594266d2c6e4d48f9b4d431ac2b507 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:06:53 +0100 Subject: [PATCH 31/34] ci: add go caching (#3091) `actions/setup-go` requires `actions/checkout` to read the `go.sum` file. ![CleanShot 2024-11-08 at 11 55 28@2x](https://github.com/user-attachments/assets/9014b6f4-d415-487a-a624-d5879e69d2c2) Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/build_template.yml | 6 +++--- .github/workflows/lint_template.yml | 6 +++--- .github/workflows/test_template.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_template.yml b/.github/workflows/build_template.yml index 430aa393a73..a2c96f2d37e 100644 --- a/.github/workflows/build_template.yml +++ b/.github/workflows/build_template.yml @@ -12,14 +12,14 @@ jobs: generated: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} - - name: Checkout code - uses: actions/checkout@v4 - - name: Check generated files are up to date working-directory: ${{ inputs.modulepath }} run: | diff --git a/.github/workflows/lint_template.yml b/.github/workflows/lint_template.yml index 65679633240..5b792269c02 100644 --- a/.github/workflows/lint_template.yml +++ b/.github/workflows/lint_template.yml @@ -13,16 +13,16 @@ jobs: lint: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} - - name: Checkout code - uses: actions/checkout@v4 - name: Lint uses: golangci/golangci-lint-action@v6 with: working-directory: ${{ inputs.modulepath }} args: --config=${{ github.workspace }}/.github/golangci.yml - version: v1.59 # sync with misc/devdeps \ No newline at end of file + version: v1.59 # sync with misc/devdeps diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index 38fa10e096b..97b675e5531 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -18,12 +18,12 @@ jobs: test: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} - - name: Checkout code - uses: actions/checkout@v4 - name: Go test working-directory: ${{ inputs.modulepath }} env: From da79c846c949169188361635ec58439d0f3f6227 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:06:57 +0100 Subject: [PATCH 32/34] test(ci): coverpkg=gno.land/... for txtar tests (#3088) Note: I'm uncertain about what will happen after the merge. Fixes #3085 Addresses #3003 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Morgan --- .github/workflows/gnoland.yml | 1 + .github/workflows/main_template.yml | 6 +++++- .github/workflows/test_template.yml | 17 ++++++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 9451d6da3a1..4817e2db0e3 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -13,5 +13,6 @@ jobs: uses: ./.github/workflows/main_template.yml with: modulepath: "gno.land" + tests-extra-args: "-coverpkg=github.com/gnolang/gno/gno.land/..." secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/main_template.yml b/.github/workflows/main_template.yml index 8efb0277816..5b3437b54a1 100644 --- a/.github/workflows/main_template.yml +++ b/.github/workflows/main_template.yml @@ -4,6 +4,9 @@ on: modulepath: required: true type: string + tests-extra-args: + required: false + type: string secrets: codecov-token: required: true @@ -32,6 +35,7 @@ jobs: modulepath: ${{ inputs.modulepath }} tests-timeout: "30m" go-version: "1.22.x" + tests-extra-args: ${{ inputs.tests-extra-args }} secrets: codecov-token: ${{ secrets.codecov-token }} - \ No newline at end of file + diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index 97b675e5531..b032718ff62 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -5,11 +5,14 @@ on: required: true type: string tests-timeout: - required: true - type: string + required: true + type: string go-version: - required: true - type: string + required: true + type: string + tests-extra-args: + required: false + type: string secrets: codecov-token: required: true @@ -23,7 +26,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go-version }} + go-version: ${{ inputs.go-version }} - name: Go test working-directory: ${{ inputs.modulepath }} env: @@ -42,7 +45,7 @@ jobs: # confusing and meticulous. There will be some improvements in Go # 1.23 regarding coverage, so we can use this as a workaround until # then. - go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} ./... -test.gocoverdir=$GOCOVERDIR + go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} ${{ inputs.tests-extra-args }} ./... -test.gocoverdir=$GOCOVERDIR # Print results (set +x; echo 'go coverage results:') @@ -70,7 +73,7 @@ jobs: # - name: Install Go # uses: actions/setup-go@v5 # with: - # go-version: ${{ inputs.go-version }} + # go-version: ${{ inputs.go-version }} # - name: Checkout code # uses: actions/checkout@v4 # - name: Go race test From 4f27a57230320b77d577dd1ab3c773db6189908c Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Fri, 8 Nov 2024 18:09:14 +0100 Subject: [PATCH 33/34] fix(cmd/gno/clean): allow to run `gno clean -modcache` from anywhere + rename and use `gnomod.ModCachePath` + tmp `GNOHOME` in main tests (#3083) - In `gno clean`, run the `-modcache` case before checking for presence of a `gno.mod` to allow to run `gno clean -modcache` from anywhere (like go) - Refactor `gno clean -modcache` to use the `gnomod.GetGnoModPath` helper to get the modcache path - Rename `gnomod.GetGnoModPath` -> `gnomod.ModCachePath` - Improve `gno` cmd tests by using a tmp `GNOHOME` instead of the system one
    Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
    --------- Signed-off-by: Norman Meier --- gnovm/cmd/gno/clean.go | 27 ++++++++++++++------------- gnovm/cmd/gno/clean_test.go | 11 +++++++++++ gnovm/cmd/gno/env_test.go | 6 +++--- gnovm/cmd/gno/main_test.go | 8 ++++++++ gnovm/cmd/gno/mod.go | 2 +- gnovm/pkg/gnomod/gnomod.go | 10 +++++----- 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go index 19a73c51794..0ca2e940d58 100644 --- a/gnovm/cmd/gno/clean.go +++ b/gnovm/cmd/gno/clean.go @@ -9,7 +9,6 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -55,7 +54,7 @@ func (c *cleanCfg) RegisterFlags(fs *flag.FlagSet) { &c.modCache, "modcache", false, - "remove the entire module download cache", + "remove the entire module download cache and exit", ) } @@ -64,6 +63,19 @@ func execClean(cfg *cleanCfg, args []string, io commands.IO) error { return flag.ErrHelp } + if cfg.modCache { + modCacheDir := gnomod.ModCachePath() + if !cfg.dryRun { + if err := os.RemoveAll(modCacheDir); err != nil { + return err + } + } + if cfg.dryRun || cfg.verbose { + io.Println("rm -rf", modCacheDir) + } + return nil + } + path, err := os.Getwd() if err != nil { return err @@ -81,17 +93,6 @@ func execClean(cfg *cleanCfg, args []string, io commands.IO) error { return err } - if cfg.modCache { - modCacheDir := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") - if !cfg.dryRun { - if err := os.RemoveAll(modCacheDir); err != nil { - return err - } - } - if cfg.dryRun || cfg.verbose { - io.Println("rm -rf", modCacheDir) - } - } return nil } diff --git a/gnovm/cmd/gno/clean_test.go b/gnovm/cmd/gno/clean_test.go index cfca2655031..401d0c87ddc 100644 --- a/gnovm/cmd/gno/clean_test.go +++ b/gnovm/cmd/gno/clean_test.go @@ -32,6 +32,17 @@ func TestCleanApp(t *testing.T) { testDir: "../../tests/integ/minimalist_gnomod", simulateExternalRepo: true, }, + { + args: []string{"clean", "-modcache"}, + testDir: "../../tests/integ/empty_dir", + simulateExternalRepo: true, + }, + { + args: []string{"clean", "-modcache", "-n"}, + testDir: "../../tests/integ/empty_dir", + simulateExternalRepo: true, + stdoutShouldContain: "rm -rf ", + }, } testMainCaseRun(t, tc) diff --git a/gnovm/cmd/gno/env_test.go b/gnovm/cmd/gno/env_test.go index 8aeb84ab2cc..b15658ed4f5 100644 --- a/gnovm/cmd/gno/env_test.go +++ b/gnovm/cmd/gno/env_test.go @@ -18,13 +18,13 @@ func TestEnvApp(t *testing.T) { {args: []string{"env", "foo"}, stdoutShouldBe: "\n"}, {args: []string{"env", "foo", "bar"}, stdoutShouldBe: "\n\n"}, {args: []string{"env", "GNOROOT"}, stdoutShouldBe: testGnoRootEnv + "\n"}, - {args: []string{"env", "GNOHOME", "storm"}, stdoutShouldBe: testGnoHomeEnv + "\n\n"}, + {args: []string{"env", "GNOHOME", "storm"}, stdoutShouldBe: testGnoHomeEnv + "\n\n", noTmpGnohome: true}, {args: []string{"env"}, stdoutShouldContain: fmt.Sprintf("GNOROOT=%q", testGnoRootEnv)}, - {args: []string{"env"}, stdoutShouldContain: fmt.Sprintf("GNOHOME=%q", testGnoHomeEnv)}, + {args: []string{"env"}, stdoutShouldContain: fmt.Sprintf("GNOHOME=%q", testGnoHomeEnv), noTmpGnohome: true}, // json {args: []string{"env", "-json"}, stdoutShouldContain: fmt.Sprintf("\"GNOROOT\": %q", testGnoRootEnv)}, - {args: []string{"env", "-json"}, stdoutShouldContain: fmt.Sprintf("\"GNOHOME\": %q", testGnoHomeEnv)}, + {args: []string{"env", "-json"}, stdoutShouldContain: fmt.Sprintf("\"GNOHOME\": %q", testGnoHomeEnv), noTmpGnohome: true}, { args: []string{"env", "-json", "GNOROOT"}, stdoutShouldBe: fmt.Sprintf("{\n\t\"GNOROOT\": %q\n}\n", testGnoRootEnv), diff --git a/gnovm/cmd/gno/main_test.go b/gnovm/cmd/gno/main_test.go index 069c42db379..76c67f6807b 100644 --- a/gnovm/cmd/gno/main_test.go +++ b/gnovm/cmd/gno/main_test.go @@ -26,6 +26,7 @@ type testMainCase struct { args []string testDir string simulateExternalRepo bool + noTmpGnohome bool // for the following FooContain+FooBe expected couples, if both are empty, // then the test suite will require that the "got" is not empty. @@ -58,6 +59,13 @@ func testMainCaseRun(t *testing.T, tc []testMainCase) { mockOut := bytes.NewBufferString("") mockErr := bytes.NewBufferString("") + if !test.noTmpGnohome { + tmpGnoHome, err := os.MkdirTemp(os.TempDir(), "gnotesthome_") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpGnoHome) }) + t.Setenv("GNOHOME", tmpGnoHome) + } + checkOutputs := func(t *testing.T) { t.Helper() diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 03b2bb348a8..67af5631c71 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -177,7 +177,7 @@ func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { } // fetch dependencies - if err := gnoMod.FetchDeps(gnomod.GetGnoModPath(), cfg.remote, cfg.verbose); err != nil { + if err := gnoMod.FetchDeps(gnomod.ModCachePath(), cfg.remote, cfg.verbose); err != nil { return fmt.Errorf("fetch: %w", err) } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 553bb32f4b5..9384c41c293 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -19,20 +19,20 @@ import ( const queryPathFile = "vm/qfile" -// GetGnoModPath returns the path for gno modules -func GetGnoModPath() string { +// ModCachePath returns the path for gno modules +func ModCachePath() string { return filepath.Join(gnoenv.HomeDir(), "pkg", "mod") } // PackageDir resolves a given module.Version to the path on the filesystem. -// If root is dir, it is defaulted to the value of [GetGnoModPath]. +// If root is dir, it is defaulted to the value of [ModCachePath]. func PackageDir(root string, v module.Version) string { // This is also used internally exactly like filepath.Join; but we'll keep // the calls centralized to make sure we can change the path centrally should // we start including the module version in the path. if root == "" { - root = GetGnoModPath() + root = ModCachePath() } return filepath.Join(root, v.Path) } @@ -89,7 +89,7 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err func GnoToGoMod(f File) (*File, error) { // TODO(morgan): good candidate to move to pkg/transpiler. - gnoModPath := GetGnoModPath() + gnoModPath := ModCachePath() if !gnolang.IsStdlib(f.Module.Mod.Path) { f.AddModuleStmt(transpiler.TranspileImportPath(f.Module.Mod.Path)) From 5f85d50e7dc88f6290192bc8f71669b8ccac43dc Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Tue, 12 Nov 2024 03:18:23 +0100 Subject: [PATCH 34/34] feat: add p/moul/realmpath (#3094) Lightweight `Render.path` parsing and link generation library with an idiomatic API, closely resembling that of `net/url`. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/moul/realmpath/gno.mod | 6 + .../gno.land/p/moul/realmpath/realmpath.gno | 100 ++++++++++++ .../p/moul/realmpath/realmpath_test.gno | 151 ++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 examples/gno.land/p/moul/realmpath/gno.mod create mode 100644 examples/gno.land/p/moul/realmpath/realmpath.gno create mode 100644 examples/gno.land/p/moul/realmpath/realmpath_test.gno diff --git a/examples/gno.land/p/moul/realmpath/gno.mod b/examples/gno.land/p/moul/realmpath/gno.mod new file mode 100644 index 00000000000..e391b76390f --- /dev/null +++ b/examples/gno.land/p/moul/realmpath/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/moul/realmpath + +require ( + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/moul/realmpath/realmpath.gno b/examples/gno.land/p/moul/realmpath/realmpath.gno new file mode 100644 index 00000000000..c46c97b4bed --- /dev/null +++ b/examples/gno.land/p/moul/realmpath/realmpath.gno @@ -0,0 +1,100 @@ +// Package realmpath is a lightweight Render.path parsing and link generation +// library with an idiomatic API, closely resembling that of net/url. +// +// This package provides utilities for parsing request paths and query +// parameters, allowing you to extract path segments and manipulate query +// values. +// +// Example usage: +// +// import "gno.land/p/moul/realmpath" +// +// func Render(path string) string { +// // Parsing a sample path with query parameters +// path = "hello/world?foo=bar&baz=foobar" +// req := realmpath.Parse(path) +// +// // Accessing parsed path and query parameters +// println(req.Path) // Output: hello/world +// println(req.PathPart(0)) // Output: hello +// println(req.PathPart(1)) // Output: world +// println(req.Query.Get("foo")) // Output: bar +// println(req.Query.Get("baz")) // Output: foobar +// +// // Rebuilding the URL +// println(req.String()) // Output: /r/current/realm:hello/world?baz=foobar&foo=bar +// } +package realmpath + +import ( + "net/url" + "std" + "strings" +) + +const chainDomain = "gno.land" // XXX: std.ChainDomain (#2911) + +// Request represents a parsed request. +type Request struct { + Path string // The path of the request + Query url.Values // The parsed query parameters + Realm string // The realm associated with the request +} + +// Parse takes a raw path string and returns a Request object. +// It splits the path into its components and parses any query parameters. +func Parse(rawPath string) *Request { + // Split the raw path into path and query components + path, query := splitPathAndQuery(rawPath) + + // Parse the query string into url.Values + queryValues, _ := url.ParseQuery(query) + + return &Request{ + Path: path, // Set the path + Query: queryValues, // Set the parsed query values + } +} + +// PathParts returns the segments of the path as a slice of strings. +// It trims leading and trailing slashes and splits the path by slashes. +func (r *Request) PathParts() []string { + return strings.Split(strings.Trim(r.Path, "/"), "/") +} + +// PathPart returns the specified part of the path. +// If the index is out of bounds, it returns an empty string. +func (r *Request) PathPart(index int) string { + parts := r.PathParts() // Get the path segments + if index < 0 || index >= len(parts) { + return "" // Return empty if index is out of bounds + } + return parts[index] // Return the specified path part +} + +// String rebuilds the URL from the path and query values. +// If the Realm is not set, it automatically retrieves the current realm path. +func (r *Request) String() string { + // Automatically set the Realm if it is not already defined + if r.Realm == "" { + r.Realm = std.CurrentRealm().PkgPath() // Get the current realm path + } + + // Rebuild the path using the realm and path parts + relativePkgPath := strings.TrimPrefix(r.Realm, chainDomain) // Trim the chain domain prefix + reconstructedPath := relativePkgPath + ":" + strings.Join(r.PathParts(), "/") + + // Rebuild the query string + queryString := r.Query.Encode() // Encode the query parameters + if queryString != "" { + return reconstructedPath + "?" + queryString // Return the full URL with query + } + return reconstructedPath // Return the path without query parameters +} + +func splitPathAndQuery(rawPath string) (string, string) { + if idx := strings.Index(rawPath, "?"); idx != -1 { + return rawPath[:idx], rawPath[idx+1:] // Split at the first '?' found + } + return rawPath, "" // No query string present +} diff --git a/examples/gno.land/p/moul/realmpath/realmpath_test.gno b/examples/gno.land/p/moul/realmpath/realmpath_test.gno new file mode 100644 index 00000000000..a638b40d3ca --- /dev/null +++ b/examples/gno.land/p/moul/realmpath/realmpath_test.gno @@ -0,0 +1,151 @@ +package realmpath_test + +import ( + "net/url" + "std" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" + "gno.land/p/moul/realmpath" +) + +func TestExample(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/lorem/ipsum")) + + // initial parsing + path := "hello/world?foo=bar&baz=foobar" + req := realmpath.Parse(path) + urequire.False(t, req == nil, "req should not be nil") + uassert.Equal(t, req.Path, "hello/world") + uassert.Equal(t, req.Query.Get("foo"), "bar") + uassert.Equal(t, req.Query.Get("baz"), "foobar") + uassert.Equal(t, req.String(), "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar") + + // alter query + req.Query.Set("hey", "salut") + uassert.Equal(t, req.String(), "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar&hey=salut") + + // alter path + req.Path = "bye/ciao" + uassert.Equal(t, req.String(), "/r/lorem/ipsum:bye/ciao?baz=foobar&foo=bar&hey=salut") +} + +func TestParse(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/lorem/ipsum")) + + tests := []struct { + rawPath string + realm string // optional + expectedPath string + expectedQuery url.Values + expectedString string + }{ + { + rawPath: "hello/world?foo=bar&baz=foobar", + expectedPath: "hello/world", + expectedQuery: url.Values{ + "foo": []string{"bar"}, + "baz": []string{"foobar"}, + }, + expectedString: "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar", + }, + { + rawPath: "api/v1/resource?search=test&limit=10", + expectedPath: "api/v1/resource", + expectedQuery: url.Values{ + "search": []string{"test"}, + "limit": []string{"10"}, + }, + expectedString: "/r/lorem/ipsum:api/v1/resource?limit=10&search=test", + }, + { + rawPath: "singlepath", + expectedPath: "singlepath", + expectedQuery: url.Values{}, + expectedString: "/r/lorem/ipsum:singlepath", + }, + { + rawPath: "path/with/trailing/slash/", + expectedPath: "path/with/trailing/slash/", + expectedQuery: url.Values{}, + expectedString: "/r/lorem/ipsum:path/with/trailing/slash", + }, + { + rawPath: "emptyquery?", + expectedPath: "emptyquery", + expectedQuery: url.Values{}, + expectedString: "/r/lorem/ipsum:emptyquery", + }, + { + rawPath: "path/with/special/characters/?key=val%20ue&anotherKey=with%21special%23chars", + expectedPath: "path/with/special/characters/", + expectedQuery: url.Values{ + "key": []string{"val ue"}, + "anotherKey": []string{"with!special#chars"}, + }, + expectedString: "/r/lorem/ipsum:path/with/special/characters?anotherKey=with%21special%23chars&key=val+ue", + }, + { + rawPath: "path/with/empty/key?keyEmpty&=valueEmpty", + expectedPath: "path/with/empty/key", + expectedQuery: url.Values{ + "keyEmpty": []string{""}, + "": []string{"valueEmpty"}, + }, + expectedString: "/r/lorem/ipsum:path/with/empty/key?=valueEmpty&keyEmpty=", + }, + { + rawPath: "path/with/multiple/empty/keys?=empty1&=empty2", + expectedPath: "path/with/multiple/empty/keys", + expectedQuery: url.Values{ + "": []string{"empty1", "empty2"}, + }, + expectedString: "/r/lorem/ipsum:path/with/multiple/empty/keys?=empty1&=empty2", + }, + { + rawPath: "path/with/percent-encoded/%20space?query=hello%20world", + expectedPath: "path/with/percent-encoded/%20space", // XXX: should we decode? + expectedQuery: url.Values{ + "query": []string{"hello world"}, + }, + expectedString: "/r/lorem/ipsum:path/with/percent-encoded/%20space?query=hello+world", + }, + { + rawPath: "path/with/very/long/query?key1=value1&key2=value2&key3=value3&key4=value4&key5=value5&key6=value6", + expectedPath: "path/with/very/long/query", + expectedQuery: url.Values{ + "key1": []string{"value1"}, + "key2": []string{"value2"}, + "key3": []string{"value3"}, + "key4": []string{"value4"}, + "key5": []string{"value5"}, + "key6": []string{"value6"}, + }, + expectedString: "/r/lorem/ipsum:path/with/very/long/query?key1=value1&key2=value2&key3=value3&key4=value4&key5=value5&key6=value6", + }, + { + rawPath: "custom/realm?foo=bar&baz=foobar", + realm: "gno.land/r/foo/bar", + expectedPath: "custom/realm", + expectedQuery: url.Values{ + "foo": []string{"bar"}, + "baz": []string{"foobar"}, + }, + expectedString: "/r/foo/bar:custom/realm?baz=foobar&foo=bar", + }, + } + + for _, tt := range tests { + t.Run(tt.rawPath, func(t *testing.T) { + req := realmpath.Parse(tt.rawPath) + req.Realm = tt.realm // set optional realm + urequire.False(t, req == nil, "req should not be nil") + uassert.Equal(t, req.Path, tt.expectedPath) + urequire.Equal(t, len(req.Query), len(tt.expectedQuery)) + uassert.Equal(t, req.Query.Encode(), tt.expectedQuery.Encode()) + // XXX: uassert.Equal(t, req.Query, tt.expectedQuery) + uassert.Equal(t, req.String(), tt.expectedString) + }) + } +}