From ddb4d103201491c41a4baf450fb16f85fbde5095 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:53:25 +0200 Subject: [PATCH 1/8] Revert "feat(stdlibs): add math/rand (#2455)" This reverts commit f547d7dcac11de7937664413497fed35eb1a03ff. --- .github/workflows/docs-linter.yml | 4 +- .github/workflows/examples.yml | 5 +- CONTRIBUTING.md | 2 +- contribs/gnodev/go.mod | 2 +- contribs/gnofaucet/go.mod | 2 +- contribs/gnokeykc/go.mod | 2 +- contribs/gnomd/go.mod | 2 +- .../local-setup/installation.md | 16 +- .../validators/connect-to-existing-chain.md | 4 +- .../validators/setting-up-a-new-chain.md | 4 +- docs/reference/go-gno-compatibility.md | 5 +- examples/gno.land/p/demo/rand/gno.mod | 3 + examples/gno.land/p/demo/rand/rand.gno | 139 +++++++ .../gno.land/p/demo/rand/rand0_filetest.gno | 56 +++ examples/gno.land/p/demo/rand/rand_test.gno | 49 +++ examples/gno.land/r/demo/art/gnoface/gno.mod | 7 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 14 +- .../r/demo/art/gnoface/gnoface_test.gno | 70 ++-- examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 +- .../gno.land/r/x/manfred_outfmt/outfmt.gno | 15 +- .../r/x/manfred_outfmt/outfmt_test.gno | 22 +- gnovm/stdlibs/bytes/buffer_test.gno | 4 +- gnovm/stdlibs/bytes/bytes_test.gno | 6 +- gnovm/stdlibs/math/rand/auto_test.gno | 39 -- gnovm/stdlibs/math/rand/pcg.gno | 121 ------- gnovm/stdlibs/math/rand/pcg_test.gno | 78 ---- gnovm/stdlibs/math/rand/rand.gno | 340 ------------------ gnovm/stdlibs/math/rand/zipf.gno | 77 ---- gnovm/stdlibs/sort/sort_test.gno | 12 +- gnovm/tests/files/extern/p1/s2.gno | 2 + gnovm/tests/files/import4.gno | 2 +- gnovm/tests/imports.go | 13 +- go.mod | 2 +- misc/autocounterd/go.mod | 2 +- misc/devdeps/go.mod | 2 +- misc/docs-linter/go.mod | 2 +- misc/loop/go.mod | 2 +- tm2/pkg/libtm/README.md | 2 +- tm2/pkg/libtm/go.mod | 2 +- 39 files changed, 372 insertions(+), 764 deletions(-) create mode 100644 examples/gno.land/p/demo/rand/gno.mod create mode 100644 examples/gno.land/p/demo/rand/rand.gno create mode 100644 examples/gno.land/p/demo/rand/rand0_filetest.gno create mode 100644 examples/gno.land/p/demo/rand/rand_test.gno delete mode 100644 gnovm/stdlibs/math/rand/auto_test.gno delete mode 100644 gnovm/stdlibs/math/rand/pcg.gno delete mode 100644 gnovm/stdlibs/math/rand/pcg_test.gno delete mode 100644 gnovm/stdlibs/math/rand/rand.gno delete mode 100644 gnovm/stdlibs/math/rand/zipf.gno diff --git a/.github/workflows/docs-linter.yml b/.github/workflows/docs-linter.yml index e56c1a663ea..0ffa67dfe95 100644 --- a/.github/workflows/docs-linter.yml +++ b/.github/workflows/docs-linter.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.22' + go-version: '1.21' - name: Install dependencies run: go mod download @@ -28,4 +28,4 @@ jobs: run: make -C docs/ build - name: Run linter - run: make -C docs/ lint + run: make -C docs/ lint \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d11710344b1..cea52e74cd4 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,6 +20,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" runs-on: ubuntu-latest timeout-minutes: 30 @@ -35,6 +36,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -58,6 +60,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -105,4 +108,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc125a6da73..e041ab18875 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.22+) +- Go (version 1.21+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 6d2c7a34293..ec82f09e467 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index e4b63c7a9e9..dc5267fff2b 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 711cafed241..d9e785f5226 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 8bc352d4848..39043bae144 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnomd -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index a3658fa6ab3..8700ff9a2b2 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -5,15 +5,15 @@ id: installation # Installation ## Overview -In this tutorial, you will learn how to set up the Gno development environment -locally, so you can get up and running writing Gno code. You will download and +In this tutorial, you will learn how to set up the Gno development environment +locally, so you can get up and running writing Gno code. You will download and install all the necessary tooling, and validate that it is correctly configured to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: @@ -30,7 +30,7 @@ GitHub repository somewhere on disk: git clone https://github.com/gnolang/gno.git ``` -## 2. Installing the required tools +## 2. Installing the required tools There are three tools that should be used for getting started with Gno development: - `gno` - the GnoVM binary @@ -42,7 +42,7 @@ To install all three tools, simply run the following in the root of the repo: make install ``` -## 3. Verifying installation +## 3. Verifying installation ### `gno` `gno` provides ample functionality to the user, among which is running, @@ -59,7 +59,7 @@ You should get the help output from the command: ![gno help](../../assets/getting-started/local-setup/local-setup/gno-help.gif) -Alternatively, if you don't want to have the binary callable system-wide, you +Alternatively, if you don't want to have the binary callable system-wide, you can run the binary directly: ```bash @@ -68,8 +68,8 @@ 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 +`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 (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/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md index f1acf06049f..a1a337a5a48 100644 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -12,7 +12,7 @@ In this tutorial, you will learn how to start a local Gno node and connect to an - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -107,4 +107,4 @@ gnoland start \ That's it! 🎉 -Your new Gno node should be up and running, and syncing block data from the remote chain. +Your new Gno node should be up and running, and syncing block data from the remote chain. \ No newline at end of file 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 8f94037dc1d..1927679db8f 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -13,7 +13,7 @@ Additionally, you will see the different options you can use to make your Gno in - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -451,4 +451,4 @@ Genesis block generation happens only once during the lifetime of a Gno chain. This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from block 0), the specified balance sheet will not be applied. -::: +::: \ No newline at end of file diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index a2f83f2bbc6..89ad4f7b990 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -34,7 +34,7 @@ id: go-gno-compatibility Generics are currently not implemented. -Note that Gno does not support shadowing of built-in types. +Note that Gno does not support shadowing of built-in types. While the following built-in typecasting assignment would work in Go, this is not supported in Gno. ```go @@ -205,7 +205,7 @@ Legend: | math/big | `tbd` | | math/bits | `full` | | math/cmplx | `tbd` | -| math/rand | `full`[^9] | +| math/rand | `todo` | | mime | `tbd` | | mime/multipart | `tbd` | | mime/quotedprintable | `tbd` | @@ -291,7 +291,6 @@ Legend: determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. [^8]: `crypto/ed25519` is currently only implemented for `Verify`, which should still cover a majority of use cases. A full implementation is welcome. -[^9]: `math/rand` in Gno ports over Go's `math/rand/v2`. ## Tooling (`gno` binary) diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod new file mode 100644 index 00000000000..098af152648 --- /dev/null +++ b/examples/gno.land/p/demo/rand/gno.mod @@ -0,0 +1,3 @@ +// Draft + +module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/rand/rand.gno b/examples/gno.land/p/demo/rand/rand.gno new file mode 100644 index 00000000000..2fa16d627be --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand.gno @@ -0,0 +1,139 @@ +package rand + +// Disclaimer: this package is unsafe and won't prevent others to +// guess values in advance. +// +// the goal of this package is to implement a random library that +// is fully deterministic for validators while being hard to guess. +// +// We use the Bernstein's hash djb2 to be CPU-cycle efficient. + +import ( + "math/rand" + "std" + "time" +) + +type Instance struct { + seed int64 +} + +func New() *Instance { + r := Instance{seed: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed int64) *Instance { + r := Instance{seed: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() int64 { + return i.seed +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Int64(int64(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Int64(input int64) { + i.seed = (i.seed << 5) + i.seed + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + i.djb2Int64(height) + } + + // time + { + secs := time.Now().Second() + i.djb2Int64(int64(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Int64(int64(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Float32() float32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Float32() +} + +func (i *Instance) Float64() float64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Float64() +} + +func (i *Instance) Int() int { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int() +} + +func (i *Instance) Intn(n int) int { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Intn(n) +} + +func (i *Instance) Int63() int64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int63() +} + +func (i *Instance) Int63n(n int64) int64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int63n(n) +} + +func (i *Instance) Int31() int32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int31() +} + +func (i *Instance) Int31n(n int32) int32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int31n(n) +} + +func (i *Instance) Uint32() uint32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Uint32() +} + +func (i *Instance) Uint64() uint64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Uint64() +} + +func (i *Instance) Read(p []byte) (n int, err error) { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Read(p) +} + +func (i *Instance) Shuffle(n int, swap func(i, j int)) { + i.addEntropy() + rand.New(rand.NewSource(i.seed)).Shuffle(n, swap) +} diff --git a/examples/gno.land/p/demo/rand/rand0_filetest.gno b/examples/gno.land/p/demo/rand/rand0_filetest.gno new file mode 100644 index 00000000000..446e04b696d --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand0_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/rand" +) + +func main() { + // initial + println("---") + r := rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + + // should be the same + println("---") + r = rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + + std.TestSkipHeights(1) + println("---") + r = rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) +} + +// Output: +// --- +// 777 +// 257 +// 74 +// 177 +// 802 +// --- +// 777 +// 257 +// 74 +// 177 +// 802 +// --- +// 269 +// 233 +// 591 +// 936 +// 908 diff --git a/examples/gno.land/p/demo/rand/rand_test.gno b/examples/gno.land/p/demo/rand/rand_test.gno new file mode 100644 index 00000000000..2651f0af089 --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand_test.gno @@ -0,0 +1,49 @@ +package rand + +import ( + "fmt" + "std" + "strings" + "testing" + + "gno.land/p/demo/rand" +) + +func TestInstance(t *testing.T) { + instance := rand.New() + if instance == nil { + t.Errorf("instance should not be nil") + } +} + +func TestIntn(t *testing.T) { + baseRand := rand.New() + baseResult := computeIntn(t, baseRand) + + sameHeightRand := rand.New() + sameHeightResult := computeIntn(t, sameHeightRand) + + if baseResult != sameHeightResult { + t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) + } + + std.TestSkipHeights(1) + differentHeightRand := rand.New() + differentHeightResult := computeIntn(t, differentHeightRand) + + if baseResult == differentHeightResult { + t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) + } +} + +func computeIntn(t *testing.T, r *rand.Instance) string { + t.Helper() + + arr := []string{} + for i := 0; i < 10; i++ { + arr = append(arr, fmt.Sprintf("%d", r.Intn(1000))) + } + + out := strings.Join(arr, ",") + return out +} diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index f2d3ddebadc..6276629cba2 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,8 @@ +// Draft + module gno.land/r/demo/art/gnoface -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/rand v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 9e85c5c7387..95493b52bf5 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -1,16 +1,16 @@ package gnoface import ( - "math/rand" "std" "strconv" "strings" + "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := std.GetHeight() path = strings.TrimSpace(path) if path != "" { @@ -18,7 +18,7 @@ func Render(path string) string { if err != nil { panic(err) } - seed = uint64(s) + seed = int64(s) } output := ufmt.Sprintf("Gnoface #%d\n", seed) @@ -26,7 +26,7 @@ func Render(path string) string { return output } -func Draw(seed uint64) string { +func Draw(seed int64) string { var ( hairs = []string{ " s", @@ -102,7 +102,7 @@ func Draw(seed uint64) string { } ) - r := rand.New(rand.NewPCG(seed, 0xdeadbeef)) + r := rand.FromSeed(seed) return pick(r, hairs) + "\n" + pick(r, headtop) + "\n" + @@ -117,8 +117,8 @@ func Draw(seed uint64) string { pick(r, headbottom) + "\n" } -func pick(r *rand.Rand, slice []string) string { - return slice[r.IntN(len(slice))] +func pick(r *rand.Instance, slice []string) string { + return slice[r.Intn(len(slice))] } // based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index e82bd819483..630cce85c55 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno @@ -8,21 +8,21 @@ import ( func TestDraw(t *testing.T) { cases := []struct { - seed uint64 + seed int64 expected string }{ { seed: 42, expected: ` ||||||| - ||||||||| + ////////\ | | - | . ~ | -)| v v |O + | ~ . | +)| X X |. | | - | L | + | C | | | - | ___ | + | __/ | | | \~~~~~~~/ `[1:], @@ -30,31 +30,31 @@ func TestDraw(t *testing.T) { { seed: 1337, expected: ` - ....... - ||||||||| + s + /|||||||\ | | - | . _ | -D| x X |O + | . * | +o| ~ ~ |. | | - | ~ | + | O | | | - | ~~~ | + | __/ | | | - \~~~~~~~/ + \_______/ `[1:], }, { seed: 123456789, expected: ` - ....... - ////////\ + s + /~~~~~~~\ | | - | ~ * | -|| x X |o + | ~ . | +<| ~ ~ |< | | | V | | | - | . | + | \_/ | | | \-------/ `[1:], @@ -80,14 +80,14 @@ func TestRender(t *testing.T) { path: "42", expected: "Gnoface #42\n```" + ` ||||||| - ||||||||| + ////////\ | | - | . ~ | -)| v v |O + | ~ . | +)| X X |. | | - | L | + | C | | | - | ___ | + | __/ | | | \~~~~~~~/ ` + "```\n", @@ -95,31 +95,31 @@ func TestRender(t *testing.T) { { path: "1337", expected: "Gnoface #1337\n```" + ` - ....... - ||||||||| + s + /|||||||\ | | - | . _ | -D| x X |O + | . * | +o| ~ ~ |. | | - | ~ | + | O | | | - | ~~~ | + | __/ | | | - \~~~~~~~/ + \_______/ ` + "```\n", }, { path: "123456789", expected: "Gnoface #123456789\n```" + ` - ....... - ////////\ + s + /~~~~~~~\ | | - | ~ * | -|| x X |o + | ~ . | +<| ~ ~ |< | | | V | | | - | . | + | \_/ | | | \-------/ ` + "```\n", diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index 7044f0f72b3..9804aecc7f1 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -2,4 +2,7 @@ module gno.land/r/x/manfred_outfmt -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/rand v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno index 01981024189..5468a65c06f 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -2,9 +2,9 @@ package outfmt import ( "encoding/json" - "math/rand" "strings" + "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) @@ -27,21 +27,22 @@ func (res *Result) String() string { return output } -var rSeed = rand.NewPCG(0, 0) +var rSeed int64 func genResult() Result { - r := rand.New(rSeed) - // init rand + r := rand.FromSeed(rSeed) + defer func() { rSeed = r.Seed() }() + res := Result{ Text: "Hello Gnomes!", - Number: r.IntN(1000), + Number: r.Intn(1000), } - length := r.IntN(8) + 2 + length := r.Intn(8) + 2 res.Numbers = make([]int, length) for i := 0; i < length; i++ { - res.Numbers[i] = r.IntN(100) + res.Numbers[i] = r.Intn(100) } return res diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index 69c07bbbf16..a60aeb384db 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -15,7 +15,7 @@ func TestRender(t *testing.T) { * [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -23,11 +23,11 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 222 -Numbers: 34 44 39 7 72 48 74 +Number: 957 +Numbers: 3 54 32 88 ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -35,11 +35,11 @@ Numbers: 34 44 39 7 72 48 74 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 898 -Numbers: 24 25 2 +Number: 141 +Numbers: 98 27 ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -47,18 +47,18 @@ Numbers: 24 25 2 { got := outfmt.Render("?fmt=json") - expected := `{"Number":746,"Text":"Hello Gnomes!","Numbers":[57,82,16,14,28,32]}` + expected := `{"Number":801,"Text":"Hello Gnomes!","Numbers":[5,78,51,78,91,41]}` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":795,"Text":"Hello Gnomes!","Numbers":[29,51,88,61,93,21,2,66,79]})` + expected := `callback({"Number":63,"Text":"Hello Gnomes!","Numbers":[2,66,50,73,81]})` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } } diff --git a/gnovm/stdlibs/bytes/buffer_test.gno b/gnovm/stdlibs/bytes/buffer_test.gno index 601901955cd..a8837494224 100644 --- a/gnovm/stdlibs/bytes/buffer_test.gno +++ b/gnovm/stdlibs/bytes/buffer_test.gno @@ -216,14 +216,14 @@ func TestMixedReadsAndWrites(t *testing.T) { var buf bytes.Buffer s := "" for i := 0; i < 50; i++ { - wlen := rand.IntN(len(testString)) + wlen := rand.Intn(len(testString)) if i%2 == 0 { s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen]) } else { s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen]) } - rlen := rand.IntN(len(testString)) + rlen := rand.Intn(len(testString)) fub := make([]byte, rlen) n, _ := buf.Read(fub) s = s[n:] diff --git a/gnovm/stdlibs/bytes/bytes_test.gno b/gnovm/stdlibs/bytes/bytes_test.gno index 927f89c5559..c7762f2f67b 100644 --- a/gnovm/stdlibs/bytes/bytes_test.gno +++ b/gnovm/stdlibs/bytes/bytes_test.gno @@ -1707,7 +1707,7 @@ var makeFieldsInput = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space. for i := range x { - switch rand.IntN(10) { + switch rand.Intn(10) { case 0: x[i] = ' ' case 1: @@ -1729,7 +1729,7 @@ var makeFieldsInputASCII = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, rest ASCII non-space. for i := range x { - if rand.IntN(10) == 0 { + if rand.Intn(10) == 0 { x[i] = ' ' } else { x[i] = 'x' @@ -1827,7 +1827,7 @@ func makeBenchInputHard() []byte { } x := make([]byte, 0, 1<<20) for { - i := rand.IntN(len(tokens)) + i := rand.Intn(len(tokens)) if len(x)+len(tokens[i]) >= 1<<20 { break } diff --git a/gnovm/stdlibs/math/rand/auto_test.gno b/gnovm/stdlibs/math/rand/auto_test.gno deleted file mode 100644 index 5945039ae18..00000000000 --- a/gnovm/stdlibs/math/rand/auto_test.gno +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 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 rand - -import ( - "testing" -) - -// This test is first, in its own file with an alphabetically early name, -// to try to make sure that it runs early. It has the best chance of -// detecting deterministic seeding if it's the first test that runs. - -func TestAuto(t *testing.T) { - // Pull out 10 int64s from the global source - // and then check that they don't appear in that - // order in the deterministic seeded result. - var out []int64 - for i := 0; i < 10; i++ { - out = append(out, Int64()) - } - - // Look for out in seeded output. - // Strictly speaking, we should look for them in order, - // but this is good enough and not significantly more - // likely to have a false positive. - r := New(NewPCG(1, 0)) - found := 0 - for i := 0; i < 1000; i++ { - x := r.Int64() - if x == out[found] { - found++ - if found == len(out) { - t.Fatalf("found unseeded output in Seed(1) output") - } - } - } -} diff --git a/gnovm/stdlibs/math/rand/pcg.gno b/gnovm/stdlibs/math/rand/pcg.gno deleted file mode 100644 index 77708d799e2..00000000000 --- a/gnovm/stdlibs/math/rand/pcg.gno +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2023 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 rand - -import ( - "errors" - "math/bits" -) - -// https://numpy.org/devdocs/reference/random/upgrading-pcg64.html -// https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005 - -// A PCG is a PCG generator with 128 bits of internal state. -// A zero PCG is equivalent to NewPCG(0, 0). -type PCG struct { - hi uint64 - lo uint64 -} - -// NewPCG returns a new PCG seeded with the given values. -func NewPCG(seed1, seed2 uint64) *PCG { - return &PCG{seed1, seed2} -} - -// Seed resets the PCG to behave the same way as NewPCG(seed1, seed2). -func (p *PCG) Seed(seed1, seed2 uint64) { - p.hi = seed1 - p.lo = seed2 -} - -// binary.bigEndian.Uint64, copied to avoid dependency -func beUint64(b []byte) uint64 { - _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 - return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | - uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 -} - -// binary.bigEndian.PutUint64, copied to avoid dependency -func bePutUint64(b []byte, v uint64) { - _ = b[7] // early bounds check to guarantee safety of writes below - b[0] = byte(v >> 56) - b[1] = byte(v >> 48) - b[2] = byte(v >> 40) - b[3] = byte(v >> 32) - b[4] = byte(v >> 24) - b[5] = byte(v >> 16) - b[6] = byte(v >> 8) - b[7] = byte(v) -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (p *PCG) MarshalBinary() ([]byte, error) { - b := make([]byte, 20) - copy(b, "pcg:") - bePutUint64(b[4:], p.hi) - bePutUint64(b[4+8:], p.lo) - return b, nil -} - -var errUnmarshalPCG = errors.New("invalid PCG encoding") - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. -func (p *PCG) UnmarshalBinary(data []byte) error { - if len(data) != 20 || string(data[:4]) != "pcg:" { - return errUnmarshalPCG - } - p.hi = beUint64(data[4:]) - p.lo = beUint64(data[4+8:]) - return nil -} - -func (p *PCG) next() (hi, lo uint64) { - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161 - // - // Numpy's PCG multiplies by the 64-bit value cheapMul - // instead of the 128-bit value used here and in the official PCG code. - // This does not seem worthwhile, at least for Go: not having any high - // bits in the multiplier reduces the effect of low bits on the highest bits, - // and it only saves 1 multiply out of 3. - // (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.) - const ( - mulHi = 2549297995355413924 - mulLo = 4865540595714422341 - incHi = 6364136223846793005 - incLo = 1442695040888963407 - ) - - // state = state * mul + inc - hi, lo = bits.Mul64(p.lo, mulLo) - hi += p.hi*mulLo + p.lo*mulHi - lo, c := bits.Add64(lo, incLo, 0) - hi, _ = bits.Add64(hi, incHi, c) - p.lo = lo - p.hi = hi - return hi, lo -} - -// Uint64 return a uniformly-distributed random uint64 value. -func (p *PCG) Uint64() uint64 { - hi, lo := p.next() - - // XSL-RR would be - // hi, lo := p.next() - // return bits.RotateLeft64(lo^hi, -int(hi>>58)) - // but Numpy uses DXSM and O'Neill suggests doing the same. - // See https://github.com/golang/go/issues/21835#issuecomment-739065688 - // and following comments. - - // DXSM "double xorshift multiply" - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015 - - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176 - const cheapMul = 0xda942042e4dd58b5 - hi ^= hi >> 32 - hi *= cheapMul - hi ^= hi >> 48 - hi *= (lo | 1) - return hi -} diff --git a/gnovm/stdlibs/math/rand/pcg_test.gno b/gnovm/stdlibs/math/rand/pcg_test.gno deleted file mode 100644 index 843506d7234..00000000000 --- a/gnovm/stdlibs/math/rand/pcg_test.gno +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 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 rand - -import ( - "testing" -) - -func BenchmarkPCG_DXSM(b *testing.B) { - var p PCG - var t uint64 - for n := b.N; n > 0; n-- { - t += p.Uint64() - } - Sink = t -} - -func TestPCGMarshal(t *testing.T) { - var p PCG - const ( - seed1 = 0x123456789abcdef0 - seed2 = 0xfedcba9876543210 - want = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10" - ) - p.Seed(seed1, seed2) - data, err := p.MarshalBinary() - if string(data) != want || err != nil { - t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want) - } - - q := PCG{} - if err := q.UnmarshalBinary([]byte(want)); err != nil { - t.Fatalf("UnmarshalBinary(): %v", err) - } - if q != p { - t.Fatalf("after round trip, q = %#x, but p = %#x", q, p) - } - - qu := q.Uint64() - pu := p.Uint64() - if qu != pu { - t.Errorf("after round trip, q.Uint64() = %#x, but p.Uint64() = %#x", qu, pu) - } -} - -func TestPCG(t *testing.T) { - p := NewPCG(1, 2) - want := []uint64{ - 0xc4f5a58656eef510, - 0x9dcec3ad077dec6c, - 0xc8d04605312f8088, - 0xcbedc0dcb63ac19a, - 0x3bf98798cae97950, - 0xa8c6d7f8d485abc, - 0x7ffa3780429cd279, - 0x730ad2626b1c2f8e, - 0x21ff2330f4a0ad99, - 0x2f0901a1947094b0, - 0xa9735a3cfbe36cef, - 0x71ddb0a01a12c84a, - 0xf0e53e77a78453bb, - 0x1f173e9663be1e9d, - 0x657651da3ac4115e, - 0xc8987376b65a157b, - 0xbb17008f5fca28e7, - 0x8232bd645f29ed22, - 0x12be8f07ad14c539, - 0x54908a48e8e4736e, - } - - for i, x := range want { - if u := p.Uint64(); u != x { - t.Errorf("PCG #%d = %#x, want %#x", i, u, x) - } - } -} diff --git a/gnovm/stdlibs/math/rand/rand.gno b/gnovm/stdlibs/math/rand/rand.gno deleted file mode 100644 index 250ea7c5a05..00000000000 --- a/gnovm/stdlibs/math/rand/rand.gno +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2009 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 rand implements pseudo-random number generators suitable for tasks -// such as simulation, but it should not be used for security-sensitive work. -// -// IMPORTANT NOTE: This does not equate to Go's math/rand; instead, it uses -// the implementation in math/rand/v2, which improves on its algorithm and -// usability. -// -// Random numbers are generated by a [Source], usually wrapped in a [Rand]. -// Both types should be used by a single goroutine at a time: sharing among -// multiple goroutines requires some kind of synchronization. -// -// Top-level functions, such as [Float64] and [Int], -// are safe for concurrent use by multiple goroutines. -// -// This package's outputs might be easily predictable regardless of how it's -// seeded. -package rand - -import ( - "math/bits" -) - -// A Source is a source of uniformly-distributed -// pseudo-random uint64 values in the range [0, 1<<64). -// -// A Source is not safe for concurrent use by multiple goroutines. -type Source interface { - Uint64() uint64 -} - -// A Rand is a source of random numbers. -type Rand struct { - src Source -} - -// New returns a new Rand that uses random values from src -// to generate other random values. -func New(src Source) *Rand { - return &Rand{src: src} -} - -// Int64 returns a non-negative pseudo-random 63-bit integer as an int64. -func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) } - -// Uint32 returns a pseudo-random 32-bit value as a uint32. -func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) } - -// Uint64 returns a pseudo-random 64-bit value as a uint64. -func (r *Rand) Uint64() uint64 { return r.src.Uint64() } - -// Int32 returns a non-negative pseudo-random 31-bit integer as an int32. -func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) } - -// Int returns a non-negative pseudo-random int. -func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) } - -// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) Int64N(n int64) int64 { - if n <= 0 { - panic("invalid argument to Int64N") - } - return int64(r.uint64n(uint64(n))) -} - -// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) Uint64N(n uint64) uint64 { - if n == 0 { - panic("invalid argument to Uint64N") - } - return r.uint64n(n) -} - -// uint64n is the no-bounds-checks version of Uint64N. -func (r *Rand) uint64n(n uint64) uint64 { - if is32bit && uint64(uint32(n)) == n { - return uint64(r.uint32n(uint32(n))) - } - if n&(n-1) == 0 { // n is power of two, can mask - return r.Uint64() & (n - 1) - } - - // Suppose we have a uint64 x uniform in the range [0,2⁶⁴) - // and want to reduce it to the range [0,n) preserving exact uniformity. - // We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by - // the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴. - // Since there are 2⁶⁴ possible inputs x and only n possible outputs, - // the output is necessarily biased if n does not divide 2⁶⁴. - // In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴). - // There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products - // in that range, depending on k. - // But suppose we reject the sample and try again when - // x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible - // outcomes out of the 2⁶⁴. - // Now there are exactly floor(2⁶⁴/n) possible ways to produce - // each output value k, so we've restored uniformity. - // To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n, - // so the direct implementation of this algorithm would be: - // - // hi, lo := bits.Mul64(r.Uint64(), n) - // thresh := -n % n - // for lo < thresh { - // hi, lo = bits.Mul64(r.Uint64(), n) - // } - // - // That still leaves an expensive 64-bit division that we would rather avoid. - // We know that thresh < n, and n is usually much less than 2⁶⁴, so we can - // avoid the last four lines unless lo < n. - // - // See also: - // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction - // https://lemire.me/blog/2016/06/30/fast-random-shuffling - hi, lo := bits.Mul64(r.Uint64(), n) - if lo < n { - thresh := -n % n - for lo < thresh { - hi, lo = bits.Mul64(r.Uint64(), n) - } - } - return hi -} - -// uint32n is an identical computation to uint64n -// but optimized for 32-bit systems. -func (r *Rand) uint32n(n uint32) uint32 { - if n&(n-1) == 0 { // n is power of two, can mask - return uint32(r.Uint64()) & (n - 1) - } - // On 64-bit systems we still use the uint64 code below because - // the probability of a random uint64 lo being < a uint32 n is near zero, - // meaning the unbiasing loop almost never runs. - // On 32-bit systems, here we need to implement that same logic in 32-bit math, - // both to preserve the exact output sequence observed on 64-bit machines - // and to preserve the optimization that the unbiasing loop almost never runs. - // - // We want to compute - // hi, lo := bits.Mul64(r.Uint64(), n) - // In terms of 32-bit halves, this is: - // x1:x0 := r.Uint64() - // 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n) - // Writing out the multiplication in terms of bits.Mul32 allows - // using direct hardware instructions and avoiding - // the computations involving these zeros. - x := r.Uint64() - lo1a, lo0 := bits.Mul32(uint32(x), n) - hi, lo1b := bits.Mul32(uint32(x>>32), n) - lo1, c := bits.Add32(lo1a, lo1b, 0) - hi += c - if lo1 == 0 && lo0 < uint32(n) { - n64 := uint64(n) - thresh := uint32(-n64 % n64) - for lo1 == 0 && lo0 < thresh { - x := r.Uint64() - lo1a, lo0 = bits.Mul32(uint32(x), n) - hi, lo1b = bits.Mul32(uint32(x>>32), n) - lo1, c = bits.Add32(lo1a, lo1b, 0) - hi += c - } - } - return hi -} - -// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) Int32N(n int32) int32 { - if n <= 0 { - panic("invalid argument to Int32N") - } - return int32(r.uint64n(uint64(n))) -} - -// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) Uint32N(n uint32) uint32 { - if n == 0 { - panic("invalid argument to Uint32N") - } - return uint32(r.uint64n(uint64(n))) -} - -const is32bit = ^uint(0)>>32 == 0 - -// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) IntN(n int) int { - if n <= 0 { - panic("invalid argument to IntN") - } - return int(r.uint64n(uint64(n))) -} - -// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) UintN(n uint) uint { - if n == 0 { - panic("invalid argument to UintN") - } - return uint(r.uint64n(uint64(n))) -} - -// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). -func (r *Rand) Float64() float64 { - // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53). - return float64(r.Uint64()<<11>>11) / (1 << 53) -} - -// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). -func (r *Rand) Float32() float32 { - // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24). - return float32(r.Uint32()<<8>>8) / (1 << 24) -} - -// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers -// in the half-open interval [0,n). -func (r *Rand) Perm(n int) []int { - p := make([]int, n) - for i := range p { - p[i] = i - } - r.Shuffle(len(p), func(i, j int) { p[i], p[j] = p[j], p[i] }) - return p -} - -// Shuffle pseudo-randomizes the order of elements. -// n is the number of elements. Shuffle panics if n < 0. -// swap swaps the elements with indexes i and j. -func (r *Rand) Shuffle(n int, swap func(i, j int)) { - if n < 0 { - panic("invalid argument to Shuffle") - } - - // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - // Shuffle really ought not be called with n that doesn't fit in 32 bits. - // Not only will it take a very long time, but with 2³¹! possible permutations, - // there's no way that any PRNG can have a big enough internal state to - // generate even a minuscule percentage of the possible permutations. - // Nevertheless, the right API signature accepts an int n, so handle it as best we can. - for i := n - 1; i > 0; i-- { - j := int(r.uint64n(uint64(i + 1))) - swap(i, j) - } -} - -/* - * Top-level convenience functions - */ - -// globalRand is the source of random numbers for the top-level -// convenience functions. -var globalRand = &Rand{src: &PCG{}} - -// Int64 returns a non-negative pseudo-random 63-bit integer as an int64 -// from the default Source. -func Int64() int64 { return globalRand.Int64() } - -// Uint32 returns a pseudo-random 32-bit value as a uint32 -// from the default Source. -func Uint32() uint32 { return globalRand.Uint32() } - -// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) } - -// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) } - -// Uint64 returns a pseudo-random 64-bit value as a uint64 -// from the default Source. -func Uint64() uint64 { return globalRand.Uint64() } - -// Int32 returns a non-negative pseudo-random 31-bit integer as an int32 -// from the default Source. -func Int32() int32 { return globalRand.Int32() } - -// Int returns a non-negative pseudo-random int from the default Source. -func Int() int { return globalRand.Int() } - -// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Int64N(n int64) int64 { return globalRand.Int64N(n) } - -// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Int32N(n int32) int32 { return globalRand.Int32N(n) } - -// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func IntN(n int) int { return globalRand.IntN(n) } - -// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func UintN(n uint) uint { return globalRand.UintN(n) } - -// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) -// from the default Source. -func Float64() float64 { return globalRand.Float64() } - -// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) -// from the default Source. -func Float32() float32 { return globalRand.Float32() } - -// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers -// in the half-open interval [0,n) from the default Source. -func Perm(n int) []int { return globalRand.Perm(n) } - -// Shuffle pseudo-randomizes the order of elements using the default Source. -// n is the number of elements. Shuffle panics if n < 0. -// swap swaps the elements with indexes i and j. -func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } - -// NormFloat64 returns a normally distributed float64 in the range -// [-math.MaxFloat64, +math.MaxFloat64] with -// standard normal distribution (mean = 0, stddev = 1) -// from the default Source. -// To produce a different normal distribution, callers can -// adjust the output using: -// -// sample = NormFloat64() * desiredStdDev + desiredMean -func NormFloat64() float64 { return globalRand.NormFloat64() } - -// ExpFloat64 returns an exponentially distributed float64 in the range -// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter -// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. -// To produce a distribution with a different rate parameter, -// callers can adjust the output using: -// -// sample = ExpFloat64() / desiredRateParameter -func ExpFloat64() float64 { return globalRand.ExpFloat64() } diff --git a/gnovm/stdlibs/math/rand/zipf.gno b/gnovm/stdlibs/math/rand/zipf.gno deleted file mode 100644 index f04c814eb75..00000000000 --- a/gnovm/stdlibs/math/rand/zipf.gno +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2009 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. - -// W.Hormann, G.Derflinger: -// "Rejection-Inversion to Generate Variates -// from Monotone Discrete Distributions" -// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz - -package rand - -import "math" - -// A Zipf generates Zipf distributed variates. -type Zipf struct { - r *Rand - imax float64 - v float64 - q float64 - s float64 - oneminusQ float64 - oneminusQinv float64 - hxm float64 - hx0minusHxm float64 -} - -func (z *Zipf) h(x float64) float64 { - return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv -} - -func (z *Zipf) hinv(x float64) float64 { - return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v -} - -// NewZipf returns a Zipf variate generator. -// The generator generates values k ∈ [0, imax] -// such that P(k) is proportional to (v + k) ** (-s). -// Requirements: s > 1 and v >= 1. -func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { - z := new(Zipf) - if s <= 1.0 || v < 1 { - return nil - } - z.r = r - z.imax = float64(imax) - z.v = v - z.q = s - z.oneminusQ = 1.0 - z.q - z.oneminusQinv = 1.0 / z.oneminusQ - z.hxm = z.h(z.imax + 0.5) - z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm - z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) - return z -} - -// Uint64 returns a value drawn from the Zipf distribution described -// by the Zipf object. -func (z *Zipf) Uint64() uint64 { - if z == nil { - panic("rand: nil Zipf") - } - k := 0.0 - - for { - r := z.r.Float64() // r on [0,1] - ur := z.hxm + r*z.hx0minusHxm - x := z.hinv(ur) - k = math.Floor(x + 0.5) - if k-x <= z.s { - break - } - if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { - break - } - } - return uint64(k) -} diff --git a/gnovm/stdlibs/sort/sort_test.gno b/gnovm/stdlibs/sort/sort_test.gno index dac6ab11f67..8416f0b77e1 100644 --- a/gnovm/stdlibs/sort/sort_test.gno +++ b/gnovm/stdlibs/sort/sort_test.gno @@ -98,7 +98,7 @@ func TestSortLarge_Random(t *testing.T) { } data := make([]int, n) for i := 0; i < len(data); i++ { - data[i] = rand.IntN(100) + data[i] = rand.Intn(100) } if sort.IntsAreSorted(data) { t.Fatalf("terrible rand.rand") @@ -160,7 +160,7 @@ func TestNonDeterministicComparison(t *testing.T) { }() td := &nonDeterministicTestingData{ - r: rand.New(rand.NewPCG(0, 0)), + r: rand.New(rand.NewSource(0)), } for i := 0; i < 10; i++ { @@ -377,13 +377,13 @@ func testBentleyMcIlroy(t *testing.T, sortFn func(sort.Interface), maxswap func( case _Sawtooth: data[i] = i % m case _Rand: - data[i] = rand.IntN(m) + data[i] = rand.Intn(m) case _Stagger: data[i] = (i*m + i) % n case _Plateau: data[i] = min(i, m) case _Shuffle: - if rand.IntN(m) != 0 { + if rand.Intn(m) != 0 { j += 2 data[i] = j } else { @@ -587,7 +587,7 @@ func TestStability(t *testing.T) { // random distribution for i := 0; i < len(data); i++ { - data[i].a = rand.IntN(m) + data[i].a = rand.Intn(m) } if sort.IsSorted(data) { t.Fatalf("terrible rand.rand") @@ -643,7 +643,7 @@ func countOps(t *testing.T, algo func(sort.Interface), name string) { maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { - td.data[i] = rand.IntN(n / 5) + td.data[i] = rand.Intn(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) diff --git a/gnovm/tests/files/extern/p1/s2.gno b/gnovm/tests/files/extern/p1/s2.gno index 01aa3f47da1..cfed6498051 100644 --- a/gnovm/tests/files/extern/p1/s2.gno +++ b/gnovm/tests/files/extern/p1/s2.gno @@ -3,3 +3,5 @@ package p1 import "math/rand" var Uint32 = rand.Uint32 + +func init() { rand.Seed(1) } diff --git a/gnovm/tests/files/import4.gno b/gnovm/tests/files/import4.gno index 91364f45389..dc52adafc31 100644 --- a/gnovm/tests/files/import4.gno +++ b/gnovm/tests/files/import4.gno @@ -5,4 +5,4 @@ import "github.com/gnolang/gno/_test/p1" func main() { println("num:", p1.Uint32()) } // Output: -// num: 956301160 +// num: 2596996162 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 86c81be9a18..3dbd292ea68 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -23,7 +23,7 @@ import ( "log" "math" "math/big" - "math/rand/v2" + "math/rand" "net" "net/url" "os" @@ -114,6 +114,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || pkgPath == "math/big" || + pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || mode == ImportModeNativePreferred { switch pkgPath { @@ -270,10 +271,12 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri case "math/rand": // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) - // make native rand same as gno rand. - rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec - pkg.DefineGoNativeValue("IntN", rnd.IntN) - pkg.DefineGoNativeValue("Uint32", rnd.Uint32) + pkg.DefineGoNativeValue("Intn", rand.Intn) + pkg.DefineGoNativeValue("Uint32", rand.Uint32) + pkg.DefineGoNativeValue("Seed", rand.Seed) + pkg.DefineGoNativeValue("New", rand.New) + pkg.DefineGoNativeValue("NewSource", rand.NewSource) + pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) return pkg, pkg.NewPackage() case "crypto/rand": pkg := gno.NewPackageNode("rand", pkgPath, nil) diff --git a/go.mod b/go.mod index d2b4a5f9825..30a7f4dd84c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 12297e3c6ca..0236341ccc3 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,6 +1,6 @@ module loop -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index e0cb385020e..25d9a218cf7 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/misc/devdeps -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index be771c9a952..b92418db3dd 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,6 +1,6 @@ module linter -go 1.22 +go 1.21.6 toolchain go1.22.4 diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 91d0d6cdf34..4bb42f103e4 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,6 +1,6 @@ module loop -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md index bf58440e85e..d55a4cad305 100644 --- a/tm2/pkg/libtm/README.md +++ b/tm2/pkg/libtm/README.md @@ -45,7 +45,7 @@ To get up and running with the `libtm` package, you can add it to your project u go get -u github.com/gnolang/libtm ``` -Currently, the minimum required go version is `go 1.22`. +Currently, the minimum required go version is `go 1.21`. ## Usage Examples diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod index e903dd664e6..a8f80c76e06 100644 --- a/tm2/pkg/libtm/go.mod +++ b/tm2/pkg/libtm/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/libtm -go 1.22 +go 1.21 toolchain go1.22.4 From fadcbe100153fc6679cfbaf05549819ee6449a12 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:56:06 +0200 Subject: [PATCH 2/8] chore: reapply f547d7dcac11de7937664413497fed35eb1a03ff partially Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/docs-linter.yml | 4 +- .github/workflows/examples.yml | 5 +- CONTRIBUTING.md | 2 +- contribs/gnodev/go.mod | 2 +- contribs/gnofaucet/go.mod | 2 +- contribs/gnokeykc/go.mod | 2 +- contribs/gnomd/go.mod | 2 +- .../local-setup/installation.md | 16 +- .../validators/connect-to-existing-chain.md | 4 +- .../validators/setting-up-a-new-chain.md | 4 +- docs/reference/go-gno-compatibility.md | 5 +- examples/gno.land/r/demo/art/gnoface/gno.mod | 7 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 14 +- .../r/demo/art/gnoface/gnoface_test.gno | 70 ++-- examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 +- .../gno.land/r/x/manfred_outfmt/outfmt.gno | 15 +- .../r/x/manfred_outfmt/outfmt_test.gno | 22 +- gnovm/stdlibs/bytes/buffer_test.gno | 4 +- gnovm/stdlibs/bytes/bytes_test.gno | 6 +- gnovm/stdlibs/math/rand/auto_test.gno | 39 ++ gnovm/stdlibs/math/rand/pcg.gno | 121 +++++++ gnovm/stdlibs/math/rand/pcg_test.gno | 78 ++++ gnovm/stdlibs/math/rand/rand.gno | 340 ++++++++++++++++++ gnovm/stdlibs/math/rand/zipf.gno | 77 ++++ gnovm/stdlibs/sort/sort_test.gno | 12 +- gnovm/tests/files/extern/p1/s2.gno | 2 - gnovm/tests/files/import4.gno | 2 +- gnovm/tests/imports.go | 13 +- go.mod | 2 +- misc/autocounterd/go.mod | 2 +- misc/devdeps/go.mod | 2 +- misc/docs-linter/go.mod | 2 +- misc/loop/go.mod | 2 +- tm2/pkg/libtm/README.md | 2 +- tm2/pkg/libtm/go.mod | 2 +- 35 files changed, 764 insertions(+), 125 deletions(-) create mode 100644 gnovm/stdlibs/math/rand/auto_test.gno create mode 100644 gnovm/stdlibs/math/rand/pcg.gno create mode 100644 gnovm/stdlibs/math/rand/pcg_test.gno create mode 100644 gnovm/stdlibs/math/rand/rand.gno create mode 100644 gnovm/stdlibs/math/rand/zipf.gno diff --git a/.github/workflows/docs-linter.yml b/.github/workflows/docs-linter.yml index 0ffa67dfe95..e56c1a663ea 100644 --- a/.github/workflows/docs-linter.yml +++ b/.github/workflows/docs-linter.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.21' + go-version: '1.22' - name: Install dependencies run: go mod download @@ -28,4 +28,4 @@ jobs: run: make -C docs/ build - name: Run linter - run: make -C docs/ lint \ No newline at end of file + run: make -C docs/ lint diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index cea52e74cd4..d11710344b1 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,7 +20,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" runs-on: ubuntu-latest timeout-minutes: 30 @@ -36,7 +35,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -60,7 +58,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -108,4 +105,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e041ab18875..bc125a6da73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.21+) +- Go (version 1.22+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index ec82f09e467..6d2c7a34293 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index dc5267fff2b..e4b63c7a9e9 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index d9e785f5226..711cafed241 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 39043bae144..8bc352d4848 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnomd -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index 8700ff9a2b2..a3658fa6ab3 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -5,15 +5,15 @@ id: installation # Installation ## Overview -In this tutorial, you will learn how to set up the Gno development environment -locally, so you can get up and running writing Gno code. You will download and +In this tutorial, you will learn how to set up the Gno development environment +locally, so you can get up and running writing Gno code. You will download and install all the necessary tooling, and validate that it is correctly configured to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: @@ -30,7 +30,7 @@ GitHub repository somewhere on disk: git clone https://github.com/gnolang/gno.git ``` -## 2. Installing the required tools +## 2. Installing the required tools There are three tools that should be used for getting started with Gno development: - `gno` - the GnoVM binary @@ -42,7 +42,7 @@ To install all three tools, simply run the following in the root of the repo: make install ``` -## 3. Verifying installation +## 3. Verifying installation ### `gno` `gno` provides ample functionality to the user, among which is running, @@ -59,7 +59,7 @@ You should get the help output from the command: ![gno help](../../assets/getting-started/local-setup/local-setup/gno-help.gif) -Alternatively, if you don't want to have the binary callable system-wide, you +Alternatively, if you don't want to have the binary callable system-wide, you can run the binary directly: ```bash @@ -68,8 +68,8 @@ 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 +`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 (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/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md index a1a337a5a48..f1acf06049f 100644 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -12,7 +12,7 @@ In this tutorial, you will learn how to start a local Gno node and connect to an - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -107,4 +107,4 @@ gnoland start \ That's it! 🎉 -Your new Gno node should be up and running, and syncing block data from the remote chain. \ No newline at end of file +Your new Gno node should be up and running, and syncing block data from the remote chain. 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 1927679db8f..8f94037dc1d 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -13,7 +13,7 @@ Additionally, you will see the different options you can use to make your Gno in - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -451,4 +451,4 @@ Genesis block generation happens only once during the lifetime of a Gno chain. This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from block 0), the specified balance sheet will not be applied. -::: \ No newline at end of file +::: diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 89ad4f7b990..a2f83f2bbc6 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -34,7 +34,7 @@ id: go-gno-compatibility Generics are currently not implemented. -Note that Gno does not support shadowing of built-in types. +Note that Gno does not support shadowing of built-in types. While the following built-in typecasting assignment would work in Go, this is not supported in Gno. ```go @@ -205,7 +205,7 @@ Legend: | math/big | `tbd` | | math/bits | `full` | | math/cmplx | `tbd` | -| math/rand | `todo` | +| math/rand | `full`[^9] | | mime | `tbd` | | mime/multipart | `tbd` | | mime/quotedprintable | `tbd` | @@ -291,6 +291,7 @@ Legend: determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. [^8]: `crypto/ed25519` is currently only implemented for `Verify`, which should still cover a majority of use cases. A full implementation is welcome. +[^9]: `math/rand` in Gno ports over Go's `math/rand/v2`. ## Tooling (`gno` binary) diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index 6276629cba2..f2d3ddebadc 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,8 +1,3 @@ -// Draft - module gno.land/r/demo/art/gnoface -require ( - gno.land/p/demo/rand 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/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 95493b52bf5..9e85c5c7387 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -1,16 +1,16 @@ package gnoface import ( + "math/rand" "std" "strconv" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := std.GetHeight() + seed := uint64(std.GetHeight()) path = strings.TrimSpace(path) if path != "" { @@ -18,7 +18,7 @@ func Render(path string) string { if err != nil { panic(err) } - seed = int64(s) + seed = uint64(s) } output := ufmt.Sprintf("Gnoface #%d\n", seed) @@ -26,7 +26,7 @@ func Render(path string) string { return output } -func Draw(seed int64) string { +func Draw(seed uint64) string { var ( hairs = []string{ " s", @@ -102,7 +102,7 @@ func Draw(seed int64) string { } ) - r := rand.FromSeed(seed) + r := rand.New(rand.NewPCG(seed, 0xdeadbeef)) return pick(r, hairs) + "\n" + pick(r, headtop) + "\n" + @@ -117,8 +117,8 @@ func Draw(seed int64) string { pick(r, headbottom) + "\n" } -func pick(r *rand.Instance, slice []string) string { - return slice[r.Intn(len(slice))] +func pick(r *rand.Rand, slice []string) string { + return slice[r.IntN(len(slice))] } // based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index 630cce85c55..e82bd819483 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno @@ -8,21 +8,21 @@ import ( func TestDraw(t *testing.T) { cases := []struct { - seed int64 + seed uint64 expected string }{ { seed: 42, expected: ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ `[1:], @@ -30,31 +30,31 @@ func TestDraw(t *testing.T) { { seed: 1337, expected: ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ `[1:], }, { seed: 123456789, expected: ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ `[1:], @@ -80,14 +80,14 @@ func TestRender(t *testing.T) { path: "42", expected: "Gnoface #42\n```" + ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ ` + "```\n", @@ -95,31 +95,31 @@ func TestRender(t *testing.T) { { path: "1337", expected: "Gnoface #1337\n```" + ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ ` + "```\n", }, { path: "123456789", expected: "Gnoface #123456789\n```" + ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ ` + "```\n", diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index 9804aecc7f1..7044f0f72b3 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -2,7 +2,4 @@ module gno.land/r/x/manfred_outfmt -require ( - gno.land/p/demo/rand 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/r/x/manfred_outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno index 5468a65c06f..01981024189 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -2,9 +2,9 @@ package outfmt import ( "encoding/json" + "math/rand" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) @@ -27,22 +27,21 @@ func (res *Result) String() string { return output } -var rSeed int64 +var rSeed = rand.NewPCG(0, 0) func genResult() Result { - // init rand - r := rand.FromSeed(rSeed) - defer func() { rSeed = r.Seed() }() + r := rand.New(rSeed) + // init rand res := Result{ Text: "Hello Gnomes!", - Number: r.Intn(1000), + Number: r.IntN(1000), } - length := r.Intn(8) + 2 + length := r.IntN(8) + 2 res.Numbers = make([]int, length) for i := 0; i < length; i++ { - res.Numbers[i] = r.Intn(100) + res.Numbers[i] = r.IntN(100) } return res diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index a60aeb384db..69c07bbbf16 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -15,7 +15,7 @@ func TestRender(t *testing.T) { * [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -23,11 +23,11 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 957 -Numbers: 3 54 32 88 +Number: 222 +Numbers: 34 44 39 7 72 48 74 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -35,11 +35,11 @@ Numbers: 3 54 32 88 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 141 -Numbers: 98 27 +Number: 898 +Numbers: 24 25 2 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -47,18 +47,18 @@ Numbers: 98 27 { got := outfmt.Render("?fmt=json") - expected := `{"Number":801,"Text":"Hello Gnomes!","Numbers":[5,78,51,78,91,41]}` + expected := `{"Number":746,"Text":"Hello Gnomes!","Numbers":[57,82,16,14,28,32]}` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":63,"Text":"Hello Gnomes!","Numbers":[2,66,50,73,81]})` + expected := `callback({"Number":795,"Text":"Hello Gnomes!","Numbers":[29,51,88,61,93,21,2,66,79]})` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } } diff --git a/gnovm/stdlibs/bytes/buffer_test.gno b/gnovm/stdlibs/bytes/buffer_test.gno index a8837494224..601901955cd 100644 --- a/gnovm/stdlibs/bytes/buffer_test.gno +++ b/gnovm/stdlibs/bytes/buffer_test.gno @@ -216,14 +216,14 @@ func TestMixedReadsAndWrites(t *testing.T) { var buf bytes.Buffer s := "" for i := 0; i < 50; i++ { - wlen := rand.Intn(len(testString)) + wlen := rand.IntN(len(testString)) if i%2 == 0 { s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen]) } else { s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen]) } - rlen := rand.Intn(len(testString)) + rlen := rand.IntN(len(testString)) fub := make([]byte, rlen) n, _ := buf.Read(fub) s = s[n:] diff --git a/gnovm/stdlibs/bytes/bytes_test.gno b/gnovm/stdlibs/bytes/bytes_test.gno index c7762f2f67b..927f89c5559 100644 --- a/gnovm/stdlibs/bytes/bytes_test.gno +++ b/gnovm/stdlibs/bytes/bytes_test.gno @@ -1707,7 +1707,7 @@ var makeFieldsInput = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space. for i := range x { - switch rand.Intn(10) { + switch rand.IntN(10) { case 0: x[i] = ' ' case 1: @@ -1729,7 +1729,7 @@ var makeFieldsInputASCII = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, rest ASCII non-space. for i := range x { - if rand.Intn(10) == 0 { + if rand.IntN(10) == 0 { x[i] = ' ' } else { x[i] = 'x' @@ -1827,7 +1827,7 @@ func makeBenchInputHard() []byte { } x := make([]byte, 0, 1<<20) for { - i := rand.Intn(len(tokens)) + i := rand.IntN(len(tokens)) if len(x)+len(tokens[i]) >= 1<<20 { break } diff --git a/gnovm/stdlibs/math/rand/auto_test.gno b/gnovm/stdlibs/math/rand/auto_test.gno new file mode 100644 index 00000000000..5945039ae18 --- /dev/null +++ b/gnovm/stdlibs/math/rand/auto_test.gno @@ -0,0 +1,39 @@ +// Copyright 2022 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 rand + +import ( + "testing" +) + +// This test is first, in its own file with an alphabetically early name, +// to try to make sure that it runs early. It has the best chance of +// detecting deterministic seeding if it's the first test that runs. + +func TestAuto(t *testing.T) { + // Pull out 10 int64s from the global source + // and then check that they don't appear in that + // order in the deterministic seeded result. + var out []int64 + for i := 0; i < 10; i++ { + out = append(out, Int64()) + } + + // Look for out in seeded output. + // Strictly speaking, we should look for them in order, + // but this is good enough and not significantly more + // likely to have a false positive. + r := New(NewPCG(1, 0)) + found := 0 + for i := 0; i < 1000; i++ { + x := r.Int64() + if x == out[found] { + found++ + if found == len(out) { + t.Fatalf("found unseeded output in Seed(1) output") + } + } + } +} diff --git a/gnovm/stdlibs/math/rand/pcg.gno b/gnovm/stdlibs/math/rand/pcg.gno new file mode 100644 index 00000000000..77708d799e2 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg.gno @@ -0,0 +1,121 @@ +// Copyright 2023 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 rand + +import ( + "errors" + "math/bits" +) + +// https://numpy.org/devdocs/reference/random/upgrading-pcg64.html +// https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005 + +// A PCG is a PCG generator with 128 bits of internal state. +// A zero PCG is equivalent to NewPCG(0, 0). +type PCG struct { + hi uint64 + lo uint64 +} + +// NewPCG returns a new PCG seeded with the given values. +func NewPCG(seed1, seed2 uint64) *PCG { + return &PCG{seed1, seed2} +} + +// Seed resets the PCG to behave the same way as NewPCG(seed1, seed2). +func (p *PCG) Seed(seed1, seed2 uint64) { + p.hi = seed1 + p.lo = seed2 +} + +// binary.bigEndian.Uint64, copied to avoid dependency +func beUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +// binary.bigEndian.PutUint64, copied to avoid dependency +func bePutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (p *PCG) MarshalBinary() ([]byte, error) { + b := make([]byte, 20) + copy(b, "pcg:") + bePutUint64(b[4:], p.hi) + bePutUint64(b[4+8:], p.lo) + return b, nil +} + +var errUnmarshalPCG = errors.New("invalid PCG encoding") + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (p *PCG) UnmarshalBinary(data []byte) error { + if len(data) != 20 || string(data[:4]) != "pcg:" { + return errUnmarshalPCG + } + p.hi = beUint64(data[4:]) + p.lo = beUint64(data[4+8:]) + return nil +} + +func (p *PCG) next() (hi, lo uint64) { + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161 + // + // Numpy's PCG multiplies by the 64-bit value cheapMul + // instead of the 128-bit value used here and in the official PCG code. + // This does not seem worthwhile, at least for Go: not having any high + // bits in the multiplier reduces the effect of low bits on the highest bits, + // and it only saves 1 multiply out of 3. + // (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.) + const ( + mulHi = 2549297995355413924 + mulLo = 4865540595714422341 + incHi = 6364136223846793005 + incLo = 1442695040888963407 + ) + + // state = state * mul + inc + hi, lo = bits.Mul64(p.lo, mulLo) + hi += p.hi*mulLo + p.lo*mulHi + lo, c := bits.Add64(lo, incLo, 0) + hi, _ = bits.Add64(hi, incHi, c) + p.lo = lo + p.hi = hi + return hi, lo +} + +// Uint64 return a uniformly-distributed random uint64 value. +func (p *PCG) Uint64() uint64 { + hi, lo := p.next() + + // XSL-RR would be + // hi, lo := p.next() + // return bits.RotateLeft64(lo^hi, -int(hi>>58)) + // but Numpy uses DXSM and O'Neill suggests doing the same. + // See https://github.com/golang/go/issues/21835#issuecomment-739065688 + // and following comments. + + // DXSM "double xorshift multiply" + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015 + + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176 + const cheapMul = 0xda942042e4dd58b5 + hi ^= hi >> 32 + hi *= cheapMul + hi ^= hi >> 48 + hi *= (lo | 1) + return hi +} diff --git a/gnovm/stdlibs/math/rand/pcg_test.gno b/gnovm/stdlibs/math/rand/pcg_test.gno new file mode 100644 index 00000000000..843506d7234 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg_test.gno @@ -0,0 +1,78 @@ +// Copyright 2023 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 rand + +import ( + "testing" +) + +func BenchmarkPCG_DXSM(b *testing.B) { + var p PCG + var t uint64 + for n := b.N; n > 0; n-- { + t += p.Uint64() + } + Sink = t +} + +func TestPCGMarshal(t *testing.T) { + var p PCG + const ( + seed1 = 0x123456789abcdef0 + seed2 = 0xfedcba9876543210 + want = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10" + ) + p.Seed(seed1, seed2) + data, err := p.MarshalBinary() + if string(data) != want || err != nil { + t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want) + } + + q := PCG{} + if err := q.UnmarshalBinary([]byte(want)); err != nil { + t.Fatalf("UnmarshalBinary(): %v", err) + } + if q != p { + t.Fatalf("after round trip, q = %#x, but p = %#x", q, p) + } + + qu := q.Uint64() + pu := p.Uint64() + if qu != pu { + t.Errorf("after round trip, q.Uint64() = %#x, but p.Uint64() = %#x", qu, pu) + } +} + +func TestPCG(t *testing.T) { + p := NewPCG(1, 2) + want := []uint64{ + 0xc4f5a58656eef510, + 0x9dcec3ad077dec6c, + 0xc8d04605312f8088, + 0xcbedc0dcb63ac19a, + 0x3bf98798cae97950, + 0xa8c6d7f8d485abc, + 0x7ffa3780429cd279, + 0x730ad2626b1c2f8e, + 0x21ff2330f4a0ad99, + 0x2f0901a1947094b0, + 0xa9735a3cfbe36cef, + 0x71ddb0a01a12c84a, + 0xf0e53e77a78453bb, + 0x1f173e9663be1e9d, + 0x657651da3ac4115e, + 0xc8987376b65a157b, + 0xbb17008f5fca28e7, + 0x8232bd645f29ed22, + 0x12be8f07ad14c539, + 0x54908a48e8e4736e, + } + + for i, x := range want { + if u := p.Uint64(); u != x { + t.Errorf("PCG #%d = %#x, want %#x", i, u, x) + } + } +} diff --git a/gnovm/stdlibs/math/rand/rand.gno b/gnovm/stdlibs/math/rand/rand.gno new file mode 100644 index 00000000000..250ea7c5a05 --- /dev/null +++ b/gnovm/stdlibs/math/rand/rand.gno @@ -0,0 +1,340 @@ +// Copyright 2009 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 rand implements pseudo-random number generators suitable for tasks +// such as simulation, but it should not be used for security-sensitive work. +// +// IMPORTANT NOTE: This does not equate to Go's math/rand; instead, it uses +// the implementation in math/rand/v2, which improves on its algorithm and +// usability. +// +// Random numbers are generated by a [Source], usually wrapped in a [Rand]. +// Both types should be used by a single goroutine at a time: sharing among +// multiple goroutines requires some kind of synchronization. +// +// Top-level functions, such as [Float64] and [Int], +// are safe for concurrent use by multiple goroutines. +// +// This package's outputs might be easily predictable regardless of how it's +// seeded. +package rand + +import ( + "math/bits" +) + +// A Source is a source of uniformly-distributed +// pseudo-random uint64 values in the range [0, 1<<64). +// +// A Source is not safe for concurrent use by multiple goroutines. +type Source interface { + Uint64() uint64 +} + +// A Rand is a source of random numbers. +type Rand struct { + src Source +} + +// New returns a new Rand that uses random values from src +// to generate other random values. +func New(src Source) *Rand { + return &Rand{src: src} +} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64. +func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) } + +// Uint32 returns a pseudo-random 32-bit value as a uint32. +func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64. +func (r *Rand) Uint64() uint64 { return r.src.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32. +func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) } + +// Int returns a non-negative pseudo-random int. +func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) } + +// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int64N(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int64N") + } + return int64(r.uint64n(uint64(n))) +} + +// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint64N(n uint64) uint64 { + if n == 0 { + panic("invalid argument to Uint64N") + } + return r.uint64n(n) +} + +// uint64n is the no-bounds-checks version of Uint64N. +func (r *Rand) uint64n(n uint64) uint64 { + if is32bit && uint64(uint32(n)) == n { + return uint64(r.uint32n(uint32(n))) + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Uint64() & (n - 1) + } + + // Suppose we have a uint64 x uniform in the range [0,2⁶⁴) + // and want to reduce it to the range [0,n) preserving exact uniformity. + // We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by + // the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴. + // Since there are 2⁶⁴ possible inputs x and only n possible outputs, + // the output is necessarily biased if n does not divide 2⁶⁴. + // In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴). + // There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products + // in that range, depending on k. + // But suppose we reject the sample and try again when + // x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible + // outcomes out of the 2⁶⁴. + // Now there are exactly floor(2⁶⁴/n) possible ways to produce + // each output value k, so we've restored uniformity. + // To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n, + // so the direct implementation of this algorithm would be: + // + // hi, lo := bits.Mul64(r.Uint64(), n) + // thresh := -n % n + // for lo < thresh { + // hi, lo = bits.Mul64(r.Uint64(), n) + // } + // + // That still leaves an expensive 64-bit division that we would rather avoid. + // We know that thresh < n, and n is usually much less than 2⁶⁴, so we can + // avoid the last four lines unless lo < n. + // + // See also: + // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction + // https://lemire.me/blog/2016/06/30/fast-random-shuffling + hi, lo := bits.Mul64(r.Uint64(), n) + if lo < n { + thresh := -n % n + for lo < thresh { + hi, lo = bits.Mul64(r.Uint64(), n) + } + } + return hi +} + +// uint32n is an identical computation to uint64n +// but optimized for 32-bit systems. +func (r *Rand) uint32n(n uint32) uint32 { + if n&(n-1) == 0 { // n is power of two, can mask + return uint32(r.Uint64()) & (n - 1) + } + // On 64-bit systems we still use the uint64 code below because + // the probability of a random uint64 lo being < a uint32 n is near zero, + // meaning the unbiasing loop almost never runs. + // On 32-bit systems, here we need to implement that same logic in 32-bit math, + // both to preserve the exact output sequence observed on 64-bit machines + // and to preserve the optimization that the unbiasing loop almost never runs. + // + // We want to compute + // hi, lo := bits.Mul64(r.Uint64(), n) + // In terms of 32-bit halves, this is: + // x1:x0 := r.Uint64() + // 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n) + // Writing out the multiplication in terms of bits.Mul32 allows + // using direct hardware instructions and avoiding + // the computations involving these zeros. + x := r.Uint64() + lo1a, lo0 := bits.Mul32(uint32(x), n) + hi, lo1b := bits.Mul32(uint32(x>>32), n) + lo1, c := bits.Add32(lo1a, lo1b, 0) + hi += c + if lo1 == 0 && lo0 < uint32(n) { + n64 := uint64(n) + thresh := uint32(-n64 % n64) + for lo1 == 0 && lo0 < thresh { + x := r.Uint64() + lo1a, lo0 = bits.Mul32(uint32(x), n) + hi, lo1b = bits.Mul32(uint32(x>>32), n) + lo1, c = bits.Add32(lo1a, lo1b, 0) + hi += c + } + } + return hi +} + +// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int32N(n int32) int32 { + if n <= 0 { + panic("invalid argument to Int32N") + } + return int32(r.uint64n(uint64(n))) +} + +// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint32N(n uint32) uint32 { + if n == 0 { + panic("invalid argument to Uint32N") + } + return uint32(r.uint64n(uint64(n))) +} + +const is32bit = ^uint(0)>>32 == 0 + +// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) IntN(n int) int { + if n <= 0 { + panic("invalid argument to IntN") + } + return int(r.uint64n(uint64(n))) +} + +// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) UintN(n uint) uint { + if n == 0 { + panic("invalid argument to UintN") + } + return uint(r.uint64n(uint64(n))) +} + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float64() float64 { + // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53). + return float64(r.Uint64()<<11>>11) / (1 << 53) +} + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float32() float32 { + // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24). + return float32(r.Uint32()<<8>>8) / (1 << 24) +} + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n). +func (r *Rand) Perm(n int) []int { + p := make([]int, n) + for i := range p { + p[i] = i + } + r.Shuffle(len(p), func(i, j int) { p[i], p[j] = p[j], p[i] }) + return p +} + +// Shuffle pseudo-randomizes the order of elements. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func (r *Rand) Shuffle(n int, swap func(i, j int)) { + if n < 0 { + panic("invalid argument to Shuffle") + } + + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + // Shuffle really ought not be called with n that doesn't fit in 32 bits. + // Not only will it take a very long time, but with 2³¹! possible permutations, + // there's no way that any PRNG can have a big enough internal state to + // generate even a minuscule percentage of the possible permutations. + // Nevertheless, the right API signature accepts an int n, so handle it as best we can. + for i := n - 1; i > 0; i-- { + j := int(r.uint64n(uint64(i + 1))) + swap(i, j) + } +} + +/* + * Top-level convenience functions + */ + +// globalRand is the source of random numbers for the top-level +// convenience functions. +var globalRand = &Rand{src: &PCG{}} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64 +// from the default Source. +func Int64() int64 { return globalRand.Int64() } + +// Uint32 returns a pseudo-random 32-bit value as a uint32 +// from the default Source. +func Uint32() uint32 { return globalRand.Uint32() } + +// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) } + +// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64 +// from the default Source. +func Uint64() uint64 { return globalRand.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32 +// from the default Source. +func Int32() int32 { return globalRand.Int32() } + +// Int returns a non-negative pseudo-random int from the default Source. +func Int() int { return globalRand.Int() } + +// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int64N(n int64) int64 { return globalRand.Int64N(n) } + +// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int32N(n int32) int32 { return globalRand.Int32N(n) } + +// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func IntN(n int) int { return globalRand.IntN(n) } + +// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func UintN(n uint) uint { return globalRand.UintN(n) } + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float64() float64 { return globalRand.Float64() } + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float32() float32 { return globalRand.Float32() } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n) from the default Source. +func Perm(n int) []int { return globalRand.Perm(n) } + +// Shuffle pseudo-randomizes the order of elements using the default Source. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } + +// NormFloat64 returns a normally distributed float64 in the range +// [-math.MaxFloat64, +math.MaxFloat64] with +// standard normal distribution (mean = 0, stddev = 1) +// from the default Source. +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func NormFloat64() float64 { return globalRand.NormFloat64() } + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func ExpFloat64() float64 { return globalRand.ExpFloat64() } diff --git a/gnovm/stdlibs/math/rand/zipf.gno b/gnovm/stdlibs/math/rand/zipf.gno new file mode 100644 index 00000000000..f04c814eb75 --- /dev/null +++ b/gnovm/stdlibs/math/rand/zipf.gno @@ -0,0 +1,77 @@ +// Copyright 2009 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. + +// W.Hormann, G.Derflinger: +// "Rejection-Inversion to Generate Variates +// from Monotone Discrete Distributions" +// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz + +package rand + +import "math" + +// A Zipf generates Zipf distributed variates. +type Zipf struct { + r *Rand + imax float64 + v float64 + q float64 + s float64 + oneminusQ float64 + oneminusQinv float64 + hxm float64 + hx0minusHxm float64 +} + +func (z *Zipf) h(x float64) float64 { + return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv +} + +func (z *Zipf) hinv(x float64) float64 { + return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v +} + +// NewZipf returns a Zipf variate generator. +// The generator generates values k ∈ [0, imax] +// such that P(k) is proportional to (v + k) ** (-s). +// Requirements: s > 1 and v >= 1. +func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { + z := new(Zipf) + if s <= 1.0 || v < 1 { + return nil + } + z.r = r + z.imax = float64(imax) + z.v = v + z.q = s + z.oneminusQ = 1.0 - z.q + z.oneminusQinv = 1.0 / z.oneminusQ + z.hxm = z.h(z.imax + 0.5) + z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm + z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) + return z +} + +// Uint64 returns a value drawn from the Zipf distribution described +// by the Zipf object. +func (z *Zipf) Uint64() uint64 { + if z == nil { + panic("rand: nil Zipf") + } + k := 0.0 + + for { + r := z.r.Float64() // r on [0,1] + ur := z.hxm + r*z.hx0minusHxm + x := z.hinv(ur) + k = math.Floor(x + 0.5) + if k-x <= z.s { + break + } + if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { + break + } + } + return uint64(k) +} diff --git a/gnovm/stdlibs/sort/sort_test.gno b/gnovm/stdlibs/sort/sort_test.gno index 8416f0b77e1..dac6ab11f67 100644 --- a/gnovm/stdlibs/sort/sort_test.gno +++ b/gnovm/stdlibs/sort/sort_test.gno @@ -98,7 +98,7 @@ func TestSortLarge_Random(t *testing.T) { } data := make([]int, n) for i := 0; i < len(data); i++ { - data[i] = rand.Intn(100) + data[i] = rand.IntN(100) } if sort.IntsAreSorted(data) { t.Fatalf("terrible rand.rand") @@ -160,7 +160,7 @@ func TestNonDeterministicComparison(t *testing.T) { }() td := &nonDeterministicTestingData{ - r: rand.New(rand.NewSource(0)), + r: rand.New(rand.NewPCG(0, 0)), } for i := 0; i < 10; i++ { @@ -377,13 +377,13 @@ func testBentleyMcIlroy(t *testing.T, sortFn func(sort.Interface), maxswap func( case _Sawtooth: data[i] = i % m case _Rand: - data[i] = rand.Intn(m) + data[i] = rand.IntN(m) case _Stagger: data[i] = (i*m + i) % n case _Plateau: data[i] = min(i, m) case _Shuffle: - if rand.Intn(m) != 0 { + if rand.IntN(m) != 0 { j += 2 data[i] = j } else { @@ -587,7 +587,7 @@ func TestStability(t *testing.T) { // random distribution for i := 0; i < len(data); i++ { - data[i].a = rand.Intn(m) + data[i].a = rand.IntN(m) } if sort.IsSorted(data) { t.Fatalf("terrible rand.rand") @@ -643,7 +643,7 @@ func countOps(t *testing.T, algo func(sort.Interface), name string) { maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { - td.data[i] = rand.Intn(n / 5) + td.data[i] = rand.IntN(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) diff --git a/gnovm/tests/files/extern/p1/s2.gno b/gnovm/tests/files/extern/p1/s2.gno index cfed6498051..01aa3f47da1 100644 --- a/gnovm/tests/files/extern/p1/s2.gno +++ b/gnovm/tests/files/extern/p1/s2.gno @@ -3,5 +3,3 @@ package p1 import "math/rand" var Uint32 = rand.Uint32 - -func init() { rand.Seed(1) } diff --git a/gnovm/tests/files/import4.gno b/gnovm/tests/files/import4.gno index dc52adafc31..91364f45389 100644 --- a/gnovm/tests/files/import4.gno +++ b/gnovm/tests/files/import4.gno @@ -5,4 +5,4 @@ import "github.com/gnolang/gno/_test/p1" func main() { println("num:", p1.Uint32()) } // Output: -// num: 2596996162 +// num: 956301160 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 3dbd292ea68..86c81be9a18 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -23,7 +23,7 @@ import ( "log" "math" "math/big" - "math/rand" + "math/rand/v2" "net" "net/url" "os" @@ -114,7 +114,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || pkgPath == "math/big" || - pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || mode == ImportModeNativePreferred { switch pkgPath { @@ -271,12 +270,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri case "math/rand": // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Intn", rand.Intn) - pkg.DefineGoNativeValue("Uint32", rand.Uint32) - pkg.DefineGoNativeValue("Seed", rand.Seed) - pkg.DefineGoNativeValue("New", rand.New) - pkg.DefineGoNativeValue("NewSource", rand.NewSource) - pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) + // make native rand same as gno rand. + rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec + pkg.DefineGoNativeValue("IntN", rnd.IntN) + pkg.DefineGoNativeValue("Uint32", rnd.Uint32) return pkg, pkg.NewPackage() case "crypto/rand": pkg := gno.NewPackageNode("rand", pkgPath, nil) diff --git a/go.mod b/go.mod index 30a7f4dd84c..d2b4a5f9825 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 0236341ccc3..12297e3c6ca 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,6 +1,6 @@ module loop -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index 25d9a218cf7..e0cb385020e 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/misc/devdeps -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index b92418db3dd..be771c9a952 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,6 +1,6 @@ module linter -go 1.21.6 +go 1.22 toolchain go1.22.4 diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 4bb42f103e4..91d0d6cdf34 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,6 +1,6 @@ module loop -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md index d55a4cad305..bf58440e85e 100644 --- a/tm2/pkg/libtm/README.md +++ b/tm2/pkg/libtm/README.md @@ -45,7 +45,7 @@ To get up and running with the `libtm` package, you can add it to your project u go get -u github.com/gnolang/libtm ``` -Currently, the minimum required go version is `go 1.21`. +Currently, the minimum required go version is `go 1.22`. ## Usage Examples diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod index a8f80c76e06..e903dd664e6 100644 --- a/tm2/pkg/libtm/go.mod +++ b/tm2/pkg/libtm/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/libtm -go 1.21 +go 1.22 toolchain go1.22.4 From f356eab6e8c7bc578bf3748dd23c7a25bf72fd8e Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:57:40 +0200 Subject: [PATCH 3/8] feat: add r/demo/users.ClaimDefaultName Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/demo/users/users.gno | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 5b4b8ec2c14..cd02434e239 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -26,14 +26,15 @@ var ( //---------------------------------------- // Top-level functions +func ClaimDefaultName(profile string) { + std.AssertOriginCall() + caller := std.PrevRealm().Addr() +} + func Register(inviter std.Address, name string, profile string) { // assert CallTx call. std.AssertOriginCall() - // assert invited or paid. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). - } + caller := std.PrevRealm().Addr() sentCoins := std.GetOrigSend() minCoin := std.NewCoin("ugnot", minFee) if inviter == "" { From ecb5522bf7323f5cd5db7a941d55a3012020ed23 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 21:24:59 +0200 Subject: [PATCH 4/8] feat: add p/demo/rand as p/demo/entropy Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/entropy/entropy.gno | 85 +++++++++++ .../entropy_test.gno} | 29 ++-- examples/gno.land/p/demo/entropy/gno.mod | 1 + .../gno.land/p/demo/entropy/z_filetest.gno | 56 +++++++ examples/gno.land/p/demo/rand/gno.mod | 3 - examples/gno.land/p/demo/rand/rand.gno | 139 ------------------ .../gno.land/p/demo/rand/rand0_filetest.gno | 56 ------- examples/gno.land/r/demo/art/gnoface/gno.mod | 5 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 3 +- 9 files changed, 162 insertions(+), 215 deletions(-) create mode 100644 examples/gno.land/p/demo/entropy/entropy.gno rename examples/gno.land/p/demo/{rand/rand_test.gno => entropy/entropy_test.gno} (50%) create mode 100644 examples/gno.land/p/demo/entropy/gno.mod create mode 100644 examples/gno.land/p/demo/entropy/z_filetest.gno delete mode 100644 examples/gno.land/p/demo/rand/gno.mod delete mode 100644 examples/gno.land/p/demo/rand/rand.gno delete mode 100644 examples/gno.land/p/demo/rand/rand0_filetest.gno diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno new file mode 100644 index 00000000000..1dd827940d1 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -0,0 +1,85 @@ +// Entropy generates fully deterministic, cost-effective, and hard to guess +// numbers. +// +// It is designed both for for single-usage, like seeding math/rand or for being +// reused which increases the entropy and its cost effectiveness. +// +// Disclaimer: this package is unsafe and won't prevent others to guess values +// in advance. +// +// It uses the Bernstein's hash djb2 to be CPU-cycle efficient. +package entropy + +import ( + "std" + "time" +) + +type Instance struct { + value int64 +} + +func New() *Instance { + r := Instance{value: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed int64) *Instance { + r := Instance{value: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() int64 { + return i.value +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Int64(int64(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Int64(input int64) { + i.value = (i.value << 5) + i.value + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + i.djb2Int64(height) + } + + // time + { + secs := time.Now().Second() + i.djb2Int64(int64(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Int64(int64(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Value() int64 { + i.addEntropy() + return i.value +} diff --git a/examples/gno.land/p/demo/rand/rand_test.gno b/examples/gno.land/p/demo/entropy/entropy_test.gno similarity index 50% rename from examples/gno.land/p/demo/rand/rand_test.gno rename to examples/gno.land/p/demo/entropy/entropy_test.gno index 2651f0af089..013709bc4e9 100644 --- a/examples/gno.land/p/demo/rand/rand_test.gno +++ b/examples/gno.land/p/demo/entropy/entropy_test.gno @@ -1,49 +1,48 @@ -package rand +package entropy import ( "fmt" "std" + "strconv" "strings" "testing" - - "gno.land/p/demo/rand" ) func TestInstance(t *testing.T) { - instance := rand.New() + instance := New() if instance == nil { t.Errorf("instance should not be nil") } } -func TestIntn(t *testing.T) { - baseRand := rand.New() - baseResult := computeIntn(t, baseRand) +func TestInstanceValue(t *testing.T) { + baseEntropy := New() + baseResult := computeValue(t, baseEntropy) - sameHeightRand := rand.New() - sameHeightResult := computeIntn(t, sameHeightRand) + sameHeightEntropy := New() + sameHeightResult := computeValue(t, sameHeightEntropy) if baseResult != sameHeightResult { t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) } std.TestSkipHeights(1) - differentHeightRand := rand.New() - differentHeightResult := computeIntn(t, differentHeightRand) + differentHeightEntropy := New() + differentHeightResult := computeValue(t, differentHeightEntropy) if baseResult == differentHeightResult { t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) } } -func computeIntn(t *testing.T, r *rand.Instance) string { +func computeValue(t *testing.T, r *Instance) string { t.Helper() - arr := []string{} + out := "" for i := 0; i < 10; i++ { - arr = append(arr, fmt.Sprintf("%d", r.Intn(1000))) + val := int(r.Value()) + out += strconv.Itoa(val) + " " } - out := strings.Join(arr, ",") return out } diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod new file mode 100644 index 00000000000..9a6db8f5b61 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/entropy/z_filetest.gno b/examples/gno.land/p/demo/entropy/z_filetest.gno new file mode 100644 index 00000000000..b09f950b179 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/z_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/entropy" +) + +func main() { + // initial + println("---") + r := entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + // should be the same + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + std.TestSkipHeights(1) + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) +} + +// Output: +// --- +// 2206404796634629535 +// 6369783909128776508 +// -273967328977352263 +// 3685013872379606294 +// -321443877504368301 +// --- +// 2206404796634629535 +// 6369783909128776508 +// -273967328977352263 +// 3685013872379606294 +// -321443877504368301 +// --- +// 2389594832880495019 +// 2483785090669486254 +// 902493904282098385 +// 5349860799650907156 +// 3037689170578704503 diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod deleted file mode 100644 index 098af152648..00000000000 --- a/examples/gno.land/p/demo/rand/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -// Draft - -module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/rand/rand.gno b/examples/gno.land/p/demo/rand/rand.gno deleted file mode 100644 index 2fa16d627be..00000000000 --- a/examples/gno.land/p/demo/rand/rand.gno +++ /dev/null @@ -1,139 +0,0 @@ -package rand - -// Disclaimer: this package is unsafe and won't prevent others to -// guess values in advance. -// -// the goal of this package is to implement a random library that -// is fully deterministic for validators while being hard to guess. -// -// We use the Bernstein's hash djb2 to be CPU-cycle efficient. - -import ( - "math/rand" - "std" - "time" -) - -type Instance struct { - seed int64 -} - -func New() *Instance { - r := Instance{seed: 5381} - r.addEntropy() - return &r -} - -func FromSeed(seed int64) *Instance { - r := Instance{seed: seed} - r.addEntropy() - return &r -} - -func (i *Instance) Seed() int64 { - return i.seed -} - -func (i *Instance) djb2String(input string) { - for _, c := range input { - i.djb2Int64(int64(c)) - } -} - -// super fast random algorithm. -// http://www.cse.yorku.ca/~oz/hash.html -func (i *Instance) djb2Int64(input int64) { - i.seed = (i.seed << 5) + i.seed + input -} - -// AddEntropy uses various runtime variables to add entropy to the existing seed. -func (i *Instance) addEntropy() { - // FIXME: reapply the 5381 initial value? - - // inherit previous entropy - // nothing to do - - // handle callers - { - caller1 := std.GetCallerAt(1).String() - i.djb2String(caller1) - caller2 := std.GetCallerAt(2).String() - i.djb2String(caller2) - } - - // height - { - height := std.GetHeight() - i.djb2Int64(height) - } - - // time - { - secs := time.Now().Second() - i.djb2Int64(int64(secs)) - nsecs := time.Now().Nanosecond() - i.djb2Int64(int64(nsecs)) - } - - // FIXME: compute other hard-to-guess but deterministic variables, like real gas? -} - -func (i *Instance) Float32() float32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float32() -} - -func (i *Instance) Float64() float64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float64() -} - -func (i *Instance) Int() int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int() -} - -func (i *Instance) Intn(n int) int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Intn(n) -} - -func (i *Instance) Int63() int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63() -} - -func (i *Instance) Int63n(n int64) int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63n(n) -} - -func (i *Instance) Int31() int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31() -} - -func (i *Instance) Int31n(n int32) int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31n(n) -} - -func (i *Instance) Uint32() uint32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint32() -} - -func (i *Instance) Uint64() uint64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint64() -} - -func (i *Instance) Read(p []byte) (n int, err error) { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Read(p) -} - -func (i *Instance) Shuffle(n int, swap func(i, j int)) { - i.addEntropy() - rand.New(rand.NewSource(i.seed)).Shuffle(n, swap) -} diff --git a/examples/gno.land/p/demo/rand/rand0_filetest.gno b/examples/gno.land/p/demo/rand/rand0_filetest.gno deleted file mode 100644 index 446e04b696d..00000000000 --- a/examples/gno.land/p/demo/rand/rand0_filetest.gno +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "std" - - "gno.land/p/demo/rand" -) - -func main() { - // initial - println("---") - r := rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - // should be the same - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - std.TestSkipHeights(1) - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) -} - -// Output: -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 269 -// 233 -// 591 -// 936 -// 908 diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index f2d3ddebadc..d3c8feb3c84 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/demo/art/gnoface -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/entropy v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 9e85c5c7387..e8239412c4f 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -6,11 +6,12 @@ import ( "strconv" "strings" + "gno.land/p/demo/entropy" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := uint64(entropy.New().Value()) path = strings.TrimSpace(path) if path != "" { From 4eef904493d9b952cb7619d23708d704f4de2e63 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:20:58 +0200 Subject: [PATCH 5/8] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../gno.land/p/demo/wordlist/adjective.gno | 23 ++++++ examples/gno.land/p/demo/wordlist/color.gno | 28 ++++++++ examples/gno.land/p/demo/wordlist/gno.mod | 1 + examples/gno.land/p/demo/wordlist/job.gno | 24 +++++++ examples/gno.land/r/demo/users/gno.mod | 4 ++ examples/gno.land/r/demo/users/users.gno | 70 ++++++++++++++++--- examples/gno.land/r/demo/users/users_test.gno | 12 ++++ 7 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 examples/gno.land/p/demo/wordlist/adjective.gno create mode 100644 examples/gno.land/p/demo/wordlist/color.gno create mode 100644 examples/gno.land/p/demo/wordlist/gno.mod create mode 100644 examples/gno.land/p/demo/wordlist/job.gno create mode 100644 examples/gno.land/r/demo/users/users_test.gno diff --git a/examples/gno.land/p/demo/wordlist/adjective.gno b/examples/gno.land/p/demo/wordlist/adjective.gno new file mode 100644 index 00000000000..b034480fac7 --- /dev/null +++ b/examples/gno.land/p/demo/wordlist/adjective.gno @@ -0,0 +1,23 @@ +package wordlist + +import "math/rand" + +var ( + adjectives = []string{ + "angry", + "brave", + "cocky", + "goofy", + "happy", + "jolly", + "lucid", + "nifty", + "sharp", + "stoic", + } + adjectivesL = len(adjectives) +) + +func RandomAdjective(r *rand.Rand) string { + return adjectives[r.IntN(adjectivesL)] +} diff --git a/examples/gno.land/p/demo/wordlist/color.gno b/examples/gno.land/p/demo/wordlist/color.gno new file mode 100644 index 00000000000..754bc866277 --- /dev/null +++ b/examples/gno.land/p/demo/wordlist/color.gno @@ -0,0 +1,28 @@ +package wordlist + +import "math/rand" + +var ( + colors = []string{ + "aqua", + "black", + "blue", + "fuchsia", + "gray", + "green", + "lime", + "maroon", + "navy", + "olive", + "purple", + "silver", + "teal", + "white", + "yellow", + } + colorsL = len(colors) +) + +func RandomColor(r *rand.Rand) string { + return colors[r.IntN(colorsL)] +} diff --git a/examples/gno.land/p/demo/wordlist/gno.mod b/examples/gno.land/p/demo/wordlist/gno.mod new file mode 100644 index 00000000000..e093c7c6a69 --- /dev/null +++ b/examples/gno.land/p/demo/wordlist/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/wordlist diff --git a/examples/gno.land/p/demo/wordlist/job.gno b/examples/gno.land/p/demo/wordlist/job.gno new file mode 100644 index 00000000000..673baf20d9d --- /dev/null +++ b/examples/gno.land/p/demo/wordlist/job.gno @@ -0,0 +1,24 @@ +package wordlist + +import "math/rand" + +var ( + jobs = []string{ + "architect", + "baker", + "cleaner", + "cooker", + "doctor", + "designer", + "developer", + "engineer", + "officer", + "priest", + "teacher", + } + jobsL = len(jobs) +) + +func RandomJob(r *rand.Rand) string { + return jobs[r.IntN(jobsL)] +} diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod index a2ee2ea86ba..a6c26aa8fc9 100644 --- a/examples/gno.land/r/demo/users/gno.mod +++ b/examples/gno.land/r/demo/users/gno.mod @@ -2,5 +2,9 @@ module gno.land/r/demo/users require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/entropy 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/users v0.0.0-latest + gno.land/p/demo/wordlist 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 cd02434e239..b90ead01282 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -1,37 +1,86 @@ package users import ( + "errors" + "math/rand" "regexp" "std" "strconv" "strings" "gno.land/p/demo/avl" + "gno.land/p/demo/entropy" + "gno.land/p/demo/ufmt" "gno.land/p/demo/users" + "gno.land/p/demo/wordlist" ) //---------------------------------------- // State var ( - admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" - name2User avl.Tree // Name -> *users.User - addr2User avl.Tree // std.Address -> *users.User - invites avl.Tree // string(inviter+":"+invited) -> true - counter int // user id counter - minFee int64 = 200 * 1000000 // minimum gnot must be paid to register. - maxFeeMult int64 = 10 // maximum multiples of minFee accepted. + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" + name2User avl.Tree // Name -> *users.User + addr2User avl.Tree // std.Address -> *users.User + invites avl.Tree // string(inviter+":"+invited) -> true + counter int // user id counter + minFee int64 = 200 * 1000000 // minimum gnot must be paid to register. + maxFeeMult int64 = 10 // maximum multiples of minFee accepted. + ent *entropy.Instance = entropy.New() ) +var ErrAddressAlreadyRegistered = errors.New("address already registered") + //---------------------------------------- // Top-level functions -func ClaimDefaultName(profile string) { +// ClaimDefaultName registers a random name for free. +func ClaimDefaultName(profile string) string { std.AssertOriginCall() caller := std.PrevRealm().Addr() + + _, ok := addr2User.Get(caller.String()) + if ok { + panic(ErrAddressAlreadyRegistered) + } + + seed := uint64(ent.Value()) + r := rand.New(rand.NewPCG(seed, 0xbadcafe)) + + var name string + // gas is the limit. + for { + var ( + adjective = wordlist.RandomAdjective(r) + job = wordlist.RandomJob(r) + color = wordlist.RandomColor(r) + nb = r.IntN(100) + ) + name = ufmt.Sprintf("%s_%s_%s_%d%d", adjective, color, job, nb/10, nb%10) + _, ok := name2User.Get(name) + if !ok { + break + } + } + + // register. + counter++ + user := &users.User{ + Address: caller, + Name: name, + Profile: profile, + Number: counter, + } + name2User.Set(name, user) + addr2User.Set(caller.String(), user) + + return name } +// Register registers a chosen name. func Register(inviter std.Address, name string, profile string) { + // XXX: Register could override an existing default name, but we need to implement symlnks. + // assert CallTx call. std.AssertOriginCall() caller := std.PrevRealm().Addr() @@ -59,11 +108,11 @@ func Register(inviter std.Address, name string, profile string) { // assert not already registered. _, ok := name2User.Get(name) if ok { - panic("name already registered") + panic("name already exists") } _, ok = addr2User.Get(caller.String()) if ok { - panic("address already registered") + panic(ErrAddressAlreadyRegistered) } // assert name is valid. if !reName.MatchString(name) { @@ -79,6 +128,7 @@ func Register(inviter std.Address, name string, profile string) { } } } + // register. counter++ user := &users.User{ diff --git a/examples/gno.land/r/demo/users/users_test.gno b/examples/gno.land/r/demo/users/users_test.gno new file mode 100644 index 00000000000..c59cb929cc0 --- /dev/null +++ b/examples/gno.land/r/demo/users/users_test.gno @@ -0,0 +1,12 @@ +package users + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestClaimDefaultName(t *testing.T) { + profile := "hello" + println(ClaimDefaultName(profile)) +} From a1aa347ee35522f70a466446d5e889dcd75348b3 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:26:14 +0200 Subject: [PATCH 6/8] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/uassert/uassert.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 3 +-- examples/gno.land/r/demo/users/users_test.gno | 8 ++++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno index 61885574360..72fd1f276a9 100644 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -126,7 +126,7 @@ func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { if av, ok := actual.(string); ok { equal = ev == av ok_ = true - es, as = ev, as + es, as = ev, av } case std.Address: if av, ok := actual.(std.Address); ok { diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index b90ead01282..2ded2142175 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -48,8 +48,7 @@ func ClaimDefaultName(profile string) string { r := rand.New(rand.NewPCG(seed, 0xbadcafe)) var name string - // gas is the limit. - for { + for { // gas is the limit. var ( adjective = wordlist.RandomAdjective(r) job = wordlist.RandomJob(r) diff --git a/examples/gno.land/r/demo/users/users_test.gno b/examples/gno.land/r/demo/users/users_test.gno index c59cb929cc0..f6ddd310e3e 100644 --- a/examples/gno.land/r/demo/users/users_test.gno +++ b/examples/gno.land/r/demo/users/users_test.gno @@ -3,10 +3,14 @@ package users import ( "testing" + "gno.land/p/demo/entropy" "gno.land/p/demo/uassert" ) func TestClaimDefaultName(t *testing.T) { - profile := "hello" - println(ClaimDefaultName(profile)) + ent = entropy.FromSeed(42) // fixed entropy + profile := "hello world" + name := ClaimDefaultName(profile) + expected := "nifty_teal_engineer_51" // this can easily break if p/demo/wordlist is modified + uassert.Equal(t, expected, name) } From 114488115b9b8ab84e314c075dbea5e74ad37a36 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Wed, 3 Jul 2024 00:13:49 +0200 Subject: [PATCH 7/8] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../porting-solidity-to-gno/porting-6.gno | 83 ++++++++++--------- examples/gno.land/r/demo/users/users.gno | 6 +- examples/gno.land/r/demo/users/users_test.gno | 33 ++++++++ .../gno.land/r/demo/users/z_0_b_filetest.gno | 15 ---- .../gno.land/r/demo/users/z_0_filetest.gno | 16 ---- .../gno.land/r/demo/users/z_1_filetest.gno | 15 ---- 6 files changed, 80 insertions(+), 88 deletions(-) delete mode 100644 examples/gno.land/r/demo/users/z_0_b_filetest.gno delete mode 100644 examples/gno.land/r/demo/users/z_0_filetest.gno delete mode 100644 examples/gno.land/r/demo/users/z_1_filetest.gno diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno index b544d0017c4..f95d7e8b50a 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno @@ -1,41 +1,42 @@ -// Bid Function Test - Send Coin -func TestBidCoins(t *testing.T) { - // Sending two types of coins - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) - shouldPanic(t, Bid) - - // Sending lower amount than the current highest bid - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) - shouldPanic(t, Bid) - - // Sending more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) -} - -// Bid Function Test - Bid by two or more people -func TestBidCoins(t *testing.T) { - // bidder01 bidding with 1 coin - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 1) - shouldEqual(t, highestBidder, bidder01) - shouldEqual(t, pendingReturns.Size(), 0) - - // bidder02 bidding with 1 coin - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldPanic(t, Bid) - - // bidder02 bidding with 2 coins - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 2) - shouldEqual(t, highestBidder, bidder02) - shouldEqual(t, pendingReturns.Size(), 1) -} +docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) +docs/how-to-guides/porting-solidity-to-gno.md: std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) +docs/reference/stdlibs/std/testing.md:std.TestSetOrigSend(sent, spent Coins) +examples/gno.land/r/demo/banktest/z_0_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 100_000_000}}, nil) +examples/gno.land/r/demo/banktest/z_1_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) +examples/gno.land/r/demo/banktest/z_2_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) +examples/gno.land/r/demo/boards/z_12_a_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/boards/z_5_b_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/boards/z_5_c_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 101000000}}, nil) +examples/gno.land/r/demo/boards/z_5_d_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/groups/z_1_c_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/groups/z_2_d_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/groups/z_2_g_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) +examples/gno.land/r/demo/users/users_test.gno: std.TestSetOrigSend("199000000ugnot") +examples/gno.land/r/demo/users/z_0_filetest.gno: std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) +examples/gno.land/r/demo/users/z_3_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/users/z_4_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/users/z_5_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/users/z_7_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/users/z_7b_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/users/z_8_filetest.gno: std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) +examples/gno.land/r/demo/wugnot/z0_filetest.gno: std.TestSetOrigSend(std.Coins{{"ugnot", 123_400}}, nil) diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 2ded2142175..188c825af14 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -78,10 +78,14 @@ func ClaimDefaultName(profile string) string { // Register registers a chosen name. func Register(inviter std.Address, name string, profile string) { + std.AssertOriginCall() + register(inviter, name, profile) +} + +func register(inviter std.Address, name string, profile string) { // XXX: Register could override an existing default name, but we need to implement symlnks. // assert CallTx call. - std.AssertOriginCall() caller := std.PrevRealm().Addr() sentCoins := std.GetOrigSend() minCoin := std.NewCoin("ugnot", minFee) diff --git a/examples/gno.land/r/demo/users/users_test.gno b/examples/gno.land/r/demo/users/users_test.gno index f6ddd310e3e..e783bf0a67f 100644 --- a/examples/gno.land/r/demo/users/users_test.gno +++ b/examples/gno.land/r/demo/users/users_test.gno @@ -1,12 +1,45 @@ package users import ( + "std" "testing" "gno.land/p/demo/entropy" + "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" ) +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") +) + +/* + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + std.TestIssueCoins(alice, std.Coins{{"ugnot", 199000000}}) + std.TestSetOrigSend(std.Coins{{"ugnot", 199000000}}, nil) +*/ + +func TestRegister_successWithPayment(t *testing.T) { + std.TestSetOrigSend(std.Coins{std.NewCoin("ugnot", 200000000)}, nil) + Register("", "gnouser", "myprofile") +} + +func TestRegister_notEnoughPayment(t *testing.T) { + std.TestSetOrigSend(std.Coins{std.NewCoin("ugnot", 199000000)}, nil) + uassert.PanicsWithMessage(t, "payment must not be less than 200000000", func() { + register("", "gnouser", "myprofile") + }) +} + +func TestRegister_invalidDenom(t *testing.T) { + std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) + uassert.PanicsWithMessage(t, "incompatible coin denominations: dontcare, ugnot", func() { + register("", "gnouser", "myprofile") + }) +} + func TestClaimDefaultName(t *testing.T) { ent = entropy.FromSeed(42) // fixed entropy profile := "hello world" diff --git a/examples/gno.land/r/demo/users/z_0_b_filetest.gno b/examples/gno.land/r/demo/users/z_0_b_filetest.gno deleted file mode 100644 index 9095057076c..00000000000 --- a/examples/gno.land/r/demo/users/z_0_b_filetest.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -// SEND: 199000000ugnot - -import ( - "gno.land/r/demo/users" -) - -func main() { - users.Register("", "gnouser", "my profile") - println("done") -} - -// Error: -// payment must not be less than 200000000 diff --git a/examples/gno.land/r/demo/users/z_0_filetest.gno b/examples/gno.land/r/demo/users/z_0_filetest.gno deleted file mode 100644 index cbb2e9209f4..00000000000 --- a/examples/gno.land/r/demo/users/z_0_filetest.gno +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "std" - - "gno.land/r/demo/users" -) - -func main() { - std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) - users.Register("", "gnouser", "my profile") - println("done") -} - -// Error: -// incompatible coin denominations: dontcare, ugnot diff --git a/examples/gno.land/r/demo/users/z_1_filetest.gno b/examples/gno.land/r/demo/users/z_1_filetest.gno deleted file mode 100644 index a1c7e682022..00000000000 --- a/examples/gno.land/r/demo/users/z_1_filetest.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -// SEND: 2000000000ugnot - -import ( - "gno.land/r/demo/users" -) - -func main() { - users.Register("", "gnouser", "my profile") - println("done") -} - -// Output: -// done From ac7f6f11dba1a3cb3d165aecc9304fdd70aab3bb Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:41:28 +0200 Subject: [PATCH 8/8] chore: standalone unit tests Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gnovm/cmd/gno/test.go | 159 ++++++++++++++++---------------- gnovm/pkg/gnolang/nodes_copy.go | 3 + 2 files changed, 84 insertions(+), 78 deletions(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 5884463a552..e25ebf85aa9 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -277,25 +277,7 @@ func gnoTestPkg( // run test files in pkg if len(tfiles.Files) > 0 { - testStore := tests.TestStore( - rootDir, "", - stdin, stdout, stderr, - mode, - ) - if verbose { - testStore.SetLogStoreOps(true) - } - - m := tests.TestMachine(testStore, stdout, gnoPkgPath) - if printRuntimeMetrics { - // from tm2/pkg/sdk/vm/keeper.go - // XXX: make maxAllocTx configurable. - maxAllocTx := int64(500 * 1000 * 1000) - - m.Alloc = gno.NewAllocator(maxAllocTx) - } - m.RunMemPackage(memPkg, true) - err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, runFlag, io) + err := runTestFiles(cfg, memPkg, tfiles, verbose, printRuntimeMetrics, runFlag, io) if err != nil { errs = multierr.Append(errs, err) } @@ -312,8 +294,6 @@ func gnoTestPkg( testStore.SetLogStoreOps(true) } - m := tests.TestMachine(testStore, stdout, testPkgName) - memFiles := make([]*std.MemFile, 0, len(ifiles.FileNames())+1) for _, f := range memPkg.Files { for _, ifileName := range ifiles.FileNames() { @@ -323,13 +303,13 @@ func gnoTestPkg( } } } + memPkg := &std.MemPackage{ + Name: testPkgName, + Files: memFiles, + Path: pkgPath + "_test", + } - memPkg.Files = memFiles - memPkg.Name = testPkgName - memPkg.Path = memPkg.Path + "_test" - m.RunMemPackage(memPkg, true) - - err := runTestFiles(m, ifiles, testPkgName, verbose, printRuntimeMetrics, runFlag, io) + err := runTestFiles(cfg, memPkg, ifiles, verbose, printRuntimeMetrics, runFlag, io) if err != nil { errs = multierr.Append(errs, err) } @@ -414,19 +394,20 @@ func pkgPathFromRootDir(pkgPath, rootDir string) string { } func runTestFiles( - m *gno.Machine, + cfg *testCfg, + memPkg *std.MemPackage, files *gno.FileSet, - pkgName string, verbose bool, printRuntimeMetrics bool, runFlag string, io commands.IO, ) (errs error) { - defer func() { - if r := recover(); r != nil { - errs = multierr.Append(fmt.Errorf("panic: %v\nstack:\n%v\ngno machine: %v", r, string(debug.Stack()), m.String()), errs) - } - }() + pkgName := memPkg.Name + stdout := io.Out() + stdin := io.In() + stderr := io.Err() + rootDir := cfg.rootDir + mode := tests.ImportModeStdlibsOnly testFuncs := &testFuncs{ PackageName: pkgName, @@ -435,63 +416,85 @@ func runTestFiles( } loadTestFuncs(pkgName, testFuncs, files) - // before/after statistics - numPackagesBefore := m.Store.NumMemPackages() - testmain, err := formatTestmain(testFuncs) if err != nil { log.Fatal(err) } - m.RunFiles(files.Files...) - n := gno.MustParseFile("main_test.gno", testmain) - m.RunFiles(n) - for _, test := range testFuncs.Tests { - testFuncStr := fmt.Sprintf("%q", test.Name) + func() { + testStore := tests.TestStore( + rootDir, "", + stdin, stdout, stderr, + mode, + ) + testStore.ClearCache() - eval := m.Eval(gno.Call("runtest", testFuncStr)) + m := tests.TestMachine(testStore, stdout, memPkg.Name) + testFuncStr := fmt.Sprintf("%q", test.Name) - ret := eval[0].GetString() - if ret == "" { - err := errors.New("failed to execute unit test: %q", test.Name) - errs = multierr.Append(errs, err) - io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", test.Name) - continue - } + // before/after statistics + numPackagesBefore := m.Store.NumMemPackages() - // TODO: replace with amino or send native type? - var rep report - err = json.Unmarshal([]byte(ret), &rep) - if err != nil { - errs = multierr.Append(errs, err) - io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", test.Name) - continue - } + defer func() { + if r := recover(); r != nil { + err := fmt.Errorf("panic: %v\nstack:\n%v\ngno machine: %v", r, string(debug.Stack()), m.String()) + errs = multierr.Append(err, errs) + } + }() + m.RunMemPackage(memPkg, true) - if rep.Failed { - err := errors.New("failed: %q", test.Name) - errs = multierr.Append(errs, err) - } + filesCopy := files.CopyFileSet() + //filesCopy := files + //fmt.Printf("AAA: %v\n", files) + //fmt.Printf("BBB: %v\n", filesCopy) + m.RunFiles(filesCopy.Files...) + n := gno.MustParseFile("main_test.gno", testmain) + m.RunFiles(n) + + eval := m.Eval(gno.Call("runtest", testFuncStr)) + + ret := eval[0].GetString() + if ret == "" { + err := errors.New("failed to execute unit test: %q", test.Name) + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", test.Name) + return + } - if printRuntimeMetrics { - imports := m.Store.NumMemPackages() - numPackagesBefore - 1 - // XXX: store changes - // XXX: max mem consumption - allocsVal := "n/a" - if m.Alloc != nil { - maxAllocs, allocs := m.Alloc.Status() - allocsVal = fmt.Sprintf("%s(%.2f%%)", - prettySize(allocs), - float64(allocs)/float64(maxAllocs)*100, + // TODO: replace with amino or send native type? + var rep report + err = json.Unmarshal([]byte(ret), &rep) + if err != nil { + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", test.Name) + return + } + + if rep.Failed { + err := errors.New("failed: %q", test.Name) + errs = multierr.Append(errs, err) + } + + if printRuntimeMetrics { + imports := m.Store.NumMemPackages() - numPackagesBefore - 1 + // XXX: store changes + // XXX: max mem consumption + allocsVal := "n/a" + if m.Alloc != nil { + maxAllocs, allocs := m.Alloc.Status() + allocsVal = fmt.Sprintf("%s(%.2f%%)", + prettySize(allocs), + float64(allocs)/float64(maxAllocs)*100, + ) + } + io.ErrPrintfln("--- runtime: cycle=%s imports=%d allocs=%s", + prettySize(m.Cycles), + imports, + allocsVal, ) } - io.ErrPrintfln("--- runtime: cycle=%s imports=%d allocs=%s", - prettySize(m.Cycles), - imports, - allocsVal, - ) - } + }() } return errs diff --git a/gnovm/pkg/gnolang/nodes_copy.go b/gnovm/pkg/gnolang/nodes_copy.go index e8d7dbb31ec..a8ece095e5b 100644 --- a/gnovm/pkg/gnolang/nodes_copy.go +++ b/gnovm/pkg/gnolang/nodes_copy.go @@ -362,6 +362,9 @@ func (fs *FileSet) CopyFileSet() *FileSet { func (x *FileNode) Copy() Node { return &FileNode{ + // XXX: Attributes? + // XXX: StaticBlock? + Name: x.Name, PkgName: x.PkgName, Decls: copyDecls(x.Decls), }